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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.content.pm.UserInfo; 26 import android.util.SparseBooleanArray; 27 28 import com.android.internal.app.IntentForwarderActivity; 29 import com.android.internal.util.CollectionUtils; 30 import com.android.server.pm.pkg.PackageStateInternal; 31 import com.android.server.pm.resolution.ComponentResolverApi; 32 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 import java.util.function.Function; 38 39 /** 40 * Cross profile resolver used as default strategy. Primary known use-case for this resolver is 41 * work/managed profile . 42 */ 43 public final class DefaultCrossProfileResolver extends CrossProfileResolver { 44 45 private final DomainVerificationManagerInternal mDomainVerificationManager; 46 47 DefaultCrossProfileResolver(ComponentResolverApi componentResolver, UserManagerService userManager, DomainVerificationManagerInternal domainVerificationManager)48 public DefaultCrossProfileResolver(ComponentResolverApi componentResolver, 49 UserManagerService userManager, 50 DomainVerificationManagerInternal domainVerificationManager) { 51 super(componentResolver, userManager); 52 mDomainVerificationManager = domainVerificationManager; 53 } 54 55 /** 56 * This is Default resolution strategy primarily used by Work Profile. 57 * First, it checks if we have to skip source profile and just resolve in target profile. If 58 * yes, then it will return result from target profile. 59 * Secondly, it find specific resolve infos in target profile 60 * Thirdly, if it is web intent it finds if parent can also resolve it. The results of this 61 * stage gets higher priority as compared to second stage. 62 * 63 * @param computer ComputerEngine instance that would be needed by ComponentResolverApi 64 * @param intent request 65 * @param resolvedType the MIME data type of intent request 66 * @param userId source/initiating user 67 * @param targetUserId target user id 68 * @param flags of intent request 69 * @param pkgName the application package name this Intent is limited to. 70 * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user, 71 * targeting the targetUserId 72 * @param hasNonNegativePriorityResult if source have any non-negative(active and valid) 73 * resolveInfo in their profile. 74 * @param pkgSettingFunction function to find PackageStateInternal for given package 75 * @return list of {@link CrossProfileDomainInfo} 76 */ 77 @Override resolveIntent(Computer computer, Intent intent, String resolvedType, int userId, int targetUserId, long flags, String pkgName, List<CrossProfileIntentFilter> matchingFilters, boolean hasNonNegativePriorityResult, Function<String, PackageStateInternal> pkgSettingFunction)78 public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent, 79 String resolvedType, int userId, int targetUserId, 80 long flags, String pkgName, List<CrossProfileIntentFilter> matchingFilters, 81 boolean hasNonNegativePriorityResult, 82 Function<String, PackageStateInternal> pkgSettingFunction) { 83 84 List<CrossProfileDomainInfo> xpResult = new ArrayList<>(); 85 if (pkgName != null) return xpResult; 86 CrossProfileDomainInfo skipProfileInfo = querySkipCurrentProfileIntents(computer, 87 matchingFilters, intent, resolvedType, flags, userId, pkgSettingFunction); 88 89 if (skipProfileInfo != null) { 90 xpResult.add(skipProfileInfo); 91 return filterIfNotSystemUser(xpResult, userId); 92 } 93 94 CrossProfileDomainInfo specificXpInfo = queryCrossProfileIntents(computer, 95 matchingFilters, intent, resolvedType, flags, userId, 96 hasNonNegativePriorityResult, pkgSettingFunction); 97 98 if (intent.hasWebURI()) { 99 CrossProfileDomainInfo generalXpInfo = null; 100 final UserInfo parent = getProfileParent(userId); 101 if (parent != null) { 102 generalXpInfo = computer.getCrossProfileDomainPreferredLpr(intent, resolvedType, 103 flags, userId, parent.id); 104 } 105 CrossProfileDomainInfo prioritizedXpInfo = 106 generalXpInfo != null ? generalXpInfo : specificXpInfo; 107 if (prioritizedXpInfo != null) { 108 xpResult.add(prioritizedXpInfo); 109 } 110 } else if (specificXpInfo != null) { 111 xpResult.add(specificXpInfo); 112 } 113 114 return xpResult; 115 } 116 117 /** 118 * Filters out CrossProfileDomainInfo if it does not have higher approval level as compared to 119 * given approval level 120 * @param intent request 121 * @param crossProfileDomainInfos resolved in target user 122 * @param flags for intent resolution 123 * @param sourceUserId source user 124 * @param targetUserId target user 125 * @param highestApprovalLevel highest level of domain approval 126 * @return filtered list of CrossProfileDomainInfo 127 */ 128 @Override filterResolveInfoWithDomainPreferredActivity( Intent intent, List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId, int targetUserId, int highestApprovalLevel)129 public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity( 130 Intent intent, List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, 131 int sourceUserId, int targetUserId, int highestApprovalLevel) { 132 133 List<CrossProfileDomainInfo> filteredCrossProfileDomainInfos = new ArrayList<>(); 134 135 if (crossProfileDomainInfos != null && !crossProfileDomainInfos.isEmpty()) { 136 for (int index = 0; index < crossProfileDomainInfos.size(); index++) { 137 CrossProfileDomainInfo crossProfileDomainInfo = crossProfileDomainInfos.get(index); 138 if (crossProfileDomainInfo.mHighestApprovalLevel > highestApprovalLevel) { 139 filteredCrossProfileDomainInfos.add(crossProfileDomainInfo); 140 } 141 } 142 } 143 144 return filteredCrossProfileDomainInfos; 145 } 146 147 /** 148 * If current/source profile needs to be skipped, returns CrossProfileDomainInfo from target 149 * profile. If any of the matchingFilters have flag {@link PackageManager#SKIP_CURRENT_PROFILE} 150 * set that would signify that current profile needs to be skipped. 151 * @param computer ComputerEngine instance that would be needed by ComponentResolverApi 152 * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user, 153 * targeting the targetUserId 154 * @param intent request 155 * @param resolvedType the MIME data type of intent request 156 * @param flags for intent resolution 157 * @param sourceUserId source user 158 * @param pkgSettingFunction function to find PackageStateInternal for given package 159 * @return CrossProfileDomainInfo if current profile needs to be skipped, else null 160 */ 161 @Nullable querySkipCurrentProfileIntents(Computer computer, List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, long flags, int sourceUserId, Function<String, PackageStateInternal> pkgSettingFunction)162 private CrossProfileDomainInfo querySkipCurrentProfileIntents(Computer computer, 163 List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, 164 long flags, int sourceUserId, 165 Function<String, PackageStateInternal> pkgSettingFunction) { 166 if (matchingFilters != null) { 167 int size = matchingFilters.size(); 168 for (int i = 0; i < size; i++) { 169 CrossProfileIntentFilter filter = matchingFilters.get(i); 170 if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) { 171 // Checking if there are activities in the target user that can handle the 172 // intent. 173 CrossProfileDomainInfo info = createForwardingResolveInfo(computer, filter, 174 intent, resolvedType, flags, sourceUserId, pkgSettingFunction); 175 if (info != null) { 176 return info; 177 } 178 } 179 } 180 } 181 return null; 182 } 183 184 /** 185 * Resolves and returns CrossProfileDomainInfo(ForwardingResolveInfo) from target profile if 186 * current profile should be skipped when there is no result or if target profile should not 187 * be skipped. 188 * 189 * @param computer ComputerEngine instance that would be needed by ComponentResolverApi 190 * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user, 191 * targeting the targetUserId 192 * @param intent request 193 * @param resolvedType the MIME data type of intent request 194 * @param flags for intent resolution 195 * @param sourceUserId source user 196 * @param matchInCurrentProfile true if current/source profile have some non-negative 197 * resolveInfo 198 * @param pkgSettingFunction function to find PackageStateInternal for given package 199 * @return CrossProfileDomainInfo returns forwarding intent resolver in CrossProfileDomainInfo. 200 * It returns null if there are no matching filters or no valid/active activity available 201 */ 202 @Nullable queryCrossProfileIntents(Computer computer, List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, long flags, int sourceUserId, boolean matchInCurrentProfile, Function<String, PackageStateInternal> pkgSettingFunction)203 private CrossProfileDomainInfo queryCrossProfileIntents(Computer computer, 204 List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, 205 long flags, int sourceUserId, boolean matchInCurrentProfile, 206 Function<String, PackageStateInternal> pkgSettingFunction) { 207 if (matchingFilters == null) { 208 return null; 209 } 210 // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and 211 // match the same intent. For performance reasons, it is better not to 212 // run queryIntent twice for the same userId 213 SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray(); 214 215 CrossProfileDomainInfo resultInfo = null; 216 217 int size = matchingFilters.size(); 218 for (int i = 0; i < size; i++) { 219 CrossProfileIntentFilter filter = matchingFilters.get(i); 220 int targetUserId = filter.getTargetUserId(); 221 boolean skipCurrentProfile = 222 (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0; 223 boolean skipCurrentProfileIfNoMatchFound = 224 (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0; 225 if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId) 226 && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) { 227 // Checking if there are activities in the target user that can handle the 228 // intent. 229 CrossProfileDomainInfo info = createForwardingResolveInfo(computer, filter, intent, 230 resolvedType, flags, sourceUserId, pkgSettingFunction); 231 if (info != null) { 232 resultInfo = info; 233 break; 234 } 235 alreadyTriedUserIds.put(targetUserId, true); 236 } 237 } 238 239 if (resultInfo == null) { 240 return null; 241 } 242 243 ResolveInfo forwardingResolveInfo = resultInfo.mResolveInfo; 244 if (!isUserEnabled(forwardingResolveInfo.targetUserId)) { 245 return null; 246 } 247 248 List<CrossProfileDomainInfo> filteredResult = 249 filterIfNotSystemUser(Collections.singletonList(resultInfo), sourceUserId); 250 if (filteredResult.isEmpty()) { 251 return null; 252 } 253 254 return resultInfo; 255 } 256 257 /** 258 * Creates a Forwarding Resolve Info, used when we have to signify that target profile's 259 * resolveInfo should be considered without providing list of resolve infos. 260 * @param computer ComputerEngine instance that would be needed by ComponentResolverApi 261 * @param filter {@link CrossProfileIntentFilter} configured for source user, 262 * targeting the targetUserId 263 * @param intent request 264 * @param resolvedType the MIME data type of intent request 265 * @param flags for intent resolution 266 * @param sourceUserId source user 267 * @return CrossProfileDomainInfo whose ResolveInfo is forwarding. It would be resolved by 268 * {@link IntentForwarderActivity}. It returns null if there are no valid/active activities 269 */ 270 @Nullable createForwardingResolveInfo(Computer computer, @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent, @Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId, @NonNull Function<String, PackageStateInternal> pkgSettingFunction)271 protected CrossProfileDomainInfo createForwardingResolveInfo(Computer computer, 272 @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent, 273 @Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, 274 int sourceUserId, @NonNull Function<String, PackageStateInternal> pkgSettingFunction) { 275 int targetUserId = filter.getTargetUserId(); 276 if (!isUserEnabled(targetUserId)) { 277 return null; 278 } 279 280 List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent, 281 resolvedType, flags, targetUserId); 282 if (CollectionUtils.isEmpty(resultTargetUser)) { 283 return null; 284 } 285 286 ResolveInfo forwardingInfo = null; 287 for (int i = resultTargetUser.size() - 1; i >= 0; i--) { 288 ResolveInfo targetUserResolveInfo = resultTargetUser.get(i); 289 if ((targetUserResolveInfo.activityInfo.applicationInfo.flags 290 & ApplicationInfo.FLAG_SUSPENDED) == 0) { 291 forwardingInfo = computer.createForwardingResolveInfoUnchecked(filter, sourceUserId, 292 targetUserId); 293 break; 294 } 295 } 296 297 if (forwardingInfo == null) { 298 // If all the matches in the target profile are suspended, return null. 299 return null; 300 } 301 302 int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; 303 304 int size = resultTargetUser.size(); 305 for (int i = 0; i < size; i++) { 306 ResolveInfo riTargetUser = resultTargetUser.get(i); 307 if (riTargetUser.handleAllWebDataURI) { 308 continue; 309 } 310 String packageName = riTargetUser.activityInfo.packageName; 311 PackageStateInternal ps = pkgSettingFunction.apply(packageName); 312 if (ps == null) { 313 continue; 314 } 315 highestApprovalLevel = Math.max(highestApprovalLevel, mDomainVerificationManager 316 .approvalLevelForDomain(ps, intent, flags, targetUserId)); 317 } 318 319 return new CrossProfileDomainInfo(forwardingInfo, highestApprovalLevel, targetUserId); 320 } 321 } 322