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