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

Commit 7b20ccdc authored by Adam Bookatz's avatar Adam Bookatz
Browse files

UserController.startInBackgroundTemporarily

Provide a method to start a user in the background for some duration of
time, after which - if the user is still in the background - the system
will attempt to stop it.

This can be used in the situation that we are only starting a user in
the background to determine what alarms it has after boot-up. We don't
want that user to persist in the background for long, since it will
needlessly consume resources. This allows us to instead quickly stop the
user in such a case. We switch to using this method for this case.

We do this by passing in a parameter to startUser(), indicating when to
schedule stopping the user. We really have no choice but to pass a
parameter: If we instead tried to complete startUser() and only then
schedule the stopping (if the user wasn't already running), we would be
stuck in the situation that there are multiple schedules (such as due to
mBackgroundUserScheduledStopTimeSecs): depending on our implementation,
we would have to *always* either pick the shortest or longest such
duration, and we wouldn't want that.

Test: UserControllerTest
Flag: android.multiuser.schedule_stop_of_background_user
Bug: 330351042
Bug: 401340391
Change-Id: I62310c375571e6e91d0e30b200cbba7b98d8cec8
parent 959076ef
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -4525,8 +4525,10 @@ public class AlarmManagerService extends SystemService {
                                    mUserWakeupStore.getUserIdsToWakeup(nowELAPSED);
                            for (int i = 0; i < userIds.length; i++) {
                                if (mActivityManagerInternal.isUserRunning(userIds[i], 0)
                                        || !mActivityManagerInternal.startUserInBackground(
                                                userIds[i])) {
                                        || !mActivityManagerInternal
                                                .startUserInBackgroundTemporarily(
                                                        userIds[i],
                                                        UserWakeupStore.USER_RUN_FOR_TIME_SECS)) {
                                    mUserWakeupStore.removeUserWakeup(userIds[i]);
                                }
                            }
+3 −0
Original line number Diff line number Diff line
@@ -80,6 +80,9 @@ public class UserWakeupStore {
     */
    @VisibleForTesting
    static final long BUFFER_TIME_MS = TimeUnit.SECONDS.toMillis(30);

    /** When a user is being woken due to a pending alarm, for how long should it be woken up. */
    public static final int USER_RUN_FOR_TIME_SECS = (int) TimeUnit.MINUTES.toSeconds(10);
    /**
     * Maximum time deviation limit to introduce a 5-second time window for user starts.
     */
+12 −2
Original line number Diff line number Diff line
@@ -162,11 +162,21 @@ public abstract class ActivityManagerInternal {
    public abstract void onUserRemoved(@UserIdInt int userId);

    /**
     * Start user, if it is not already running, but don't bring it to foreground.
     * Start user in the background but only temporarily; if the user hasn't left the background
     * in the provided duration, it may be automatically stopped (at the system's discretion).
     *
     * The automatic stopping is not guaranteed, and there are many cases in which it won't be.
     *
     * Conversely, there is no guarantee that the user will not be stopped prior to the given
     * duration; e.g. if the default inactive stopping time (from
     * config_backgroundUserScheduledStopTimeSecs) is sooner, then we may stop it then
     * instead. There is no guarantee about this implementation.
     *
     * @param userId ID of the user to start
     * @param durSecs in how many seconds we should attempt to stop the user
     * @return true if the user has been successfully started
     */
    public abstract boolean startUserInBackground(int userId);
    public abstract boolean startUserInBackgroundTemporarily(@UserIdInt int userId, int durSecs);

    /**
     * Kill foreground apps from the specified user.
+2 −2
Original line number Diff line number Diff line
@@ -16789,8 +16789,8 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
        @Override
        public boolean startUserInBackground(final int userId) {
            return ActivityManagerService.this.startUserInBackground(userId);
        public boolean startUserInBackgroundTemporarily(@UserIdInt int userId, int durSecs) {
            return mUserController.startUserInBackgroundTemporarily(userId, durSecs);
        }
        @Override
+101 −38
Original line number Diff line number Diff line
@@ -202,7 +202,7 @@ class UserController implements Handler.Callback {
    static final int START_USER_SWITCH_FG_MSG = 120;
    static final int COMPLETE_USER_SWITCH_MSG = 130;
    static final int USER_COMPLETED_EVENT_MSG = 140;
    static final int SCHEDULED_STOP_BACKGROUND_USER_MSG = 150;
    static final int SCHEDULE_STOP_BACKGROUND_USER_MSG = 150;
    static final int USER_SWITCHING_DIALOG_ANIMATION_TIMEOUT_MSG = 160;

    private static final int NO_ARG2 = 0;
@@ -1276,7 +1276,7 @@ class UserController implements Handler.Callback {
            KeyEvictedCallback keyEvictedCallback) {
        Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
        if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
            mHandler.removeEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG,
                    Integer.valueOf(userId));
        }
        final UserState uss = mStartedUsers.get(userId);
@@ -1576,8 +1576,10 @@ class UserController implements Handler.Callback {
            for (PendingUserStart userStart: mPendingUserStarts) {
                if (userStart.userId == userId) {
                    Slogf.i(TAG, "resumePendingUserStart for" + userStart);
                    mHandler.post(() -> startUser(userStart.userId,
                            userStart.userStartMode, userStart.unlockListener));
                    // TODO(b/416746742): Don't we need to track the displayId, not use the DEFAULT?
                    mHandler.post(() -> startUserNoChecks(userStart.userId, Display.DEFAULT_DISPLAY,
                            userStart.userStartMode, userStart.autoStopUserInSecs,
                            userStart.unlockListener));

                    handledUserStarts.add(userStart);
                }
@@ -1884,6 +1886,11 @@ class UserController implements Handler.Callback {
                USER_START_MODE_BACKGROUND_VISIBLE, unlockListener);
    }

    boolean startUserInBackgroundTemporarily(@UserIdInt int userId, int durationSecs) {
        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, USER_START_MODE_BACKGROUND,
                durationSecs, /* unlockListener= */ null);
    }

    @VisibleForTesting
    boolean startUser(@UserIdInt int userId, @UserStartMode int userStartMode) {
        return startUser(userId, userStartMode, /* unlockListener= */ null);
@@ -1966,22 +1973,30 @@ class UserController implements Handler.Callback {

    private boolean startUserNoChecks(@UserIdInt int userId, int displayId,
            @UserStartMode int userStartMode, @Nullable IProgressListener unlockListener) {
        return startUserNoChecks(userId, displayId, userStartMode, -1, unlockListener);
    }

    private boolean startUserNoChecks(@UserIdInt int userId, int displayId,
            @UserStartMode int userStartMode, int autoStopUserInSecs,
            @Nullable IProgressListener unlockListener) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();

        t.traceBegin("UserController.startUser-" + userId
                + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
                + "-" + (userStartMode == USER_START_MODE_FOREGROUND ? "fg" : "bg")
                + (autoStopUserInSecs > 0 ? "-for-" + autoStopUserInSecs + "s" : "")
                + "-start-mode-" + userStartMode);
        try {
            return startUserInternal(userId, displayId, userStartMode, unlockListener, t);
            return startUserInternal(userId, displayId, userStartMode, autoStopUserInSecs,
                    unlockListener, t);
        } finally {
            t.traceEnd();
        }
    }

    private boolean startUserInternal(@UserIdInt int userId, int displayId,
            @UserStartMode int userStartMode, @Nullable IProgressListener unlockListener,
            TimingsTraceAndSlog t) {
            @UserStartMode int userStartMode, int autoStopUserInSecs,
            @Nullable IProgressListener unlockListener, TimingsTraceAndSlog t) {
        if (DEBUG_MU) {
            Slogf.i(TAG, "Starting user %d on display %d with mode  %s", userId, displayId,
                    userStartModeToString(userStartMode));
@@ -1993,6 +2008,11 @@ class UserController implements Handler.Callback {
            Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
                    + "on secondary display (%d)", userId, displayId);
        }
        if (autoStopUserInSecs > 0) {
            Preconditions.checkArgument(userStartMode == USER_START_MODE_BACKGROUND,
                    "Cannot auto-stop a non-bg (%d) user %d in %d s",
                    userStartMode, userId, autoStopUserInSecs);
        }
        EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId, foreground ? 1 : 0,
                displayId);

@@ -2001,8 +2021,8 @@ class UserController implements Handler.Callback {
        final long ident = Binder.clearCallingIdentity();
        try {
            t.traceBegin("getStartedUserState");
            final int oldUserId = getCurrentUserId();
            if (oldUserId == userId) {
            final int oldCurrentUserId = getCurrentUserId();
            if (oldCurrentUserId == userId) {
                // The user we're requested to start is already the current user.
                final UserState state = getStartedUserState(userId);
                if (state == null) {
@@ -2082,8 +2102,9 @@ class UserController implements Handler.Callback {
                        || mDoNotAbortShutdownUserIds.contains(userId)) {
                    Slogf.i(TAG, "User #" + userId
                            + " is shutting down - will start after full shutdown");
                    // TODO(b/416746742): Don't we need to track the displayId too?
                    mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
                            unlockListener));
                            autoStopUserInSecs, unlockListener));
                    t.traceEnd(); // updateStartedUserArrayStarting
                    return true;
                }
@@ -2092,10 +2113,6 @@ class UserController implements Handler.Callback {
            // No matter what, the fact that we're requested to start the user (even if it is
            // already running) puts it towards the end of the mUserLru list.
            addUserToUserLru(userId);
            if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
                mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
                        Integer.valueOf(userId));
            }

            if (unlockListener != null) {
                uss.mUnlockProgress.addListener(unlockListener);
@@ -2111,7 +2128,8 @@ class UserController implements Handler.Callback {
            UserState finalUss = uss;
            boolean finalNeedStart = needStart;
            final Runnable continueStartUserInternal = () -> continueStartUserInternal(userInfo,
                    oldUserId, userStartMode, finalUss, finalNeedStart, callingUid, callingPid);
                    oldCurrentUserId, userStartMode, autoStopUserInSecs, finalUss, finalNeedStart,
                    callingUid, callingPid);
            if (foreground) {
                mHandler.post(() -> dispatchOnBeforeUserSwitching(userId, () ->
                        mHandler.post(continueStartUserInternal)));
@@ -2125,8 +2143,9 @@ class UserController implements Handler.Callback {
        return true;
    }

    private void continueStartUserInternal(UserInfo userInfo, int oldUserId, int userStartMode,
            UserState uss, boolean needStart, int callingUid, int callingPid) {
    private void continueStartUserInternal(UserInfo userInfo, int oldCurUserId,
            int userStartMode, int autoStopUserInSecs, UserState uss, boolean needStart,
            int callingUid, int callingPid) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
        final int userId = userInfo.id;
@@ -2165,9 +2184,6 @@ class UserController implements Handler.Callback {
            // of mUserLru, so we need to ensure that the foreground user isn't displaced.
            addUserToUserLru(mCurrentUserId);
        }
        if (userStartMode == USER_START_MODE_BACKGROUND && userInfo.isFull()) {
            scheduleStopOfBackgroundUser(userId);
        }
        t.traceEnd();

        // Make sure user is in the started state.  If it is currently
@@ -2210,15 +2226,33 @@ class UserController implements Handler.Callback {
            t.traceEnd();
        }

        if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            if (userStartMode == USER_START_MODE_BACKGROUND && userInfo.isFull() &&
                    (autoStopUserInSecs = determineWhenToScheduleStop(autoStopUserInSecs)) > 0) {
                if (!needStart
                        && !mHandler.hasEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG,
                                Integer.valueOf(userId))) {
                    Slogf.d(TAG, "Not scheduling background user stop: user %d is already running"
                            + " in background in perpetuity, so keep it that way", userId);
                } else {
                    scheduleStopOfBackgroundUser(userId, autoStopUserInSecs);
                }
            } else {
                // This start shouldn't be scheduled for stopping. Clear existing scheduled stops.
                mHandler.removeEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG,
                        Integer.valueOf(userId));
            }
        }

        t.traceBegin("sendMessages");
        if (foreground) {
            mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
            mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldCurUserId));
            mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
            mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
                    oldUserId, userId, uss));
                    oldCurUserId, userId, uss));
            mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                    oldUserId, userId, uss), getUserSwitchTimeoutMs());
                    oldCurUserId, userId, uss), getUserSwitchTimeoutMs());
        }

        if (userInfo.preCreated) {
@@ -2256,6 +2290,22 @@ class UserController implements Handler.Callback {
        }
    }

    /**
     * Determines when to schedule stopping a background user, balancing between the provided
     * parameter and the default {@link #mBackgroundUserScheduledStopTimeSecs}. It is the smallest
     * positive value among them.
     * Returns a non-positive value (i.e. don't schedule) if both are non-positive.
     */
    private int determineWhenToScheduleStop(int autoStopUserInSecs) {
        if (autoStopUserInSecs <= 0) {
            return mBackgroundUserScheduledStopTimeSecs;
        }
        if (mBackgroundUserScheduledStopTimeSecs <= 0) {
            return autoStopUserInSecs;
        }
        return Math.min(mBackgroundUserScheduledStopTimeSecs, autoStopUserInSecs);
    }

    /**
     * Start user, if it's not already running, and bring it to foreground.
     */
