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

Commit edef883a authored by Jahdiel Alvarez's avatar Jahdiel Alvarez
Browse files

Stop previous user's packages early during user switch

On devices in which the previous user is stopped during user switch, we
will kill the previous user's packages after all SystemServices are
notified of the user switch via onUserSwitching. The entire user switch
flow remains the same, except for this new step. The early killing of
the packages significantly reduces CPU and memory pressure on the
system.

Packages with OOM score below FOREGROUND_APP_ADJ (0) are not killed
given system server binds to them. They will be ultimately killed once
the user is stopped, which is the current behavior.

Test: Manually run user switch on device
Test: atest UserControllerTest ActivityManagerInternalTest
Test: atest ActivityManagerServiceTest ActiveServicesTest
Bug: 323200731
Flag: android.multiuser.stop_previous_user_apps
Change-Id: I83e980432d30885c05a5748e05a360ec38875cc0
parent 19f4d1a9
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -959,6 +959,17 @@ public abstract class ActivityManagerInternal {
    public abstract void setVoiceInteractionManagerProvider(
            @Nullable VoiceInteractionManagerProvider provider);

    /**
     * Get whether or not the previous user's packages will be killed before the user is
     * stopped during a user switch.
     *
     * <p> The primary use case of this method is for {@link com.android.server.SystemService}
     * classes to call this API in their
     * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent
     * restarting any of the previous user's processes that will be killed during the user switch.
     */
    public abstract boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId);

    /**
     * Sets whether the current foreground user (and its profiles) should be stopped after switched
     * out.
+7 −0
Original line number Diff line number Diff line
@@ -267,6 +267,13 @@ flag {
    bug: "330351042"
}

flag {
    name: "stop_previous_user_apps"
    namespace: "multiuser"
    description: "Stop the previous user apps early in a user switch"
    bug: "323200731"
}

flag {
    name: "disable_private_space_items_on_home"
    namespace: "profile_experiences"
+16 −3
Original line number Diff line number Diff line
@@ -6961,7 +6961,8 @@ public final class ActiveServices {
    }

    private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses,
            boolean evenPersistent, boolean doit, ArrayMap<ComponentName, ServiceRecord> services) {
            boolean evenPersistent, boolean doit, int minOomAdj,
            ArrayMap<ComponentName, ServiceRecord> services) {
        boolean didSomething = false;
        for (int i = services.size() - 1; i >= 0; i--) {
            ServiceRecord service = services.valueAt(i);
@@ -6969,6 +6970,11 @@ public final class ActiveServices {
                    || (service.packageName.equals(packageName)
                        && (filterByClasses == null
                            || filterByClasses.contains(service.name.getClassName())));
            if (service.app != null && service.app.mState.getCurAdj() < minOomAdj) {
                Slog.i(TAG, "Skip force stopping service " + service
                            + ": below minimum oom adj level");
                continue;
            }
            if (sameComponent
                    && (service.app == null || evenPersistent || !service.app.isPersistent())) {
                if (!doit) {
@@ -6992,6 +6998,12 @@ public final class ActiveServices {

    boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses,
            int userId, boolean evenPersistent, boolean fullStop, boolean doit) {
        return bringDownDisabledPackageServicesLocked(packageName, filterByClasses, userId,
                evenPersistent, fullStop, doit, ProcessList.INVALID_ADJ);
    }

    boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses,
            int userId, boolean evenPersistent, boolean fullStop, boolean doit, int minOomAdj) {
        boolean didSomething = false;

        if (mTmpCollectionResults != null) {
@@ -7001,7 +7013,8 @@ public final class ActiveServices {
        if (userId == UserHandle.USER_ALL) {
            for (int i = mServiceMap.size() - 1; i >= 0; i--) {
                didSomething |= collectPackageServicesLocked(packageName, filterByClasses,
                        evenPersistent, doit, mServiceMap.valueAt(i).mServicesByInstanceName);
                        evenPersistent, doit, minOomAdj,
                        mServiceMap.valueAt(i).mServicesByInstanceName);
                if (!doit && didSomething) {
                    return true;
                }
@@ -7014,7 +7027,7 @@ public final class ActiveServices {
            if (smap != null) {
                ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName;
                didSomething = collectPackageServicesLocked(packageName, filterByClasses,
                        evenPersistent, doit, items);
                        evenPersistent, doit, minOomAdj, items);
            }
            if (doit && filterByClasses == null) {
                forceStopPackageLocked(packageName, userId);
+28 −3
Original line number Diff line number Diff line
@@ -4368,6 +4368,16 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
    }
    @GuardedBy("this")
    final boolean forceStopUserPackagesLocked(int userId, String reasonString,
            boolean evenImportantServices) {
        int minOomAdj = evenImportantServices ? ProcessList.INVALID_ADJ
                : ProcessList.FOREGROUND_APP_ADJ;
        return forceStopPackageInternalLocked(null, -1, false, false,
                true, false, false, false, userId, reasonString,
                ApplicationExitInfo.REASON_USER_STOPPED, minOomAdj);
    }
    @GuardedBy("this")
    final boolean forceStopPackageLocked(String packageName, int appId,
            boolean callerWillRestart, boolean purgeCache, boolean doit,
@@ -4377,7 +4387,6 @@ public class ActivityManagerService extends IActivityManager.Stub
                : ApplicationExitInfo.REASON_USER_REQUESTED;
        return forceStopPackageLocked(packageName, appId, callerWillRestart, purgeCache, doit,
                evenPersistent, uninstalling, packageStateStopped, userId, reasonString, reason);
    }
    @GuardedBy("this")
@@ -4385,6 +4394,16 @@ public class ActivityManagerService extends IActivityManager.Stub
            boolean callerWillRestart, boolean purgeCache, boolean doit,
            boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
            int userId, String reasonString, int reason) {
        return forceStopPackageInternalLocked(packageName, appId, callerWillRestart, purgeCache,
                doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString,
                reason, ProcessList.INVALID_ADJ);
    }
    @GuardedBy("this")
    private boolean forceStopPackageInternalLocked(String packageName, int appId,
            boolean callerWillRestart, boolean purgeCache, boolean doit,
            boolean evenPersistent, boolean uninstalling, boolean packageStateStopped,
            int userId, String reasonString, int reason, int minOomAdj) {
        int i;
        if (userId == UserHandle.USER_ALL && packageName == null) {
@@ -4423,7 +4442,7 @@ public class ActivityManagerService extends IActivityManager.Stub
            }
            didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
                    ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
                    minOomAdj, callerWillRestart, false /* allowRestart */, doit,
                    evenPersistent, true /* setRemoved */, uninstalling,
                    reason,
                    subReason,
