From 73c3d5a2229e3b8c39d2dc9ac21df858c92d76ee Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Tue, 23 Mar 2021 16:22:28 -0700 Subject: [PATCH] Add an API for a prioritized alarm Adding an API that system apps can use to get alarms while the device is in doze. These alarms will be prioritized over other alarms in the sense that the device may wake up to send these alarms and ignore other pending due alarms at that time. Test: atest CtsAlarmManagerTestCases atest FrameworksMockingServicesTests:com.android.server.alarm CTS-Coverage-Bug: 183661625 Bug: 182843915 Change-Id: Iccae4f532bf145f4e0b130241ca15ff0ee0f106a --- .../java/android/app/AlarmManager.java | 99 ++++++++++++++++--- .../server/alarm/AlarmManagerService.java | 83 ++++++++++++++-- core/api/system-current.txt | 2 + core/res/AndroidManifest.xml | 9 ++ .../server/alarm/AlarmManagerServiceTest.java | 93 +++++++++++++++++ 5 files changed, 264 insertions(+), 22 deletions(-) diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 78c5b156bc459..3ea1922547d59 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -18,6 +18,7 @@ package android.app; import android.Manifest; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; @@ -29,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -42,7 +44,9 @@ import com.android.i18n.timezone.ZoneInfoDb; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; +import java.util.Objects; import java.util.WeakHashMap; +import java.util.concurrent.Executor; /** * This class provides access to the system alarm services. These allow you @@ -193,6 +197,15 @@ public class AlarmManager { */ public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5; + /** + * Flag for alarms: Used to mark prioritized alarms. These alarms will get to execute while idle + * and can be sent separately from other alarms that may be already due at the time. + * These alarms can be set via + * {@link #setPrioritized(int, long, long, String, Executor, OnAlarmListener)} + * @hide + */ + public static final int FLAG_PRIORITIZE = 1 << 6; + /** * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and @@ -227,15 +240,15 @@ public class AlarmManager { final class ListenerWrapper extends IAlarmListener.Stub implements Runnable { final OnAlarmListener mListener; - Handler mHandler; + Executor mExecutor; IAlarmCompleteListener mCompletion; public ListenerWrapper(OnAlarmListener listener) { mListener = listener; } - public void setHandler(Handler h) { - mHandler = h; + void setExecutor(Executor e) { + mExecutor = e; } public void cancel() { @@ -250,7 +263,7 @@ public class AlarmManager { public void doAlarm(IAlarmCompleteListener alarmManager) { mCompletion = alarmManager; - mHandler.post(this); + mExecutor.execute(this); } @Override @@ -368,7 +381,7 @@ public class AlarmManager { */ public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null, - null, null, null); + (Handler) null, null, null); } /** @@ -457,7 +470,7 @@ public class AlarmManager { public void setRepeating(@AlarmType int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, - null, null, null, null, null); + null, null, (Handler) null, null, null); } /** @@ -507,7 +520,7 @@ public class AlarmManager { public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) { setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, - null, null, null, null, null); + null, null, (Handler) null, null, null); } /** @@ -525,6 +538,53 @@ public class AlarmManager { targetHandler, null, null); } + /** + * Schedule an alarm that is prioritized by the system while the device is in power saving modes + * such as battery saver and device idle (doze). + * + *

+ * Apps that use this are not guaranteed to get all alarms as requested during power saving + * modes, i.e. the system may still impose restrictions on how frequently these alarms will go + * off for a particular application, like requiring a certain minimum duration be elapsed + * between consecutive alarms. This duration will be normally be in the order of a few minutes. + * + *

+ * When the system wakes up to deliver these alarms, it may not deliver any of the other pending + * alarms set earlier by the calling app, even the special ones set via + * {@link #setAndAllowWhileIdle(int, long, PendingIntent)} or + * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)}. So the caller should not + * expect these to arrive in any relative order to its other alarms. + * + * @param type type of alarm + * @param windowStartMillis The earliest time, in milliseconds, that the alarm should + * be delivered, expressed in the appropriate clock's units (depending on the alarm + * type). + * @param windowLengthMillis The length of the requested delivery window, + * in milliseconds. The alarm will be delivered no later than this many + * milliseconds after {@code windowStartMillis}. Note that this parameter + * is a duration, not the timestamp of the end of the window. + * @param tag string describing the alarm, used for logging and battery-use + * attribution + * @param listener {@link OnAlarmListener} instance whose + * {@link OnAlarmListener#onAlarm() onAlarm()} method will be + * called when the alarm time is reached. A given OnAlarmListener instance can + * only be the target of a single pending alarm, just as a given PendingIntent + * can only be used with one alarm at a time. + * @param executor {@link Executor} on which to execute the listener's onAlarm() + * callback. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) + public void setPrioritized(@AlarmType int type, long windowStartMillis, long windowLengthMillis, + @NonNull String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(tag); + Objects.requireNonNull(listener); + setImpl(type, windowStartMillis, windowLengthMillis, 0, FLAG_PRIORITIZE, null, listener, + tag, executor, null, null); + } + /** * Schedule an alarm to be delivered precisely at the stated time. * @@ -565,7 +625,7 @@ public class AlarmManager { */ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true) public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null, + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null, null, null); } @@ -645,7 +705,7 @@ public class AlarmManager { @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, - null, null, null, null, info); + null, null, (Handler) null, null, info); } /** @hide */ @@ -654,7 +714,7 @@ public class AlarmManager { public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, long intervalMillis, PendingIntent operation, WorkSource workSource) { setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null, - null, workSource, null); + (Handler) null, workSource, null); } /** @@ -698,6 +758,15 @@ public class AlarmManager { long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag, Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) { + final Handler handlerToUse = (targetHandler != null) ? targetHandler : mMainThreadHandler; + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation, listener, + listenerTag, new HandlerExecutor(handlerToUse), workSource, alarmClock); + } + + private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis, + long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener, + String listenerTag, Executor targetExecutor, WorkSource workSource, + AlarmClockInfo alarmClock) { if (triggerAtMillis < 0) { /* NOTYET if (mAlwaysExact) { @@ -726,9 +795,7 @@ public class AlarmManager { sWrappers.put(listener, new WeakReference<>(recipientWrapper)); } } - - final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler; - recipientWrapper.setHandler(handler); + recipientWrapper.setExecutor(targetExecutor); } try { @@ -834,7 +901,7 @@ public class AlarmManager { public void setInexactRepeating(@AlarmType int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, - null, null, null, null); + null, (Handler) null, null, null); } /** @@ -884,7 +951,7 @@ public class AlarmManager { public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, - operation, null, null, null, null, null); + operation, null, null, (Handler) null, null, null); } /** @@ -945,7 +1012,7 @@ public class AlarmManager { public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, - null, null, null, null, null); + null, null, (Handler) null, null, null); } /** 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 58fc87476f2a0..3c9496f557a17 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -23,6 +23,7 @@ import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; +import static android.app.AlarmManager.FLAG_PRIORITIZE; import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.INTERVAL_DAY; import static android.app.AlarmManager.INTERVAL_HOUR; @@ -100,6 +101,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -221,6 +223,7 @@ public class AlarmManagerService extends SystemService { AlarmHandler mHandler; AppWakeupHistory mAppWakeupHistory; AppWakeupHistory mAllowWhileIdleHistory; + private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray(); ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); IBinder.DeathRecipient mListenerDeathRecipient; @@ -432,6 +435,8 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps"; + @VisibleForTesting + static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -470,6 +475,8 @@ public class AlarmManagerService extends SystemService { // TODO (b/171306433): Change to true by default. private static final boolean DEFAULT_CRASH_NON_CLOCK_APPS = false; + private static final long DEFAULT_PRIORITY_ALARM_DELAY = 9 * 60_000; + // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -525,6 +532,12 @@ public class AlarmManagerService extends SystemService { */ public boolean CRASH_NON_CLOCK_APPS = DEFAULT_CRASH_NON_CLOCK_APPS; + /** + * Minimum delay between two slots that an app can get for their prioritized alarms, while + * the device is in doze. + */ + public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY; + private long mLastAllowWhileIdleWhitelistDuration = -1; Constants() { @@ -662,6 +675,10 @@ public class AlarmManagerService extends SystemService { CRASH_NON_CLOCK_APPS = properties.getBoolean(KEY_CRASH_NON_CLOCK_APPS, DEFAULT_CRASH_NON_CLOCK_APPS); break; + case KEY_PRIORITY_ALARM_DELAY: + PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY, + DEFAULT_PRIORITY_ALARM_DELAY); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -809,6 +826,11 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_CRASH_NON_CLOCK_APPS, CRASH_NON_CLOCK_APPS); pw.println(); + pw.print(KEY_PRIORITY_ALARM_DELAY); + pw.print("="); + TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw); + pw.println(); + pw.decreaseIndent(); } @@ -1794,6 +1816,11 @@ public class AlarmManagerService extends SystemService { batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage( alarm.sourcePackage, userId, quota) + window; } + } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { + final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0); + batterySaverPolicyElapsed = (lastDispatch == 0) + ? nowElapsed + : lastDispatch + mConstants.PRIORITY_ALARM_DELAY; } else { // Not allowed. batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY; @@ -1849,6 +1876,12 @@ public class AlarmManagerService extends SystemService { alarm.sourcePackage, userId, quota) + window; deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed()); } + } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { + final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0); + final long whenAllowed = (lastDispatch == 0) + ? nowElapsed + : lastDispatch + mConstants.PRIORITY_ALARM_DELAY; + deviceIdlePolicyTime = Math.min(whenAllowed, mPendingIdleUntil.getWhenElapsed()); } else { // Not allowed. deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed(); @@ -2025,7 +2058,12 @@ public class AlarmManagerService extends SystemService { // make sure the caller is allowed to use the requested kind of alarm, and also // decide what quota and broadcast options to use. Bundle idleOptions = null; - if (exact || allowWhileIdle) { + if ((flags & FLAG_PRIORITIZE) != 0) { + getContext().enforcePermission( + Manifest.permission.SCHEDULE_PRIORITIZED_ALARM, + Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized"); + flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT); + } else if (exact || allowWhileIdle) { final boolean needsPermission; boolean lowerQuota; if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, @@ -2107,6 +2145,7 @@ public class AlarmManagerService extends SystemService { flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~FLAG_ALLOW_WHILE_IDLE; flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT; + flags &= ~FLAG_PRIORITIZE; idleOptions = null; } @@ -2489,6 +2528,19 @@ public class AlarmManagerService extends SystemService { pw.println("Allow while idle history:"); mAllowWhileIdleHistory.dump(pw, nowELAPSED); + if (mLastPriorityAlarmDispatch.size() > 0) { + pw.println("Last priority alarm dispatches:"); + pw.increaseIndent(); + for (int i = 0; i < mLastPriorityAlarmDispatch.size(); i++) { + pw.print("UID: "); + UserHandle.formatUid(pw, mLastPriorityAlarmDispatch.keyAt(i)); + pw.print(": "); + TimeUtils.formatDuration(mLastPriorityAlarmDispatch.valueAt(i), nowELAPSED, pw); + pw.println(); + } + pw.decreaseIndent(); + } + if (mLog.dump(pw, "Recent problems:")) { pw.println(); } @@ -3303,6 +3355,11 @@ public class AlarmManagerService extends SystemService { mPendingBackgroundAlarms.removeAt(i); } } + for (int i = mLastPriorityAlarmDispatch.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mLastPriorityAlarmDispatch.keyAt(i)) == userHandle) { + mLastPriorityAlarmDispatch.removeAt(i); + } + } if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) { mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); if (mPendingIdleUntil != null) { @@ -4103,6 +4160,7 @@ public class AlarmManagerService extends SystemService { IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); + sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiver(this, sdFilter); } @@ -4132,6 +4190,9 @@ public class AlarmManagerService extends SystemService { mAllowWhileIdleHistory.removeForUser(userHandle); } return; + case Intent.ACTION_UID_REMOVED: + mLastPriorityAlarmDispatch.delete(uid); + return; case Intent.ACTION_PACKAGE_REMOVED: if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // This package is being updated; don't kill its alarms. @@ -4522,11 +4583,11 @@ public class AlarmManagerService extends SystemService { if (inflight.isBroadcast()) { notifyBroadcastAlarmPendingLocked(alarm.uid); } - if (isAllowedWhileIdleRestricted(alarm)) { - final boolean doze = (mPendingIdleUntil != null); - final boolean batterySaver = (mAppStateTracker != null - && mAppStateTracker.isForceAllAppsStandbyEnabled()); - if (doze || batterySaver) { + final boolean doze = (mPendingIdleUntil != null); + final boolean batterySaver = (mAppStateTracker != null + && mAppStateTracker.isForceAllAppsStandbyEnabled()); + if (doze || batterySaver) { + if (isAllowedWhileIdleRestricted(alarm)) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the // device was in doze or battery saver. mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage, @@ -4538,6 +4599,16 @@ public class AlarmManagerService extends SystemService { return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a)); }); + } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { + mLastPriorityAlarmDispatch.put(alarm.creatorUid, nowELAPSED); + mAlarmStore.updateAlarmDeliveries(a -> { + if (a.creatorUid != alarm.creatorUid + || (alarm.flags & FLAG_PRIORITIZE) == 0) { + return false; + } + return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) + || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a)); + }); } if (RECORD_DEVICE_IDLE_ALARMS) { IdleDispatchEntry ent = new IdleDispatchEntry(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index edc6ab8767a58..d6a37e027a217 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -242,6 +242,7 @@ package android { field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES"; field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; field public static final String ROTATE_SURFACE_FLINGER = "android.permission.ROTATE_SURFACE_FLINGER"; + field public static final String SCHEDULE_PRIORITIZED_ALARM = "android.permission.SCHEDULE_PRIORITIZED_ALARM"; field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"; field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"; @@ -420,6 +421,7 @@ package android.app { public class AlarmManager { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource); + method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener); } public class AppOpsManager { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8a471350bb365..35a1e9e118a35 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3998,6 +3998,15 @@ + + + diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 28940b34c82a6..84819619ad79e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -21,6 +21,7 @@ import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; +import static android.app.AlarmManager.FLAG_PRIORITIZE; import static android.app.AlarmManager.FLAG_STANDALONE; import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.RTC; @@ -62,6 +63,7 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INT import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW; +import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY; import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX; import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; @@ -443,6 +445,12 @@ public class AlarmManagerServiceTest { TEST_CALLING_UID); } + private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) { + mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", + FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID, + TEST_CALLING_PACKAGE, null); + } + private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi, boolean unrestricted) { final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE; @@ -579,6 +587,7 @@ public class AlarmManagerServiceTest { setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 40); setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 45); setDeviceConfigLong(KEY_MIN_WINDOW, 50); + setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, 55); assertEquals(5, mService.mConstants.MIN_FUTURITY); assertEquals(10, mService.mConstants.MIN_INTERVAL); assertEquals(15, mService.mConstants.MAX_INTERVAL); @@ -589,6 +598,7 @@ public class AlarmManagerServiceTest { assertEquals(40, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(45, mService.mConstants.LISTENER_TIMEOUT); assertEquals(50, mService.mConstants.MIN_WINDOW); + assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY); } @Test @@ -1585,6 +1595,89 @@ public class AlarmManagerServiceTest { getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow); } + @Test + public void prioritizedAlarmsInBatterySaver() throws Exception { + when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(true); + when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); + final long minDelay = 5; + setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay); + + final long firstTrigger = mNowElapsedTest + 4; + final AtomicInteger alarmsFired = new AtomicInteger(0); + final int numAlarms = 10; + for (int i = 0; i < numAlarms; i++) { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + alarmsFired.incrementAndGet(); + } + }); + } + assertEquals(firstTrigger, mTestTimer.getElapsed()); + mNowElapsedTest = firstTrigger; + mTestTimer.expire(); + while (alarmsFired.get() < numAlarms) { + assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + assertEquals(numAlarms, alarmsFired.get()); + } + + @Test + public void prioritizedAlarmsInDeviceIdle() throws Exception { + doReturn(0).when(mService).fuzzForDuration(anyLong()); + + final long minDelay = 5; + setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay); + + final long idleUntil = mNowElapsedTest + 1000; + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil, getNewMockPendingIntent()); + assertNotNull(mService.mPendingIdleUntil); + + final long firstTrigger = mNowElapsedTest + 4; + final AtomicInteger alarmsFired = new AtomicInteger(0); + final int numAlarms = 10; + for (int i = 0; i < numAlarms; i++) { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + alarmsFired.incrementAndGet(); + } + }); + } + assertEquals(firstTrigger, mTestTimer.getElapsed()); + mNowElapsedTest = firstTrigger; + mTestTimer.expire(); + while (alarmsFired.get() < numAlarms) { + assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + assertEquals(numAlarms, alarmsFired.get()); + + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }); + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }); + assertEquals(idleUntil - 3, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + assertEquals(idleUntil, mTestTimer.getElapsed()); + } + @Test public void dispatchOrder() throws Exception { doReturn(0).when(mService).fuzzForDuration(anyLong());