From e236bd48a3605df6b047b3259c5d811e2c7593f8 Mon Sep 17 00:00:00 2001 From: Sarup Dalwani Date: Thu, 18 Aug 2022 18:42:18 +0000 Subject: [PATCH] Adding iterative calls for intent resolution in chained profiles To facilitate calls for multiple chained profiles, adding Breadth First Search to cross profile resolution. Also adding flag FLAG_ALLOW_CHAINED_RESOLUTION to allow resolution across chained profiles. If flag is not set, the traversal would stop at that node, if flag is set it will iteratively call to more neighbours. Bug: 242885222 Test: Manually tested for work -> owner -> clone profile for Intent.ACTION_SEND, I was able to send message to clone profile's whatsapp from work profile. Change-Id: Idbcd36a0a6ef836b2e83fffb9b4ef0d962f71183 --- .../server/pm/CrossProfileIntentFilter.java | 9 + .../pm/CrossProfileIntentResolverEngine.java | 192 ++++++++++++------ 2 files changed, 144 insertions(+), 57 deletions(-) diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java index 798217f226cf8..04bd135cc77e4 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java @@ -49,6 +49,15 @@ class CrossProfileIntentFilter extends WatchedIntentFilter { //flag to decide if intent needs to be resolved cross profile if pkgName is already defined public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008; + /* + This flag, denotes if further cross profile resolution is allowed, e.g. if profile#0 is linked + to profile#1 and profile#2 . When intent resolution from profile#1 is started we resolve it in + profile#1 and profile#0. The profile#0 is also linked to profile#2, we will only resolve in + profile#2 if CrossProfileIntentFilter between profile#1 and profile#0 have set flag + FLAG_ALLOW_CHAINED_RESOLUTION. + */ + public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010; + private static final String TAG = "CrossProfileIntentFilter"; /** diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java index 5ae4cab8e7a6d..4362956b3c094 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java @@ -36,14 +36,19 @@ import android.util.FeatureFlagUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.server.LocalServices; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationUtils; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Queue; +import java.util.Set; import java.util.function.Function; /** @@ -115,73 +120,111 @@ public class CrossProfileIntentResolverEngine { Intent intent, String resolvedType, int userId, long flags, String pkgName, boolean hasNonNegativePriorityResult, Function pkgSettingFunction) { - + Queue pendingUsers = new ArrayDeque<>(); + Set visitedUserIds = new HashSet<>(); + SparseBooleanArray hasNonNegativePriorityResultFromParent = new SparseBooleanArray(); + visitedUserIds.add(userId); + pendingUsers.add(userId); + hasNonNegativePriorityResultFromParent.put(userId, hasNonNegativePriorityResult); + UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); List crossProfileDomainInfos = new ArrayList<>(); + while (!pendingUsers.isEmpty()) { + int currentUserId = pendingUsers.poll(); + List matchingFilters = + computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, + currentUserId); - List matchingFilters = - computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); - - if (matchingFilters == null || matchingFilters.isEmpty()) { - /** if intent is web intent, checking if parent profile should handle the intent even - if there is no matching filter. The configuration is based on user profile - restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/ - if (intent.hasWebURI()) { - UserInfo parent = computer.getProfileParent(userId); - if (parent != null) { - CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer - .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, userId, - parent.id); - if (generalizedCrossProfileDomainInfo != null) { - crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo); + if (matchingFilters == null || matchingFilters.isEmpty()) { + /** if intent is web intent, checking if parent profile should handle the intent + * even if there is no matching filter. The configuration is based on user profile + * restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/ + if (currentUserId == userId && intent.hasWebURI()) { + UserInfo parent = computer.getProfileParent(currentUserId); + if (parent != null) { + CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer + .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, + currentUserId, parent.id); + if (generalizedCrossProfileDomainInfo != null) { + crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo); + } } } + continue; } - return crossProfileDomainInfos; - } - UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); - UserInfo sourceUserInfo = umInternal.getUserInfo(userId); + UserInfo sourceUserInfo = umInternal.getUserInfo(currentUserId); - // Grouping the CrossProfileIntentFilters based on targerId - SparseArray> crossProfileIntentFiltersByUser = - new SparseArray<>(); + // Grouping the CrossProfileIntentFilters based on targerId + SparseArray> crossProfileIntentFiltersByUser = + new SparseArray<>(); - for (int index = 0; index < matchingFilters.size(); index++) { - CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index); + for (int index = 0; index < matchingFilters.size(); index++) { + CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index); - if (!crossProfileIntentFiltersByUser - .contains(crossProfileIntentFilter.mTargetUserId)) { - crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId, - new ArrayList<>()); + if (!crossProfileIntentFiltersByUser + .contains(crossProfileIntentFilter.mTargetUserId)) { + crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId, + new ArrayList<>()); + } + crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId) + .add(crossProfileIntentFilter); } - crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId) - .add(crossProfileIntentFilter); - } - /* - For each target user, we would call their corresponding strategy - {@link CrossProfileResolver} to resolve intent in corresponding user - */ - for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) { + /* + For each target user, we would call their corresponding strategy + {@link CrossProfileResolver} to resolve intent in corresponding user + */ + for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) { - UserInfo targetUserInfo = umInternal.getUserInfo(crossProfileIntentFiltersByUser - .keyAt(index)); + int targetUserId = crossProfileIntentFiltersByUser.keyAt(index); - // Choosing strategy based on source and target user - CrossProfileResolver crossProfileResolver = - chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo); + //if user is already visited then skip resolution for particular user. + if (visitedUserIds.contains(targetUserId)) { + continue; + } + + UserInfo targetUserInfo = umInternal.getUserInfo(targetUserId); + + // Choosing strategy based on source and target user + CrossProfileResolver crossProfileResolver = + chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo); /* If {@link CrossProfileResolver} is available for source,target pair we will call it to get {@link CrossProfileDomainInfo}s from that user. */ - if (crossProfileResolver != null) { - List crossProfileInfos = crossProfileResolver - .resolveIntent(computer, intent, resolvedType, userId, - crossProfileIntentFiltersByUser.keyAt(index), flags, pkgName, - crossProfileIntentFiltersByUser.valueAt(index), - hasNonNegativePriorityResult, pkgSettingFunction); - crossProfileDomainInfos.addAll(crossProfileInfos); + if (crossProfileResolver != null) { + List crossProfileInfos = crossProfileResolver + .resolveIntent(computer, intent, resolvedType, currentUserId, + targetUserId, flags, pkgName, + crossProfileIntentFiltersByUser.valueAt(index), + hasNonNegativePriorityResultFromParent.get(currentUserId), + pkgSettingFunction); + crossProfileDomainInfos.addAll(crossProfileInfos); + + hasNonNegativePriorityResultFromParent.put(targetUserId, + hasNonNegativePriority(crossProfileInfos)); + + /* + Adding target user to queue if flag + {@link CrossProfileIntentFilter#FLAG_ALLOW_CHAINED_RESOLUTION} is set for any + {@link CrossProfileIntentFilter} + */ + boolean allowChainedResolution = false; + for (int filterIndex = 0; filterIndex < crossProfileIntentFiltersByUser + .valueAt(index).size(); filterIndex++) { + if ((CrossProfileIntentFilter + .FLAG_ALLOW_CHAINED_RESOLUTION & crossProfileIntentFiltersByUser + .valueAt(index).get(filterIndex).mFlags) != 0) { + allowChainedResolution = true; + break; + } + } + if (allowChainedResolution) { + pendingUsers.add(targetUserId); + } + visitedUserIds.add(targetUserId); + } } } @@ -237,7 +280,7 @@ public class CrossProfileIntentResolverEngine { /** * Returns true if we source user can reach target user for given intent. The source can - * directly or indirectly reach to target. This will perform depth first search to check if + * directly or indirectly reach to target. This will perform breadth first search to check if * source can reach target. * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi} * @param intent request @@ -251,13 +294,38 @@ public class CrossProfileIntentResolverEngine { @UserIdInt int targetUserId) { if (sourceUserId == targetUserId) return true; - List matches = - computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId); - if (matches != null) { - for (int index = 0; index < matches.size(); index++) { - CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index); - if (crossProfileIntentFilter.mTargetUserId == targetUserId) { - return true; + Queue pendingUsers = new ArrayDeque<>(); + Set visitedUserIds = new HashSet<>(); + visitedUserIds.add(sourceUserId); + pendingUsers.add(sourceUserId); + + while (!pendingUsers.isEmpty()) { + int currentUserId = pendingUsers.poll(); + + List matches = + computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, + currentUserId); + if (matches != null) { + for (int index = 0; index < matches.size(); index++) { + CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index); + if (crossProfileIntentFilter.mTargetUserId == targetUserId) { + return true; + } + if (visitedUserIds.contains(crossProfileIntentFilter.mTargetUserId)) { + continue; + } + + /* + If source cannot directly reach to target, we will add + CrossProfileIntentFilter.mTargetUserId user to queue to check if target user + can be reached via CrossProfileIntentFilter.mTargetUserId i.e. it can be + indirectly reached through chained/linked profiles. + */ + if ((CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION + & crossProfileIntentFilter.mFlags) != 0) { + pendingUsers.add(crossProfileIntentFilter.mTargetUserId); + visitedUserIds.add(crossProfileIntentFilter.mTargetUserId); + } } } } @@ -605,4 +673,14 @@ public class CrossProfileIntentResolverEngine { return resolveInfoList; } + + /** + * @param crossProfileDomainInfos list of cross profile domain info in descending priority order + * @return if the list contains a resolve info with non-negative priority + */ + private boolean hasNonNegativePriority(List crossProfileDomainInfos) { + return crossProfileDomainInfos.size() > 0 + && crossProfileDomainInfos.get(0).mResolveInfo != null + && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0; + } }