@@ -4432,7 +4451,8 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
        if (mServices.bringDownDisabledPackageServicesLocked(
                packageName, null /* filterByClasses */, userId, evenPersistent, true, doit)) {
                packageName, null /* filterByClasses */, userId, evenPersistent,
                true, doit, minOomAdj)) {
            if (!doit) {
                return true;
            }
@@ -19846,6 +19866,11 @@ public class ActivityManagerService extends IActivityManager.Stub
            ActivityManagerService.this.setVoiceInteractionManagerProvider(provider);
        }
        @Override
        public boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) {
            return mUserController.isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId);
        }
        @Override
        public void setStopUserOnSwitch(int value) {
            ActivityManagerService.this.setStopUserOnSwitch(value);
+57 −9
Original line number Diff line number Diff line
@@ -439,6 +439,15 @@ class UserController implements Handler.Callback {
    @GuardedBy("mLock")
    private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();

    /**
     * Contains users which cannot abort the shutdown process.
     *
     * <p> For example, we don't abort shutdown for users whose processes have already been stopped
     * due to {@link #isEarlyPackageKillEnabledForUserSwitch(int, int)}.
     */
    @GuardedBy("mLock")
    private final ArraySet<Integer> mDoNotAbortShutdownUserIds = new ArraySet<>();

    private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
        @Override
        public void onUserCreated(UserInfo user, Object token) {
@@ -509,11 +518,11 @@ class UserController implements Handler.Callback {
        }
    }

    private boolean shouldStopUserOnSwitch() {
    private boolean isStopUserOnSwitchEnabled() {
        synchronized (mLock) {
            if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) {
                final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE;
                Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value);
                Slogf.i(TAG, "isStopUserOnSwitchEnabled(): returning overridden value (%b)", value);
                return value;
            }
        }
@@ -521,6 +530,26 @@ class UserController implements Handler.Callback {
        return property == -1 ? mDelayUserDataLocking : property == 1;
    }

    /**
     * Get whether or not the previous user's packages will be killed before the user is
     * stopped during a user switch.
     *
     * <p> The primary use case of this method is for {@link com.android.server.SystemService}
     * classes to call this API in their
     * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent
     * restarting any of the previous user's processes that will be killed during the user switch.
     */
    boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) {
        // NOTE: The logic in this method could be extended to cover other cases where
        // the previous user is also stopped like: guest users, ephemeral users,
        // and users with DISALLOW_RUN_IN_BACKGROUND. Currently, this is not done
        // because early killing is not enabled for these cases by default.
        if (fromUserId == UserHandle.USER_SYSTEM) {
            return false;
        }
        return isStopUserOnSwitchEnabled();
    }

    void finishUserSwitch(UserState uss) {
        // This call holds the AM lock so we post to the handler.
        mHandler.post(() -> {
@@ -1247,6 +1276,7 @@ class UserController implements Handler.Callback {
                return;
            }
            uss.setState(UserState.STATE_SHUTDOWN);
            mDoNotAbortShutdownUserIds.remove(userId);
        }
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]");
@@ -1555,7 +1585,8 @@ class UserController implements Handler.Callback {

    private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
        if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
        mInjector.activityManagerForceStopPackage(userId, reason);
        mInjector.activityManagerForceStopUserPackages(userId, reason,
                /* evenImportantServices= */ true);
        if (mInjector.getUserManager().isPreCreated(userId)) {
            // Don't fire intent for precreated.
            return;
@@ -1608,6 +1639,21 @@ class UserController implements Handler.Callback {
        }
    }

    private void stopPreviousUserPackagesIfEnabled(int fromUserId, int toUserId) {
        if (!android.multiuser.Flags.stopPreviousUserApps()
                || !isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId)) {
            return;
        }
        // Stop the previous user's packages early to reduce resource usage
        // during user switching. Only do this when the previous user will
        // be stopped regardless.
        synchronized (mLock) {
            mDoNotAbortShutdownUserIds.add(fromUserId);
        }
        mInjector.activityManagerForceStopUserPackages(fromUserId,
                "early stop user packages", /* evenImportantServices= */ false);
    }

    void scheduleStartProfiles() {
        // Parent user transition to RUNNING_UNLOCKING happens on FgThread, so it is busy, there is
        // a chance the profile will reach RUNNING_LOCKED while parent is still locked, so no
@@ -1889,7 +1935,8 @@ class UserController implements Handler.Callback {
                    updateStartedUserArrayLU();
                    needStart = true;
                    updateUmState = true;
                } else if (uss.state == UserState.STATE_SHUTDOWN) {
                } else if (uss.state == UserState.STATE_SHUTDOWN
                        || mDoNotAbortShutdownUserIds.contains(userId)) {
                    Slogf.i(TAG, "User #" + userId
                            + " is shutting down - will start after full shutdown");
                    mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
@@ -2293,7 +2340,7 @@ class UserController implements Handler.Callback {
                hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
        synchronized (mLock) {
            // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
            if (hasRestriction || shouldStopUserOnSwitch()) {
            if (hasRestriction || isStopUserOnSwitchEnabled()) {
                Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
                stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
                return;
@@ -3425,7 +3472,7 @@ class UserController implements Handler.Callback {
            pw.println("  mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
            pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
            pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
            pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
            pw.println("  isStopUserOnSwitchEnabled():" + isStopUserOnSwitchEnabled());
            pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
            pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
            pw.println("  mBackgroundUserScheduledStopTimeSecs:"
@@ -3522,6 +3569,7 @@ class UserController implements Handler.Callback {
                        Integer.toString(msg.arg1), msg.arg1);

                mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1);
                stopPreviousUserPackagesIfEnabled(msg.arg2, msg.arg1);
                scheduleOnUserCompletedEvent(msg.arg1,
                        UserCompletedEventType.EVENT_TYPE_USER_SWITCHING,
                        USER_COMPLETED_EVENT_DELAY_MS);
@@ -3896,10 +3944,10 @@ class UserController implements Handler.Callback {
            }.sendNext();
        }

        void activityManagerForceStopPackage(@UserIdInt int userId, String reason) {
        void activityManagerForceStopUserPackages(@UserIdInt int userId, String reason,
                boolean evenImportantServices) {
            synchronized (mService) {
                mService.forceStopPackageLocked(null, -1, false, false, true, false, false, false,
                        userId, reason);
                mService.forceStopUserPackagesLocked(userId, reason, evenImportantServices);
            }
        };

Loading