summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Adam Bookatz <bookatz@google.com> 2024-08-12 16:25:49 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-08-12 16:25:49 +0000
commitf249256c8b49dceba7015bba34999bb16b527012 (patch)
treebfccd764f6e4529e3491afd1650edd1d1acf6c4c
parent3028ce3e2e65ba4790d11f90a71222295f8a4a71 (diff)
parentce09fdef598f11bc50b26ea50e800309ea8ce247 (diff)
Merge "Don't stop scheduled background user near alarm" into main
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java8
-rw-r--r--services/core/java/com/android/server/AlarmManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/am/UserController.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java42
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;
}