diff options
author | 2024-08-12 16:25:49 +0000 | |
---|---|---|
committer | 2024-08-12 16:25:49 +0000 | |
commit | f249256c8b49dceba7015bba34999bb16b527012 (patch) | |
tree | bfccd764f6e4529e3491afd1650edd1d1acf6c4c | |
parent | 3028ce3e2e65ba4790d11f90a71222295f8a4a71 (diff) | |
parent | ce09fdef598f11bc50b26ea50e800309ea8ce247 (diff) |
Merge "Don't stop scheduled background user near alarm" into main
4 files changed, 90 insertions, 0 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index ba66ff72bfdd..d61439c0751c 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -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) { diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java index b7f2b8d4ffe6..191137ade3a3 100644 --- a/services/core/java/com/android/server/AlarmManagerInternal.java +++ b/services/core/java/com/android/server/AlarmManagerInternal.java @@ -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); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index e57fe133eac8..bdba6bcf66c0 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -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; @@ -247,6 +248,12 @@ class UserController implements Handler.Callback { 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. * * <p>This parameter only affects how many background users will be stopped when switching to a @@ -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); } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index a8e350b05f18..1db46bf17655 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -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); @@ -1839,6 +1876,11 @@ public class UserControllerTest { } @Override + AlarmManagerInternal getAlarmManagerInternal() { + return mAlarmManagerInternal; + } + + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; } |