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

Commit f0f84c8f authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Let admin block suspend in some cases

There may be policy critical apps that must not be suspended by the
user in a managed profile. The owner can now use either of the following
to block suspension of apps:
 - DISALLOW_APPS_CONTROL: Blocks suspension of all apps in the user
 - DISALLOW_UNINSTALL_APPS: Blocks suspension of all apps in the user
 - setUninstallBlocked: Blocks suspension of a given package.

The same also block any of the DistractionRestriction to be set via
PackageManager#setDistractingPackageRestrictions. This is to make sure
the apps can still show notifications.

Since the owner should have the final call, these do not block the owner
from adding app suspensions itself. Whenever either of these are set,
any app suspensions that were not originally added by the owner are
lifted immediately and any distraction restrictions that were added are
removed.

Also, clearing restrictions and suspensions if an app with SUSPEND_APPS
permission is disabled. Even though it is expected that UI not allow
such an app to be disabled, it is hard to enforce across all device
implementations. And a missed edge case would lead to permanently
unusable apps on the device.

This change also fixes a bug where any DistractionRestrictions set
weren't cleared on suspending app data clear.

Test: atest GtsSuspendAppsTestCases

Bug: 144826981
Bug: 145735990
Change-Id: I81a492e1d07a8cc9aeb0acd7e5142826824a42ae
parent d1c394c5
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -216,6 +216,33 @@ public abstract class PackageManagerInternal {
     */
    public abstract boolean isPackageSuspended(String packageName, int userId);

    /**
     * Removes all package suspensions imposed by any non-system packages.
     */
    public abstract void removeAllNonSystemPackageSuspensions(int userId);

    /**
     * Removes all suspensions imposed on the given package by non-system packages.
     */
    public abstract void removeNonSystemPackageSuspensions(String packageName, int userId);

    /**
     * Removes all {@link PackageManager.DistractionRestriction restrictions} set on the given
     * package
     */
    public abstract void removeDistractingPackageRestrictions(String packageName, int userId);

    /**
     * Removes all {@link PackageManager.DistractionRestriction restrictions} set on all the
     * packages.
     */
    public abstract void removeAllDistractingPackageRestrictions(int userId);

    /**
     * Flushes package restrictions for the given user immediately to disk.
     */
    public abstract void flushPackageRestrictions(int userId);

    /**
     * Get the name of the package that suspended the given package. Packages can be suspended by
     * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
+154 −14
Original line number Diff line number Diff line
@@ -12765,7 +12765,11 @@ public class PackageManagerService extends IPackageManager.Stub
            throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
                    + userId);
        }
        Preconditions.checkNotNull(packageNames, "packageNames cannot be null");
        Objects.requireNonNull(packageNames, "packageNames cannot be null");
        if (restrictionFlags != 0 && !isSuspendAllowedForUser(userId)) {
            Slog.w(TAG, "Cannot restrict packages due to restrictions on user " + userId);
            return packageNames;
        }
        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
        final IntArray changedUids = new IntArray(packageNames.length);
@@ -12852,6 +12856,10 @@ public class PackageManagerService extends IPackageManager.Stub
        if (ArrayUtils.isEmpty(packageNames)) {
            return packageNames;
        }
        if (suspended && !isSuspendAllowedForUser(userId)) {
            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
            return packageNames;
        }
        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
        final IntArray changedUids = new IntArray(packageNames.length);
@@ -12990,30 +12998,41 @@ public class PackageManagerService extends IPackageManager.Stub
        }
    }
    void unsuspendForSuspendingPackage(String suspendingPackage, int userId) {
        final String[] allPackages;
        synchronized (mLock) {
            allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
        }
        removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId);
    }
    /**
     * Immediately unsuspends any packages suspended by the given package. To be called
     * when such a package's data is cleared or it is removed from the device.
     * Removes any suspensions on given packages that were added by packages that pass the given
     * predicate.
     *
     * <p><b>Should not be used on a frequent code path</b> as it flushes state to disk
     * synchronously
     * <p> Caller must flush package restrictions if it cares about immediate data consistency.
     *
     * @param suspendingPackage The suspending package
     * @param packagesToChange The packages on which the suspension are to be removed.
     * @param suspendingPackagePredicate A predicate identifying the suspending packages whose
     *                                   suspensions will be removed.
     * @param userId The user for which the changes are taking place.
     */
    private void unsuspendForSuspendingPackage(String suspendingPackage, int userId) {
    void removeSuspensionsBySuspendingPackage(String[] packagesToChange,
            Predicate<String> suspendingPackagePredicate, int userId) {
        final List<String> unsuspendedPackages = new ArrayList<>();
        final IntArray unsuspendedUids = new IntArray();
        synchronized (mLock) {
            for (PackageSetting ps : mSettings.mPackages.values()) {
                final PackageUserState pus = ps.readUserState(userId);
                if (pus.suspended) {
                    ps.removeSuspension(suspendingPackage, userId);
            for (String packageName : packagesToChange) {
                final PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps.getSuspended(userId)) {
                    ps.removeSuspension(suspendingPackagePredicate, userId);
                    if (!ps.getSuspended(userId)) {
                        unsuspendedPackages.add(ps.name);
                        unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
                    }
                }
            }
            scheduleWritePackageRestrictionsLocked(userId);
        }
        if (!unsuspendedPackages.isEmpty()) {
            final String[] packageArray = unsuspendedPackages.toArray(
@@ -13021,13 +13040,67 @@ public class PackageManagerService extends IPackageManager.Stub
            sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
            sendPackagesSuspendedForUser(packageArray, unsuspendedUids.toArray(), userId, false);
        }
        // Write package restrictions immediately to avoid an inconsistent state.
        mSettings.writePackageRestrictionsLPr(userId);
    }
    void removeAllDistractingPackageRestrictions(int userId) {
        final String[] allPackages;
        synchronized (mLock) {
            allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
        }
        PackageManagerService.this.removeDistractingPackageRestrictions(allPackages, userId);
    }
    /**
     * Removes any {@link android.content.pm.PackageManager.DistractionRestriction restrictions}
     * set on given packages.
     *
     * <p> Caller must flush package restrictions if it cares about immediate data consistency.
     *
     * @param packagesToChange The packages on which restrictions are to be removed.
     * @param userId the user for which changes are taking place.
     */
    void removeDistractingPackageRestrictions(String[] packagesToChange, int userId) {
        final List<String> changedPackages = new ArrayList<>();
        final IntArray changedUids = new IntArray();
        synchronized (mLock) {
            for (String packageName : packagesToChange) {
                final PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps.getDistractionFlags(userId) != 0) {
                    ps.setDistractionFlags(0, userId);
                    changedPackages.add(ps.name);
                    changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
                }
            }
            if (!changedPackages.isEmpty()) {
                final String[] packageArray = changedPackages.toArray(
                        new String[changedPackages.size()]);
                sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId, 0);
                scheduleWritePackageRestrictionsLocked(userId);
            }
        }
    }
    private boolean isCallerDeviceOrProfileOwner(int userId) {
        final int callingUid = Binder.getCallingUid();
        if (callingUid == Process.SYSTEM_UID) {
            return true;
        }
        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
        if (ownerPackage != null) {
            return callingUid == getPackageUidInternal(ownerPackage, 0, userId, callingUid);
        }
        return false;
    }
    private boolean isSuspendAllowedForUser(int userId) {
        return isCallerDeviceOrProfileOwner(userId)
                || (!mUserManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)
                && !mUserManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId));
    }
    @Override
    public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) {
        Preconditions.checkNotNull(packageNames, "packageNames cannot be null");
        Objects.requireNonNull(packageNames, "packageNames cannot be null");
        mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
                "getUnsuspendablePackagesForUser");
        final int callingUid = Binder.getCallingUid();
