diff options
| author | 2015-06-17 11:09:48 -0700 | |
|---|---|---|
| committer | 2015-06-22 14:39:44 -0700 | |
| commit | 9edbda18df025527e18614cf0c45d538a27af30f (patch) | |
| tree | ec9366acabcae184e68031222a23f3eb8cf7fb47 | |
| parent | 49e11f805dcf5c8611e4bfbb115e56a22db4a396 (diff) | |
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
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/os/UserManager.java | 16 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 132 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserManagerService.java | 2 |
5 files changed, 141 insertions, 11 deletions
diff --git a/api/current.txt b/api/current.txt index 90e44e775dba..cd02f6032a3b 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 8a99eaf49aac..094400632986 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 00350ec92a36..9770941babc4 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -449,6 +449,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 <code>false</code>. + * + * <p/>Key for user restrictions. + * <p/>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 06e27fc55f31..15c2c0b72a3a 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<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent, String resolvedType, int userId) { CrossProfileIntentResolver resolver = mSettings.mCrossProfileIntentResolvers.get(userId); @@ -4128,11 +4145,11 @@ public class PackageManagerService extends IPackageManager.Stub { List<CrossProfileIntentFilter> 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<ResolveInfo> result = new ArrayList<ResolveInfo>(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<ResolveInfo> 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<ResolveInfo> filterCandidatesWithDomainPreferedActivitiesLPr( - int flags, List<ResolveInfo> candidates) { + private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr( + int flags, List<ResolveInfo> 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 4082ff3a957a..8a8d2a6b104f 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, |