summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nicolas Prevot <nprevot@google.com> 2015-06-17 11:09:48 -0700
committer Nicolas Prevot <nprevot@google.com> 2015-06-22 14:39:44 -0700
commit9edbda18df025527e18614cf0c45d538a27af30f (patch)
treeec9366acabcae184e68031222a23f3eb8cf7fb47
parent49e11f805dcf5c8611e4bfbb115e56a22db4a396 (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.txt1
-rw-r--r--api/system-current.txt1
-rw-r--r--core/java/android/os/UserManager.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java132
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java2
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,