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 2f48610bf0a45..761774b373b7c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -244,6 +244,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";
@@ -423,6 +424,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 b14a373a90e4b..b3b66c48fe6c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4003,6 +4003,15 @@