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