@@ -13035,11 +13108,23 @@ public class PackageManagerService extends IPackageManager.Stub
            throw new SecurityException("Calling uid " + callingUid
                    + " cannot query getUnsuspendablePackagesForUser for user " + userId);
        }
        if (!isSuspendAllowedForUser(userId)) {
            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
            return packageNames;
        }
        final ArraySet<String> unactionablePackages = new ArraySet<>();
        final boolean[] canSuspend = canSuspendPackageForUserInternal(packageNames, userId);
        for (int i = 0; i < packageNames.length; i++) {
            if (!canSuspend[i]) {
                unactionablePackages.add(packageNames[i]);
                continue;
            }
            synchronized (mLock) {
                final PackageSetting ps = mSettings.mPackages.get(packageNames[i]);
                if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
                    Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
                    unactionablePackages.add(packageNames[i]);
                }
            }
        }
        return unactionablePackages.toArray(new String[unactionablePackages.size()]);
@@ -13056,6 +13141,7 @@ public class PackageManagerService extends IPackageManager.Stub
    @NonNull
    private boolean[] canSuspendPackageForUserInternal(@NonNull String[] packageNames, int userId) {
        final boolean[] canSuspend = new boolean[packageNames.length];
        final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId);
        final long callingId = Binder.clearCallingIdentity();
        try {
            final String activeLauncherPackageName = getActiveLauncherPackageName(userId);
@@ -13105,6 +13191,11 @@ public class PackageManagerService extends IPackageManager.Stub
                                + "\": protected package");
                        continue;
                    }
                    if (!isCallerOwner && mSettings.getBlockUninstallLPr(userId, packageName)) {
                        Slog.w(TAG, "Cannot suspend package \"" + packageName
                                + "\": blocked by admin");
                        continue;
                    }
                    // Cannot suspend static shared libs as they are considered
                    // a part of the using app (emulating static linking). Also
