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

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

Merge "Add can interact/request to interact across profiles APIs"

parents b73f706c 946df39c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -11356,6 +11356,8 @@ package android.content.pm {
  }
  public class CrossProfileApps {
    method public boolean canInteractAcrossProfiles();
    method public boolean canRequestInteractAcrossProfiles();
    method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
    method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
    method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+22 −0
Original line number Diff line number Diff line
@@ -17,9 +17,12 @@
package android.app.admin;

import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.os.UserHandle;

import java.util.List;
import java.util.Set;

/**
 * Device policy manager local system service interface.
@@ -165,4 +168,23 @@ public abstract class DevicePolicyManagerInternal {
     * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
     */
    protected abstract DeviceStateCache getDeviceStateCache();

    /**
     * Returns the combined set of the following:
     * <ul>
     * <li>The package names that the admin has previously set as allowed to request user consent
     * for cross-profile communication, via {@link
     * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li>
     * <li>The default package names that are allowed to request user consent for cross-profile
     * communication without being explicitly enabled by the admin , via {@link
     * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li>
     * </ul>
     *
     * @return the combined set of whitelisted package names set via
     * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and
     * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}
     *
     * @hide
     */
    public abstract List<String> getAllCrossProfilePackages();
}
+57 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.internal.R;
import com.android.internal.util.UserIcons;

import java.util.List;
import java.util.Set;

/**
 * Class for handling cross profile operations. Apps can use this class to interact with its
@@ -169,6 +170,62 @@ public class CrossProfileApps {
        }
    }

    /**
     * Returns whether the calling package can request to interact across profiles.
     *
     * <p>The package's current ability to interact across profiles can be checked with
     * {@link #canInteractAcrossProfiles()}.
     *
     * <p>Specifically, returns whether the following are all true:
     * <ul>
     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
     * <li>The calling app has requested</li>
     * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.
     * <li>The calling package has either been whitelisted by default by the OEM or has been
     * explicitly whitelisted by the admin via
     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
     * </li>
     * </ul>
     *
     * @return true if the calling package can request to interact across profiles.
     */
    public boolean canRequestInteractAcrossProfiles() {
        try {
            return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    /**
     * Returns whether the calling package can interact across profiles.
     *
     * <p>The package's current ability to request to interact across profiles can be checked with
     * {@link #canRequestInteractAcrossProfiles()}.
     *
     * <p>Specifically, returns whether the following are all true:
     * <ul>
     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
     * <li>The user has previously consented to cross-profile communication for the calling
     * package.</li>
     * <li>The calling package has either been whitelisted by default by the OEM or has been
     * explicitly whitelisted by the admin via
     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
     * </li>
     * </ul>
     *
     * @return true if the calling package can interact across profiles.
     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
     * calling UID.
     */
    public boolean canInteractAcrossProfiles() {
        try {
            return mService.canInteractAcrossProfiles(mContext.getPackageName());
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    private void verifyCanAccessUser(UserHandle userHandle) {
        if (!getTargetUserProfiles().contains(userHandle)) {
            throw new SecurityException("Not allowed to access " + userHandle);
+2 −0
Original line number Diff line number Diff line
@@ -30,4 +30,6 @@ interface ICrossProfileApps {
    void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
            in ComponentName component, int userId, boolean launchMainActivity);
    List<UserHandle> getTargetUserProfiles(in String callingPackage);
    boolean canInteractAcrossProfiles(in String callingPackage);
    boolean canRequestInteractAcrossProfiles(in String callingPackage);
}
 No newline at end of file
+86 −1
Original line number Diff line number Diff line
@@ -15,35 +15,45 @@
 */
package com.android.server.pm;

import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;

import android.Manifest;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ICrossProfileApps;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.appop.AppOpsService;
import com.android.server.wm.ActivityTaskManagerInternal;

import java.util.ArrayList;
@@ -55,6 +65,9 @@ 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));
@@ -64,6 +77,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
    CrossProfileAppsServiceImpl(Context context, Injector injector) {
        mContext = context;
        mInjector = injector;
        mIpm = AppGlobals.getPackageManager();
        mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
    }

    @Override
@@ -153,6 +168,63 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
                userId);
    }

    @Override
    public boolean canRequestInteractAcrossProfiles(String callingPackage) {
        Objects.requireNonNull(callingPackage);
        verifyCallingPackage(callingPackage);

        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
                callingPackage, mInjector.getCallingUserId());
        if (targetUserProfiles.isEmpty()) {
            return false;
        }

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

    private boolean hasRequestedAppOpPermission(String permission, String packageName) {
        try {
            String[] packages = mIpm.getAppOpPermissionPackages(permission);
            return ArrayUtils.contains(packages, packageName);
        } catch (RemoteException exc) {
            Slog.e(TAG, "PackageManager dead. Cannot get permission info");
            return false;
        }
    }

    @Override
    public boolean canInteractAcrossProfiles(String callingPackage) {
        Objects.requireNonNull(callingPackage);
        verifyCallingPackage(callingPackage);

        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
                callingPackage, mInjector.getCallingUserId());
        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)
                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid)
                || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation(
                OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null,
                /*shouldCollectAsyncNotedOp= */false, /*message= */null);
    }

    private boolean isCrossProfilePackageWhitelisted(String packageName) {
        final long ident = mInjector.clearCallingIdentity();
        try {
            return mDpmi.getAllCrossProfilePackages().contains(packageName);
        } finally {
            mInjector.restoreCallingIdentity(ident);
        }
    }

    private List<UserHandle> getTargetUserProfilesUnchecked(
            String callingPackage, @UserIdInt int callingUserId) {
        final long ident = mInjector.clearCallingIdentity();
@@ -239,6 +311,19 @@ 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(
                permission, uid, /* owningUid= */-1, /* exported= */ true);
    }

    private AppOpsService getAppOpsService() {
        if (mAppOpsService == null) {
            IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
            mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b);
        }
        return mAppOpsService;
    }

    private static class InjectorImpl implements Injector {
        private Context mContext;

Loading