Merge "Adding a permission to protect exact alarms" into sc-dev

This commit is contained in:
Suprabh Shukla
2021-02-18 07:13:39 +00:00
committed by Android (Google) Code Review
12 changed files with 839 additions and 161 deletions

View File

@@ -22,6 +22,7 @@ java_library {
], ],
}, },
libs: [ libs: [
"app-compat-annotations",
"framework-minus-apex", "framework-minus-apex",
"unsupportedappusage", "unsupportedappusage",
], ],

View File

@@ -16,11 +16,14 @@
package android.app; package android.app;
import android.Manifest;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.RequiresPermission; import android.annotation.RequiresPermission;
import android.annotation.SdkConstant; import android.annotation.SdkConstant;
import android.annotation.SystemApi; import android.annotation.SystemApi;
import android.annotation.SystemService; import android.annotation.SystemService;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -183,6 +186,25 @@ public class AlarmManager {
@UnsupportedAppUsage @UnsupportedAppUsage
public static final int FLAG_IDLE_UNTIL = 1<<4; 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 @UnsupportedAppUsage
private final IAlarmManager mService; private final IAlarmManager mService;
private final Context mContext; private final Context mContext;
@@ -588,6 +610,11 @@ public class AlarmManager {
* This method is like {@link #setExact(int, long, PendingIntent)}, but implies * This method is like {@link #setExact(int, long, PendingIntent)}, but implies
* {@link #RTC_WAKEUP}. * {@link #RTC_WAKEUP}.
* *
* <p>
* 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 info
* @param operation Action to perform when the alarm goes off; * @param operation Action to perform when the alarm goes off;
* typically comes from {@link PendingIntent#getBroadcast * typically comes from {@link PendingIntent#getBroadcast
@@ -603,6 +630,7 @@ public class AlarmManager {
* @see android.content.Context#registerReceiver * @see android.content.Context#registerReceiver
* @see android.content.Intent#filterEquals * @see android.content.Intent#filterEquals
*/ */
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
null, null, null, null, info); 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 * device is idle it may take even more liberties with scheduling in order to optimize
* for battery life.</p> * for battery life.</p>
* *
* <p>
* 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 type type of alarm.
* @param triggerAtMillis time in milliseconds that the alarm should go * @param triggerAtMillis time in milliseconds that the alarm should go
* off, using the appropriate clock (depending on the alarm type). * off, using the appropriate clock (depending on the alarm type).
@@ -895,6 +929,7 @@ public class AlarmManager {
* @see #RTC * @see #RTC
* @see #RTC_WAKEUP * @see #RTC_WAKEUP
*/ */
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) { PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, 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. * Gets information about the next alarm clock currently scheduled.
* *

View File

@@ -41,4 +41,5 @@ interface IAlarmManager {
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId); AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
long currentNetworkTimeMillis(); long currentNetworkTimeMillis();
boolean canScheduleExactAlarms();
} }

View File

@@ -26,6 +26,7 @@ import static com.android.server.alarm.AlarmManagerService.clampPositive;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.IAlarmListener; import android.app.IAlarmListener;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.os.Bundle;
import android.os.WorkSource; import android.os.WorkSource;
import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter;
import android.util.TimeUtils; import android.util.TimeUtils;
@@ -92,10 +93,12 @@ class Alarm {
private long mWhenElapsed; private long mWhenElapsed;
private long mMaxWhenElapsed; private long mMaxWhenElapsed;
public AlarmManagerService.PriorityClass priorityClass; 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, Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags, 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; this.type = type;
origWhen = when; origWhen = when;
wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -115,6 +118,7 @@ class Alarm {
alarmClock = info; alarmClock = info;
this.uid = uid; this.uid = uid;
packageName = pkgName; packageName = pkgName;
mIdleOptions = idleOptions;
sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid; creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
} }
@@ -303,6 +307,10 @@ class Alarm {
ipw.print("listener="); ipw.print("listener=");
ipw.println(listener.asBinder()); ipw.println(listener.asBinder());
} }
if (mIdleOptions != null) {
ipw.print("idle-options=");
ipw.println(mIdleOptions.toString());
}
} }
public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) { public void dumpDebug(ProtoOutputStream proto, long fieldId, long nowElapsed) {

View File

@@ -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;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; 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;
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_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL; 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;
import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.RTC_WAKEUP;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; 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 android.os.UserHandle.USER_SYSTEM;
import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; 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.DEVICE_IDLE_POLICY_INDEX;
import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
import android.Manifest;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.Activity; import android.app.Activity;
@@ -43,12 +48,14 @@ import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener; import android.app.IAlarmListener;
import android.app.IAlarmManager; import android.app.IAlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal;
import android.net.Uri; import android.net.Uri;
import android.os.BatteryManager; import android.os.BatteryManager;
@@ -65,6 +72,7 @@ import android.os.PowerManager;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback; import android.os.ShellCallback;
import android.os.ShellCommand; import android.os.ShellCommand;
import android.os.SystemClock; import android.os.SystemClock;
@@ -95,6 +103,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; 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.DumpUtils;
import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LocalLog; import com.android.internal.util.LocalLog;
@@ -177,6 +187,7 @@ public class AlarmManagerService extends SystemService {
final LocalLog mLog = new LocalLog(TAG); final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps; AppOpsManager mAppOps;
IAppOpsService mAppOpsService;
DeviceIdleInternal mLocalDeviceIdleController; DeviceIdleInternal mLocalDeviceIdleController;
private UsageStatsManagerInternal mUsageStatsManagerInternal; private UsageStatsManagerInternal mUsageStatsManagerInternal;
private ActivityManagerInternal mActivityManagerInternal; private ActivityManagerInternal mActivityManagerInternal;
@@ -253,10 +264,8 @@ public class AlarmManagerService extends SystemService {
"REORDER_ALARMS_FOR_STANDBY", "REORDER_ALARMS_FOR_STANDBY",
}); });
/** BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
* Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
*/
Bundle mIdleOptions;
// TODO(b/172085676): Move inside alarm store. // TODO(b/172085676): Move inside alarm store.
private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser = private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -410,6 +419,10 @@ public class AlarmManagerService extends SystemService {
@VisibleForTesting @VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota"; 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_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS; 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_LAZY_BATCHING = true;
private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = 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 // Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; 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; 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; private long mLastAllowWhileIdleWhitelistDuration = -1;
Constants() { Constants() {
@@ -480,9 +512,11 @@ public class AlarmManagerService extends SystemService {
public void updateAllowWhileIdleWhitelistDurationLocked() { public void updateAllowWhileIdleWhitelistDurationLocked() {
if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) { if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION; mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION); mOptsWithFgs.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
mIdleOptions = opts.toBundle(); 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; ALLOW_WHILE_IDLE_QUOTA = 1;
} }
break; 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: case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong( ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
@@ -643,10 +698,14 @@ public class AlarmManagerService extends SystemService {
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw); TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
pw.println(); pw.println();
pw.print("allow_while_idle_window="); pw.print(KEY_ALLOW_WHILE_IDLE_WINDOW);
pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
pw.println(); 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.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
pw.println(); pw.println();
@@ -830,7 +889,15 @@ public class AlarmManagerService extends SystemService {
if (futurity < MIN_FUZZABLE_INTERVAL) { if (futurity < MIN_FUZZABLE_INTERVAL) {
futurity = 0; 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. // 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, setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null, nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid, null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
alarm.packageName); alarm.packageName, null);
// Kernel alarms will be rescheduled as needed in setImplLocked // Kernel alarms will be rescheduled as needed in setImplLocked
} }
} }
@@ -1241,7 +1308,8 @@ public class AlarmManagerService extends SystemService {
mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater); mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW); 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; mNextWakeup = mNextNonWakeup = 0;
@@ -1327,6 +1395,28 @@ public class AlarmManagerService extends SystemService {
synchronized (mLock) { synchronized (mLock) {
mConstants.start(); mConstants.start();
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); 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 = mLocalDeviceIdleController =
LocalServices.getService(DeviceIdleInternal.class); LocalServices.getService(DeviceIdleInternal.class);
mUsageStatsManagerInternal = mUsageStatsManagerInternal =
@@ -1428,7 +1518,7 @@ public class AlarmManagerService extends SystemService {
void setImpl(int type, long triggerAtTime, long windowLength, long interval, void setImpl(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag, PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock, int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int callingUid, String callingPackage) { int callingUid, String callingPackage, Bundle idleOptions) {
if ((operation == null && directReceiver == null) if ((operation == null && directReceiver == null)
|| (operation != null && directReceiver != null)) { || (operation != null && directReceiver != null)) {
Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver"); 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, setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
callingPackage); callingPackage, idleOptions);
} }
} }
private void setImplLocked(int type, long when, long whenElapsed, long windowLength, private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
long interval, PendingIntent operation, IAlarmListener directReceiver, long interval, PendingIntent operation, IAlarmListener directReceiver,
String listenerTag, int flags, WorkSource workSource, 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, final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock, operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage); callingUid, callingPackage, idleOptions);
if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) { if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ " -- package not allowed to start"); + " -- package not allowed to start");
@@ -1605,19 +1696,21 @@ public class AlarmManagerService extends SystemService {
return false; return false;
} }
if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver( if (mAppStateTracker == null || !mAppStateTracker.areAlarmsRestrictedByBatterySaver(
alarm.creatorUid, alarm.sourcePackage))) { alarm.creatorUid, alarm.sourcePackage)) {
return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed); return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
} }
final long batterySaverPolicyElapsed; final long batterySaverPolicyElapsed;
if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) { if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
// Unrestricted. // Unrestricted.
batterySaverPolicyElapsed = nowElapsed; batterySaverPolicyElapsed = nowElapsed;
} else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { } else if (isAllowedWhileIdleRestricted(alarm)) {
// Allowed but limited. // Allowed but limited.
final int userId = UserHandle.getUserId(alarm.creatorUid); 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( final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
alarm.sourcePackage, userId); alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) { if (dispatchesInWindow < quota) {
@@ -1625,7 +1718,7 @@ public class AlarmManagerService extends SystemService {
batterySaverPolicyElapsed = nowElapsed; batterySaverPolicyElapsed = nowElapsed;
} else { } else {
batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage( batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW; alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
} }
} else { } else {
// Not allowed. // Not allowed.
@@ -1634,6 +1727,16 @@ public class AlarmManagerService extends SystemService {
return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterySaverPolicyElapsed); 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. * 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; final long deviceIdlePolicyTime;
if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE)) != 0) {
| AlarmManager.FLAG_WAKE_FROM_IDLE)) != 0) {
// Unrestricted. // Unrestricted.
deviceIdlePolicyTime = nowElapsed; deviceIdlePolicyTime = nowElapsed;
} else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { } else if (isAllowedWhileIdleRestricted(alarm)) {
// Allowed but limited. // Allowed but limited.
final int userId = UserHandle.getUserId(alarm.creatorUid); 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( final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
alarm.sourcePackage, userId); alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) { if (dispatchesInWindow < quota) {
@@ -1662,7 +1766,7 @@ public class AlarmManagerService extends SystemService {
deviceIdlePolicyTime = nowElapsed; deviceIdlePolicyTime = nowElapsed;
} else { } else {
final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage( 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()); deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
} }
} else { } else {
@@ -1749,7 +1853,7 @@ public class AlarmManagerService extends SystemService {
} else if (mPendingIdleUntil != null) { } else if (mPendingIdleUntil != null) {
adjustDeliveryTimeBasedOnDeviceIdle(a); adjustDeliveryTimeBasedOnDeviceIdle(a);
} }
if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { if ((a.flags & FLAG_WAKE_FROM_IDLE) != 0) {
if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed() if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
> a.getWhenElapsed()) { > a.getWhenElapsed()) {
mNextWakeFromIdle = a; 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 * Public-facing binder interface
*/ */
@@ -1824,6 +1936,54 @@ public class AlarmManagerService extends SystemService {
// wakelock time spent in alarm delivery // wakelock time spent in alarm delivery
mAppOps.checkPackage(callingUid, callingPackage); 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 // Repeating alarms must use PendingIntent, not direct listener
if (interval != 0) { if (interval != 0) {
if (directReceiver != null) { if (directReceiver != null) {
@@ -1840,8 +2000,7 @@ public class AlarmManagerService extends SystemService {
// No incoming callers can request either WAKE_FROM_IDLE or // No incoming callers can request either WAKE_FROM_IDLE or
// ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate. // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
| AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
// Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm // 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. // 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 // 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. // use it to wake early from idle if needed.
if (alarmClock != null) { 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 // 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. // 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 // This means we will allow these alarms to go off as normal even while idle, with no
// timing restrictions. // timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID } else if (workSource == null && (UserHandle.isCore(callingUid)
|| UserHandle.isSameApp(callingUid, mSystemUiUid) || UserHandle.isSameApp(callingUid, mSystemUiUid)
|| ((mAppStateTracker != null) || ((mAppStateTracker != null)
&& mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; flags &= ~FLAG_ALLOW_WHILE_IDLE;
flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
idleOptions = null;
} }
setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, 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 @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<Alarm> whichAlarms =
a -> (a.uid == uid && a.packageName.equals(packageName)
&& ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null));
final ArrayList<Alarm> 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<Alarm> 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) { void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
if (operation == null && directReceiver == null) { if (operation == null && directReceiver == null) {
if (localLOGV) { 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) { if (alarm.alarmClock != null) {
return true; return true;
} }
@@ -3142,7 +3382,7 @@ public class AlarmManagerService extends SystemService {
alarm.count = 1; alarm.count = 1;
triggerList.add(alarm); 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, EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
alarm.statsTag); alarm.statsTag);
} }
@@ -3180,7 +3420,7 @@ public class AlarmManagerService extends SystemService {
setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed, setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null, nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid, null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
alarm.packageName); alarm.packageName, null);
} }
if (alarm.wakeup) { if (alarm.wakeup) {
@@ -3257,7 +3497,6 @@ public class AlarmManagerService extends SystemService {
mLastAlarmDeliveryTime = nowELAPSED; mLastAlarmDeliveryTime = nowELAPSED;
for (int i = 0; i < triggerList.size(); i++) { for (int i = 0; i < triggerList.size(); i++) {
Alarm alarm = triggerList.get(i); Alarm alarm = triggerList.get(i);
final boolean allowWhileIdle = (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
if (alarm.wakeup) { if (alarm.wakeup) {
Trace.traceBegin(Trace.TRACE_TAG_POWER, Trace.traceBegin(Trace.TRACE_TAG_POWER,
"Dispatch wakeup alarm to " + alarm.packageName); "Dispatch wakeup alarm to " + alarm.packageName);
@@ -3273,7 +3512,7 @@ public class AlarmManagerService extends SystemService {
mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource, mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource,
alarm.uid, alarm.statsTag); alarm.uid, alarm.statsTag);
} }
mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle); mDeliveryTracker.deliverLocked(alarm, nowELAPSED);
} catch (RuntimeException e) { } catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", 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) 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 @VisibleForTesting
@@ -3368,6 +3608,11 @@ public class AlarmManagerService extends SystemService {
MATCH_SYSTEM_ONLY, USER_SYSTEM); MATCH_SYSTEM_ONLY, USER_SYSTEM);
} }
IAppOpsService getAppOpsService() {
return IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
}
ClockReceiver getClockReceiver(AlarmManagerService service) { ClockReceiver getClockReceiver(AlarmManagerService service) {
return service.new ClockReceiver(); 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 APP_STANDBY_BUCKET_CHANGED = 5;
public static final int CHARGING_STATUS_CHANGED = 6; public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_CANCELED = 7; public static final int REMOVE_FOR_CANCELED = 7;
public static final int REMOVE_EXACT_ALARMS = 8;
AlarmHandler() { AlarmHandler() {
super(Looper.myLooper()); super(Looper.myLooper());
@@ -3645,6 +3891,14 @@ public class AlarmManagerService extends SystemService {
} }
break; break;
case REMOVE_EXACT_ALARMS:
final int uid = msg.arg1;
final String packageName = (String) msg.obj;
synchronized (mLock) {
removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
}
break;
default: default:
// nope, just ignore it // nope, just ignore it
break; break;
@@ -3720,7 +3974,7 @@ public class AlarmManagerService extends SystemService {
setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0, setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null, 0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null,
Process.myUid(), "android"); Process.myUid(), "android", null);
// Finally, remember when we set the tick alarm // Finally, remember when we set the tick alarm
synchronized (mLock) { synchronized (mLock) {
@@ -3740,7 +3994,7 @@ public class AlarmManagerService extends SystemService {
final WorkSource workSource = null; // Let system take blame for date change events. final WorkSource workSource = null; // Let system take blame for date change events.
setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null, setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
AlarmManager.FLAG_STANDALONE, workSource, 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 * Deliver an alarm and set up the post-delivery handling appropriately
*/ */
@GuardedBy("mLock") @GuardedBy("mLock")
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) { public void deliverLocked(Alarm alarm, long nowELAPSED) {
final long workSourceToken = ThreadLocalWorkSource.setUid( final long workSourceToken = ThreadLocalWorkSource.setUid(
getAlarmAttributionUid(alarm)); getAlarmAttributionUid(alarm));
try { try {
@@ -4122,10 +4376,8 @@ public class AlarmManagerService extends SystemService {
try { try {
alarm.operation.send(getContext(), 0, alarm.operation.send(getContext(), 0,
mBackgroundIntent.putExtra( mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
Intent.EXTRA_ALARM_COUNT, alarm.count), mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
mDeliveryTracker, mHandler, null,
allowWhileIdle ? mIdleOptions : null);
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) { if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this // This IntentSender is no longer valid, but this
@@ -4194,7 +4446,7 @@ public class AlarmManagerService extends SystemService {
if (inflight.isBroadcast()) { if (inflight.isBroadcast()) {
notifyBroadcastAlarmPendingLocked(alarm.uid); notifyBroadcastAlarmPendingLocked(alarm.uid);
} }
if (allowWhileIdle) { if (isAllowedWhileIdleRestricted(alarm)) {
final boolean doze = (mPendingIdleUntil != null); final boolean doze = (mPendingIdleUntil != null);
final boolean batterySaver = (mAppStateTracker != null final boolean batterySaver = (mAppStateTracker != null
&& mAppStateTracker.isForceAllAppsStandbyEnabled()); && mAppStateTracker.isForceAllAppsStandbyEnabled());
@@ -4204,8 +4456,7 @@ public class AlarmManagerService extends SystemService {
mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage, mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid), nowELAPSED); UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
mAlarmStore.updateAlarmDeliveries(a -> { mAlarmStore.updateAlarmDeliveries(a -> {
if (a.creatorUid != alarm.creatorUid if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
|| (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
return false; return false;
} }
return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))

View File

@@ -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_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 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 @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_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 SEND_SMS = "android.permission.SEND_SMS";
field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -4328,16 +4329,17 @@ package android.app {
} }
public class AlarmManager { public class AlarmManager {
method public boolean canScheduleExactAlarms();
method public void cancel(android.app.PendingIntent); method public void cancel(android.app.PendingIntent);
method public void cancel(android.app.AlarmManager.OnAlarmListener); method public void cancel(android.app.AlarmManager.OnAlarmListener);
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock(); method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent); 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 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 setAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setExact(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 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 setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(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); method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long);

View File

@@ -795,7 +795,8 @@ public class AppOpsManager {
// when adding one of these: // when adding one of these:
// - increment _NUM_OP // - increment _NUM_OP
// - define an OPSTR_* constant (marked as @SystemApi) // - 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 descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app) // - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -1174,13 +1175,19 @@ public class AppOpsManager {
* *
* @hide * @hide
*/ */
// TODO: Add as AppProtoEnums public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
public static final int OP_RECORD_AUDIO_OUTPUT = 106;
/**
* 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 */ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @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. */ /** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; 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"; 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 */ /** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1633,6 +1647,7 @@ public class AppOpsManager {
OP_LOADER_USAGE_STATS, OP_LOADER_USAGE_STATS,
OP_MANAGE_ONGOING_CALLS, OP_MANAGE_ONGOING_CALLS,
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OP_SCHEDULE_EXACT_ALARM,
}; };
/** /**
@@ -1751,6 +1766,7 @@ public class AppOpsManager {
OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT 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_MANAGE_CREDENTIALS,
OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OPSTR_RECORD_AUDIO_OUTPUT, OPSTR_RECORD_AUDIO_OUTPUT,
OPSTR_SCHEDULE_EXACT_ALARM,
}; };
/** /**
@@ -1978,6 +1995,7 @@ public class AppOpsManager {
"MANAGE_CREDENTIALS", "MANAGE_CREDENTIALS",
"USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
"RECORD_AUDIO_OUTPUT", "RECORD_AUDIO_OUTPUT",
"SCHEDULE_EXACT_ALARM",
}; };
/** /**
@@ -2093,6 +2111,7 @@ public class AppOpsManager {
null, // no permission for OP_MANAGE_CREDENTIALS null, // no permission for OP_MANAGE_CREDENTIALS
Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
null, // no permission for OP_RECORD_AUDIO_OUTPUT null, // no permission for OP_RECORD_AUDIO_OUTPUT
Manifest.permission.SCHEDULE_EXACT_ALARM,
}; };
/** /**
@@ -2208,6 +2227,7 @@ public class AppOpsManager {
null, // MANAGE_CREDENTIALS null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT null, // RECORD_AUDIO_OUTPUT
null, // SCHEDULE_EXACT_ALARM
}; };
/** /**
@@ -2322,6 +2342,7 @@ public class AppOpsManager {
null, // MANAGE_CREDENTIALS null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT null, // RECORD_AUDIO_OUTPUT
null, // SCHEDULE_EXACT_ALARM
}; };
/** /**
@@ -2435,6 +2456,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
}; };
/** /**
@@ -2552,6 +2574,7 @@ public class AppOpsManager {
false, // MANAGE_CREDENTIALS false, // MANAGE_CREDENTIALS
true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
false, // RECORD_AUDIO_OUTPUT false, // RECORD_AUDIO_OUTPUT
false, // SCHEDULE_EXACT_ALARM
}; };
/** /**

View File

@@ -3905,6 +3905,12 @@
<permission android:name="android.permission.SET_KEYBOARD_LAYOUT" <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
android:protectionLevel="signature" /> android:protectionLevel="signature" />
<!-- Allows an app to use exact alarm scheduling APIs to perform timing
sensitive background work.
-->
<permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:protectionLevel="normal|appop"/>
<!-- Allows an application to query tablet mode state and monitor changes <!-- Allows an application to query tablet mode state and monitor changes
in it. in it.
<p>Not for use by third-party applications. <p>Not for use by third-party applications.

View File

@@ -18,6 +18,7 @@ package com.android.server.alarm;
import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; 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;
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_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL; import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
import static android.app.AlarmManager.FLAG_STANDALONE; import static android.app.AlarmManager.FLAG_STANDALONE;
@@ -25,11 +26,14 @@ import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.WINDOW_EXACT; import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AlarmManager.WINDOW_HEURISTIC;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
@@ -44,7 +48,7 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
@@ -61,6 +65,7 @@ import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
import static com.android.server.alarm.Constants.TEST_CALLING_UID; import static com.android.server.alarm.Constants.TEST_CALLING_UID;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -75,20 +80,29 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import android.Manifest;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.IActivityManager; import android.app.IActivityManager;
import android.app.IAlarmCompleteListener; import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener; import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.PermissionChecker;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.UserHandle; import android.os.UserHandle;
@@ -102,9 +116,12 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.MockedVoidMethod; import com.android.dx.mockito.inline.extended.MockedVoidMethod;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.server.AlarmManagerInternal; import com.android.server.AlarmManagerInternal;
import com.android.server.AppStateTracker; import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl; import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices; import com.android.server.LocalServices;
import com.android.server.SystemService; import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal;
@@ -125,6 +142,7 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.LongConsumer;
@Presubmit @Presubmit
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@@ -134,14 +152,21 @@ public class AlarmManagerServiceTest {
private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID); private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
private long mAppStandbyWindow; private long mAppStandbyWindow;
private long mAllowWhileIdleWindow;
private AlarmManagerService mService; private AlarmManagerService mService;
private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener; private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener;
private AlarmManagerService.ChargingReceiver mChargingReceiver; private AlarmManagerService.ChargingReceiver mChargingReceiver;
private IAppOpsCallback mIAppOpsCallback;
private IAlarmManager mBinder;
@Mock @Mock
private Context mMockContext; private Context mMockContext;
@Mock @Mock
private IActivityManager mIActivityManager; private IActivityManager mIActivityManager;
@Mock @Mock
private IAppOpsService mIAppOpsService;
@Mock
private DeviceIdleInternal mDeviceIdleInternal;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInternal; private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Mock @Mock
private AppStandbyInternal mAppStandbyInternal; private AppStandbyInternal mAppStandbyInternal;
@@ -280,6 +305,11 @@ public class AlarmManagerServiceTest {
// "IllegalStateException: Querying activity state off main thread is not allowed." // "IllegalStateException: Querying activity state off main thread is not allowed."
// when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener(). // when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener().
} }
@Override
IAppOpsService getAppOpsService() {
return mIAppOpsService;
}
} }
@Before @Before
@@ -287,15 +317,20 @@ public class AlarmManagerServiceTest {
mMockingSession = mockitoSession() mMockingSession = mockitoSession()
.initMocks(this) .initMocks(this)
.spyStatic(ActivityManager.class) .spyStatic(ActivityManager.class)
.mockStatic(CompatChanges.class)
.spyStatic(DeviceConfig.class) .spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class) .mockStatic(LocalServices.class)
.spyStatic(Looper.class) .spyStatic(Looper.class)
.mockStatic(PermissionChecker.class)
.mockStatic(Settings.Global.class) .mockStatic(Settings.Global.class)
.mockStatic(ServiceManager.class) .mockStatic(ServiceManager.class)
.spyStatic(UserHandle.class) .spyStatic(UserHandle.class)
.strictness(Strictness.WARN) .strictness(Strictness.WARN)
.startMocking(); .startMocking();
doReturn(mIActivityManager).when(ActivityManager::getService); doReturn(mIActivityManager).when(ActivityManager::getService);
doReturn(mDeviceIdleInternal).when(
() -> LocalServices.getService(DeviceIdleInternal.class));
doReturn(mActivityManagerInternal).when( doReturn(mActivityManagerInternal).when(
() -> LocalServices.getService(ActivityManagerInternal.class)); () -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class)); doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
@@ -330,6 +365,9 @@ public class AlarmManagerServiceTest {
() -> DeviceConfig.getProperties( () -> DeviceConfig.getProperties(
eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any())); eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any()));
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(
mock(AppOpsManager.class));
mInjector = new Injector(mMockContext); mInjector = new Injector(mMockContext);
mService = new AlarmManagerService(mMockContext, mInjector); mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService); spyOn(mService);
@@ -346,6 +384,7 @@ public class AlarmManagerServiceTest {
// Other boot phases don't matter // Other boot phases don't matter
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW;
ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor = ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor =
ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
verify(mAppStandbyInternal).addListener(captor.capture()); verify(mAppStandbyInternal).addListener(captor.capture());
@@ -358,6 +397,20 @@ public class AlarmManagerServiceTest {
&& filter.hasAction(BatteryManager.ACTION_DISCHARGING))); && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
mChargingReceiver = chargingReceiverCaptor.getValue(); mChargingReceiver = chargingReceiverCaptor.getValue();
ArgumentCaptor<IBinder> binderCaptor = ArgumentCaptor.forClass(IBinder.class);
verify(() -> ServiceManager.addService(eq(Context.ALARM_SERVICE), binderCaptor.capture(),
anyBoolean(), anyInt()));
mBinder = IAlarmManager.Stub.asInterface(binderCaptor.getValue());
ArgumentCaptor<IAppOpsCallback> appOpsCallbackCaptor = ArgumentCaptor.forClass(
IAppOpsCallback.class);
try {
verify(mIAppOpsService).startWatchingMode(eq(AppOpsManager.OP_SCHEDULE_EXACT_ALARM),
isNull(), appOpsCallbackCaptor.capture());
} catch (RemoteException e) {
// Not expected on a mock.
}
mIAppOpsCallback = appOpsCallbackCaptor.getValue();
setTestableQuotas(); setTestableQuotas();
} }
@@ -389,13 +442,18 @@ public class AlarmManagerServiceTest {
private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval, private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
int flags, int callingUid) { int flags, int callingUid) {
setTestAlarm(type, triggerTime, operation, interval, flags, callingUid, null);
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
int flags, int callingUid, Bundle idleOptions) {
mService.setImpl(type, triggerTime, WINDOW_EXACT, interval, operation, null, "test", flags, mService.setImpl(type, triggerTime, WINDOW_EXACT, interval, operation, null, "test", flags,
null, null, callingUid, TEST_CALLING_PACKAGE); null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions);
} }
private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE); FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null);
} }
@@ -506,14 +564,27 @@ public class AlarmManagerServiceTest {
setDeviceConfigLong(KEY_MIN_INTERVAL, 10); setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
setDeviceConfigLong(KEY_MAX_INTERVAL, 15); setDeviceConfigLong(KEY_MAX_INTERVAL, 15);
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20);
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 25);
setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30);
setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35); setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35);
assertEquals(5, mService.mConstants.MIN_FUTURITY); assertEquals(5, mService.mConstants.MIN_FUTURITY);
assertEquals(10, mService.mConstants.MIN_INTERVAL); assertEquals(10, mService.mConstants.MIN_INTERVAL);
assertEquals(15, mService.mConstants.MAX_INTERVAL); assertEquals(15, mService.mConstants.MAX_INTERVAL);
assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
assertEquals(35, mService.mConstants.LISTENER_TIMEOUT); assertEquals(35, mService.mConstants.LISTENER_TIMEOUT);
// Test safeguards.
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 0);
assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, -8);
assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 0);
assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA);
} }
@Test @Test
@@ -559,56 +630,45 @@ public class AlarmManagerServiceTest {
assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed()); assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed());
} }
private void testQuotasDeferralOnSet(int standbyBucket) throws Exception { private void testQuotasDeferralOnSet(LongConsumer alarmSetter, int quota, long window)
final int quota = mService.getQuotaForBucketLocked(standbyBucket); throws Exception {
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
final long firstTrigger = mNowElapsedTest + 10; final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) { for (int i = 0; i < quota; i++) {
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, alarmSetter.accept(firstTrigger + i);
getNewMockPendingIntent());
mNowElapsedTest = mTestTimer.getElapsed(); mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire(); mTestTimer.expire();
} }
// This one should get deferred on set // This one should get deferred on set
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, alarmSetter.accept(firstTrigger + quota);
getNewMockPendingIntent()); final long expectedNextTrigger = firstTrigger + window;
final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
} }
private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception { private void testQuotasDeferralOnExpiration(LongConsumer alarmSetter, int quota, long window)
final int quota = mService.getQuotaForBucketLocked(standbyBucket); throws Exception {
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
final long firstTrigger = mNowElapsedTest + 10; final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) { for (int i = 0; i < quota; i++) {
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, alarmSetter.accept(firstTrigger + i);
getNewMockPendingIntent());
} }
// This one should get deferred after the latest alarm expires // This one should get deferred after the latest alarm expires.
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, alarmSetter.accept(firstTrigger + quota);
getNewMockPendingIntent());
for (int i = 0; i < quota; i++) { for (int i = 0; i < quota; i++) {
mNowElapsedTest = mTestTimer.getElapsed(); mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire(); mTestTimer.expire();
} }
final long expectedNextTrigger = firstTrigger + mAppStandbyWindow; final long expectedNextTrigger = firstTrigger + window;
assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
} }
private void testQuotasNoDeferral(int standbyBucket) throws Exception { private void testQuotasNoDeferral(LongConsumer alarmSetter, int quota, long window)
final int quota = mService.getQuotaForBucketLocked(standbyBucket); throws Exception {
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
final long firstTrigger = mNowElapsedTest + 10; final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) { for (int i = 0; i < quota; i++) {
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, alarmSetter.accept(firstTrigger + i);
getNewMockPendingIntent());
} }
// This delivery time maintains the quota invariant. Should not be deferred. // This delivery time maintains the quota invariant. Should not be deferred.
final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5; final long expectedNextTrigger = firstTrigger + window + 5;
setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent()); alarmSetter.accept(expectedNextTrigger);
for (int i = 0; i < quota; i++) { for (int i = 0; i < quota; i++) {
mNowElapsedTest = mTestTimer.getElapsed(); mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire(); mTestTimer.expire();
@@ -616,64 +676,88 @@ public class AlarmManagerServiceTest {
assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
} }
private void testStandbyQuotasDeferralOnSet(int standbyBucket) throws Exception {
final int quota = mService.getQuotaForBucketLocked(standbyBucket);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent()), quota, mAppStandbyWindow);
}
private void testStandbyQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
final int quota = mService.getQuotaForBucketLocked(standbyBucket);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent()), quota, mAppStandbyWindow);
}
private void testStandbyQuotasNoDeferral(int standbyBucket) throws Exception {
final int quota = mService.getQuotaForBucketLocked(standbyBucket);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent()), quota, mAppStandbyWindow);
}
@Test @Test
public void testActiveQuota_deferredOnSet() throws Exception { public void testActiveQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE); testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
} }
@Test @Test
public void testActiveQuota_deferredOnExpiration() throws Exception { public void testActiveQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE); testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
} }
@Test @Test
public void testActiveQuota_notDeferred() throws Exception { public void testActiveQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE); testStandbyQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
} }
@Test @Test
public void testWorkingQuota_deferredOnSet() throws Exception { public void testWorkingQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET); testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
} }
@Test @Test
public void testWorkingQuota_deferredOnExpiration() throws Exception { public void testWorkingQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET); testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
} }
@Test @Test
public void testWorkingQuota_notDeferred() throws Exception { public void testWorkingQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET); testStandbyQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
} }
@Test @Test
public void testFrequentQuota_deferredOnSet() throws Exception { public void testFrequentQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT); testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
} }
@Test @Test
public void testFrequentQuota_deferredOnExpiration() throws Exception { public void testFrequentQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT); testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
} }
@Test @Test
public void testFrequentQuota_notDeferred() throws Exception { public void testFrequentQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT); testStandbyQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
} }
@Test @Test
public void testRareQuota_deferredOnSet() throws Exception { public void testRareQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_RARE); testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
} }
@Test @Test
public void testRareQuota_deferredOnExpiration() throws Exception { public void testRareQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE); testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
} }
@Test @Test
public void testRareQuota_notDeferred() throws Exception { public void testRareQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_RARE); testStandbyQuotasNoDeferral(STANDBY_BUCKET_RARE);
} }
@Test @Test
@@ -731,7 +815,7 @@ public class AlarmManagerServiceTest {
} }
@Test @Test
public void testQuotaDowngrade() throws Exception { public void testStandbyQuotaDowngrade() throws Exception {
final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET); final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
@@ -758,7 +842,7 @@ public class AlarmManagerServiceTest {
} }
@Test @Test
public void testQuotaUpgrade() throws Exception { public void testStandbyQuotaUpgrade() throws Exception {
final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT); final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT); anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
@@ -1308,7 +1392,7 @@ public class AlarmManagerServiceTest {
public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception { public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong()); doReturn(0).when(mService).fuzzForDuration(anyLong());
setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000, setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + mAllowWhileIdleWindow + 1000,
getNewMockPendingIntent()); getNewMockPendingIntent());
assertNotNull(mService.mPendingIdleUntil); assertNotNull(mService.mPendingIdleUntil);
@@ -1323,7 +1407,7 @@ public class AlarmManagerServiceTest {
// This one should get deferred on set. // This one should get deferred on set.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent(), false); getNewMockPendingIntent(), false);
final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
assertEquals("Incorrect trigger when no quota left", expectedNextTrigger, assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
mTestTimer.getElapsed()); mTestTimer.getElapsed());
@@ -1449,61 +1533,24 @@ public class AlarmManagerServiceTest {
when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(true); TEST_CALLING_PACKAGE)).thenReturn(true);
when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) { testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
getNewMockPendingIntent(), false);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// This one should get deferred on set.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent(), false);
long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
mTestTimer.getElapsed());
// Refresh the state // Refresh the state
mService.removeLocked(TEST_CALLING_UID); mService.removeLocked(TEST_CALLING_UID);
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
firstTrigger = mNowElapsedTest + 10; testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
for (int i = 0; i < quota; i++) { trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent(), false);
}
// This one should get deferred after the latest alarm expires.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent(), false);
for (int i = 0; i < quota; i++) {
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
mTestTimer.getElapsed());
// Refresh the state // Refresh the state
mService.removeLocked(TEST_CALLING_UID); mService.removeLocked(TEST_CALLING_UID);
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
firstTrigger = mNowElapsedTest + 10; testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
for (int i = 0; i < quota; i++) { getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent(), false);
}
// This delivery time maintains the quota invariant. Should not be deferred.
expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5;
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger,
getNewMockPendingIntent(), false);
for (int i = 0; i < quota; i++) {
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
mTestTimer.getElapsed());
} }
@Test @Test
@@ -1544,6 +1591,259 @@ public class AlarmManagerServiceTest {
} }
} }
@Test
public void canScheduleExactAlarms() throws RemoteException {
doReturn(PermissionChecker.PERMISSION_GRANTED).when(
() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
assertTrue(mBinder.canScheduleExactAlarms());
doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
assertFalse(mBinder.canScheduleExactAlarms());
doReturn(PermissionChecker.PERMISSION_SOFT_DENIED).when(
() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
assertFalse(mBinder.canScheduleExactAlarms());
}
@Test
public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
// alarm clock
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
getNewMockPendingIntent(), null, null, null,
mock(AlarmManager.AlarmClockInfo.class));
// exact, allow-while-idle
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
// inexact, allow-while-idle
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM), never());
verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
}
@Test
public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(),
eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
public void alarmClockBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
final PendingIntent alarmPi = getNewMockPendingIntent();
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
alarmPi, null, null, null, alarmClock);
verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull());
}
@Test
public void alarmClockBinderCall() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
doReturn(PermissionChecker.PERMISSION_GRANTED).when(
() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
final PendingIntent alarmPi = getNewMockPendingIntent();
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
alarmPi, null, null, null, alarmClock);
// Correct permission checks are invoked.
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
isNull(), eq(alarmClock), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE),
bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
// Permission check is granted by default by the mock.
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(),
eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(),
eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
// If permission is denied, only then allowlist will be checked.
doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM));
verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(),
eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(),
eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
public void inexactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM), never());
verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
public void inexactAllowWhileIdleBinderCallWithoutAllowlist() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM), never());
verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
final Bundle idleOptions = bundleCaptor.getValue();
final int type = idleOptions.getInt("android:broadcast.temporaryAppWhitelistType");
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type);
}
@Test
public void idleOptionsSentOnExpiration() throws Exception {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
final Bundle idleOptions = new Bundle();
idleOptions.putChar("TEST_CHAR_KEY", 'x');
idleOptions.putInt("TEST_INT_KEY", 53);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi, 0, 0, TEST_CALLING_UID,
idleOptions);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
any(), any(Handler.class), isNull(), eq(idleOptions));
}
@Test @Test
public void alarmStoreMigration() { public void alarmStoreMigration() {
setDeviceConfigBoolean(KEY_LAZY_BATCHING, false); setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);

View File

@@ -69,12 +69,12 @@ public class AlarmStoreTest {
mock(PendingIntent.class)); mock(PendingIntent.class));
return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0, return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0,
mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID, mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID,
TEST_CALLING_PACKAGE); TEST_CALLING_PACKAGE, null);
} }
private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) { private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) {
return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class), return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class),
null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE); null, null, null, flags, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null);
} }
private void addAlarmsToStore(Alarm... alarms) { private void addAlarmsToStore(Alarm... alarms) {

View File

@@ -17,10 +17,17 @@
package com.android.server.alarm; package com.android.server.alarm;
import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME;
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_STANDALONE;
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.RTC_WAKEUP;
import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX; import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
import static com.android.server.alarm.Alarm.NUM_POLICIES; import static com.android.server.alarm.Alarm.NUM_POLICIES;
import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX; import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
import static com.android.server.alarm.AlarmManagerService.isExemptFromAppStandby;
import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE; import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
import static com.android.server.alarm.Constants.TEST_CALLING_UID; import static com.android.server.alarm.Constants.TEST_CALLING_UID;
@@ -28,7 +35,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -43,15 +52,29 @@ import java.util.Random;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AlarmTest { public class AlarmTest {
private Alarm createDefaultAlarm(long requestedElapsed, long windowLength) { private Alarm createDefaultAlarm(long requestedElapsed, long windowLength, int flags) {
return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0, return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0,
mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID, createAlarmSender(), null, null, null, flags, null, TEST_CALLING_UID,
TEST_CALLING_PACKAGE); TEST_CALLING_PACKAGE, null);
}
private Alarm createAlarmClock(long requestedRtc) {
final AlarmManager.AlarmClockInfo info = mock(AlarmManager.AlarmClockInfo.class);
return new Alarm(RTC_WAKEUP, requestedRtc, requestedRtc, 0, 0, createAlarmSender(),
null, null, null, FLAG_WAKE_FROM_IDLE | FLAG_STANDALONE, info, TEST_CALLING_UID,
TEST_CALLING_PACKAGE, null);
}
private PendingIntent createAlarmSender() {
final PendingIntent alarmPi = mock(PendingIntent.class);
when(alarmPi.getCreatorPackage()).thenReturn(TEST_CALLING_PACKAGE);
when(alarmPi.getCreatorUid()).thenReturn(TEST_CALLING_UID);
return alarmPi;
} }
@Test @Test
public void initSetsOnlyRequesterPolicy() { public void initSetsOnlyRequesterPolicy() {
final Alarm a = createDefaultAlarm(4567, 2); final Alarm a = createDefaultAlarm(4567, 2, 0);
for (int i = 0; i < NUM_POLICIES; i++) { for (int i = 0; i < NUM_POLICIES; i++) {
if (i == REQUESTER_POLICY_INDEX) { if (i == REQUESTER_POLICY_INDEX) {
@@ -86,7 +109,7 @@ public class AlarmTest {
@Test @Test
public void whenElapsed() { public void whenElapsed() {
final Alarm a = createDefaultAlarm(0, 0); final Alarm a = createDefaultAlarm(0, 0, 0);
final long[][] uniqueData = generatePolicyTestMatrix(NUM_POLICIES); final long[][] uniqueData = generatePolicyTestMatrix(NUM_POLICIES);
for (int i = 0; i < NUM_POLICIES; i++) { for (int i = 0; i < NUM_POLICIES; i++) {
@@ -104,7 +127,7 @@ public class AlarmTest {
@Test @Test
public void maxWhenElapsed() { public void maxWhenElapsed() {
final Alarm a = createDefaultAlarm(10, 12); final Alarm a = createDefaultAlarm(10, 12, 0);
assertEquals(22, a.getMaxWhenElapsed()); assertEquals(22, a.getMaxWhenElapsed());
a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15); a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15);
@@ -128,7 +151,7 @@ public class AlarmTest {
@Test @Test
public void setPolicyElapsedExact() { public void setPolicyElapsedExact() {
final Alarm exactAlarm = createDefaultAlarm(10, 0); final Alarm exactAlarm = createDefaultAlarm(10, 0, 0);
assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4)); assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10)); assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
@@ -143,7 +166,7 @@ public class AlarmTest {
@Test @Test
public void setPolicyElapsedInexact() { public void setPolicyElapsedInexact() {
final Alarm inexactAlarm = createDefaultAlarm(10, 5); final Alarm inexactAlarm = createDefaultAlarm(10, 5, 0);
assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4)); assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10)); assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
@@ -154,4 +177,20 @@ public class AlarmTest {
assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8)); assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
} }
@Test
public void isExemptFromStandby() {
final long anything = 35412; // Arbitrary number, doesn't matter for this test.
assertFalse("Basic alarm exempt", isExemptFromAppStandby(
createDefaultAlarm(anything, anything, 0)));
assertFalse("FLAG_ALLOW_WHILE_IDLE_COMPAT exempt", isExemptFromAppStandby(
createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_COMPAT)));
assertTrue("ALLOW_WHILE_IDLE not exempt", isExemptFromAppStandby(
createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE)));
assertTrue("ALLOW_WHILE_IDLE_UNRESTRICTED not exempt", isExemptFromAppStandby(
createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)));
assertTrue("Alarm clock not exempt", isExemptFromAppStandby(createAlarmClock(anything)));
}
} }

View File

@@ -45,7 +45,7 @@ public class BackgroundRestrictedAlarmsTest {
} }
uidAlarms.add(new Alarm( uidAlarms.add(new Alarm(
removeIt ? RTC : RTC_WAKEUP, removeIt ? RTC : RTC_WAKEUP,
0, 0, 0, 0, null, null, null, null, 0, null, uid, name)); 0, 0, 0, 0, null, null, null, null, 0, null, uid, name, null));
return all; return all;
} }