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

Commit f249256c authored by Adam Bookatz's avatar Adam Bookatz Committed by Android (Google) Code Review
Browse files

Merge "Don't stop scheduled background user near alarm" into main

parents 3028ce3e ce09fdef
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.
     *
@@ -2418,6 +2425,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;
@@ -2437,6 +2450,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);
@@ -3908,6 +3933,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
@@ -105,6 +105,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;
@@ -124,6 +125,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;
@@ -730,6 +732,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.
@@ -1747,6 +1782,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;

@@ -1769,6 +1805,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);
@@ -1838,6 +1875,11 @@ public class UserControllerTest {
            return mPowerManagerInternal;
        }

        @Override
        AlarmManagerInternal getAlarmManagerInternal() {
            return mAlarmManagerInternal;
        }

        @Override
        KeyguardManager getKeyguardManager() {
            return mKeyguardManagerMock;