@@ -2583,14 +2633,21 @@ class UserController implements Handler.Callback {

    /**
     * Possibly schedules the user to be stopped at a future point. To be used to stop background
     * users that haven't been actively used in a long time.
     * users that haven't been actively used in a long time, using the default delay for that.
     * This is only intended for full users that are currently in the background.
     */
    private void scheduleStopOfInactiveBackgroundUser(@UserIdInt int oldUserId) {
        scheduleStopOfBackgroundUser(oldUserId, mBackgroundUserScheduledStopTimeSecs);
    }

    /**
     * Possibly schedules the user to be stopped at after the given number of seconds.
     * This is only intended for full users that are currently in the background.
     */
    private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId) {
    private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId, int delayUptimeSecs) {
        if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            return;
        }
        final int delayUptimeSecs = mBackgroundUserScheduledStopTimeSecs;
        if (delayUptimeSecs <= 0 || UserManager.isVisibleBackgroundUsersEnabled()) {
            // Feature is not enabled on this device.
            return;
@@ -2616,9 +2673,8 @@ class UserController implements Handler.Callback {
        Slogf.d(TAG, "Scheduling to stop user %d in %d seconds", oldUserId, delayUptimeSecs);
        final int delayUptimeMs = delayUptimeSecs * 1000;
        final Object msgObj = oldUserId;
        mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj);
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj),
                mHandler.obtainMessage(SCHEDULE_STOP_BACKGROUND_USER_MSG, msgObj),
                delayUptimeMs);
    }

