diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 6650e677544b0..fd35537cd7b77 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -22,6 +22,7 @@ java_library {
],
},
libs: [
+ "app-compat-annotations",
"framework-minus-apex",
"unsupportedappusage",
],
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 7851087bac198..7c7b21001c3b5 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -16,11 +16,14 @@
package android.app;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -183,6 +186,25 @@ public class AlarmManager {
@UnsupportedAppUsage
public static final int FLAG_IDLE_UNTIL = 1<<4;
+ /**
+ * Flag for alarms: Used to provide backwards compatibility for apps with targetSdkVersion less
+ * than {@link Build.VERSION_CODES#S}
+ * @hide
+ */
+ public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
+
+ /**
+ * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
+ * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
+ * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
+ * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+ *
+ * @hide
+ */
+ @ChangeId
+ @Disabled // TODO (b/171306433): Enable starting S.
+ public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
+
@UnsupportedAppUsage
private final IAlarmManager mService;
private final Context mContext;
@@ -588,6 +610,11 @@ public class AlarmManager {
* This method is like {@link #setExact(int, long, PendingIntent)}, but implies
* {@link #RTC_WAKEUP}.
*
+ *
+ * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission. Alarms scheduled via this API
+ * will be allowed to start a foreground service even if the app is in the background.
+ *
* @param info
* @param operation Action to perform when the alarm goes off;
* typically comes from {@link PendingIntent#getBroadcast
@@ -603,6 +630,7 @@ public class AlarmManager {
* @see android.content.Context#registerReceiver
* @see android.content.Intent#filterEquals
*/
+ @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);
@@ -876,6 +904,12 @@ public class AlarmManager {
* device is idle it may take even more liberties with scheduling in order to optimize
* for battery life.
*
+ *
+ * Starting from API {@link Build.VERSION_CODES#S}, using this method requires the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission, unless the app is exempt from
+ * battery restrictions. Alarms scheduled via this API will be allowed to start a foreground
+ * service even if the app is in the background.
+ *
* @param type type of alarm.
* @param triggerAtMillis time in milliseconds that the alarm should go
* off, using the appropriate clock (depending on the alarm type).
@@ -895,6 +929,7 @@ public class AlarmManager {
* @see #RTC
* @see #RTC_WAKEUP
*/
+ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
@@ -1017,6 +1052,18 @@ public class AlarmManager {
}
}
+ /**
+ * Called to check if the caller has permission to use alarms set via {@link }
+ * @return
+ */
+ public boolean canScheduleExactAlarms() {
+ try {
+ return mService.canScheduleExactAlarms();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
/**
* Gets information about the next alarm clock currently scheduled.
*
diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
index 2c51935dc4469..2f21ce395df53 100644
--- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
@@ -41,4 +41,5 @@ interface IAlarmManager {
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
long currentNetworkTimeMillis();
+ boolean canScheduleExactAlarms();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index 657c368d0aee6..3bc7b307c3344 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -26,6 +26,7 @@ import static com.android.server.alarm.AlarmManagerService.clampPositive;
import android.app.AlarmManager;
import android.app.IAlarmListener;
import android.app.PendingIntent;
+import android.os.Bundle;
import android.os.WorkSource;
import android.util.IndentingPrintWriter;
import android.util.TimeUtils;
@@ -92,10 +93,12 @@ class Alarm {
private long mWhenElapsed;
private long mMaxWhenElapsed;
public AlarmManagerService.PriorityClass priorityClass;
+ /** Broadcast options to use when delivering this alarm */
+ public Bundle mIdleOptions;
Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
- AlarmManager.AlarmClockInfo info, int uid, String pkgName) {
+ AlarmManager.AlarmClockInfo info, int uid, String pkgName, Bundle idleOptions) {
this.type = type;
origWhen = when;
wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -115,6 +118,7 @@ class Alarm {
alarmClock = info;
this.uid = uid;
packageName = pkgName;
+ mIdleOptions = idleOptions;
sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
}
@@ -303,6 +307,10 @@ class Alarm {
ipw.print("listener=");
ipw.println(listener.asBinder());
}
+ if (mIdleOptions != null) {
+ ipw.print("idle-options=");
+ ipw.println(mIdleOptions.toString());
+ }
}
public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) {
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 f6a1b8af9e49a..559a43491c7f3 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -20,11 +20,15 @@ import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
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_WAKE_FROM_IDLE;
+import static android.app.AlarmManager.INTERVAL_HOUR;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
@@ -32,6 +36,7 @@ import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX;
import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX;
import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -43,12 +48,14 @@ import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.BatteryManager;
@@ -65,6 +72,7 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
@@ -95,6 +103,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LocalLog;
@@ -177,6 +187,7 @@ public class AlarmManagerService extends SystemService {
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
+ IAppOpsService mAppOpsService;
DeviceIdleInternal mLocalDeviceIdleController;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
@@ -253,10 +264,8 @@ public class AlarmManagerService extends SystemService {
"REORDER_ALARMS_FOR_STANDBY",
});
- /**
- * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE.
- */
- Bundle mIdleOptions;
+ BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
+ BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
// TODO(b/172085676): Move inside alarm store.
private final SparseArray mNextAlarmClockForUser =
@@ -410,6 +419,10 @@ public class AlarmManagerService extends SystemService {
@VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
+ @VisibleForTesting
+ static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota";
+ private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window";
+
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
@@ -433,8 +446,14 @@ public class AlarmManagerService extends SystemService {
private static final boolean DEFAULT_LAZY_BATCHING = true;
private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;
- private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7;
- public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
+ /**
+ * Default quota for pre-S apps. Enough to accommodate the existing policy of an alarm
+ * every ALLOW_WHILE_IDLE_LONG_DELAY, which was 9 minutes.
+ */
+ private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 7;
+ private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72;
+
+ private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -463,6 +482,19 @@ public class AlarmManagerService extends SystemService {
public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
+ /**
+ * Used to provide backwards compatibility to pre-S apps with a quota equivalent to the
+ * earlier delay throttling mechanism.
+ */
+ public int ALLOW_WHILE_IDLE_COMPAT_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
+
+ /**
+ * The window used for enforcing {@link #ALLOW_WHILE_IDLE_QUOTA} and
+ * {@link #ALLOW_WHILE_IDLE_COMPAT_QUOTA}. Can be configured, but only recommended for
+ * testing.
+ */
+ public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
+
private long mLastAllowWhileIdleWhitelistDuration = -1;
Constants() {
@@ -480,9 +512,11 @@ public class AlarmManagerService extends SystemService {
public void updateAllowWhileIdleWhitelistDurationLocked() {
if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
- BroadcastOptions opts = BroadcastOptions.makeBasic();
- opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
- mIdleOptions = opts.toBundle();
+
+ mOptsWithFgs.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+ mOptsWithoutFgs.setTemporaryAppWhitelistDuration(
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+ ALLOW_WHILE_IDLE_WHITELIST_DURATION);
}
}
@@ -516,6 +550,27 @@ public class AlarmManagerService extends SystemService {
ALLOW_WHILE_IDLE_QUOTA = 1;
}
break;
+ case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA:
+ ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt(
+ KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA,
+ DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+ if (ALLOW_WHILE_IDLE_COMPAT_QUOTA <= 0) {
+ Slog.w(TAG, "Cannot have quota lower than 1.");
+ ALLOW_WHILE_IDLE_COMPAT_QUOTA = 1;
+ }
+ break;
+ case KEY_ALLOW_WHILE_IDLE_WINDOW:
+ ALLOW_WHILE_IDLE_WINDOW = properties.getLong(
+ KEY_ALLOW_WHILE_IDLE_WINDOW, DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
+ if (ALLOW_WHILE_IDLE_WINDOW > DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
+ Slog.w(TAG, "Cannot have allow_while_idle_window > "
+ + DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
+ ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
+ } else if (ALLOW_WHILE_IDLE_WINDOW < DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
+ Slog.w(TAG, "Using a non-default allow_while_idle_window = "
+ + ALLOW_WHILE_IDLE_WINDOW);
+ }
+ break;
case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
@@ -643,10 +698,14 @@ public class AlarmManagerService extends SystemService {
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
pw.println();
- pw.print("allow_while_idle_window=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_WINDOW);
+ pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
pw.println();
+ pw.print(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, ALLOW_WHILE_IDLE_COMPAT_QUOTA);
+ pw.println();
+
pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
pw.println();
@@ -830,7 +889,15 @@ public class AlarmManagerService extends SystemService {
if (futurity < MIN_FUZZABLE_INTERVAL) {
futurity = 0;
}
- return clampPositive(triggerAtTime + (long) (.75 * futurity));
+ long maxElapsed = triggerAtTime + (long) (0.75 * futurity);
+ // For non-repeating alarms, window is capped at a maximum of one hour from the requested
+ // delivery time. This allows for inexact-while-idle alarms to be slightly more reliable.
+ // In practice, the delivery window should generally be much smaller than that
+ // when the device is not idling.
+ if (interval == 0) {
+ maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR);
+ }
+ return clampPositive(maxElapsed);
}
// The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
@@ -998,7 +1065,7 @@ public class AlarmManagerService extends SystemService {
setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
- alarm.packageName);
+ alarm.packageName, null);
// Kernel alarms will be rescheduled as needed in setImplLocked
}
}
@@ -1241,7 +1308,8 @@ public class AlarmManagerService extends SystemService {
mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
- mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW);
+ mAllowWhileIdleHistory = new AppWakeupHistory(
+ Constants.DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
mNextWakeup = mNextNonWakeup = 0;
@@ -1327,6 +1395,28 @@ public class AlarmManagerService extends SystemService {
synchronized (mLock) {
mConstants.start();
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mAppOpsService = mInjector.getAppOpsService();
+ try {
+ mAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
+ new IAppOpsCallback.Stub() {
+ @Override
+ public void opChanged(int op, int uid, String packageName)
+ throws RemoteException {
+ if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) {
+ return;
+ }
+ final int mode = mAppOpsService.checkOperation(op, uid,
+ packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED
+ && mode != AppOpsManager.MODE_DEFAULT) {
+ mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
+ uid, 0, packageName).sendToTarget();
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ }
+
mLocalDeviceIdleController =
LocalServices.getService(DeviceIdleInternal.class);
mUsageStatsManagerInternal =
@@ -1428,7 +1518,7 @@ public class AlarmManagerService extends SystemService {
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
- int callingUid, String callingPackage) {
+ int callingUid, String callingPackage, Bundle idleOptions) {
if ((operation == null && directReceiver == null)
|| (operation != null && directReceiver != null)) {
Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
@@ -1519,17 +1609,18 @@ public class AlarmManagerService extends SystemService {
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
- callingPackage);
+ callingPackage, idleOptions);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
long interval, PendingIntent operation, IAlarmListener directReceiver,
String listenerTag, int flags, WorkSource workSource,
- AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
+ AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage,
+ Bundle idleOptions) {
final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
- callingUid, callingPackage);
+ callingUid, callingPackage, idleOptions);
if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ " -- package not allowed to start");
@@ -1605,19 +1696,21 @@ public class AlarmManagerService extends SystemService {
return false;
}
- if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver(
- alarm.creatorUid, alarm.sourcePackage))) {
+ if (mAppStateTracker == null || !mAppStateTracker.areAlarmsRestrictedByBatterySaver(
+ alarm.creatorUid, alarm.sourcePackage)) {
return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
}
final long batterySaverPolicyElapsed;
- if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
+ if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
// Unrestricted.
batterySaverPolicyElapsed = nowElapsed;
- } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+ } else if (isAllowedWhileIdleRestricted(alarm)) {
// Allowed but limited.
final int userId = UserHandle.getUserId(alarm.creatorUid);
- final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+ final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+ ? mConstants.ALLOW_WHILE_IDLE_QUOTA
+ : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) {
@@ -1625,7 +1718,7 @@ public class AlarmManagerService extends SystemService {
batterySaverPolicyElapsed = nowElapsed;
} else {
batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
- alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+ alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
}
} else {
// Not allowed.
@@ -1634,6 +1727,16 @@ public class AlarmManagerService extends SystemService {
return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterySaverPolicyElapsed);
}
+ /**
+ * Returns {@code true} if the given alarm has the flag
+ * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE} or
+ * {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE_COMPAT}
+ *
+ */
+ private static boolean isAllowedWhileIdleRestricted(Alarm a) {
+ return (a.flags & (FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT)) != 0;
+ }
+
/**
* Adjusts the delivery time of the alarm based on device_idle (doze) rules.
*
@@ -1647,14 +1750,15 @@ public class AlarmManagerService extends SystemService {
}
final long deviceIdlePolicyTime;
- if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
- | AlarmManager.FLAG_WAKE_FROM_IDLE)) != 0) {
+ if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE)) != 0) {
// Unrestricted.
deviceIdlePolicyTime = nowElapsed;
- } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
+ } else if (isAllowedWhileIdleRestricted(alarm)) {
// Allowed but limited.
final int userId = UserHandle.getUserId(alarm.creatorUid);
- final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+ final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
+ ? mConstants.ALLOW_WHILE_IDLE_QUOTA
+ : mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) {
@@ -1662,7 +1766,7 @@ public class AlarmManagerService extends SystemService {
deviceIdlePolicyTime = nowElapsed;
} else {
final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
- alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+ alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
}
} else {
@@ -1749,7 +1853,7 @@ public class AlarmManagerService extends SystemService {
} else if (mPendingIdleUntil != null) {
adjustDeliveryTimeBasedOnDeviceIdle(a);
}
- if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ if ((a.flags & FLAG_WAKE_FROM_IDLE) != 0) {
if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
> a.getWhenElapsed()) {
mNextWakeFromIdle = a;
@@ -1809,6 +1913,14 @@ public class AlarmManagerService extends SystemService {
}
}
+ /**
+ * Returns true if the given uid is on the system or user's power save exclusion list.
+ */
+ boolean isWhitelisted(int uid) {
+ return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist(
+ UserHandle.getAppId(uid)));
+ }
+
/**
* Public-facing binder interface
*/
@@ -1824,6 +1936,54 @@ public class AlarmManagerService extends SystemService {
// wakelock time spent in alarm delivery
mAppOps.checkPackage(callingUid, callingPackage);
+ final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+
+ Bundle idleOptions = null;
+ if (alarmClock != null || allowWhileIdle) {
+ // make sure the caller is allowed to use the requested kind of alarm, and also
+ // decide what broadcast options to use.
+ final boolean needsPermission;
+ boolean lowQuota;
+ if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+ callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
+ if (windowLength != AlarmManager.WINDOW_EXACT) {
+ needsPermission = false;
+ lowQuota = true;
+ idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle()
+ : mOptsWithoutFgs.toBundle();
+ } else if (alarmClock != null) {
+ needsPermission = true;
+ lowQuota = false;
+ idleOptions = mOptsWithFgs.toBundle();
+ } else {
+ needsPermission = true;
+ lowQuota = false;
+ idleOptions = mOptsWithFgs.toBundle();
+ }
+ } else {
+ needsPermission = false;
+ lowQuota = allowWhileIdle;
+ idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
+ }
+ if (needsPermission && !canScheduleExactAlarms()) {
+ if (alarmClock == null && isWhitelisted(callingUid)) {
+ // If the app is on the full system allow-list (not except-idle), we still
+ // allow the alarms, but with a lower quota to keep pre-S compatibility.
+ lowQuota = true;
+ } else {
+ final String errorMessage = "Caller needs to hold "
+ + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
+ + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock")
+ + " alarms.";
+ throw new SecurityException(errorMessage);
+ }
+ }
+ if (lowQuota) {
+ flags &= ~FLAG_ALLOW_WHILE_IDLE;
+ flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
+ }
+ }
+
// Repeating alarms must use PendingIntent, not direct listener
if (interval != 0) {
if (directReceiver != null) {
@@ -1840,8 +2000,7 @@ public class AlarmManagerService extends SystemService {
// No incoming callers can request either WAKE_FROM_IDLE or
// ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
- flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
- | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
+ flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
// Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
// manager when to come out of idle mode, which is only for DeviceIdleController.
@@ -1857,22 +2016,32 @@ public class AlarmManagerService extends SystemService {
// If this alarm is for an alarm clock, then it must be standalone and we will
// use it to wake early from idle if needed.
if (alarmClock != null) {
- flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+ flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
// If the caller is a core system component or on the user's whitelist, and not calling
// to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
// This means we will allow these alarms to go off as normal even while idle, with no
// timing restrictions.
- } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
+ } else if (workSource == null && (UserHandle.isCore(callingUid)
|| UserHandle.isSameApp(callingUid, mSystemUiUid)
|| ((mAppStateTracker != null)
&& mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
- flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
- flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+ flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+ flags &= ~FLAG_ALLOW_WHILE_IDLE;
+ flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
+ idleOptions = null;
}
setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
- listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
+ listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
+ idleOptions);
+ }
+
+ @Override
+ public boolean canScheduleExactAlarms() {
+ return PermissionChecker.checkCallingOrSelfPermissionForPreflight(getContext(),
+ Manifest.permission.SCHEDULE_EXACT_ALARM)
+ == PermissionChecker.PERMISSION_GRANTED;
}
@Override
@@ -2755,6 +2924,77 @@ public class AlarmManagerService extends SystemService {
}
}
+ /**
+ * Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms
+ * that the app is no longer eligible to use.
+ * TODO (b/179541791): Revisit and write tests once UX is final.
+ */
+ void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
+ if (UserHandle.isCore(uid) || uid == mSystemUiUid) {
+ return;
+ }
+ if (isWhitelisted(uid)) {
+ return;
+ }
+ if (!CompatChanges.isChangeEnabled(
+ AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+ packageName, UserHandle.getUserHandleForUid(uid))) {
+ return;
+ }
+
+ final Predicate whichAlarms =
+ a -> (a.uid == uid && a.packageName.equals(packageName)
+ && ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null));
+ final ArrayList removed = mAlarmStore.remove(whichAlarms);
+ final boolean didRemove = !removed.isEmpty();
+ if (didRemove) {
+ decrementAlarmCount(uid, removed.size());
+ }
+
+ for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+ final ArrayList alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
+ for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
+ final Alarm alarm = alarmsForUid.get(j);
+ if (whichAlarms.test(alarm)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ alarmsForUid.remove(j);
+ decrementAlarmCount(alarm.uid, 1);
+ }
+ }
+ if (alarmsForUid.size() == 0) {
+ mPendingBackgroundAlarms.removeAt(i);
+ }
+ }
+ for (int i = mPendingNonWakeupAlarms.size() - 1; i >= 0; i--) {
+ final Alarm a = mPendingNonWakeupAlarms.get(i);
+ if (whichAlarms.test(a)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingNonWakeupAlarms.remove(i);
+ decrementAlarmCount(a.uid, 1);
+ }
+ }
+
+ if (didRemove) {
+ if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
+ mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+ if (mPendingIdleUntil != null) {
+ final boolean idleUntilUpdated = mAlarmStore.updateAlarmDeliveries(alarm -> {
+ if (alarm != mPendingIdleUntil) {
+ return false;
+ }
+ return adjustIdleUntilTime(alarm);
+ });
+ if (idleUntilUpdated) {
+ mAlarmStore.updateAlarmDeliveries(
+ alarm -> adjustDeliveryTimeBasedOnDeviceIdle(alarm));
+ }
+ }
+ }
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+ }
+
void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
if (operation == null && directReceiver == null) {
if (localLOGV) {
@@ -3082,7 +3322,7 @@ public class AlarmManagerService extends SystemService {
}
}
- private boolean isExemptFromBatterySaver(Alarm alarm) {
+ private static boolean isExemptFromBatterySaver(Alarm alarm) {
if (alarm.alarmClock != null) {
return true;
}
@@ -3142,7 +3382,7 @@ public class AlarmManagerService extends SystemService {
alarm.count = 1;
triggerList.add(alarm);
- if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ if ((alarm.flags & FLAG_WAKE_FROM_IDLE) != 0) {
EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
alarm.statsTag);
}
@@ -3180,7 +3420,7 @@ public class AlarmManagerService extends SystemService {
setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
- alarm.packageName);
+ alarm.packageName, null);
}
if (alarm.wakeup) {
@@ -3257,7 +3497,6 @@ public class AlarmManagerService extends SystemService {
mLastAlarmDeliveryTime = nowELAPSED;
for (int i = 0; i < triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
- final boolean allowWhileIdle = (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
if (alarm.wakeup) {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"Dispatch wakeup alarm to " + alarm.packageName);
@@ -3273,7 +3512,7 @@ public class AlarmManagerService extends SystemService {
mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource,
alarm.uid, alarm.statsTag);
}
- mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
+ mDeliveryTracker.deliverLocked(alarm, nowELAPSED);
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
@@ -3282,9 +3521,10 @@ public class AlarmManagerService extends SystemService {
}
}
- private boolean isExemptFromAppStandby(Alarm a) {
+ @VisibleForTesting
+ static boolean isExemptFromAppStandby(Alarm a) {
return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
- || (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
+ || (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0;
}
@VisibleForTesting
@@ -3368,6 +3608,11 @@ public class AlarmManagerService extends SystemService {
MATCH_SYSTEM_ONLY, USER_SYSTEM);
}
+ IAppOpsService getAppOpsService() {
+ return IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+
ClockReceiver getClockReceiver(AlarmManagerService service) {
return service.new ClockReceiver();
}
@@ -3566,6 +3811,7 @@ public class AlarmManagerService extends SystemService {
public static final int APP_STANDBY_BUCKET_CHANGED = 5;
public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_CANCELED = 7;
+ public static final int REMOVE_EXACT_ALARMS = 8;
AlarmHandler() {
super(Looper.myLooper());
@@ -3645,6 +3891,14 @@ public class AlarmManagerService extends SystemService {
}
break;
+ case REMOVE_EXACT_ALARMS:
+ final int uid = msg.arg1;
+ final String packageName = (String) msg.obj;
+ synchronized (mLock) {
+ removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
+ }
+ break;
+
default:
// nope, just ignore it
break;
@@ -3720,7 +3974,7 @@ public class AlarmManagerService extends SystemService {
setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null,
- Process.myUid(), "android");
+ Process.myUid(), "android", null);
// Finally, remember when we set the tick alarm
synchronized (mLock) {
@@ -3740,7 +3994,7 @@ public class AlarmManagerService extends SystemService {
final WorkSource workSource = null; // Let system take blame for date change events.
setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
AlarmManager.FLAG_STANDALONE, workSource, null,
- Process.myUid(), "android");
+ Process.myUid(), "android", null);
}
}
@@ -4112,7 +4366,7 @@ public class AlarmManagerService extends SystemService {
* Deliver an alarm and set up the post-delivery handling appropriately
*/
@GuardedBy("mLock")
- public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+ public void deliverLocked(Alarm alarm, long nowELAPSED) {
final long workSourceToken = ThreadLocalWorkSource.setUid(
getAlarmAttributionUid(alarm));
try {
@@ -4122,10 +4376,8 @@ public class AlarmManagerService extends SystemService {
try {
alarm.operation.send(getContext(), 0,
- mBackgroundIntent.putExtra(
- Intent.EXTRA_ALARM_COUNT, alarm.count),
- mDeliveryTracker, mHandler, null,
- allowWhileIdle ? mIdleOptions : null);
+ mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
+ mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
} catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
@@ -4194,7 +4446,7 @@ public class AlarmManagerService extends SystemService {
if (inflight.isBroadcast()) {
notifyBroadcastAlarmPendingLocked(alarm.uid);
}
- if (allowWhileIdle) {
+ if (isAllowedWhileIdleRestricted(alarm)) {
final boolean doze = (mPendingIdleUntil != null);
final boolean batterySaver = (mAppStateTracker != null
&& mAppStateTracker.isForceAllAppsStandbyEnabled());
@@ -4204,8 +4456,7 @@ public class AlarmManagerService extends SystemService {
mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
mAlarmStore.updateAlarmDeliveries(a -> {
- if (a.creatorUid != alarm.creatorUid
- || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+ if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
return false;
}
return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
diff --git a/core/api/current.txt b/core/api/current.txt
index e72f943da5207..d3d754c0d8a5a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -145,6 +145,7 @@ package android {
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+ field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final String SEND_SMS = "android.permission.SEND_SMS";
field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -4328,16 +4329,17 @@ package android.app {
}
public class AlarmManager {
+ method public boolean canScheduleExactAlarms();
method public void cancel(android.app.PendingIntent);
method public void cancel(android.app.AlarmManager.OnAlarmListener);
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent);
method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
- method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
+ method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setExact(int, long, android.app.PendingIntent);
method public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
- method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
+ method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(int, long, long, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b85b1861aa025..1906ee4d85d2c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -795,7 +795,8 @@ public class AppOpsManager {
// when adding one of these:
// - increment _NUM_OP
// - define an OPSTR_* constant (marked as @SystemApi)
- // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
+ // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset,
+ // sOpRestrictions, sOpAllowSystemRestrictionBypass
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -1174,13 +1175,19 @@ public class AppOpsManager {
*
* @hide
*/
- // TODO: Add as AppProtoEnums
- public static final int OP_RECORD_AUDIO_OUTPUT = 106;
+ public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+
+ /**
+ * App can schedule exact alarm to perform timing based background work
+ *
+ * @hide
+ */
+ public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 107;
+ public static final int _NUM_OP = 108;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1553,6 +1560,13 @@ public class AppOpsManager {
*/
public static final String OPSTR_RECORD_AUDIO_OUTPUT = "android:record_audio_output";
+ /**
+ * App can schedule exact alarm to perform timing based background work.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1633,6 +1647,7 @@ public class AppOpsManager {
OP_LOADER_USAGE_STATS,
OP_MANAGE_ONGOING_CALLS,
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+ OP_SCHEDULE_EXACT_ALARM,
};
/**
@@ -1751,6 +1766,7 @@ public class AppOpsManager {
OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT
+ OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM
};
/**
@@ -1864,6 +1880,7 @@ public class AppOpsManager {
OPSTR_MANAGE_CREDENTIALS,
OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OPSTR_RECORD_AUDIO_OUTPUT,
+ OPSTR_SCHEDULE_EXACT_ALARM,
};
/**
@@ -1978,6 +1995,7 @@ public class AppOpsManager {
"MANAGE_CREDENTIALS",
"USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
"RECORD_AUDIO_OUTPUT",
+ "SCHEDULE_EXACT_ALARM",
};
/**
@@ -2093,6 +2111,7 @@ public class AppOpsManager {
null, // no permission for OP_MANAGE_CREDENTIALS
Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
null, // no permission for OP_RECORD_AUDIO_OUTPUT
+ Manifest.permission.SCHEDULE_EXACT_ALARM,
};
/**
@@ -2208,6 +2227,7 @@ public class AppOpsManager {
null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT
+ null, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2322,6 +2342,7 @@ public class AppOpsManager {
null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT
+ null, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2435,6 +2456,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
+ AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2552,6 +2574,7 @@ public class AppOpsManager {
false, // MANAGE_CREDENTIALS
true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
false, // RECORD_AUDIO_OUTPUT
+ false, // SCHEDULE_EXACT_ALARM
};
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 18cc3acd294e9..c61802d54e043 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3905,6 +3905,12 @@
+
+
+