Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9edbda18 authored by Nicolas Prevot's avatar Nicolas Prevot
Browse files

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
parent 49e11f80
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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";
+1 −0
Original line number Diff line number Diff line
@@ -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";
+16 −0
Original line number Diff line number Diff line
@@ -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 <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.
+121 −11
Original line number Diff line number Diff line
@@ -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);
+2 −0
Original line number Diff line number Diff line
@@ -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,