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

Commit 9ee8a8cd authored by Pavel Grafov's avatar Pavel Grafov
Browse files

Suspend apps instead of stopping user in quiet mode

Previously enabling quiet mode meant shutting down work profile
user completely, so no app code could run there. With this change
Profile will no longer be shut down, but work apps will be
suspended instead, so that the user still won't be able to use
them or see their notifications.

This change is necessary to enable telephony in the work profile
as well as improve usability in other ways.

Package suspension is stored on per-suspending package basis in
package manager. Naturally, when packages are suspended for
quiet mode, the suspending package must be "android". However
when admin apps suspend packages via DPM.setPackagesSuspended()
it is recoded as suspension by "android" too. To disambiguate
between these cases this CL introduces two separate methods into
SuspendPackageHelper - one for admin suspension and another for
suspension for quiet mode. SuspendPackageHelper will combine
the two suspensions when necessary and store the resulting
state.

Bug: 258823777
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Test: atest android.devicepolicy.cts.QuietModeTest
Change-Id: Id100b77c265e3e8f4d7ce446a3ca365c50eb654e
parent ce712dee
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -307,6 +307,11 @@ public abstract class DevicePolicyManagerInternal {
     */
    public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);

    /**
     * Returns the list of packages suspended by admin on a given user.
     */
    public abstract Set<String> getPackagesSuspendedByAdmin(@UserIdInt int userId);

    /**
     * Returns whether the application exemptions feature flag is enabled.
     */
