From 9edbda18df025527e18614cf0c45d538a27af30f Mon Sep 17 00:00:00 2001 From: Nicolas Prevot Date: Wed, 17 Jun 2015 11:09:48 -0700 Subject: [PATCH] Allow cross-profile app linking from work to personal. If the profile owner sets ALLOW_PARENT_APP_LINKING: ACTION_VIEW, scheme http/https intents sent from the work profile can be resolved by personal apps if they specify a host. BUG:21701782 Change-Id: I372e2405345539eac9d6b4fb08def6bf84da14a6 --- api/current.txt | 1 + api/system-current.txt | 1 + core/java/android/os/UserManager.java | 16 +++ .../server/pm/PackageManagerService.java | 132 ++++++++++++++++-- .../android/server/pm/UserManagerService.java | 2 + 5 files changed, 141 insertions(+), 11 deletions(-) diff --git a/api/current.txt b/api/current.txt index 90e44e775dba5..cd02f6032a3be 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23665,6 +23665,7 @@ package android.os { method public deprecated void setUserRestriction(java.lang.String, boolean); method public deprecated void setUserRestrictions(android.os.Bundle); method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); + field public static final java.lang.String ALLOW_PARENT_APP_LINKING = "allow_parent_app_linking"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; diff --git a/api/system-current.txt b/api/system-current.txt index 8a99eaf49aac0..0944006329866 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -25609,6 +25609,7 @@ package android.os { method public deprecated void setUserRestriction(java.lang.String, boolean); method public deprecated void setUserRestrictions(android.os.Bundle); method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); + field public static final java.lang.String ALLOW_PARENT_APP_LINKING = "allow_parent_app_linking"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 00350ec92a367..9770941babc49 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -448,6 +448,22 @@ public class UserManager { */ public static final String DISALLOW_RECORD_AUDIO = "no_record_audio"; + /** + * This user restriction has an effect only in a managed profile. + * If set: + * Intent filters of activities in the parent profile with action + * {@link android.content.Intent#ACTION_VIEW}, + * category {@link android.content.Intent#CATEGORY_BROWSABLE}, scheme http or https, and which + * define a host can handle intents from the managed profile. + * The default value is false. + * + *

Key for user restrictions. + *

Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String ALLOW_PARENT_APP_LINKING = "allow_parent_app_linking"; + /** * Application restriction key that is used to indicate the pending arrival * of real restrictions for the app. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 06e27fc55f314..15c2c0b72a3a3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4085,9 +4085,26 @@ public class PackageManagerService extends IPackageManager.Stub { if (matches.get(i).getTargetUserId() == targetUserId) return true; } } + if (hasWebURI(intent)) { + // cross-profile app linking works only towards the parent. + final UserInfo parent = getProfileParent(sourceUserId); + synchronized(mPackages) { + return getCrossProfileDomainPreferredLpr(intent, resolvedType, 0, sourceUserId, + parent.id) != null; + } + } return false; } + private UserInfo getProfileParent(int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return sUserManager.getProfileParent(userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + private List getMatchingCrossProfileIntentFilters(Intent intent, String resolvedType, int userId) { CrossProfileIntentResolver resolver = mSettings.mCrossProfileIntentResolvers.get(userId); @@ -4128,11 +4145,11 @@ public class PackageManagerService extends IPackageManager.Stub { List matchingFilters = getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); // Check for results that need to skip the current profile. - ResolveInfo resolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, + ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, resolvedType, flags, userId); - if (resolveInfo != null && isUserEnabled(resolveInfo.targetUserId)) { + if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) { List result = new ArrayList(1); - result.add(resolveInfo); + result.add(xpResolveInfo); return filterIfNotPrimaryUser(result, userId); } @@ -4141,15 +4158,36 @@ public class PackageManagerService extends IPackageManager.Stub { intent, resolvedType, flags, userId); // Check for cross profile results. - resolveInfo = queryCrossProfileIntents( + xpResolveInfo = queryCrossProfileIntents( matchingFilters, intent, resolvedType, flags, userId); - if (resolveInfo != null && isUserEnabled(resolveInfo.targetUserId)) { - result.add(resolveInfo); + if (xpResolveInfo != null && isUserEnabled(xpResolveInfo.targetUserId)) { + result.add(xpResolveInfo); Collections.sort(result, mResolvePrioritySorter); } result = filterIfNotPrimaryUser(result, userId); - if (result.size() > 1 && hasWebURI(intent)) { - return filterCandidatesWithDomainPreferedActivitiesLPr(flags, result); + if (hasWebURI(intent)) { + CrossProfileDomainInfo xpDomainInfo = null; + final UserInfo parent = getProfileParent(userId); + if (parent != null) { + xpDomainInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType, + flags, userId, parent.id); + } + if (xpDomainInfo != null) { + if (xpResolveInfo != null) { + // If we didn't remove it, the cross-profile ResolveInfo would be twice + // in the result. + result.remove(xpResolveInfo); + } + if (result.size() == 0) { + result.add(xpDomainInfo.resolveInfo); + return result; + } + } else if (result.size() <= 1) { + return result; + } + result = filterCandidatesWithDomainPreferredActivitiesLPr(flags, result, + xpDomainInfo); + Collections.sort(result, mResolvePrioritySorter); } return result; } @@ -4164,6 +4202,67 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private static class CrossProfileDomainInfo { + /* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */ + ResolveInfo resolveInfo; + /* Best domain verification status of the activities found in the other profile */ + int bestDomainVerificationStatus; + } + + private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, + String resolvedType, int flags, int sourceUserId, int parentUserId) { + if (!sUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_APP_LINKING, + sourceUserId)) { + return null; + } + List resultTargetUser = mActivities.queryIntent(intent, + resolvedType, flags, parentUserId); + + if (resultTargetUser == null || resultTargetUser.isEmpty()) { + return null; + } + CrossProfileDomainInfo result = null; + int size = resultTargetUser.size(); + for (int i = 0; i < size; i++) { + ResolveInfo riTargetUser = resultTargetUser.get(i); + // Intent filter verification is only for filters that specify a host. So don't return + // those that handle all web uris. + if (riTargetUser.handleAllWebDataURI) { + continue; + } + String packageName = riTargetUser.activityInfo.packageName; + PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps == null) { + continue; + } + int status = getDomainVerificationStatusLPr(ps, parentUserId); + if (result == null) { + result = new CrossProfileDomainInfo(); + result.resolveInfo = + createForwardingResolveInfo(null, sourceUserId, parentUserId); + result.bestDomainVerificationStatus = status; + } else { + result.bestDomainVerificationStatus = bestDomainVerificationStatus(status, + result.bestDomainVerificationStatus); + } + } + return result; + } + + /** + * Verification statuses are ordered from the worse to the best, except for + * INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, which is the worse. + */ + private int bestDomainVerificationStatus(int status1, int status2) { + if (status1 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + return status2; + } + if (status2 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + return status1; + } + return (int) MathUtils.max(status1, status2); + } + private boolean isUserEnabled(int userId) { long callingId = Binder.clearCallingIdentity(); try { @@ -4203,8 +4302,8 @@ public class PackageManagerService extends IPackageManager.Stub { return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); } - private List filterCandidatesWithDomainPreferedActivitiesLPr( - int flags, List candidates) { + private List filterCandidatesWithDomainPreferredActivitiesLPr( + int flags, List candidates, CrossProfileDomainInfo xpDomainInfo) { if (DEBUG_PREFERRED) { Slog.v("TAG", "Filtering results with prefered activities. Candidates count: " + candidates.size()); @@ -4244,12 +4343,23 @@ public class PackageManagerService extends IPackageManager.Stub { } } } - // First try to add the "always" if there is any + // First try to add the "always" resolution for the current user if there is any if (alwaysList.size() > 0) { result.addAll(alwaysList); + // if there is an "always" for the parent user, add it. + } else if (xpDomainInfo != null && xpDomainInfo.bestDomainVerificationStatus + == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { + result.add(xpDomainInfo.resolveInfo); } else { // Add all undefined Apps as we want them to appear in the Disambiguation dialog. result.addAll(undefinedList); + if (xpDomainInfo != null && ( + xpDomainInfo.bestDomainVerificationStatus + == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED + || xpDomainInfo.bestDomainVerificationStatus + == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK)) { + result.add(xpDomainInfo.resolveInfo); + } // Also add Browsers (all of them or only the default one) if ((flags & MATCH_ALL) != 0) { result.addAll(matchAllList); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 4082ff3a957ae..8a8d2a6b104f3 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -972,6 +972,7 @@ public class UserManagerService extends IUserManager.Stub { writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_BEAM); writeBoolean(serializer, restrictions, UserManager.DISALLOW_WALLPAPER); writeBoolean(serializer, restrictions, UserManager.DISALLOW_SAFE_BOOT); + writeBoolean(serializer, restrictions, UserManager.ALLOW_PARENT_APP_LINKING); serializer.endTag(null, TAG_RESTRICTIONS); } @@ -1103,6 +1104,7 @@ public class UserManagerService extends IUserManager.Stub { readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_BEAM); readBoolean(parser, restrictions, UserManager.DISALLOW_WALLPAPER); readBoolean(parser, restrictions, UserManager.DISALLOW_SAFE_BOOT); + readBoolean(parser, restrictions, UserManager.ALLOW_PARENT_APP_LINKING); } private void readBoolean(XmlPullParser parser, Bundle restrictions,