@@ -18509,6 +18600,8 @@ public class PackageManagerService extends IPackageManager.Stub
                        if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
                                == PERMISSION_GRANTED) {
                            unsuspendForSuspendingPackage(packageName, userId);
                            removeAllDistractingPackageRestrictions(userId);
                            flushPackageRestrictionsAsUserInternalLocked(userId);
                        }
                    }
                } else {
@@ -20025,6 +20118,16 @@ public class PackageManagerService extends IPackageManager.Stub
            }
            synchronized (mLock) {
                pkgSetting.setEnabled(newState, userId, callingPackage);
                if (newState == COMPONENT_ENABLED_STATE_DISABLED_USER
                        || newState == COMPONENT_ENABLED_STATE_DISABLED
                        && pkgSetting.getPermissionsState().hasPermission(
                                Manifest.permission.SUSPEND_APPS, userId)) {
                    // This app should not generally be allowed to get disabled by the UI, but if it
                    // ever does, we don't want to end up with some of the user's apps permanently
                    // blocked
                    unsuspendForSuspendingPackage(packageName, userId);
                    removeAllDistractingPackageRestrictions(userId);
                }
            }
        } else {
            synchronized (mLock) {
@@ -23247,6 +23350,43 @@ public class PackageManagerService extends IPackageManager.Stub
            }
        }
        @Override
        public void removeAllNonSystemPackageSuspensions(int userId) {
            final String[] allPackages;
            synchronized (mLock) {
                allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
            }
            PackageManagerService.this.removeSuspensionsBySuspendingPackage(allPackages,
                    (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
                    userId);
        }
        @Override
        public void removeNonSystemPackageSuspensions(String packageName, int userId) {
            PackageManagerService.this.removeSuspensionsBySuspendingPackage(
                    new String[]{packageName},
                    (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
                    userId);
        }
        @Override
        public void flushPackageRestrictions(int userId) {
            synchronized (mLock) {
                PackageManagerService.this.flushPackageRestrictionsAsUserInternalLocked(userId);
            }
        }
        @Override
        public void removeDistractingPackageRestrictions(String packageName, int userId) {
            PackageManagerService.this.removeDistractingPackageRestrictions(
                    new String[]{packageName}, userId);
        }
        @Override
        public void removeAllDistractingPackageRestrictions(int userId) {
            PackageManagerService.this.removeAllDistractingPackageRestrictions(userId);
        }
        @Override
        public String getSuspendingPackage(String suspendedPackage, int userId) {
            synchronized (mLock) {
+17 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;

/**
 * Settings base class for pending and resolved classes.
@@ -433,6 +434,22 @@ public abstract class PackageSettingBase extends SettingBase {
        existingUserState.suspended = (existingUserState.suspendParams != null);
    }

    void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
        final PackageUserState existingUserState = modifyUserState(userId);
        if (existingUserState.suspendParams != null) {
            for (int i = existingUserState.suspendParams.size() - 1; i >= 0; i--) {
                final String suspendingPackage = existingUserState.suspendParams.keyAt(i);
                if (suspendingPackagePredicate.test(suspendingPackage)) {
                    existingUserState.suspendParams.removeAt(i);
                }
            }
            if (existingUserState.suspendParams.size() == 0) {
                existingUserState.suspendParams = null;
            }
        }
        existingUserState.suspended = (existingUserState.suspendParams != null);
    }

    public boolean getInstantApp(int userId) {
        return readUserState(userId).instantApp;
    }
+11 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Process;
@@ -42,6 +43,7 @@ import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;

import com.google.android.collect.Sets;

@@ -676,6 +678,15 @@ public class UserRestrictionsUtils {
                                Global.LOCATION_GLOBAL_KILL_SWITCH, "0");
                    }
                    break;
                case UserManager.DISALLOW_APPS_CONTROL:
                    // Intentional fall-through
                case UserManager.DISALLOW_UNINSTALL_APPS:
                    final PackageManagerInternal pmi = LocalServices.getService(
                            PackageManagerInternal.class);
                    pmi.removeAllNonSystemPackageSuspensions(userId);
                    pmi.removeAllDistractingPackageRestrictions(userId);
                    pmi.flushPackageRestrictions(userId);
                    break;
            }
        } finally {
            Binder.restoreCallingIdentity(id);
+6 −0
Original line number Diff line number Diff line
@@ -11114,6 +11114,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                mInjector.binderRestoreCallingIdentity(id);
            }
        }
        if (uninstallBlocked) {
            final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
            pmi.removeNonSystemPackageSuspensions(packageName, userId);
            pmi.removeDistractingPackageRestrictions(packageName, userId);
            pmi.flushPackageRestrictions(userId);
        }
        final boolean isDelegate = (who == null);
        DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.SET_UNINSTALL_BLOCKED)