+20 −0
Original line number Diff line number Diff line
@@ -270,6 +270,26 @@ public abstract class PackageManagerInternal {
     */
    public abstract String getSuspendingPackage(String suspendedPackage, int userId);

    /**
     * Suspend or unsuspend packages upon admin request.
     *
     * @param userId The target user.
     * @param packageNames The names of the packages to set the suspended status.
     * @param suspended Whether the packages should be suspended or unsuspended.
     * @return an array of package names for which the suspended status could not be set as
     *   requested in this method.
     */
    public abstract String[] setPackagesSuspendedByAdmin(
            @UserIdInt int userId, @NonNull String[] packageNames, boolean suspended);

    /**
     * Suspend or unsuspend packages in a profile when quiet mode is toggled.
     *
     * @param userId The target user.
     * @param suspended Whether the packages should be suspended or unsuspended.
     */
    public abstract void setPackagesSuspendedForQuietMode(@UserIdInt int userId, boolean suspended);

    /**
     * Get the information describing the dialog to be shown to the user when they try to launch a
     * suspended application.
+4 −1
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.app.BroadcastOptions;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageEvents;
import android.appwidget.AppWidgetManagerInternal;
import android.content.Context;
@@ -1459,8 +1460,10 @@ class UserController implements Handler.Callback {

    private boolean shouldStartWithParent(UserInfo user) {
        final UserProperties properties = getUserProperties(user.id);
        DevicePolicyManagerInternal dpmi =
                LocalServices.getService(DevicePolicyManagerInternal.class);
        return (properties != null && properties.getStartWithParent())
                && !user.isQuietModeEnabled();
                && (!user.isQuietModeEnabled() || dpmi.isKeepProfilesRunningEnabled());
    }

    /**
+15 −2
Original line number Diff line number Diff line
@@ -1958,8 +1958,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
                mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity,
                injector.getBackgroundHandler());
        mDexOptHelper = new DexOptHelper(this);
        mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper,
                mProtectedPackages);
        mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mUserManager,
                mBroadcastHelper, mProtectedPackages);
        mDistractingPackageHelper = new DistractingPackageHelper(this, mInjector, mBroadcastHelper,
                mSuspendPackageHelper);
        mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper,
@@ -6503,6 +6503,19 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            }
        }

        @Override
        public String[] setPackagesSuspendedByAdmin(
                @UserIdInt int userId, @NonNull String[] packageNames, boolean suspended) {
            return mSuspendPackageHelper.setPackagesSuspendedByAdmin(
                    snapshotComputer(), userId, packageNames, suspended);
        }

        @Override
        public void setPackagesSuspendedForQuietMode(int userId, boolean suspended) {
            mSuspendPackageHelper.setPackagesSuspendedForQuietMode(
                    snapshotComputer(), userId, suspended);
        }

        @Override
        public void setDeviceAndProfileOwnerPackages(
                int deviceOwnerUserId, String deviceOwnerPackage,
+105 −1
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.pm;

import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.os.Process.SYSTEM_UID;

import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -26,12 +28,15 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.SuspendDialogInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -42,6 +47,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.LocalServices;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -50,8 +56,10 @@ import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import com.android.server.utils.WatchedArrayMap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;

public final class SuspendPackageHelper {
@@ -59,6 +67,7 @@ public final class SuspendPackageHelper {
    private final PackageManagerService mPm;
    private final PackageManagerServiceInjector mInjector;

    private final UserManagerService mUserManager;
    private final BroadcastHelper mBroadcastHelper;
    private final ProtectedPackages mProtectedPackages;

@@ -66,8 +75,10 @@ public final class SuspendPackageHelper {
     * Constructor for {@link PackageManagerService}.
     */
    SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
            BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages) {
            UserManagerService userManager, BroadcastHelper broadcastHelper,
            ProtectedPackages protectedPackages) {
        mPm = pm;
        mUserManager = userManager;
        mInjector = injector;
        mBroadcastHelper = broadcastHelper;
        mProtectedPackages = protectedPackages;
@@ -597,6 +608,99 @@ public final class SuspendPackageHelper {
                null /* bOptions */));
    }

    /**
     * Suspends packages on behalf of an admin.
     *
     * @return array of packages that are unsuspendable, either because admin is not allowed to
     * suspend them (e.g. current dialer) or there was other problem (e.g. package not found).
     */
    public String[] setPackagesSuspendedByAdmin(
            Computer snapshot, int userId, String[] packageNames, boolean suspend) {
        final Set<String> toSuspend = new ArraySet<>(packageNames);
        List<String> unsuspendable = new ArrayList<>();

        if (mUserManager.isQuietModeEnabled(userId)) {
            // If the user is in quiet mode, most apps will already be suspended, we shouldn't
            // re-suspend or unsuspend them.
            final Set<String> quiet = packagesToSuspendInQuietMode(snapshot, userId);
            quiet.retainAll(toSuspend);
            if (!quiet.isEmpty()) {
                Slog.i(TAG, "Ignoring quiet packages: " + String.join(", ", quiet));
                toSuspend.removeAll(quiet);
            }

            // Some of the already suspended packages might not be suspendable by the admin
            // (e.g. current dialer package), we need to report it back as unsuspendable the same
            // way as if quiet mode wasn't enabled. In that latter case they'd be returned by
            // setPackagesSuspended below after unsuccessful attempt to suspend them.
            if (suspend) {
                unsuspendable = getUnsuspendablePackages(snapshot, userId, quiet);
            }
        }
        if (!toSuspend.isEmpty()) {
            unsuspendable.addAll(Arrays.asList(
                    setPackagesSuspended(
                            snapshot, toSuspend.toArray(new String[0]), suspend,
                            null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */,
                            PackageManagerService.PLATFORM_PACKAGE_NAME, userId, Process.SYSTEM_UID
                    )));
        }
        return unsuspendable.toArray(String[]::new);
    }

    private List<String> getUnsuspendablePackages(
            Computer snapshot, int userId, Set<String> packages) {
        final String[] toSuspendArray = packages.toArray(String[]::new);
        final boolean[] mask =
                canSuspendPackageForUser(snapshot, toSuspendArray, userId, Process.SYSTEM_UID);
        final List<String> result = new ArrayList<>();
        for (int i = 0; i < mask.length; i++) {
            if (!mask[i]) {
                result.add(toSuspendArray[i]);
            }
        }
        return result;
    }

    /**
     * Suspends or unsuspends all packages in the given user when quiet mode is toggled to prevent
     * usage while quiet mode is enabled.
     */
    public void setPackagesSuspendedForQuietMode(
            Computer snapshot, int userId, boolean suspend) {
        final Set<String> toSuspend = packagesToSuspendInQuietMode(snapshot, userId);
        if (!suspend) {
            // Note: this method is called from DPMS constructor to suspend apps on upgrade, but
            // it won't enter here because 'suspend' will equal 'true'.
            final DevicePolicyManagerInternal dpm =
                    LocalServices.getService(DevicePolicyManagerInternal.class);
            if (dpm != null) {
                toSuspend.removeAll(dpm.getPackagesSuspendedByAdmin(userId));
            } else {
                Slog.wtf(TAG,
                        "DevicePolicyManager unavailable while suspending apps for quiet mode");
            }
        }

        if (toSuspend.isEmpty()) {
            return;
        }

        setPackagesSuspended(snapshot, toSuspend.toArray(new String[0]),
                suspend, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */,
                PackageManagerService.PLATFORM_PACKAGE_NAME, userId, Process.SYSTEM_UID);
    }

    private Set<String> packagesToSuspendInQuietMode(Computer snapshot, int userId) {
        final List<PackageInfo> pkgInfos = snapshot.getInstalledPackages(
                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId).getList();
        final Set<String> result = new ArraySet<>();
        for (PackageInfo info : pkgInfos) {
            result.add(info.packageName);
        }
        return result;
    }

    private String getKnownPackageName(@NonNull Computer snapshot,
            @KnownPackages.KnownPackage int knownPackage, int userId) {
        final String[] knownPackages =
Loading