1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.pm;
18 
19 import static android.content.pm.PackageManager.MATCH_ALL;
20 
21 import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
22 import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
23 import static com.android.server.pm.PackageManagerService.TAG;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.UserIdInt;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.content.pm.UserInfo;
33 import android.content.pm.UserProperties;
34 import android.os.Process;
35 import android.os.UserHandle;
36 import android.text.TextUtils;
37 import android.util.Pair;
38 import android.util.Slog;
39 import android.util.SparseArray;
40 
41 import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
42 import com.android.server.LocalServices;
43 import com.android.server.pm.pkg.PackageStateInternal;
44 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
45 import com.android.server.pm.verify.domain.DomainVerificationUtils;
46 
47 import java.util.ArrayList;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.function.Function;
52 
53 /**
54  * Rule based engine which decides strategy to be used for source,target pair and does cross profile
55  * intent resolution. Currently, we have only default and clone strategy. The major known use-case
56  * for default is work profile.
57  */
58 public class CrossProfileIntentResolverEngine {
59 
60     private final UserManagerService mUserManager;
61     private final DomainVerificationManagerInternal mDomainVerificationManager;
62     private final DefaultAppProvider mDefaultAppProvider;
63     private final Context mContext;
64     private final UserManagerInternal mUserManagerInternal;
65 
66     private AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
67 
CrossProfileIntentResolverEngine(UserManagerService userManager, DomainVerificationManagerInternal domainVerificationManager, DefaultAppProvider defaultAppProvider, Context context)68     public CrossProfileIntentResolverEngine(UserManagerService userManager,
69             DomainVerificationManagerInternal domainVerificationManager,
70             DefaultAppProvider defaultAppProvider, Context context) {
71         mUserManager = userManager;
72         mDomainVerificationManager = domainVerificationManager;
73         mDefaultAppProvider = defaultAppProvider;
74         mContext = context;
75         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
76     }
77 
78     /**
79      * Returns the list of {@link CrossProfileDomainInfo} which contains {@link ResolveInfo} from
80      * profiles linked directly/indirectly to user. Work-Owner as well as Clone-Owner
81      * are directly related as they are child of owner. Work-Clone are indirectly linked through
82      * owner profile.
83      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
84      * @param intent request
85      * @param resolvedType the MIME data type of intent request
86      * @param userId source user for which intent request is called
87      * @param flags used for intent resolution
88      * @param pkgName the application package name this Intent is limited to.
89      * @param hasNonNegativePriorityResult signifies if current profile have any non-negative(active
90      *                                     and valid) ResolveInfo in current profile.
91      * @param resolveForStart true if resolution occurs to start an activity.
92      * @param pkgSettingFunction function to find PackageStateInternal for given package
93      * @return list of {@link CrossProfileDomainInfo} from linked profiles.
94      */
resolveIntent(@onNull Computer computer, Intent intent, String resolvedType, int userId, long flags, String pkgName, boolean hasNonNegativePriorityResult, boolean resolveForStart, Function<String, PackageStateInternal> pkgSettingFunction)95     public List<CrossProfileDomainInfo> resolveIntent(@NonNull Computer computer, Intent intent,
96             String resolvedType, int userId, long flags, String pkgName,
97             boolean hasNonNegativePriorityResult, boolean resolveForStart,
98             Function<String, PackageStateInternal> pkgSettingFunction) {
99         return resolveIntentInternal(computer, intent, resolvedType, userId, userId, flags, pkgName,
100                 hasNonNegativePriorityResult, resolveForStart, pkgSettingFunction, null);
101     }
102 
103     /**
104      * Resolves intent in directly linked profiles and return list of {@link CrossProfileDomainInfo}
105      * which contains {@link ResolveInfo}. This would also recursively call profiles not directly
106      * linked using Depth First Search.
107      *
108      * It first finds {@link CrossProfileIntentFilter} configured in current profile to find list of
109      * target user profiles that can serve current intent request. It uses corresponding strategy
110      * for each pair (source,target) user to resolve intent from target profile and returns combined
111      * results.
112      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
113      * @param intent request
114      * @param resolvedType the MIME data type of intent request
115      * @param sourceUserId source user for which intent request is called
116      * @param userId current user for cross profile resolution
117      * @param flags used for intent resolution
118      * @param pkgName the application package name this Intent is limited to.
119      * @param hasNonNegativePriorityResult signifies if current profile have any non-negative(active
120      *                                     and valid) ResolveInfo in current profile.
121      * @param resolveForStart true if resolution occurs to start an activity.
122      * @param pkgSettingFunction function to find PackageStateInternal for given package
123      * @param visitedUserIds users for which we have already performed resolution
124      * @return list of {@link CrossProfileDomainInfo} from linked profiles.
125      */
resolveIntentInternal(@onNull Computer computer, Intent intent, String resolvedType, int sourceUserId, int userId, long flags, String pkgName, boolean hasNonNegativePriorityResult, boolean resolveForStart, Function<String, PackageStateInternal> pkgSettingFunction, Set<Integer> visitedUserIds)126     private List<CrossProfileDomainInfo> resolveIntentInternal(@NonNull Computer computer,
127             Intent intent, String resolvedType, int sourceUserId, int userId, long flags,
128             String pkgName, boolean hasNonNegativePriorityResult, boolean resolveForStart,
129             Function<String, PackageStateInternal> pkgSettingFunction,
130             Set<Integer> visitedUserIds) {
131 
132         if (visitedUserIds != null) visitedUserIds.add(userId);
133         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
134 
135         List<CrossProfileIntentFilter> matchingFilters =
136                 computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
137                         userId);
138 
139         if (matchingFilters == null || matchingFilters.isEmpty()) {
140             /** if intent is web intent, checking if parent profile should handle the intent
141              * even if there is no matching filter. The configuration is based on user profile
142              * restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
143             if (sourceUserId == userId && intent.hasWebURI()) {
144                 UserInfo parent = computer.getProfileParent(userId);
145                 if (parent != null) {
146                     CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
147                             .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags,
148                                     userId, parent.id);
149                     if (generalizedCrossProfileDomainInfo != null) {
150                         crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
151                     }
152                 }
153             }
154             return crossProfileDomainInfos;
155         }
156 
157         // Grouping the CrossProfileIntentFilters based on targerId
158         SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
159                 new SparseArray<>();
160 
161         for (int index = 0; index < matchingFilters.size(); index++) {
162             CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
163 
164             if (!crossProfileIntentFiltersByUser
165                     .contains(crossProfileIntentFilter.mTargetUserId)) {
166                 crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
167                         new ArrayList<>());
168             }
169             crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
170                     .add(crossProfileIntentFilter);
171         }
172 
173         if (visitedUserIds == null) {
174             visitedUserIds = new HashSet<>();
175             visitedUserIds.add(userId);
176         }
177 
178         /*
179          For each target user, we would call their corresponding strategy
180          {@link CrossProfileResolver} to resolve intent in corresponding user
181          */
182         for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
183 
184             int targetUserId = crossProfileIntentFiltersByUser.keyAt(index);
185 
186             //if user is already visited then skip resolution for particular user.
187             if (visitedUserIds.contains(targetUserId)) {
188                 continue;
189             }
190 
191             // Choosing strategy based on source and target user
192             CrossProfileResolver crossProfileResolver =
193                     chooseCrossProfileResolver(computer, userId, targetUserId,
194                             resolveForStart, flags);
195 
196         /*
197         If {@link CrossProfileResolver} is available for source,target pair we will call it to
198         get {@link CrossProfileDomainInfo}s from that user.
199          */
200             if (crossProfileResolver != null) {
201                 List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
202                         .resolveIntent(computer, intent, resolvedType, userId,
203                                 targetUserId, flags, pkgName,
204                                 crossProfileIntentFiltersByUser.valueAt(index),
205                                 hasNonNegativePriorityResult, pkgSettingFunction);
206                 crossProfileDomainInfos.addAll(crossProfileInfos);
207                 visitedUserIds.add(targetUserId);
208 
209                 /*
210                 Adding target user to queue if flag
211                 {@link CrossProfileIntentFilter#FLAG_ALLOW_CHAINED_RESOLUTION} is set for any
212                 {@link CrossProfileIntentFilter}
213                  */
214                 boolean allowChainedResolution = false;
215                 for (int filterIndex = 0; filterIndex < crossProfileIntentFiltersByUser
216                         .valueAt(index).size(); filterIndex++) {
217                     if ((CrossProfileIntentFilter
218                             .FLAG_ALLOW_CHAINED_RESOLUTION & crossProfileIntentFiltersByUser
219                             .valueAt(index).get(filterIndex).mFlags) != 0) {
220                         allowChainedResolution = true;
221                         break;
222                     }
223                 }
224                 if (allowChainedResolution) {
225                     crossProfileDomainInfos.addAll(resolveIntentInternal(computer, intent,
226                             resolvedType, sourceUserId, targetUserId, flags, pkgName,
227                             hasNonNegativePriority(crossProfileInfos), resolveForStart,
228                             pkgSettingFunction, visitedUserIds));
229                 }
230 
231             }
232         }
233 
234         return crossProfileDomainInfos;
235     }
236 
237 
238     /**
239      * Returns {@link CrossProfileResolver} strategy based on source and target user
240      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
241      * @param sourceUserId source user
242      * @param targetUserId target user
243      * @param resolveForStart true if resolution occurs to start an activity.
244      * @param flags used for intent resolver selection
245      * @return {@code CrossProfileResolver} which has value if source and target have
246      * strategy configured otherwise null.
247      */
248     @SuppressWarnings("unused")
chooseCrossProfileResolver(@onNull Computer computer, @UserIdInt int sourceUserId, @UserIdInt int targetUserId, boolean resolveForStart, long flags)249     private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer,
250             @UserIdInt int sourceUserId, @UserIdInt int targetUserId, boolean resolveForStart,
251             long flags) {
252         /**
253          * If source or target user is clone profile, using {@link NoFilteringResolver}
254          * We would return NoFilteringResolver only if it is allowed(feature flag is set).
255          */
256         if (shouldUseNoFilteringResolver(sourceUserId, targetUserId)) {
257             if (mAppCloningDeviceConfigHelper == null) {
258                 //lazy initialization of helper till required, to improve performance.
259                 mAppCloningDeviceConfigHelper = AppCloningDeviceConfigHelper.getInstance(mContext);
260             }
261             if (NoFilteringResolver.isIntentRedirectionAllowed(mContext,
262                     mAppCloningDeviceConfigHelper, resolveForStart, flags)) {
263                 return new NoFilteringResolver(computer.getComponentResolver(),
264                         mUserManager);
265             } else {
266                 return null;
267             }
268         }
269         return new DefaultCrossProfileResolver(computer.getComponentResolver(),
270                 mUserManager, mDomainVerificationManager);
271     }
272 
273     /**
274      * Returns true if we source user can reach target user for given intent. The source can
275      * directly or indirectly reach to target.
276      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
277      * @param intent request
278      * @param resolvedType the MIME data type of intent request
279      * @param sourceUserId source user
280      * @param targetUserId target user
281      * @return true if we source user can reach target user for given intent
282      */
canReachTo(@onNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, @UserIdInt int sourceUserId, @UserIdInt int targetUserId)283     public boolean canReachTo(@NonNull Computer computer, @NonNull Intent intent,
284             @Nullable String resolvedType, @UserIdInt int sourceUserId,
285             @UserIdInt int targetUserId) {
286         Set<Integer> visitedUserIds = new HashSet<>();
287         return canReachToInternal(computer, intent, resolvedType, sourceUserId, targetUserId,
288                 visitedUserIds);
289     }
290 
291     /**
292      * Returns true if we source user can reach target user for given intent. The source can
293      * directly or indirectly reach to target. This will perform depth first search to check if
294      * source can reach target.
295      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
296      * @param intent request
297      * @param resolvedType the MIME data type of intent request
298      * @param sourceUserId source user
299      * @param targetUserId target user
300      * @param visitedUserIds users for which resolution is checked
301      * @return true if we source user can reach target user for given intent
302      */
canReachToInternal(@onNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, @UserIdInt int sourceUserId, @UserIdInt int targetUserId, Set<Integer> visitedUserIds)303     private boolean canReachToInternal(@NonNull Computer computer, @NonNull Intent intent,
304             @Nullable String resolvedType, @UserIdInt int sourceUserId,
305             @UserIdInt int targetUserId, Set<Integer> visitedUserIds) {
306         if (sourceUserId == targetUserId) return true;
307         visitedUserIds.add(sourceUserId);
308 
309         List<CrossProfileIntentFilter> matches =
310                 computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
311 
312         if (matches != null) {
313             for (int index = 0; index < matches.size(); index++) {
314                 CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
315                 if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
316                     return true;
317                 }
318                 if (visitedUserIds.contains(crossProfileIntentFilter.mTargetUserId)) {
319                     continue;
320                 }
321 
322                 /*
323                  If source cannot directly reach to target, we will add
324                  CrossProfileIntentFilter.mTargetUserId user to queue to check if target user
325                  can be reached via CrossProfileIntentFilter.mTargetUserId i.e. it can be
326                  indirectly reached through chained/linked profiles.
327                  */
328                 if ((CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION
329                         & crossProfileIntentFilter.mFlags) != 0) {
330                     visitedUserIds.add(crossProfileIntentFilter.mTargetUserId);
331                     if (canReachToInternal(computer, intent, resolvedType,
332                             crossProfileIntentFilter.mTargetUserId, targetUserId, visitedUserIds)) {
333                         return true;
334                     }
335                 }
336             }
337         }
338         return false;
339     }
340 
341     /**
342      * Checks if any of the matching {@link CrossProfileIntentFilter} suggest we should skip the
343      * current profile based on flag {@link PackageManager#SKIP_CURRENT_PROFILE}.
344      * @param computer {@link Computer} instance used to find {@link CrossProfileIntentFilter}
345      *                                 for user
346      * @param intent request
347      * @param resolvedType the MIME data type of intent request
348      * @param sourceUserId id of initiating user space
349      * @return boolean if we should skip resolution in current/source profile.
350      */
shouldSkipCurrentProfile(Computer computer, Intent intent, String resolvedType, int sourceUserId)351     public boolean shouldSkipCurrentProfile(Computer computer, Intent intent, String resolvedType,
352             int sourceUserId) {
353         List<CrossProfileIntentFilter> matches =
354                 computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
355         if (matches != null) {
356             for (int matchIndex = 0; matchIndex < matches.size(); matchIndex++) {
357                 CrossProfileIntentFilter crossProfileIntentFilter = matches.get(matchIndex);
358                 if ((crossProfileIntentFilter.getFlags()
359                         & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
360                     return true;
361                 }
362             }
363         }
364         return false;
365     }
366 
367     /**
368      * Combines result from current and cross profile. This also does filtering based on domain(if
369      * required).
370      * @param computer {@link Computer} instance
371      * @param intent request
372      * @param resolvedType the MIME data type of intent request
373      * @param instantAppPkgName package name if instant app is allowed
374      * @param pkgName the application package name this Intent is limited to.
375      * @param allowDynamicSplits true if dynamic splits is allowed
376      * @param matchFlags flags for intent request
377      * @param userId user id of source user
378      * @param filterCallingUid uid of calling process
379      * @param resolveForStart true if resolution occurs because an application is starting
380      * @param candidates resolveInfos from current profile
381      * @param crossProfileCandidates crossProfileDomainInfos from cross profile, it has ResolveInfo
382      * @param areWebInstantAppsDisabled true if web instant apps are disabled
383      * @param addInstant true if instant apps are allowed
384      * @param sortResult true if caller would need to sort the results
385      * @param pkgSettingFunction function to find PackageStateInternal for given package
386      * @return QueryIntentActivitiesResult which contains resolveInfos
387      */
combineFilterAndCreateQueryActivitiesResponse( Computer computer, Intent intent, String resolvedType, String instantAppPkgName, String pkgName, boolean allowDynamicSplits, long matchFlags, int userId, int filterCallingUid, boolean resolveForStart, List<ResolveInfo> candidates, List<CrossProfileDomainInfo> crossProfileCandidates, boolean areWebInstantAppsDisabled, boolean addInstant, boolean sortResult, Function<String, PackageStateInternal> pkgSettingFunction)388     public QueryIntentActivitiesResult combineFilterAndCreateQueryActivitiesResponse(
389             Computer computer, Intent intent, String resolvedType, String instantAppPkgName,
390             String pkgName, boolean allowDynamicSplits, long matchFlags, int userId,
391             int filterCallingUid, boolean resolveForStart, List<ResolveInfo> candidates,
392             List<CrossProfileDomainInfo> crossProfileCandidates, boolean areWebInstantAppsDisabled,
393             boolean addInstant, boolean sortResult,
394             Function<String, PackageStateInternal> pkgSettingFunction) {
395 
396         if (shouldSkipCurrentProfile(computer, intent, resolvedType, userId)) {
397             /*
398              if current profile is skipped return results from cross profile after filtering
399              ephemeral activities.
400              */
401             candidates = resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates);
402             return new QueryIntentActivitiesResult(computer.applyPostResolutionFilter(candidates,
403                     instantAppPkgName, allowDynamicSplits, filterCallingUid, resolveForStart,
404                     userId, intent));
405         }
406 
407         if (pkgName == null && intent.hasWebURI()) {
408             if (!addInstant && ((candidates.size() <= 1 && crossProfileCandidates.isEmpty())
409                     || (candidates.isEmpty() && !crossProfileCandidates.isEmpty()))) {
410                 candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
411                 return new QueryIntentActivitiesResult(computer.applyPostResolutionFilter(
412                         candidates, instantAppPkgName, allowDynamicSplits, filterCallingUid,
413                         resolveForStart, userId, intent));
414             }
415             /*
416              if there are multiple results from current and cross profile, combining and filtering
417              results based on domain priority.
418              */
419             candidates = filterCandidatesWithDomainPreferredActivitiesLPr(computer, intent,
420                     matchFlags, candidates, crossProfileCandidates, userId,
421                     areWebInstantAppsDisabled, resolveForStart, pkgSettingFunction);
422         } else {
423             candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
424         }
425         // When we have only a single result belonging to a different user space, we check
426         // if filtering is configured on cross-profile intent resolution for the originating user.
427         // Accordingly, we modify the intent to reflect the content-owner as the originating user,
428         // preventing the resolving user to be assumed the same, when activity is started.
429 
430         // In case more than one result is present, the resolver sheet is opened which takes care of
431         // cross user access.
432         if (candidates.size() == 1 && !UserHandle.of(userId).equals(candidates.get(0).userHandle)
433                 && isNoFilteringPropertyConfiguredForUser(userId)) {
434             intent.prepareToLeaveUser(userId);
435         }
436         return new QueryIntentActivitiesResult(sortResult, addInstant, candidates);
437     }
438     /**
439      * It filters and combines results from current and cross profile based on domain priority.
440      * @param computer {@link Computer} instance
441      * @param intent request
442      * @param matchFlags flags for intent request
443      * @param candidates resolveInfos from current profile
444      * @param crossProfileCandidates crossProfileDomainInfos from cross profile, it have ResolveInfo
445      * @param userId user id of source user
446      * @param areWebInstantAppsDisabled true if web instant apps are disabled
447      * @param resolveForStart true if intent is for resolution
448      * @param pkgSettingFunction function to find PackageStateInternal for given package
449      * @return list of ResolveInfo
450      */
filterCandidatesWithDomainPreferredActivitiesLPr(Computer computer, Intent intent, long matchFlags, List<ResolveInfo> candidates, List<CrossProfileDomainInfo> crossProfileCandidates, int userId, boolean areWebInstantAppsDisabled, boolean resolveForStart, Function<String, PackageStateInternal> pkgSettingFunction)451     private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Computer computer,
452             Intent intent, long matchFlags, List<ResolveInfo> candidates,
453             List<CrossProfileDomainInfo> crossProfileCandidates, int userId,
454             boolean areWebInstantAppsDisabled, boolean resolveForStart,
455             Function<String, PackageStateInternal> pkgSettingFunction) {
456         final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0;
457 
458         if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
459             Slog.v(TAG, "Filtering results with preferred activities. Candidates count: "
460                     + candidates.size());
461         }
462 
463         final List<ResolveInfo> result =
464                 filterCandidatesWithDomainPreferredActivitiesLPrBody(computer, intent, matchFlags,
465                         candidates, crossProfileCandidates, userId, areWebInstantAppsDisabled,
466                         debug, resolveForStart, pkgSettingFunction);
467 
468         if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
469             Slog.v(TAG, "Filtered results with preferred activities. New candidates count: "
470                     + result.size());
471             for (ResolveInfo info : result) {
472                 Slog.v(TAG, " + " + info.activityInfo);
473             }
474         }
475         return result;
476     }
477 
478     /**
479      * Filters candidates satisfying domain criteria.
480      * @param computer {@link Computer} instance
481      * @param intent request
482      * @param matchFlags flags for intent request
483      * @param candidates resolveInfos from current profile
484      * @param crossProfileCandidates crossProfileDomainInfos from cross profile, it have ResolveInfo
485      * @param userId user id of source user
486      * @param areWebInstantAppsDisabled true if web instant apps are disabled
487      * @param debug true if resolution logs needed to be printed
488      * @param resolveForStart true if intent is for resolution
489      * @param pkgSettingFunction function to find PackageStateInternal for given package
490      * @return list of resolve infos
491      */
filterCandidatesWithDomainPreferredActivitiesLPrBody( Computer computer, Intent intent, long matchFlags, List<ResolveInfo> candidates, List<CrossProfileDomainInfo> crossProfileCandidates, int userId, boolean areWebInstantAppsDisabled, boolean debug, boolean resolveForStart, Function<String, PackageStateInternal> pkgSettingFunction)492     private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(
493             Computer computer, Intent intent, long matchFlags, List<ResolveInfo> candidates,
494             List<CrossProfileDomainInfo> crossProfileCandidates, int userId,
495             boolean areWebInstantAppsDisabled, boolean debug, boolean resolveForStart,
496             Function<String, PackageStateInternal> pkgSettingFunction) {
497         final ArrayList<ResolveInfo> result = new ArrayList<>();
498         final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
499         final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
500 
501         // Blocking instant apps is usually done in applyPostResolutionFilter, but since
502         // domain verification can resolve to a single result, which can be an instant app,
503         // it will then be filtered to an empty list in that method. Instead, do blocking
504         // here so that instant apps can be ignored for approval filtering and a lower
505         // priority result chosen instead.
506         final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled;
507 
508         final int count = candidates.size();
509         // First, try to use approved apps.
510         for (int n = 0; n < count; n++) {
511             ResolveInfo info = candidates.get(n);
512             if (blockInstant && (info.isInstantAppAvailable
513                     || computer.isInstantAppInternal(info.activityInfo.packageName, userId,
514                     Process.SYSTEM_UID))) {
515                 continue;
516             }
517 
518             // Add to the special match all list (Browser use case)
519             if (info.handleAllWebDataURI) {
520                 matchAllList.add(info);
521             } else {
522                 undefinedList.add(info);
523             }
524         }
525 
526         // We'll want to include browser possibilities in a few cases
527         boolean includeBrowser = false;
528 
529         /**
530          * Grouping CrossProfileDomainInfo based on target user
531          */
532         SparseArray<List<CrossProfileDomainInfo>> categorizeResolveInfoByTargetUser =
533                 new SparseArray<>();
534         if (crossProfileCandidates != null && !crossProfileCandidates.isEmpty()) {
535             for (int index = 0; index < crossProfileCandidates.size(); index++) {
536                 CrossProfileDomainInfo crossProfileDomainInfo = crossProfileCandidates.get(index);
537                 if (!categorizeResolveInfoByTargetUser
538                         .contains(crossProfileDomainInfo.mTargetUserId)) {
539                     categorizeResolveInfoByTargetUser.put(crossProfileDomainInfo.mTargetUserId,
540                             new ArrayList<>());
541                 }
542                 categorizeResolveInfoByTargetUser.get(crossProfileDomainInfo.mTargetUserId)
543                         .add(crossProfileDomainInfo);
544             }
545         }
546 
547         if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) {
548             result.addAll(undefinedList);
549 
550             // calling cross profile strategy to filter corresponding results
551             result.addAll(filterCrossProfileCandidatesWithDomainPreferredActivities(computer,
552                     intent, matchFlags, categorizeResolveInfoByTargetUser, userId,
553                     DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE, resolveForStart));
554             includeBrowser = true;
555         } else {
556             Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
557                     .filterToApprovedApp(intent, undefinedList, userId, pkgSettingFunction);
558             List<ResolveInfo> approvedInfos = infosAndLevel.first;
559             Integer highestApproval = infosAndLevel.second;
560 
561             // If no apps are approved for the domain, resolve only to browsers
562             if (approvedInfos.isEmpty()) {
563                 includeBrowser = true;
564                 // calling cross profile strategy to filter corresponding results
565                 result.addAll(filterCrossProfileCandidatesWithDomainPreferredActivities(computer,
566                         intent, matchFlags, categorizeResolveInfoByTargetUser, userId,
567                         DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE, resolveForStart));
568             } else {
569                 result.addAll(approvedInfos);
570 
571                 // If the other profile has an app that's higher approval, add it
572                 // calling cross profile strategy to filter corresponding results
573                 result.addAll(filterCrossProfileCandidatesWithDomainPreferredActivities(computer,
574                         intent, matchFlags, categorizeResolveInfoByTargetUser, userId,
575                         highestApproval, resolveForStart));
576             }
577         }
578 
579         if (includeBrowser) {
580             // Also add browsers (all of them or only the default one)
581             if (DEBUG_DOMAIN_VERIFICATION) {
582                 Slog.v(TAG, " ...including browsers in candidate set");
583             }
584             if ((matchFlags & MATCH_ALL) != 0) {
585                 result.addAll(matchAllList);
586             } else {
587                 // Browser/generic handling case. If there's a default browser, go straight
588                 // to that (but only if there is no other higher-priority match).
589                 final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(
590                         userId);
591                 int maxMatchPrio = 0;
592                 ResolveInfo defaultBrowserMatch = null;
593                 final int numCandidates = matchAllList.size();
594                 for (int n = 0; n < numCandidates; n++) {
595                     ResolveInfo info = matchAllList.get(n);
596                     // track the highest overall match priority...
597                     if (info.priority > maxMatchPrio) {
598                         maxMatchPrio = info.priority;
599                     }
600                     // ...and the highest-priority default browser match
601                     if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) {
602                         if (defaultBrowserMatch == null
603                                 || (defaultBrowserMatch.priority < info.priority)) {
604                             if (debug) {
605                                 Slog.v(TAG, "Considering default browser match " + info);
606                             }
607                             defaultBrowserMatch = info;
608                         }
609                     }
610                 }
611                 if (defaultBrowserMatch != null
612                         && defaultBrowserMatch.priority >= maxMatchPrio
613                         && !TextUtils.isEmpty(defaultBrowserPackageName)) {
614                     if (debug) {
615                         Slog.v(TAG, "Default browser match " + defaultBrowserMatch);
616                     }
617                     result.add(defaultBrowserMatch);
618                 } else {
619                     result.addAll(matchAllList);
620                 }
621             }
622 
623             // If there is nothing selected, add all candidates
624             if (result.size() == 0) {
625                 result.addAll(candidates);
626             }
627         }
628         return result;
629     }
630 
631     /**
632      * Filter cross profile results by calling their respective strategy
633      * @param computer {@link Computer} instance
634      * @param intent request
635      * @param flags for intent request
636      * @param categorizeResolveInfoByTargetUser group of targetuser and its corresponding
637      *                                          CrossProfileDomainInfos
638      * @param sourceUserId user id for intent
639      * @param highestApprovalLevel domain approval level
640      * @param resolveForStart true if intent is for resolution
641      * @return list of ResolveInfos
642      */
filterCrossProfileCandidatesWithDomainPreferredActivities( Computer computer, Intent intent, long flags, SparseArray<List<CrossProfileDomainInfo>> categorizeResolveInfoByTargetUser, int sourceUserId, int highestApprovalLevel, boolean resolveForStart)643     private List<ResolveInfo> filterCrossProfileCandidatesWithDomainPreferredActivities(
644             Computer computer, Intent intent, long flags, SparseArray<List<CrossProfileDomainInfo>>
645             categorizeResolveInfoByTargetUser, int sourceUserId, int highestApprovalLevel,
646             boolean resolveForStart) {
647 
648         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
649 
650         for (int index = 0; index < categorizeResolveInfoByTargetUser.size(); index++) {
651 
652             // if resolve info does not target user or has default value, add results as they are.
653             if (categorizeResolveInfoByTargetUser.keyAt(index) == -2) {
654                 crossProfileDomainInfos.addAll(categorizeResolveInfoByTargetUser.valueAt(index));
655             } else {
656                 // finding cross profile strategy based on source and target user
657                 CrossProfileResolver crossProfileIntentResolver =
658                         chooseCrossProfileResolver(computer, sourceUserId,
659                                 categorizeResolveInfoByTargetUser.keyAt(index), resolveForStart,
660                                 flags);
661                 // if strategy is available call it and add its filtered results
662                 if (crossProfileIntentResolver != null) {
663                     crossProfileDomainInfos.addAll(crossProfileIntentResolver
664                             .filterResolveInfoWithDomainPreferredActivity(intent,
665                                     categorizeResolveInfoByTargetUser.valueAt(index),
666                                     flags, sourceUserId, categorizeResolveInfoByTargetUser
667                                             .keyAt(index), highestApprovalLevel));
668                 } else {
669                     // if strategy is not available call it, add the results
670                     crossProfileDomainInfos.addAll(categorizeResolveInfoByTargetUser
671                             .valueAt(index));
672                 }
673             }
674         }
675         return resolveInfoFromCrossProfileDomainInfo(crossProfileDomainInfos);
676     }
677 
678     /**
679      * Extract ResolveInfo from CrossProfileDomainInfo
680      * @param crossProfileDomainInfos cross profile results
681      * @return list of ResolveInfo
682      */
resolveInfoFromCrossProfileDomainInfo(List<CrossProfileDomainInfo> crossProfileDomainInfos)683     private List<ResolveInfo> resolveInfoFromCrossProfileDomainInfo(List<CrossProfileDomainInfo>
684             crossProfileDomainInfos) {
685         List<ResolveInfo> resolveInfoList = new ArrayList<>();
686 
687         for (int infoIndex = 0; infoIndex < crossProfileDomainInfos.size(); infoIndex++) {
688             resolveInfoList.add(crossProfileDomainInfos.get(infoIndex).mResolveInfo);
689         }
690 
691         return resolveInfoList;
692     }
693 
694     /**
695      * @param crossProfileDomainInfos list of cross profile domain info in descending priority order
696      * @return if the list contains a resolve info with non-negative priority
697      */
hasNonNegativePriority(List<CrossProfileDomainInfo> crossProfileDomainInfos)698     private boolean hasNonNegativePriority(List<CrossProfileDomainInfo> crossProfileDomainInfos) {
699         return crossProfileDomainInfos.size() > 0
700                 && crossProfileDomainInfos.get(0).mResolveInfo != null
701                 && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0;
702     }
703 
704     /**
705      * Deciding if we need to user {@link NoFilteringResolver} based on source and target user
706      * @param sourceUserId id of initiating user
707      * @param targetUserId id of cross profile linked user
708      * @return true if {@link NoFilteringResolver} is applicable in this case.
709      */
shouldUseNoFilteringResolver(@serIdInt int sourceUserId, @UserIdInt int targetUserId)710     private boolean shouldUseNoFilteringResolver(@UserIdInt int sourceUserId,
711             @UserIdInt int targetUserId) {
712         return isNoFilteringPropertyConfiguredForUser(sourceUserId)
713                 || isNoFilteringPropertyConfiguredForUser(targetUserId);
714     }
715 
716     /**
717      * Check if configure property for cross profile intent resolution strategy for user is
718      * {@link UserProperties#CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING}
719      * @param userId id of user to check for property
720      * @return true if user have property set to
721      * {@link UserProperties#CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING}
722      */
isNoFilteringPropertyConfiguredForUser(@serIdInt int userId)723     private boolean isNoFilteringPropertyConfiguredForUser(@UserIdInt int userId) {
724         if (!mUserManager.isProfile(userId)) return false;
725         UserProperties userProperties = mUserManagerInternal.getUserProperties(userId);
726         if (userProperties == null) return false;
727 
728         return userProperties.getCrossProfileIntentResolutionStrategy()
729                 == UserProperties.CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING;
730     }
731 }
732