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

Commit 4b0197d8 authored by Alex Kershaw's avatar Alex Kershaw
Browse files

Reset unset app-ops when the admin sets cross-profile packages.

When the admin unsets cross-profile packages, reset the
INTERACT_ACROSS_PROFILES app-op back to the default if it is no longer
configurable by the user.

Other minor changes:
- Remove the explicit app-op permission check from
setInteractAcrossProfilesAppOp, since AppOpManager performs that check
when required.
- Fix the broadcasting logic in CrossProfileAppsServiceImpl to correctly
set the component and flags.

Bug: 136249261
Bug: 148010178
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.ManagedProfileCrossProfileTest
Change-Id: Ib8b5f331fb92fc475bc95ad039adf93fac04da37
parent a263d01c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -11399,6 +11399,10 @@ public class DevicePolicyManager {
     *
     * <p>Previous calls are overridden by each subsequent call to this method.
     *
     * <p>When previously-set cross-profile packages are missing from {@code packageNames}, the
     * app-op for {@code INTERACT_ACROSS_PROFILES} will be reset for those packages. This will not
     * occur for packages that are whitelisted by the OEM.
     *
     * @param admin the {@link DeviceAdminReceiver} this request is associated with
     * @param packageNames the new cross-profile package names
     */
+66 −7
Original line number Diff line number Diff line
@@ -34,8 +34,10 @@ import android.provider.Settings;
import com.android.internal.R;
import com.android.internal.util.UserIcons;

import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Class for handling cross profile operations. Apps can use this class to interact with its
@@ -298,19 +300,24 @@ public class CrossProfileApps {
     * 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>Before calling, check {@link #canConfigureInteractAcrossProfiles(String)} 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.
     * <p>This method should be used directly whenever a user's action results in a change in an
     * app's ability to interact across profiles, as defined by the return value of {@link
     * #canInteractAcrossProfiles()}. This includes user consent changes in Settings or during
     * provisioning.
     *
     * <p>If other changes could have affected the app's ability to interact across profiles, as
     * defined by the return value of {@link #canInteractAcrossProfiles()}, such as changes to the
     * admin or OEM consent whitelists, then {@link
     * #resetInteractAcrossProfilesAppOpsIfInvalid(List)} should be used.
     *
     * @hide
     */
@@ -325,6 +332,58 @@ public class CrossProfileApps {
        }
    }

    /**
     * Returns whether the given package can have its ability to interact across profiles configured
     * by the user. This means that every other condition to interact across profiles has been set.
     *
     * <p>This differs from {@link #canRequestInteractAcrossProfiles()} since it will not return
     * {@code false} simply when the target profile is disabled.
     *
     * @hide
     */
    public boolean canConfigureInteractAcrossProfiles(@NonNull String packageName) {
        try {
            return mService.canConfigureInteractAcrossProfiles(packageName);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    /**
     * For each of the packages defined in {@code previousCrossProfilePackages} but not included in
     * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission
     * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by
     * users in Settings, as defined by {@link #canConfigureInteractAcrossProfiles(String)}.
     *
     * <p>This method should be used whenever an app's ability to interact across profiles could
     * have changed as a result of non-user actions, such as changes to admin or OEM consent
     * whitelists.
     *
     * @hide
     */
    @RequiresPermission(
            allOf={android.Manifest.permission.MANAGE_APP_OPS_MODES,
                    android.Manifest.permission.INTERACT_ACROSS_USERS})
    public void resetInteractAcrossProfilesAppOps(
            @NonNull Collection<String> previousCrossProfilePackages,
            @NonNull Set<String> newCrossProfilePackages) {
        if (previousCrossProfilePackages.isEmpty()) {
            return;
        }
        final List<String> unsetCrossProfilePackages =
                previousCrossProfilePackages.stream()
                        .filter(packageName -> !newCrossProfilePackages.contains(packageName))
                        .collect(Collectors.toList());
        if (unsetCrossProfilePackages.isEmpty()) {
            return;
        }
        try {
            mService.resetInteractAcrossProfilesAppOps(unsetCrossProfilePackages);
        } 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
@@ -35,4 +35,6 @@ interface ICrossProfileApps {
    boolean canInteractAcrossProfiles(in String callingPackage);
    boolean canRequestInteractAcrossProfiles(in String callingPackage);
    void setInteractAcrossProfilesAppOp(in String packageName, int newMode);
    boolean canConfigureInteractAcrossProfiles(in String packageName);
    void resetInteractAcrossProfilesAppOps(in List<String> packageNames);
}
 No newline at end of file
+67 −17
Original line number Diff line number Diff line
@@ -286,21 +286,21 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
    }

    private List<UserHandle> getTargetUserProfilesUnchecked(
            String callingPackage, @UserIdInt int callingUserId) {
            String packageName, @UserIdInt int userId) {
        final long ident = mInjector.clearCallingIdentity();
        try {
            final int[] enabledProfileIds =
                    mInjector.getUserManager().getEnabledProfileIds(callingUserId);
                    mInjector.getUserManager().getEnabledProfileIds(userId);

            List<UserHandle> targetProfiles = new ArrayList<>();
            for (final int userId : enabledProfileIds) {
                if (userId == callingUserId) {
            for (final int profileId : enabledProfileIds) {
                if (profileId == userId) {
                    continue;
                }
                if (!isPackageEnabled(callingPackage, userId)) {
                if (!isPackageEnabled(packageName, profileId)) {
                    continue;
                }
                targetProfiles.add(UserHandle.of(userId));
                targetProfiles.add(UserHandle.of(profileId));
            }
            return targetProfiles;
        } finally {
@@ -385,14 +385,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
                    "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)) {
                && !canConfigureInteractAcrossProfiles(packageName)) {
            // 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"
@@ -462,18 +457,73 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {

    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)) {
        final Intent intent =
                new Intent(ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED).setPackage(packageName);
        if (appDeclaresCrossProfileAttribute(uid)) {
            intent.addFlags(
                    Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND);
        } else {
            intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY);
        }
        for (ResolveInfo receiver : findBroadcastReceiversForUser(intent, userHandle)) {
            intent.setComponent(receiver.getComponentInfo().getComponentName());
            mInjector.sendBroadcastAsUser(intent, userHandle);
        }
    }

    private List<ResolveInfo> findBroadcastReceiversForUser(Intent intent, UserHandle userHandle) {
        return mInjector.getPackageManager()
                .queryBroadcastReceiversAsUser(intent, /* flags= */ 0, userHandle);
    }

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

    @Override
    public boolean canConfigureInteractAcrossProfiles(String packageName) {
        if (!hasOtherProfileWithPackageInstalled(packageName, mInjector.getCallingUserId())) {
            return false;
        }
        if (!hasRequestedAppOpPermission(
                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName)) {
            return false;
        }
        return isCrossProfilePackageWhitelisted(packageName);
    }

    private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) {
        final long ident = mInjector.clearCallingIdentity();
        try {
            final int[] profileIds =
                    mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
            for (int profileId : profileIds) {
                if (profileId != userId && isPackageInstalled(packageName, profileId)) {
                    return true;
                }
            }
        } finally {
            mInjector.restoreCallingIdentity(ident);
        }
        return false;
    }

    @Override
    public void resetInteractAcrossProfilesAppOps(List<String> packageNames) {
        packageNames.forEach(this::resetInteractAcrossProfilesAppOp);
    }

    private void resetInteractAcrossProfilesAppOp(String packageName) {
        if (canConfigureInteractAcrossProfiles(packageName)) {
            Slog.w(TAG, "Not resetting app-op for package " + packageName
                    + " since it is still configurable by users.");
            return;
        }
        final String op =
                AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
        setInteractAcrossProfilesAppOp(packageName, AppOpsManager.opToDefaultMode(op));
    }

    private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
        final long ident = mInjector.clearCallingIdentity();
        try {
+9 −0
Original line number Diff line number Diff line
@@ -156,6 +156,7 @@ import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
@@ -302,6 +303,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -310,6 +312,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * Implementation of the device policy APIs.
@@ -14887,12 +14890,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
        Objects.requireNonNull(who, "ComponentName is null");
        Objects.requireNonNull(packageNames, "Package names is null");
        final List<String> previousCrossProfilePackages;
        synchronized (getLockObject()) {
            final ActiveAdmin admin =
                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
            previousCrossProfilePackages = admin.mCrossProfilePackages;
            admin.mCrossProfilePackages = packageNames;
            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
        }
        final CrossProfileApps crossProfileApps = mContext.getSystemService(CrossProfileApps.class);
        mInjector.binderWithCleanCallingIdentity(
                () -> crossProfileApps.resetInteractAcrossProfilesAppOps(
                        previousCrossProfilePackages, new HashSet<>(packageNames)));
    }
    @Override
Loading