Loading apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +67 −1 Original line number Diff line number Diff line Loading @@ -290,6 +290,8 @@ public class AlarmManagerService extends SystemService { // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>(); private boolean mStartUserBeforeScheduledAlarms; private long mNextWakeup; private long mNextNonWakeup; private long mNextWakeUpSetAt; Loading Loading @@ -1382,6 +1384,7 @@ public class AlarmManagerService extends SystemService { @GuardedBy("mLock") AlarmStore mAlarmStore; UserWakeupStore mUserWakeupStore; // set to non-null if in idle mode; while in this mode, any alarms we don't want // to run during this time are rescehduled to go off after this alarm. Alarm mPendingIdleUntil = null; Loading Loading @@ -1882,6 +1885,7 @@ public class AlarmManagerService extends SystemService { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms(); mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms(); if (mUseFrozenStateToDropListenerAlarms) { final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> { final int size = frozenStates.length; Loading Loading @@ -2000,6 +2004,10 @@ public class AlarmManagerService extends SystemService { Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler."); } } if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore = new UserWakeupStore(); mUserWakeupStore.init(); } publishLocalService(AlarmManagerInternal.class, new LocalService()); publishBinderService(Context.ALARM_SERVICE, mService); } Loading Loading @@ -2041,6 +2049,9 @@ public class AlarmManagerService extends SystemService { public void onUserStarting(TargetUser user) { super.onUserStarting(user); final int userId = user.getUserIdentifier(); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.onUserStarting(userId); } mHandler.post(() -> { for (final int appId : mExactAlarmCandidates) { final int uid = UserHandle.getUid(userId, appId); Loading Loading @@ -3150,6 +3161,9 @@ public class AlarmManagerService extends SystemService { pw.increaseIndent(); pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS, mUseFrozenStateToDropListenerAlarms); pw.println(); pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS, mStartUserBeforeScheduledAlarms); pw.decreaseIndent(); pw.println(); pw.println(); Loading Loading @@ -3398,6 +3412,12 @@ public class AlarmManagerService extends SystemService { pw.println("]"); pw.println(); if (mStartUserBeforeScheduledAlarms) { pw.println("Scheduled user wakeups:"); mUserWakeupStore.dump(pw, nowELAPSED); pw.println(); } pw.println("App Alarm history:"); mAppWakeupHistory.dump(pw, nowELAPSED); Loading Loading @@ -3945,10 +3965,19 @@ public class AlarmManagerService extends SystemService { formatNextAlarm(getContext(), alarmClock, userId)); } mNextAlarmClockForUser.put(userId, alarmClock); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.addUserWakeup(userId, convertToElapsed( mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC)); } } else { if (DEBUG_ALARM_CLOCK) { Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None"); } if (mStartUserBeforeScheduledAlarms) { if (mActivityManagerInternal.isUserRunning(userId, 0)) { mUserWakeupStore.removeUserWakeup(userId); } } mNextAlarmClockForUser.remove(userId); } Loading Loading @@ -4003,13 +4032,20 @@ public class AlarmManagerService extends SystemService { DateFormat.format(pattern, info.getTriggerTime()).toString(); } @GuardedBy("mLock") void rescheduleKernelAlarmsLocked() { // Schedule the next upcoming wakeup alarm. If there is a deliverable batch // prior to that which contains no wakeups, we schedule that as well. final long nowElapsed = mInjector.getElapsedRealtimeMillis(); long nextNonWakeup = 0; if (mAlarmStore.size() > 0) { final long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime(); long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime(); if (mStartUserBeforeScheduledAlarms) { final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime(); if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) { firstWakeup = firstUserWakeup; } } final long first = mAlarmStore.getNextDeliveryTime(); if (firstWakeup != 0) { mNextWakeup = firstWakeup; Loading Loading @@ -4716,6 +4752,16 @@ public class AlarmManagerService extends SystemService { + ", elapsed=" + nowELAPSED); } if (mStartUserBeforeScheduledAlarms) { final int[] userIds = mUserWakeupStore.getUserIdsToWakeup(nowELAPSED); for (int i = 0; i < userIds.length; i++) { if (!mActivityManagerInternal.startUserInBackground( userIds[i])) { mUserWakeupStore.removeUserWakeup(userIds[i]); } } } mLastTrigger = nowELAPSED; final int wakeUps = triggerAlarmsLocked(triggerList, nowELAPSED); if (wakeUps == 0 && checkAllowNonWakeupDelayLocked(nowELAPSED)) { Loading Loading @@ -5164,6 +5210,10 @@ public class AlarmManagerService extends SystemService { IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); if (mStartUserBeforeScheduledAlarms) { sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); sdFilter.addAction(Intent.ACTION_USER_REMOVED); } sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiverForAllUsers(this, sdFilter, /* broadcastPermission */ null, /* scheduler */ null); Loading Loading @@ -5194,6 +5244,22 @@ public class AlarmManagerService extends SystemService { mTemporaryQuotaReserve.removeForUser(userHandle); } return; case Intent.ACTION_LOCKED_BOOT_COMPLETED: final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (handle >= 0) { if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.onUserStarted(handle); } } return; case Intent.ACTION_USER_REMOVED: final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (user >= 0) { if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.onUserRemoved(user); } } return; case Intent.ACTION_UID_REMOVED: mLastPriorityAlarmDispatch.delete(uid); mRemovalHistory.delete(uid); Loading apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java 0 → 100644 +381 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.alarm; import android.annotation.Nullable; import android.os.Environment; import android.os.SystemClock; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * User wakeup store keeps the list of user ids with the times that user needs to be started in * sorted list in order for alarms to execute even if user gets stopped. * The list of user ids with at least one alarms scheduled is also persisted to the XML file to * start them after the device reboot. */ public class UserWakeupStore { private static final boolean DEBUG = false; static final String USER_WAKEUP_TAG = UserWakeupStore.class.getSimpleName(); private static final String TAG_USERS = "users"; private static final String TAG_USER = "user"; private static final String ATTR_USER_ID = "user_id"; private static final String ATTR_VERSION = "version"; public static final int XML_VERSION_CURRENT = 1; @VisibleForTesting static final String ROOT_DIR_NAME = "alarms"; @VisibleForTesting static final String USERS_FILE_NAME = "usersWithAlarmClocks.xml"; /** * Time offset of user start before the original alarm time in milliseconds. * Also used to schedule user start after reboot to avoid starting them simultaneously. */ @VisibleForTesting static final long BUFFER_TIME_MS = TimeUnit.SECONDS.toMillis(30); /** * Maximum time deviation limit to introduce a 5-second time window for user starts. */ @VisibleForTesting static final long USER_START_TIME_DEVIATION_LIMIT_MS = TimeUnit.SECONDS.toMillis(5); /** * Delay between two consecutive user starts scheduled during user wakeup store initialization. */ @VisibleForTesting static final long INITIAL_USER_START_SCHEDULING_DELAY_MS = TimeUnit.SECONDS.toMillis(5); private final Object mUserWakeupLock = new Object(); /** * A list of wakeups for users with scheduled alarms. */ @GuardedBy("mUserWakeupLock") private final SparseLongArray mUserStarts = new SparseLongArray(); /** * A list of users that are in a phase after they have been started but before alarms were * initialized. */ @GuardedBy("mUserWakeupLock") private final SparseLongArray mStartingUsers = new SparseLongArray(); private Executor mBackgroundExecutor; private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME); private static final Random sRandom = new Random(500); /** * Initialize mUserWakeups with persisted values. */ public void init() { mBackgroundExecutor = BackgroundThread.getExecutor(); mBackgroundExecutor.execute(this::readUserIdList); } /** * Add user wakeup for the alarm. * @param userId Id of the user that scheduled alarm. * @param alarmTime time when alarm is expected to trigger. */ public void addUserWakeup(int userId, long alarmTime) { synchronized (mUserWakeupLock) { // This should not be needed, but if an app in the user is scheduling an alarm clock, we // consider the user start complete. There is a dedicated removal when user is started. mStartingUsers.delete(userId); mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset()); } updateUserListFile(); } /** * Remove wakeup scheduled for the user with given userId if present. */ public void removeUserWakeup(int userId) { synchronized (mUserWakeupLock) { mUserStarts.delete(userId); } updateUserListFile(); } /** * Get ids of users that need to be started now. * @param nowElapsed current time. * @return user ids to be started, or empty if no user needs to be started. */ public int[] getUserIdsToWakeup(long nowElapsed) { synchronized (mUserWakeupLock) { final int[] userIds = new int[mUserStarts.size()]; int index = 0; for (int i = mUserStarts.size() - 1; i >= 0; i--) { if (mUserStarts.valueAt(i) <= nowElapsed) { userIds[index++] = mUserStarts.keyAt(i); } } return Arrays.copyOfRange(userIds, 0, index); } } /** * Persist user ids that have alarms scheduled so that they can be started after device reboot. */ private void updateUserListFile() { mBackgroundExecutor.execute(() -> { try { writeUserIdList(); if (DEBUG) { synchronized (mUserWakeupLock) { Slog.i(USER_WAKEUP_TAG, "Printing out user wakeups " + mUserStarts.size()); for (int i = 0; i < mUserStarts.size(); i++) { Slog.i(USER_WAKEUP_TAG, "User id: " + mUserStarts.keyAt(i) + " time: " + mUserStarts.valueAt(i)); } } } } catch (Exception e) { Slog.e(USER_WAKEUP_TAG, "Failed to write " + e.getLocalizedMessage()); } }); } /** * Return scheduled start time for user or -1 if user does not have alarm set. */ @VisibleForTesting long getWakeupTimeForUserForTest(int userId) { synchronized (mUserWakeupLock) { return mUserStarts.get(userId, -1); } } /** * Move user from wakeup list to starting user list. */ public void onUserStarting(int userId) { synchronized (mUserWakeupLock) { mStartingUsers.put(userId, getWakeupTimeForUserForTest(userId)); mUserStarts.delete(userId); } } /** * Remove userId from starting user list once start is complete. */ public void onUserStarted(int userId) { synchronized (mUserWakeupLock) { mStartingUsers.delete(userId); } updateUserListFile(); } /** * Remove userId from the store when the user is removed. */ public void onUserRemoved(int userId) { synchronized (mUserWakeupLock) { mUserStarts.delete(userId); mStartingUsers.delete(userId); } updateUserListFile(); } /** * Get the soonest wakeup time in the store. */ public long getNextWakeupTime() { long nextWakeupTime = -1; synchronized (mUserWakeupLock) { for (int i = 0; i < mUserStarts.size(); i++) { if (mUserStarts.valueAt(i) < nextWakeupTime || nextWakeupTime == -1) { nextWakeupTime = mUserStarts.valueAt(i); } } } return nextWakeupTime; } private static long getUserWakeupOffset() { return sRandom.nextLong(USER_START_TIME_DEVIATION_LIMIT_MS * 2) - USER_START_TIME_DEVIATION_LIMIT_MS; } /** * Write a list of ids for users who have alarm scheduled. * Sample XML file: * * <?xml version='1.0' encoding='utf-8' standalone='yes' ?> * <users version="1"> * <user user_id="12" /> * <user user_id="10" /> * </users> * ~ */ private void writeUserIdList() { final AtomicFile file = getUserWakeupFile(); if (file == null) { return; } try (FileOutputStream fos = file.startWrite(SystemClock.uptimeMillis())) { final XmlSerializer out = new FastXmlSerializer(); out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_USERS); XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); final List<Pair<Integer, Long>> listOfUsers = new ArrayList<>(); synchronized (mUserWakeupLock) { for (int i = 0; i < mUserStarts.size(); i++) { listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i))); } for (int i = 0; i < mStartingUsers.size(); i++) { listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i))); } } Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second)); for (int i = 0; i < listOfUsers.size(); i++) { out.startTag(null, TAG_USER); XmlUtils.writeIntAttribute(out, ATTR_USER_ID, listOfUsers.get(i).first); out.endTag(null, TAG_USER); } out.endTag(null, TAG_USERS); out.endDocument(); file.finishWrite(fos); } catch (IOException e) { Slog.wtf(USER_WAKEUP_TAG, "Error writing user wakeup data", e); file.delete(); } } private void readUserIdList() { final AtomicFile userWakeupFile = getUserWakeupFile(); if (userWakeupFile == null) { return; } else if (!userWakeupFile.exists()) { Slog.w(USER_WAKEUP_TAG, "User wakeup file not available: " + userWakeupFile.getBaseFile()); return; } synchronized (mUserWakeupLock) { mUserStarts.clear(); mStartingUsers.clear(); } try (FileInputStream fis = userWakeupFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(fis); int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Skip } if (type != XmlPullParser.START_TAG) { Slog.e(USER_WAKEUP_TAG, "Unable to read user list. No start tag found in " + userWakeupFile.getBaseFile()); return; } int version = -1; if (parser.getName().equals(TAG_USERS)) { version = parser.getAttributeInt(null, ATTR_VERSION, version); } long counter = 0; final long currentTime = SystemClock.elapsedRealtime(); // Time delay between now and first user wakeup is scheduled. final long scheduleOffset = currentTime + BUFFER_TIME_MS + getUserWakeupOffset(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG) { if (parser.getName().equals(TAG_USER)) { final int id = parser.getAttributeInt(null, ATTR_USER_ID); synchronized (mUserWakeupLock) { mUserStarts.put(id, scheduleOffset + (counter++ * INITIAL_USER_START_SCHEDULING_DELAY_MS)); } } } } } catch (IOException | XmlPullParserException e) { Slog.wtf(USER_WAKEUP_TAG, "Error reading user wakeup data", e); } } @Nullable private AtomicFile getUserWakeupFile() { if (!USER_WAKEUP_DIR.exists() && !USER_WAKEUP_DIR.mkdir()) { Slog.wtf(USER_WAKEUP_TAG, "Failed to mkdir() user list file: " + USER_WAKEUP_DIR); return null; } final File userFile = new File(USER_WAKEUP_DIR, USERS_FILE_NAME); return new AtomicFile(userFile); } void dump(IndentingPrintWriter pw, long nowELAPSED) { synchronized (mUserWakeupLock) { pw.increaseIndent(); pw.print("User wakeup store file path: "); final AtomicFile file = getUserWakeupFile(); if (file == null) { pw.println("null"); } else { pw.println(file.getBaseFile().getAbsolutePath()); } pw.println(mUserStarts.size() + " user wakeups scheduled: "); for (int i = 0; i < mUserStarts.size(); i++) { pw.print("UserId: "); pw.print(mUserStarts.keyAt(i)); pw.print(", userStartTime: "); TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw); pw.println(); } pw.println(mStartingUsers.size() + " starting users: "); for (int i = 0; i < mStartingUsers.size(); i++) { pw.print("UserId: "); pw.print(mStartingUsers.keyAt(i)); pw.print(", userStartTime: "); TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw); pw.println(); } pw.decreaseIndent(); } } } core/java/android/app/ActivityManagerInternal.java +7 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,13 @@ public abstract class ActivityManagerInternal { */ public abstract void onUserRemoved(@UserIdInt int userId); /** * Start user, if it is not already running, but don't bring it to foreground. * @param userId ID of the user to start * @return true if the user has been successfully started */ public abstract boolean startUserInBackground(int userId); /** * Kill foreground apps from the specified user. */ Loading services/core/java/com/android/server/am/ActivityManagerService.java +5 −0 Original line number Diff line number Diff line Loading @@ -18188,6 +18188,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } @Override public boolean startUserInBackground(final int userId) { return ActivityManagerService.this.startUserInBackground(userId); } @Override public void killForegroundAppsForUser(@UserIdInt int userId) { final ArrayList<ProcessRecord> procs = new ArrayList<>(); services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +22 −2 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +67 −1 Original line number Diff line number Diff line Loading @@ -290,6 +290,8 @@ public class AlarmManagerService extends SystemService { // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>(); private boolean mStartUserBeforeScheduledAlarms; private long mNextWakeup; private long mNextNonWakeup; private long mNextWakeUpSetAt; Loading Loading @@ -1382,6 +1384,7 @@ public class AlarmManagerService extends SystemService { @GuardedBy("mLock") AlarmStore mAlarmStore; UserWakeupStore mUserWakeupStore; // set to non-null if in idle mode; while in this mode, any alarms we don't want // to run during this time are rescehduled to go off after this alarm. Alarm mPendingIdleUntil = null; Loading Loading @@ -1882,6 +1885,7 @@ public class AlarmManagerService extends SystemService { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms(); mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms(); if (mUseFrozenStateToDropListenerAlarms) { final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> { final int size = frozenStates.length; Loading Loading @@ -2000,6 +2004,10 @@ public class AlarmManagerService extends SystemService { Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler."); } } if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore = new UserWakeupStore(); mUserWakeupStore.init(); } publishLocalService(AlarmManagerInternal.class, new LocalService()); publishBinderService(Context.ALARM_SERVICE, mService); } Loading Loading @@ -2041,6 +2049,9 @@ public class AlarmManagerService extends SystemService { public void onUserStarting(TargetUser user) { super.onUserStarting(user); final int userId = user.getUserIdentifier(); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.onUserStarting(userId); } mHandler.post(() -> { for (final int appId : mExactAlarmCandidates) { final int uid = UserHandle.getUid(userId, appId); Loading Loading @@ -3150,6 +3161,9 @@ public class AlarmManagerService extends SystemService { pw.increaseIndent(); pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS, mUseFrozenStateToDropListenerAlarms); pw.println(); pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS, mStartUserBeforeScheduledAlarms); pw.decreaseIndent(); pw.println(); pw.println(); Loading Loading @@ -3398,6 +3412,12 @@ public class AlarmManagerService extends SystemService { pw.println("]"); pw.println(); if (mStartUserBeforeScheduledAlarms) { pw.println("Scheduled user wakeups:"); mUserWakeupStore.dump(pw, nowELAPSED); pw.println(); } pw.println("App Alarm history:"); mAppWakeupHistory.dump(pw, nowELAPSED); Loading Loading @@ -3945,10 +3965,19 @@ public class AlarmManagerService extends SystemService { formatNextAlarm(getContext(), alarmClock, userId)); } mNextAlarmClockForUser.put(userId, alarmClock); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.addUserWakeup(userId, convertToElapsed( mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC)); } } else { if (DEBUG_ALARM_CLOCK) { Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None"); } if (mStartUserBeforeScheduledAlarms) { if (mActivityManagerInternal.isUserRunning(userId, 0)) { mUserWakeupStore.removeUserWakeup(userId); } } mNextAlarmClockForUser.remove(userId); } Loading Loading @@ -4003,13 +4032,20 @@ public class AlarmManagerService extends SystemService { DateFormat.format(pattern, info.getTriggerTime()).toString(); } @GuardedBy("mLock") void rescheduleKernelAlarmsLocked() { // Schedule the next upcoming wakeup alarm. If there is a deliverable batch // prior to that which contains no wakeups, we schedule that as well. final long nowElapsed = mInjector.getElapsedRealtimeMillis(); long nextNonWakeup = 0; if (mAlarmStore.size() > 0) { final long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime(); long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime(); if (mStartUserBeforeScheduledAlarms) { final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime(); if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) { firstWakeup = firstUserWakeup; } } final long first = mAlarmStore.getNextDeliveryTime(); if (firstWakeup != 0) { mNextWakeup = firstWakeup; Loading Loading @@ -4716,6 +4752,16 @@ public class AlarmManagerService extends SystemService { + ", elapsed=" + nowELAPSED); } if (mStartUserBeforeScheduledAlarms) { final int[] userIds = mUserWakeupStore.getUserIdsToWakeup(nowELAPSED); for (int i = 0; i < userIds.length; i++) { if (!mActivityManagerInternal.startUserInBackground( userIds[i])) { mUserWakeupStore.removeUserWakeup(userIds[i]); } } } mLastTrigger = nowELAPSED; final int wakeUps = triggerAlarmsLocked(triggerList, nowELAPSED); if (wakeUps == 0 && checkAllowNonWakeupDelayLocked(nowELAPSED)) { Loading Loading @@ -5164,6 +5210,10 @@ public class AlarmManagerService extends SystemService { IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); if (mStartUserBeforeScheduledAlarms) { sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); sdFilter.addAction(Intent.ACTION_USER_REMOVED); } sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiverForAllUsers(this, sdFilter, /* broadcastPermission */ null, /* scheduler */ null); Loading Loading @@ -5194,6 +5244,22 @@ public class AlarmManagerService extends SystemService { mTemporaryQuotaReserve.removeForUser(userHandle); } return; case Intent.ACTION_LOCKED_BOOT_COMPLETED: final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (handle >= 0) { if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.onUserStarted(handle); } } return; case Intent.ACTION_USER_REMOVED: final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (user >= 0) { if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore.onUserRemoved(user); } } return; case Intent.ACTION_UID_REMOVED: mLastPriorityAlarmDispatch.delete(uid); mRemovalHistory.delete(uid); Loading
apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java 0 → 100644 +381 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.alarm; import android.annotation.Nullable; import android.os.Environment; import android.os.SystemClock; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * User wakeup store keeps the list of user ids with the times that user needs to be started in * sorted list in order for alarms to execute even if user gets stopped. * The list of user ids with at least one alarms scheduled is also persisted to the XML file to * start them after the device reboot. */ public class UserWakeupStore { private static final boolean DEBUG = false; static final String USER_WAKEUP_TAG = UserWakeupStore.class.getSimpleName(); private static final String TAG_USERS = "users"; private static final String TAG_USER = "user"; private static final String ATTR_USER_ID = "user_id"; private static final String ATTR_VERSION = "version"; public static final int XML_VERSION_CURRENT = 1; @VisibleForTesting static final String ROOT_DIR_NAME = "alarms"; @VisibleForTesting static final String USERS_FILE_NAME = "usersWithAlarmClocks.xml"; /** * Time offset of user start before the original alarm time in milliseconds. * Also used to schedule user start after reboot to avoid starting them simultaneously. */ @VisibleForTesting static final long BUFFER_TIME_MS = TimeUnit.SECONDS.toMillis(30); /** * Maximum time deviation limit to introduce a 5-second time window for user starts. */ @VisibleForTesting static final long USER_START_TIME_DEVIATION_LIMIT_MS = TimeUnit.SECONDS.toMillis(5); /** * Delay between two consecutive user starts scheduled during user wakeup store initialization. */ @VisibleForTesting static final long INITIAL_USER_START_SCHEDULING_DELAY_MS = TimeUnit.SECONDS.toMillis(5); private final Object mUserWakeupLock = new Object(); /** * A list of wakeups for users with scheduled alarms. */ @GuardedBy("mUserWakeupLock") private final SparseLongArray mUserStarts = new SparseLongArray(); /** * A list of users that are in a phase after they have been started but before alarms were * initialized. */ @GuardedBy("mUserWakeupLock") private final SparseLongArray mStartingUsers = new SparseLongArray(); private Executor mBackgroundExecutor; private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME); private static final Random sRandom = new Random(500); /** * Initialize mUserWakeups with persisted values. */ public void init() { mBackgroundExecutor = BackgroundThread.getExecutor(); mBackgroundExecutor.execute(this::readUserIdList); } /** * Add user wakeup for the alarm. * @param userId Id of the user that scheduled alarm. * @param alarmTime time when alarm is expected to trigger. */ public void addUserWakeup(int userId, long alarmTime) { synchronized (mUserWakeupLock) { // This should not be needed, but if an app in the user is scheduling an alarm clock, we // consider the user start complete. There is a dedicated removal when user is started. mStartingUsers.delete(userId); mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset()); } updateUserListFile(); } /** * Remove wakeup scheduled for the user with given userId if present. */ public void removeUserWakeup(int userId) { synchronized (mUserWakeupLock) { mUserStarts.delete(userId); } updateUserListFile(); } /** * Get ids of users that need to be started now. * @param nowElapsed current time. * @return user ids to be started, or empty if no user needs to be started. */ public int[] getUserIdsToWakeup(long nowElapsed) { synchronized (mUserWakeupLock) { final int[] userIds = new int[mUserStarts.size()]; int index = 0; for (int i = mUserStarts.size() - 1; i >= 0; i--) { if (mUserStarts.valueAt(i) <= nowElapsed) { userIds[index++] = mUserStarts.keyAt(i); } } return Arrays.copyOfRange(userIds, 0, index); } } /** * Persist user ids that have alarms scheduled so that they can be started after device reboot. */ private void updateUserListFile() { mBackgroundExecutor.execute(() -> { try { writeUserIdList(); if (DEBUG) { synchronized (mUserWakeupLock) { Slog.i(USER_WAKEUP_TAG, "Printing out user wakeups " + mUserStarts.size()); for (int i = 0; i < mUserStarts.size(); i++) { Slog.i(USER_WAKEUP_TAG, "User id: " + mUserStarts.keyAt(i) + " time: " + mUserStarts.valueAt(i)); } } } } catch (Exception e) { Slog.e(USER_WAKEUP_TAG, "Failed to write " + e.getLocalizedMessage()); } }); } /** * Return scheduled start time for user or -1 if user does not have alarm set. */ @VisibleForTesting long getWakeupTimeForUserForTest(int userId) { synchronized (mUserWakeupLock) { return mUserStarts.get(userId, -1); } } /** * Move user from wakeup list to starting user list. */ public void onUserStarting(int userId) { synchronized (mUserWakeupLock) { mStartingUsers.put(userId, getWakeupTimeForUserForTest(userId)); mUserStarts.delete(userId); } } /** * Remove userId from starting user list once start is complete. */ public void onUserStarted(int userId) { synchronized (mUserWakeupLock) { mStartingUsers.delete(userId); } updateUserListFile(); } /** * Remove userId from the store when the user is removed. */ public void onUserRemoved(int userId) { synchronized (mUserWakeupLock) { mUserStarts.delete(userId); mStartingUsers.delete(userId); } updateUserListFile(); } /** * Get the soonest wakeup time in the store. */ public long getNextWakeupTime() { long nextWakeupTime = -1; synchronized (mUserWakeupLock) { for (int i = 0; i < mUserStarts.size(); i++) { if (mUserStarts.valueAt(i) < nextWakeupTime || nextWakeupTime == -1) { nextWakeupTime = mUserStarts.valueAt(i); } } } return nextWakeupTime; } private static long getUserWakeupOffset() { return sRandom.nextLong(USER_START_TIME_DEVIATION_LIMIT_MS * 2) - USER_START_TIME_DEVIATION_LIMIT_MS; } /** * Write a list of ids for users who have alarm scheduled. * Sample XML file: * * <?xml version='1.0' encoding='utf-8' standalone='yes' ?> * <users version="1"> * <user user_id="12" /> * <user user_id="10" /> * </users> * ~ */ private void writeUserIdList() { final AtomicFile file = getUserWakeupFile(); if (file == null) { return; } try (FileOutputStream fos = file.startWrite(SystemClock.uptimeMillis())) { final XmlSerializer out = new FastXmlSerializer(); out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_USERS); XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); final List<Pair<Integer, Long>> listOfUsers = new ArrayList<>(); synchronized (mUserWakeupLock) { for (int i = 0; i < mUserStarts.size(); i++) { listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i))); } for (int i = 0; i < mStartingUsers.size(); i++) { listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i))); } } Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second)); for (int i = 0; i < listOfUsers.size(); i++) { out.startTag(null, TAG_USER); XmlUtils.writeIntAttribute(out, ATTR_USER_ID, listOfUsers.get(i).first); out.endTag(null, TAG_USER); } out.endTag(null, TAG_USERS); out.endDocument(); file.finishWrite(fos); } catch (IOException e) { Slog.wtf(USER_WAKEUP_TAG, "Error writing user wakeup data", e); file.delete(); } } private void readUserIdList() { final AtomicFile userWakeupFile = getUserWakeupFile(); if (userWakeupFile == null) { return; } else if (!userWakeupFile.exists()) { Slog.w(USER_WAKEUP_TAG, "User wakeup file not available: " + userWakeupFile.getBaseFile()); return; } synchronized (mUserWakeupLock) { mUserStarts.clear(); mStartingUsers.clear(); } try (FileInputStream fis = userWakeupFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(fis); int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Skip } if (type != XmlPullParser.START_TAG) { Slog.e(USER_WAKEUP_TAG, "Unable to read user list. No start tag found in " + userWakeupFile.getBaseFile()); return; } int version = -1; if (parser.getName().equals(TAG_USERS)) { version = parser.getAttributeInt(null, ATTR_VERSION, version); } long counter = 0; final long currentTime = SystemClock.elapsedRealtime(); // Time delay between now and first user wakeup is scheduled. final long scheduleOffset = currentTime + BUFFER_TIME_MS + getUserWakeupOffset(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG) { if (parser.getName().equals(TAG_USER)) { final int id = parser.getAttributeInt(null, ATTR_USER_ID); synchronized (mUserWakeupLock) { mUserStarts.put(id, scheduleOffset + (counter++ * INITIAL_USER_START_SCHEDULING_DELAY_MS)); } } } } } catch (IOException | XmlPullParserException e) { Slog.wtf(USER_WAKEUP_TAG, "Error reading user wakeup data", e); } } @Nullable private AtomicFile getUserWakeupFile() { if (!USER_WAKEUP_DIR.exists() && !USER_WAKEUP_DIR.mkdir()) { Slog.wtf(USER_WAKEUP_TAG, "Failed to mkdir() user list file: " + USER_WAKEUP_DIR); return null; } final File userFile = new File(USER_WAKEUP_DIR, USERS_FILE_NAME); return new AtomicFile(userFile); } void dump(IndentingPrintWriter pw, long nowELAPSED) { synchronized (mUserWakeupLock) { pw.increaseIndent(); pw.print("User wakeup store file path: "); final AtomicFile file = getUserWakeupFile(); if (file == null) { pw.println("null"); } else { pw.println(file.getBaseFile().getAbsolutePath()); } pw.println(mUserStarts.size() + " user wakeups scheduled: "); for (int i = 0; i < mUserStarts.size(); i++) { pw.print("UserId: "); pw.print(mUserStarts.keyAt(i)); pw.print(", userStartTime: "); TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw); pw.println(); } pw.println(mStartingUsers.size() + " starting users: "); for (int i = 0; i < mStartingUsers.size(); i++) { pw.print("UserId: "); pw.print(mStartingUsers.keyAt(i)); pw.print(", userStartTime: "); TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw); pw.println(); } pw.decreaseIndent(); } } }
core/java/android/app/ActivityManagerInternal.java +7 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,13 @@ public abstract class ActivityManagerInternal { */ public abstract void onUserRemoved(@UserIdInt int userId); /** * Start user, if it is not already running, but don't bring it to foreground. * @param userId ID of the user to start * @return true if the user has been successfully started */ public abstract boolean startUserInBackground(int userId); /** * Kill foreground apps from the specified user. */ Loading
services/core/java/com/android/server/am/ActivityManagerService.java +5 −0 Original line number Diff line number Diff line Loading @@ -18188,6 +18188,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } @Override public boolean startUserInBackground(final int userId) { return ActivityManagerService.this.startUserInBackground(userId); } @Override public void killForegroundAppsForUser(@UserIdInt int userId) { final ArrayList<ProcessRecord> procs = new ArrayList<>();
services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +22 −2 File changed.Preview size limit exceeded, changes collapsed. Show changes