diff options
author | 2024-08-02 15:58:31 -0700 | |
---|---|---|
committer | 2024-08-09 09:51:08 -0500 | |
commit | ce09fdef598f11bc50b26ea50e800309ea8ce247 (patch) | |
tree | ab1ba78cf2afc73f9aebfd61e281c3b0b6045b12 | |
parent | ecbdc5f1a341b104798685d3f0a4421b2f112ba4 (diff) |
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
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 18ee6f2c7992..e0d1d529ccac 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 30efa3e87fc6..ff64230051b6 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 @@ -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); } 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 dbab54b76a2e..6a15cefee32f 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -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); @@ -1781,6 +1818,11 @@ public class UserControllerTest { } @Override + AlarmManagerInternal getAlarmManagerInternal() { + return mAlarmManagerInternal; + } + + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; } |