@@ -2627,7 +2683,7 @@ class UserController implements Handler.Callback {
     * There is no guarantee of stopping the user; it is done discretionarily.
     *
     * This should never be called for background visible users; devices that support this should
     * not use {@link #scheduleStopOfBackgroundUser(int)}.
     * not use {@link #scheduleStopOfBackgroundUser(int, int)}.
     *
     * @param userIdInteger a full user to be stopped if it is still in the background
     */
@@ -2636,16 +2692,20 @@ class UserController implements Handler.Callback {
        final int userId = userIdInteger;
        Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);

        if (mHandler.hasEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG, userIdInteger)) {
            Slogf.i(TAG, "User %d is scheduled for bg stopping later, so wait until then", userId);
            return;
        }
        if (avoidStoppingUserDueToUpcomingAlarm(userId)) {
            // We want this user running soon for alarm-purposes, so don't stop it now. Reschedule.
            Slogf.d(TAG, "User %d will fire an alarm soon, so reschedule bg stopping", userId);
            scheduleStopOfBackgroundUser(userId);
            scheduleStopOfInactiveBackgroundUser(userId);
            return;
        }
        if (mInjector.getAudioManagerInternal().isUserPlayingAudio(userId)) {
            // User is audible (even if invisibly, e.g. via an alarm), so don't stop it. Reschedule.
            Slogf.d(TAG, "User %d is playing audio, so reschedule bg stopping", userId);
            scheduleStopOfBackgroundUser(userId);
            scheduleStopOfInactiveBackgroundUser(userId);
            return;
        }
        synchronized (mLock) {
@@ -2660,7 +2720,7 @@ class UserController implements Handler.Callback {
            final UserInfo currentOrTargetUser = getCurrentUserLU();
            if (currentOrTargetUser != null && currentOrTargetUser.isGuest()) {
                // Don't kill any background users for the sake of a Guest. Just reschedule instead.
                scheduleStopOfBackgroundUser(userId);
                scheduleStopOfInactiveBackgroundUser(userId);
                return;
            }
            Slogf.i(TAG, "Stopping background user %d due to inactivity", userId);
@@ -2675,9 +2735,9 @@ class UserController implements Handler.Callback {
    private boolean avoidStoppingUserDueToUpcomingAlarm(@UserIdInt int userId) {
        final long alarmWallclockMs
                = mInjector.getAlarmManagerInternal().getNextAlarmTriggerTimeForUser(userId);
        return System.currentTimeMillis() <  alarmWallclockMs
                && (alarmWallclockMs
                    < System.currentTimeMillis() + TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS);
        final long now = System.currentTimeMillis();
        return now < alarmWallclockMs
                && (alarmWallclockMs < now + TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS);
    }

    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
@@ -2809,7 +2869,7 @@ class UserController implements Handler.Callback {
        uss.switching = false;
        stopGuestOrEphemeralUserIfBackground(oldUserId);
        stopUserOnSwitchIfEnforced(oldUserId);
        scheduleStopOfBackgroundUser(oldUserId);
        scheduleStopOfInactiveBackgroundUser(oldUserId);

        t.traceEnd(); // end continueUserSwitch
    }
@@ -3816,7 +3876,7 @@ class UserController implements Handler.Callback {
            case COMPLETE_USER_SWITCH_MSG:
                completeUserSwitch(msg.arg1, msg.arg2);
                break;
            case SCHEDULED_STOP_BACKGROUND_USER_MSG:
            case SCHEDULE_STOP_BACKGROUND_USER_MSG:
                processScheduledStopOfBackgroundUser((Integer) msg.obj);
                break;
        }
@@ -3995,12 +4055,14 @@ class UserController implements Handler.Callback {
    private static class PendingUserStart {
        public final @UserIdInt int userId;
        public final @UserStartMode int userStartMode;
        public final int autoStopUserInSecs;
        public final IProgressListener unlockListener;

        PendingUserStart(int userId, @UserStartMode int userStartMode,
        PendingUserStart(int userId, @UserStartMode int userStartMode, int autoStopUserInSecs,
                IProgressListener unlockListener) {
            this.userId = userId;
            this.userStartMode = userStartMode;
            this.autoStopUserInSecs = autoStopUserInSecs;
            this.unlockListener = unlockListener;
        }

@@ -4009,6 +4071,7 @@ class UserController implements Handler.Callback {
            return "PendingUserStart{"
                    + "userId=" + userId
                    + ", userStartMode=" + userStartModeToString(userStartMode)
                    + ", autoStopUserInSecs=" + autoStopUserInSecs
                    + ", unlockListener=" + unlockListener
                    + '}';
        }
Loading