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

Commit 55e4896d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support setting the _PROFILES app-op"

parents afceb8c3 6fb8a02b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11399,6 +11399,7 @@ package android.content.pm {
    method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
    method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
    method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
    field public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED = "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED";
  }
  public final class FeatureGroupInfo implements android.os.Parcelable {
+45 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.AppOpsManager.Mode;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +43,18 @@ import java.util.Set;
 * use this class to start its main activity in managed profile.
 */
public class CrossProfileApps {

    /**
     * Broadcast signalling that the receiving app's ability to interact across profiles has
     * changed, as defined by the return value of {@link #canInteractAcrossProfiles()}.
     *
     * <p>Apps that have set the {@code android:crossProfile} manifest attribute to {@code true}
     * can receive this broadcast in manifest broadcast receivers. Otherwise, it can only be
     * received by dynamically-registered broadcast receivers.
     */
    public static final String ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED =
            "android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED";

    private final Context mContext;
    private final ICrossProfileApps mService;
    private final UserManager mUserManager;
@@ -254,6 +267,38 @@ public class CrossProfileApps {
        return settingsIntent;
    }

    /**
     * Sets the app-op for {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} that is
     * configurable by users in Settings. This configures it for the profile group of the calling
     * package.
     *
     * <p>Before calling, check {@link #canRequestInteractAcrossProfiles()} and do not call if it is
     * {@code false}. If presenting a user interface, do not allow the user to configure the app-op
     * in that case.
     *
     * <p>The underlying app-op {@link android.app.AppOpsManager#OP_INTERACT_ACROSS_PROFILES} should
     * never be set directly. This method ensures that the app-op is kept in sync for the app across
     * each user in the profile group and that those apps are sent a broadcast when their ability to
     * interact across profiles changes.
     *
     * <p>This method should be used whenever an app's ability to interact across profiles changes,
     * as defined by the return value of {@link #canInteractAcrossProfiles()}. This includes user
     * consent changes in Settings or during provisioning, plus changes to the admin or OEM consent
     * whitelists that make the current app-op value invalid.
     *
     * @hide
     */
    @RequiresPermission(
            allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
                    android.Manifest.permission.INTERACT_ACROSS_USERS})
    public void setInteractAcrossProfilesAppOp(@NonNull String packageName, @Mode int newMode) {
        try {
            mService.setInteractAcrossProfilesAppOp(packageName, newMode);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    private void verifyCanAccessUser(UserHandle userHandle) {
        if (!getTargetUserProfiles().contains(userHandle)) {
            throw new SecurityException("Not allowed to access " + userHandle);
+1 −0
Original line number Diff line number Diff line
@@ -32,4 +32,5 @@ interface ICrossProfileApps {
    List<UserHandle> getTargetUserProfiles(in String callingPackage);
    boolean canInteractAcrossProfiles(in String callingPackage);
    boolean canRequestInteractAcrossProfiles(in String callingPackage);
    void setInteractAcrossProfilesAppOp(in String packageName, int newMode);
}
 No newline at end of file
+145 −15
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@
package com.android.server.pm;

import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;

@@ -26,6 +28,7 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.Mode;
import android.app.IApplicationThread;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManagerInternal;
@@ -66,8 +69,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
    private Context mContext;
    private Injector mInjector;
    private AppOpsService mAppOpsService;
    private final DevicePolicyManagerInternal mDpmi;
    private final IPackageManager mIpm;

    public CrossProfileAppsServiceImpl(Context context) {
        this(context, new InjectorImpl(context));
@@ -77,8 +78,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
    CrossProfileAppsServiceImpl(Context context, Injector injector) {
        mContext = context;
        mInjector = injector;
        mIpm = AppGlobals.getPackageManager();
        mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
    }

    @Override
@@ -144,7 +143,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
            // must have the required permission and the users must be in the same profile group
            // in order to launch any of its own activities.
            if (callerUserId != userId) {
                final int permissionFlag = ActivityManager.checkComponentPermission(
                final int permissionFlag = mInjector.checkComponentPermission(
                        android.Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid,
                        -1, true);
                if (permissionFlag != PackageManager.PERMISSION_GRANTED
@@ -172,23 +171,27 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
    public boolean canRequestInteractAcrossProfiles(String callingPackage) {
        Objects.requireNonNull(callingPackage);
        verifyCallingPackage(callingPackage);

        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
        return canRequestInteractAcrossProfilesUnchecked(
                callingPackage, mInjector.getCallingUserId());
    }

    private boolean canRequestInteractAcrossProfilesUnchecked(
            String packageName, @UserIdInt int userId) {
        List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(packageName, userId);
        if (targetUserProfiles.isEmpty()) {
            return false;
        }

        if (!hasRequestedAppOpPermission(
                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) {
                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName)) {
            return false;
        }
        return isCrossProfilePackageWhitelisted(callingPackage);
        return isCrossProfilePackageWhitelisted(packageName);
    }

    private boolean hasRequestedAppOpPermission(String permission, String packageName) {
        try {
            String[] packages = mIpm.getAppOpPermissionPackages(permission);
            String[] packages =
                    mInjector.getIPackageManager().getAppOpPermissionPackages(permission);
            return ArrayUtils.contains(packages, packageName);
        } catch (RemoteException exc) {
            Slog.e(TAG, "PackageManager dead. Cannot get permission info");
@@ -206,7 +209,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
        if (targetUserProfiles.isEmpty()) {
            return false;
        }

        final int callingUid = mInjector.getCallingUid();
        return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)
@@ -219,7 +221,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
    private boolean isCrossProfilePackageWhitelisted(String packageName) {
        final long ident = mInjector.clearCallingIdentity();
        try {
            return mDpmi.getAllCrossProfilePackages().contains(packageName);
            return mInjector.getDevicePolicyManagerInternal()
                    .getAllCrossProfilePackages().contains(packageName);
        } finally {
            mInjector.restoreCallingIdentity(ident);
        }
@@ -295,6 +298,104 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
        }
    }

    @Override
    public void setInteractAcrossProfilesAppOp(String packageName, @Mode int newMode) {
        final int callingUid = mInjector.getCallingUid();
        if (!isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
                && !isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)) {
            throw new SecurityException(
                    "INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL is required to set the"
                            + " app-op for interacting across profiles.");
        }
        if (!isPermissionGranted(Manifest.permission.MANAGE_APP_OPS_MODES, callingUid)) {
            throw new SecurityException(
                    "MANAGE_APP_OPS_MODES is required to set the app-op for interacting across"
                            + " profiles.");
        }
        final int callingUserId = mInjector.getCallingUserId();
        if (newMode == AppOpsManager.MODE_ALLOWED
                && !canRequestInteractAcrossProfilesUnchecked(packageName, callingUserId)) {
            // The user should not be prompted for apps that cannot request to interact across
            // profiles. However, we return early here if required to avoid race conditions.
            Slog.e(TAG, "Tried to turn on the appop for interacting across profiles for invalid"
                    + " app " + packageName);
            return;
        }
        final int[] profileIds =
                mInjector.getUserManager().getProfileIds(callingUserId, /* enabledOnly= */ false);
        for (int profileId : profileIds) {
            if (!isPackageInstalled(packageName, profileId)) {
                continue;
            }
            setInteractAcrossProfilesAppOpForUser(packageName, newMode, profileId);
        }
    }

    private boolean isPackageInstalled(String packageName, @UserIdInt int userId) {
        final int callingUid = mInjector.getCallingUid();
        final long identity = mInjector.clearCallingIdentity();
        try {
            final PackageInfo info =
                    mInjector.getPackageManagerInternal()
                            .getPackageInfo(
                                    packageName,
                                    MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
                                    callingUid,
                                    userId);
            return info != null;
        } finally {
            mInjector.restoreCallingIdentity(identity);
        }
    }

    private void setInteractAcrossProfilesAppOpForUser(
            String packageName, @Mode int newMode, @UserIdInt int userId) {
        try {
            setInteractAcrossProfilesAppOpForUserOrThrow(packageName, newMode, userId);
        } catch (PackageManager.NameNotFoundException e) {
            Slog.e(TAG, "Missing package " + packageName + " on user ID " + userId, e);
        }
    }

    private void setInteractAcrossProfilesAppOpForUserOrThrow(
            String packageName, @Mode int newMode, @UserIdInt int userId)
            throws PackageManager.NameNotFoundException {
        final int uid = mInjector.getPackageManager()
                .getPackageUidAsUser(packageName, /* flags= */ 0, userId);
        if (currentModeEquals(newMode, packageName, uid)) {
            Slog.w(TAG,"Attempt to set mode to existing value of " + newMode + " for "
                    + packageName + " on user ID " + userId);
            return;
        }
        mInjector.getAppOpsManager()
                .setMode(OP_INTERACT_ACROSS_PROFILES,
                        uid,
                        packageName,
                        newMode);
        sendCanInteractAcrossProfilesChangedBroadcast(packageName, uid, UserHandle.of(userId));
    }

    private boolean currentModeEquals(@Mode int otherMode, String packageName, int uid) {
        final String op =
                AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
        return otherMode ==
                mInjector.getAppOpsManager().unsafeCheckOpNoThrow(op, uid, packageName);
    }

    private void sendCanInteractAcrossProfilesChangedBroadcast(
            String packageName, int uid, UserHandle userHandle) {
        final Intent intent = new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED)
                .setPackage(packageName);
        if (!appDeclaresCrossProfileAttribute(uid)) {
            intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY);
        }
        mInjector.sendBroadcastAsUser(intent, userHandle);
    }

    private boolean appDeclaresCrossProfileAttribute(int uid) {
        return mInjector.getPackageManagerInternal().getPackage(uid).isCrossProfile();
    }

    private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
        final long ident = mInjector.clearCallingIdentity();
        try {
@@ -311,8 +412,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
        mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
    }

    private static boolean isPermissionGranted(String permission, int uid) {
        return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
    private boolean isPermissionGranted(String permission, int uid) {
        return PackageManager.PERMISSION_GRANTED == mInjector.checkComponentPermission(
                permission, uid, /* owningUid= */-1, /* exported= */ true);
    }

@@ -376,6 +477,27 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
        public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
            return LocalServices.getService(ActivityTaskManagerInternal.class);
        }

        @Override
        public IPackageManager getIPackageManager() {
            return AppGlobals.getPackageManager();
        }

        @Override
        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
            return LocalServices.getService(DevicePolicyManagerInternal.class);
        }

        @Override
        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
            mContext.sendBroadcastAsUser(intent, user);
        }

        @Override
        public int checkComponentPermission(
                String permission, int uid, int owningUid, boolean exported) {
            return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
        }
    }

    @VisibleForTesting
@@ -401,5 +523,13 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
        ActivityManagerInternal getActivityManagerInternal();

        ActivityTaskManagerInternal getActivityTaskManagerInternal();

        IPackageManager getIPackageManager();

        DevicePolicyManagerInternal getDevicePolicyManagerInternal();

        void sendBroadcastAsUser(Intent intent, UserHandle user);

        int checkComponentPermission(String permission, int uid, int owningUid, boolean exported);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -43,6 +43,9 @@ android_robolectric_test {
        "platform-test-annotations",
        "testng",
    ],
    static_libs: [
        "androidx.test.ext.truth",
    ],

    instrumentation_for: "FrameworksServicesLib",
}
Loading