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

Commit ce09fdef authored by Adam Bookatz's avatar Adam Bookatz
Browse files

Don't stop scheduled background user near alarm

If a user's alarm is going to go off in the next X minutes, don't
automatically stop a background user. There's no point automatically
stopping a user that we're just going to (or want to) restart soon
due to its alarm.

Bug: 353734966
Bug: 330351042
Flag: android.multiuser.schedule_stop_of_background_user
Test: atest FrameworksServicesTests:UserControllerTest#testScheduleStopOfBackgroundUser_rescheduleIfAlarm
Change-Id: I4813804f2646279871717be723d33e6c66d10033
parent ecbdc5f1
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -357,6 +357,7 @@ public class AlarmManagerService extends SystemService {
    }

    // TODO(b/172085676): Move inside alarm store.
    @GuardedBy("mLock")
    private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
            new SparseArray<>();
    private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray =
@@ -2616,6 +2617,13 @@ public class AlarmManagerService extends SystemService {
                mInFlightListeners.add(callback);
            }
        }

        /** @see AlarmManagerInternal#getNextAlarmTriggerTimeForUser(int) */
        @Override
        public long getNextAlarmTriggerTimeForUser(@UserIdInt int userId) {
            final AlarmManager.AlarmClockInfo nextAlarm = getNextAlarmClockImpl(userId);
            return nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
        }
    }

    boolean hasUseExactAlarmInternal(String packageName, int uid) {
+11 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.UserIdInt;
import android.app.PendingIntent;

import com.android.server.SystemClockTime.TimeConfidence;
@@ -36,6 +37,16 @@ public interface AlarmManagerInternal {
    /** Returns true if AlarmManager is delaying alarms due to device idle. */
    boolean isIdling();

    /**
     * Returns the time at which the next alarm for the given user is going to trigger, or 0 if
     * there is none.
     *
     * <p>This value is UTC wall clock time in milliseconds, as returned by
     * {@link System#currentTimeMillis()} for example.
     * @see android.app.AlarmManager.AlarmClockInfo#getTriggerTime()
     */
    long getNextAlarmTriggerTimeForUser(@UserIdInt int userId);

    public void removeAlarmsForUid(int uid);

    public void registerInFlightListener(InFlightListener callback);
+29 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.AlarmManagerInternal;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -246,6 +247,12 @@ class UserController implements Handler.Callback {
    // TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
    private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;

    /**
     * If a user has an alarm in the next this many milliseconds, avoid stopping it due to
     * scheduled background stopping.
     */
    private static final long TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS = 60 * 60_000; // 60 mins

    /**
     * Maximum number of users we allow to be running at a time, including system user.
     *
@@ -2371,6 +2378,12 @@ class UserController implements Handler.Callback {
    void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
        final int userId = userIdInteger;
        Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);

        if (avoidStoppingUserDueToUpcomingAlarm(userId)) {
            // We want this user running soon for alarm-purposes, so don't stop it now. Reschedule.
            scheduleStopOfBackgroundUser(userId);
            return;
        }
        synchronized (mLock) {
            if (getCurrentOrTargetUserIdLU() == userId) {
                return;
@@ -2390,6 +2403,18 @@ class UserController implements Handler.Callback {
        }
    }

    /**
     * Returns whether we should avoid stopping the user now due to it having an alarm set to fire
     * soon.
     */
    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);
    }

    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
        t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
@@ -3860,6 +3885,10 @@ class UserController implements Handler.Callback {
            return mPowerManagerInternal;
        }

        AlarmManagerInternal getAlarmManagerInternal() {
            return LocalServices.getService(AlarmManagerInternal.class);
        }

        KeyguardManager getKeyguardManager() {
            return mService.mContext.getSystemService(KeyguardManager.class);
        }
+42 −0
Original line number Diff line number Diff line
@@ -103,6 +103,7 @@ import android.view.Display;
import androidx.test.filters.SmallTest;

import com.android.internal.widget.LockPatternUtils;
import com.android.server.AlarmManagerInternal;
import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
@@ -122,6 +123,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -727,6 +729,39 @@ public class UserControllerTest {
                mUserController.getRunningUsersLU());
    }

    /** Test scheduling stopping of background users - reschedule if user with a scheduled alarm. */
    @Test
    public void testScheduleStopOfBackgroundUser_rescheduleIfAlarm() throws Exception {
        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);

        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
                /* backgroundUserScheduledStopTimeSecs= */ 2);

        setUpAndStartUserInBackground(TEST_USER_ID);
        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID),
                new HashSet<>(mUserController.getRunningUsersLU()));

        // Initially, the background user has an alarm that will fire soon. So don't stop the user.
        when(mInjector.mAlarmManagerInternal.getNextAlarmTriggerTimeForUser(eq(TEST_USER_ID)))
                .thenReturn(System.currentTimeMillis() + Duration.ofMinutes(2).toMillis());
        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID),
                new HashSet<>(mUserController.getRunningUsersLU()));

        // Now, that alarm is gone and the next alarm isn't for a long time. Do stop the user.
        when(mInjector.mAlarmManagerInternal.getNextAlarmTriggerTimeForUser(eq(TEST_USER_ID)))
                .thenReturn(System.currentTimeMillis() + Duration.ofDays(1).toMillis());
        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
        assertEquals(newHashSet(SYSTEM_USER_ID),
                new HashSet<>(mUserController.getRunningUsersLU()));

        // No-one is scheduled to stop anymore.
        assertAndProcessScheduledStopBackgroundUser(false, null);
        verify(mInjector.mAlarmManagerInternal, never())
                .getNextAlarmTriggerTimeForUser(eq(SYSTEM_USER_ID));
    }

    /**
     * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
     * @param userId the user we are checking to see whether it is scheduled.
@@ -1689,6 +1724,7 @@ public class UserControllerTest {
        private final WindowManagerService mWindowManagerMock;
        private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
        private final PowerManagerInternal mPowerManagerInternal;
        private final AlarmManagerInternal mAlarmManagerInternal;
        private final KeyguardManager mKeyguardManagerMock;
        private final LockPatternUtils mLockPatternUtilsMock;

@@ -1711,6 +1747,7 @@ public class UserControllerTest {
            mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
            mStorageManagerMock = mock(IStorageManager.class);
            mPowerManagerInternal = mock(PowerManagerInternal.class);
            mAlarmManagerInternal = mock(AlarmManagerInternal.class);
            mKeyguardManagerMock = mock(KeyguardManager.class);
            when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
            mLockPatternUtilsMock = mock(LockPatternUtils.class);
@@ -1780,6 +1817,11 @@ public class UserControllerTest {
            return mPowerManagerInternal;
        }

        @Override
        AlarmManagerInternal getAlarmManagerInternal() {
            return mAlarmManagerInternal;
        }

        @Override
        KeyguardManager getKeyguardManager() {
            return mKeyguardManagerMock;