diff --git a/api/current.txt b/api/current.txt
index 1287c74e370b1..457a9e8510e63 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23663,6 +23663,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 1f8f7a39c6c57..08715cdda49ee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -25608,6 +25608,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 b593906a214d5..d7abad031d059 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4118,9 +4118,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);
@@ -4161,11 +4178,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);
}
@@ -4174,15 +4191,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;
}
@@ -4197,6 +4235,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 {
@@ -4236,8 +4335,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());
@@ -4277,12 +4376,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,