Loading apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +8 −0 Original line number Diff line number Diff line Loading @@ -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 = Loading Loading @@ -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) { Loading services/core/java/com/android/server/AlarmManagerInternal.java +11 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading services/core/java/com/android/server/am/UserController.java +29 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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); } Loading services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +42 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -1838,6 +1875,11 @@ public class UserControllerTest { return mPowerManagerInternal; } @Override AlarmManagerInternal getAlarmManagerInternal() { return mAlarmManagerInternal; } @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; Loading Loading
apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +8 −0 Original line number Diff line number Diff line Loading @@ -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 = Loading Loading @@ -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) { Loading
services/core/java/com/android/server/AlarmManagerInternal.java +11 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading
services/core/java/com/android/server/am/UserController.java +29 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. * Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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); } Loading
services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +42 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -1838,6 +1875,11 @@ public class UserControllerTest { return mPowerManagerInternal; } @Override AlarmManagerInternal getAlarmManagerInternal() { return mAlarmManagerInternal; } @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock; Loading