From d0bbe0b976fdefe412529cbf94a985699ed79453 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Fri, 29 Jan 2021 19:53:39 -0800 Subject: [PATCH] Adding a permission to protect exact alarms Added a new permission to protect the alarm manager APIs setAlarmClock and setExactAndAllowWhileIdle. The change will apply to apps targeting API S and above. - The permission will be granted by default to apps that request it. - This will be a user facing permission as these APIs allow for exact scheduling, and an app should generally only use them when scheduling work on behalf of the user. This means the user can revoke it when they want. - Alarms set with this API will also be allowed to start FGS from the background. - Quota on exact, allow-while-idle alarms has been relaxed generously, while alarm-clocks remain totally uninhibited. - Apps that are exempted from battery via in the sysconfig, can still use exact, allow-while-idle alarms without the permission, but with lower quota. - Inexact, allow-while-idle alarms do not require this permission, but will *not* be allowed to start FGS from the background. - A new API to query the state of this permission is included. Test: atest FrameworksMockingServicesTests:com.android.server.alarm atest CtsAlarmManagerTestCases BYPASS_INCLUSIVE_LANGUAGE_REASON=Existing APIs Bug: 171306433 Change-Id: Ifb6a3f3c42316b1c83fe6960920501f5e0ee51f2 --- apex/jobscheduler/framework/Android.bp | 1 + .../java/android/app/AlarmManager.java | 47 ++ .../java/android/app/IAlarmManager.aidl | 1 + .../java/com/android/server/alarm/Alarm.java | 10 +- .../server/alarm/AlarmManagerService.java | 357 +++++++++++-- core/api/current.txt | 6 +- core/java/android/app/AppOpsManager.java | 31 +- core/res/AndroidManifest.xml | 6 + .../server/alarm/AlarmManagerServiceTest.java | 480 ++++++++++++++---- .../android/server/alarm/AlarmStoreTest.java | 4 +- .../com/android/server/alarm/AlarmTest.java | 55 +- .../alarm/BackgroundRestrictedAlarmsTest.java | 2 +- 12 files changed, 839 insertions(+), 161 deletions(-) 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 53be53c62786a..94b2283962cf0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -144,6 +144,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"; @@ -4299,16 +4300,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 dc1cc329d460c..d4d2c73bab25b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3858,6 +3858,12 @@ + + +