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: [
"app-compat-annotations",
"framework-minus-apex",
"unsupportedappusage",
],

View File

@@ -16,11 +16,14 @@
package android.app;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -183,6 +186,25 @@ public class AlarmManager {
@UnsupportedAppUsage
public static final int FLAG_IDLE_UNTIL = 1<<4;
/**
* Flag for alarms: Used to provide backwards compatibility for apps with targetSdkVersion less
* than {@link Build.VERSION_CODES#S}
* @hide
*/
public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
/**
* For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
* {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
* {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
* permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
*
* @hide
*/
@ChangeId
@Disabled // TODO (b/171306433): Enable starting S.
public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
@UnsupportedAppUsage
private final IAlarmManager mService;
private final Context mContext;
@@ -588,6 +610,11 @@ public class AlarmManager {
* This method is like {@link #setExact(int, long, PendingIntent)}, but implies
* {@link #RTC_WAKEUP}.
*
* <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 operation Action to perform when the alarm goes off;
* typically comes from {@link PendingIntent#getBroadcast
@@ -603,6 +630,7 @@ public class AlarmManager {
* @see android.content.Context#registerReceiver
* @see android.content.Intent#filterEquals
*/
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
null, null, null, null, info);
@@ -876,6 +904,12 @@ public class AlarmManager {
* device is idle it may take even more liberties with scheduling in order to optimize
* for battery life.</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 triggerAtMillis time in milliseconds that the alarm should go
* off, using the appropriate clock (depending on the alarm type).
@@ -895,6 +929,7 @@ public class AlarmManager {
* @see #RTC
* @see #RTC_WAKEUP
*/
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
@@ -1017,6 +1052,18 @@ public class AlarmManager {
}
}
/**
* Called to check if the caller has permission to use alarms set via {@link }
* @return
*/
public boolean canScheduleExactAlarms() {
try {
return mService.canScheduleExactAlarms();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* Gets information about the next alarm clock currently scheduled.
*

View File

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

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_WAKEUP;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE;
import static android.app.AlarmManager.INTERVAL_HOUR;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
@@ -32,6 +36,7 @@ import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX;
import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX;
import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -43,12 +48,14 @@ import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.BatteryManager;
@@ -65,6 +72,7 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
@@ -95,6 +103,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LocalLog;
@@ -177,6 +187,7 @@ public class AlarmManagerService extends SystemService {
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
IAppOpsService mAppOpsService;
DeviceIdleInternal mLocalDeviceIdleController;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
@@ -253,10 +264,8 @@ public class AlarmManagerService extends SystemService {
"REORDER_ALARMS_FOR_STANDBY",
});
/**
* Broadcast options to use for FLAG_ALLOW_WHILE_IDLE.
*/
Bundle mIdleOptions;
BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
// TODO(b/172085676): Move inside alarm store.
private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -410,6 +419,10 @@ public class AlarmManagerService extends SystemService {
@VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
@VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota";
private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
@@ -433,8 +446,14 @@ public class AlarmManagerService extends SystemService {
private static final boolean DEFAULT_LAZY_BATCHING = true;
private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;
private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7;
public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
/**
* Default quota for pre-S apps. Enough to accommodate the existing policy of an alarm
* every ALLOW_WHILE_IDLE_LONG_DELAY, which was 9 minutes.
*/
private static final int DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA = 7;
private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -463,6 +482,19 @@ public class AlarmManagerService extends SystemService {
public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
/**
* Used to provide backwards compatibility to pre-S apps with a quota equivalent to the
* earlier delay throttling mechanism.
*/
public int ALLOW_WHILE_IDLE_COMPAT_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
/**
* The window used for enforcing {@link #ALLOW_WHILE_IDLE_QUOTA} and
* {@link #ALLOW_WHILE_IDLE_COMPAT_QUOTA}. Can be configured, but only recommended for
* testing.
*/
public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
private long mLastAllowWhileIdleWhitelistDuration = -1;
Constants() {
@@ -480,9 +512,11 @@ public class AlarmManagerService extends SystemService {
public void updateAllowWhileIdleWhitelistDurationLocked() {
if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
mIdleOptions = opts.toBundle();
mOptsWithFgs.setTemporaryAppWhitelistDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION);
mOptsWithoutFgs.setTemporaryAppWhitelistDuration(
TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
ALLOW_WHILE_IDLE_WHITELIST_DURATION);
}
}
@@ -516,6 +550,27 @@ public class AlarmManagerService extends SystemService {
ALLOW_WHILE_IDLE_QUOTA = 1;
}
break;
case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA:
ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt(
KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA,
DEFAULT_ALLOW_WHILE_IDLE_COMPAT_QUOTA);
if (ALLOW_WHILE_IDLE_COMPAT_QUOTA <= 0) {
Slog.w(TAG, "Cannot have quota lower than 1.");
ALLOW_WHILE_IDLE_COMPAT_QUOTA = 1;
}
break;
case KEY_ALLOW_WHILE_IDLE_WINDOW:
ALLOW_WHILE_IDLE_WINDOW = properties.getLong(
KEY_ALLOW_WHILE_IDLE_WINDOW, DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
if (ALLOW_WHILE_IDLE_WINDOW > DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
Slog.w(TAG, "Cannot have allow_while_idle_window > "
+ DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW;
} else if (ALLOW_WHILE_IDLE_WINDOW < DEFAULT_ALLOW_WHILE_IDLE_WINDOW) {
Slog.w(TAG, "Using a non-default allow_while_idle_window = "
+ ALLOW_WHILE_IDLE_WINDOW);
}
break;
case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
@@ -643,10 +698,14 @@ public class AlarmManagerService extends SystemService {
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
pw.println();
pw.print("allow_while_idle_window=");
pw.print(KEY_ALLOW_WHILE_IDLE_WINDOW);
pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
pw.println();
pw.print(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, ALLOW_WHILE_IDLE_COMPAT_QUOTA);
pw.println();
pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
pw.println();
@@ -830,7 +889,15 @@ public class AlarmManagerService extends SystemService {
if (futurity < MIN_FUZZABLE_INTERVAL) {
futurity = 0;
}
return clampPositive(triggerAtTime + (long) (.75 * futurity));
long maxElapsed = triggerAtTime + (long) (0.75 * futurity);
// For non-repeating alarms, window is capped at a maximum of one hour from the requested
// delivery time. This allows for inexact-while-idle alarms to be slightly more reliable.
// In practice, the delivery window should generally be much smaller than that
// when the device is not idling.
if (interval == 0) {
maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR);
}
return clampPositive(maxElapsed);
}
// The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
@@ -998,7 +1065,7 @@ public class AlarmManagerService extends SystemService {
setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
alarm.packageName);
alarm.packageName, null);
// Kernel alarms will be rescheduled as needed in setImplLocked
}
}
@@ -1241,7 +1308,8 @@ public class AlarmManagerService extends SystemService {
mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW);
mAllowWhileIdleHistory = new AppWakeupHistory(
Constants.DEFAULT_ALLOW_WHILE_IDLE_WINDOW);
mNextWakeup = mNextNonWakeup = 0;
@@ -1327,6 +1395,28 @@ public class AlarmManagerService extends SystemService {
synchronized (mLock) {
mConstants.start();
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mAppOpsService = mInjector.getAppOpsService();
try {
mAppOpsService.startWatchingMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, null,
new IAppOpsCallback.Stub() {
@Override
public void opChanged(int op, int uid, String packageName)
throws RemoteException {
if (op != AppOpsManager.OP_SCHEDULE_EXACT_ALARM) {
return;
}
final int mode = mAppOpsService.checkOperation(op, uid,
packageName);
if (mode != AppOpsManager.MODE_ALLOWED
&& mode != AppOpsManager.MODE_DEFAULT) {
mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
uid, 0, packageName).sendToTarget();
}
}
});
} catch (RemoteException e) {
}
mLocalDeviceIdleController =
LocalServices.getService(DeviceIdleInternal.class);
mUsageStatsManagerInternal =
@@ -1428,7 +1518,7 @@ public class AlarmManagerService extends SystemService {
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int callingUid, String callingPackage) {
int callingUid, String callingPackage, Bundle idleOptions) {
if ((operation == null && directReceiver == null)
|| (operation != null && directReceiver != null)) {
Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
@@ -1519,17 +1609,18 @@ public class AlarmManagerService extends SystemService {
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
callingPackage);
callingPackage, idleOptions);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
long interval, PendingIntent operation, IAlarmListener directReceiver,
String listenerTag, int flags, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage,
Bundle idleOptions) {
final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
callingUid, callingPackage, idleOptions);
if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ " -- package not allowed to start");
@@ -1605,19 +1696,21 @@ public class AlarmManagerService extends SystemService {
return false;
}
if (!(mAppStateTracker != null && mAppStateTracker.areAlarmsRestrictedByBatterySaver(
alarm.creatorUid, alarm.sourcePackage))) {
if (mAppStateTracker == null || !mAppStateTracker.areAlarmsRestrictedByBatterySaver(
alarm.creatorUid, alarm.sourcePackage)) {
return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
}
final long batterySaverPolicyElapsed;
if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
// Unrestricted.
batterySaverPolicyElapsed = nowElapsed;
} else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
} else if (isAllowedWhileIdleRestricted(alarm)) {
// Allowed but limited.
final int userId = UserHandle.getUserId(alarm.creatorUid);
final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
? mConstants.ALLOW_WHILE_IDLE_QUOTA
: mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) {
@@ -1625,7 +1718,7 @@ public class AlarmManagerService extends SystemService {
batterySaverPolicyElapsed = nowElapsed;
} else {
batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
}
} else {
// Not allowed.
@@ -1634,6 +1727,16 @@ public class AlarmManagerService extends SystemService {
return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterySaverPolicyElapsed);
}
/**
* Returns {@code true} if the given alarm has the flag
* {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE} or
* {@link AlarmManager#FLAG_ALLOW_WHILE_IDLE_COMPAT}
*
*/
private static boolean isAllowedWhileIdleRestricted(Alarm a) {
return (a.flags & (FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT)) != 0;
}
/**
* Adjusts the delivery time of the alarm based on device_idle (doze) rules.
*
@@ -1647,14 +1750,15 @@ public class AlarmManagerService extends SystemService {
}
final long deviceIdlePolicyTime;
if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
| AlarmManager.FLAG_WAKE_FROM_IDLE)) != 0) {
if ((alarm.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE)) != 0) {
// Unrestricted.
deviceIdlePolicyTime = nowElapsed;
} else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
} else if (isAllowedWhileIdleRestricted(alarm)) {
// Allowed but limited.
final int userId = UserHandle.getUserId(alarm.creatorUid);
final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
final int quota = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
? mConstants.ALLOW_WHILE_IDLE_QUOTA
: mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) {
@@ -1662,7 +1766,7 @@ public class AlarmManagerService extends SystemService {
deviceIdlePolicyTime = nowElapsed;
} else {
final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
alarm.sourcePackage, userId, quota) + mConstants.ALLOW_WHILE_IDLE_WINDOW;
deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
}
} else {
@@ -1749,7 +1853,7 @@ public class AlarmManagerService extends SystemService {
} else if (mPendingIdleUntil != null) {
adjustDeliveryTimeBasedOnDeviceIdle(a);
}
if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
if ((a.flags & FLAG_WAKE_FROM_IDLE) != 0) {
if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
> a.getWhenElapsed()) {
mNextWakeFromIdle = a;
@@ -1809,6 +1913,14 @@ public class AlarmManagerService extends SystemService {
}
}
/**
* Returns true if the given uid is on the system or user's power save exclusion list.
*/
boolean isWhitelisted(int uid) {
return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist(
UserHandle.getAppId(uid)));
}
/**
* Public-facing binder interface
*/
@@ -1824,6 +1936,54 @@ public class AlarmManagerService extends SystemService {
// wakelock time spent in alarm delivery
mAppOps.checkPackage(callingUid, callingPackage);
final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
Bundle idleOptions = null;
if (alarmClock != null || allowWhileIdle) {
// make sure the caller is allowed to use the requested kind of alarm, and also
// decide what broadcast options to use.
final boolean needsPermission;
boolean lowQuota;
if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
if (windowLength != AlarmManager.WINDOW_EXACT) {
needsPermission = false;
lowQuota = true;
idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle()
: mOptsWithoutFgs.toBundle();
} else if (alarmClock != null) {
needsPermission = true;
lowQuota = false;
idleOptions = mOptsWithFgs.toBundle();
} else {
needsPermission = true;
lowQuota = false;
idleOptions = mOptsWithFgs.toBundle();
}
} else {
needsPermission = false;
lowQuota = allowWhileIdle;
idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
}
if (needsPermission && !canScheduleExactAlarms()) {
if (alarmClock == null && isWhitelisted(callingUid)) {
// If the app is on the full system allow-list (not except-idle), we still
// allow the alarms, but with a lower quota to keep pre-S compatibility.
lowQuota = true;
} else {
final String errorMessage = "Caller needs to hold "
+ Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
+ ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock")
+ " alarms.";
throw new SecurityException(errorMessage);
}
}
if (lowQuota) {
flags &= ~FLAG_ALLOW_WHILE_IDLE;
flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
}
}
// Repeating alarms must use PendingIntent, not direct listener
if (interval != 0) {
if (directReceiver != null) {
@@ -1840,8 +2000,7 @@ public class AlarmManagerService extends SystemService {
// No incoming callers can request either WAKE_FROM_IDLE or
// ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
| AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
// Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
// manager when to come out of idle mode, which is only for DeviceIdleController.
@@ -1857,22 +2016,32 @@ public class AlarmManagerService extends SystemService {
// If this alarm is for an alarm clock, then it must be standalone and we will
// use it to wake early from idle if needed.
if (alarmClock != null) {
flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
// If the caller is a core system component or on the user's whitelist, and not calling
// to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
// This means we will allow these alarms to go off as normal even while idle, with no
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
} else if (workSource == null && (UserHandle.isCore(callingUid)
|| UserHandle.isSameApp(callingUid, mSystemUiUid)
|| ((mAppStateTracker != null)
&& mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~FLAG_ALLOW_WHILE_IDLE;
flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
idleOptions = null;
}
setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
idleOptions);
}
@Override
public boolean canScheduleExactAlarms() {
return PermissionChecker.checkCallingOrSelfPermissionForPreflight(getContext(),
Manifest.permission.SCHEDULE_EXACT_ALARM)
== PermissionChecker.PERMISSION_GRANTED;
}
@Override
@@ -2755,6 +2924,77 @@ public class AlarmManagerService extends SystemService {
}
}
/**
* Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms
* that the app is no longer eligible to use.
* TODO (b/179541791): Revisit and write tests once UX is final.
*/
void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
if (UserHandle.isCore(uid) || uid == mSystemUiUid) {
return;
}
if (isWhitelisted(uid)) {
return;
}
if (!CompatChanges.isChangeEnabled(
AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
packageName, UserHandle.getUserHandleForUid(uid))) {
return;
}
final Predicate<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) {
if (operation == null && directReceiver == null) {
if (localLOGV) {
@@ -3082,7 +3322,7 @@ public class AlarmManagerService extends SystemService {
}
}
private boolean isExemptFromBatterySaver(Alarm alarm) {
private static boolean isExemptFromBatterySaver(Alarm alarm) {
if (alarm.alarmClock != null) {
return true;
}
@@ -3142,7 +3382,7 @@ public class AlarmManagerService extends SystemService {
alarm.count = 1;
triggerList.add(alarm);
if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
if ((alarm.flags & FLAG_WAKE_FROM_IDLE) != 0) {
EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
alarm.statsTag);
}
@@ -3180,7 +3420,7 @@ public class AlarmManagerService extends SystemService {
setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
alarm.packageName);
alarm.packageName, null);
}
if (alarm.wakeup) {
@@ -3257,7 +3497,6 @@ public class AlarmManagerService extends SystemService {
mLastAlarmDeliveryTime = nowELAPSED;
for (int i = 0; i < triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
final boolean allowWhileIdle = (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
if (alarm.wakeup) {
Trace.traceBegin(Trace.TRACE_TAG_POWER,
"Dispatch wakeup alarm to " + alarm.packageName);
@@ -3273,7 +3512,7 @@ public class AlarmManagerService extends SystemService {
mActivityManagerInternal.noteAlarmStart(alarm.operation, alarm.workSource,
alarm.uid, alarm.statsTag);
}
mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
mDeliveryTracker.deliverLocked(alarm, nowELAPSED);
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
@@ -3282,9 +3521,10 @@ public class AlarmManagerService extends SystemService {
}
}
private boolean isExemptFromAppStandby(Alarm a) {
@VisibleForTesting
static boolean isExemptFromAppStandby(Alarm a) {
return a.alarmClock != null || UserHandle.isCore(a.creatorUid)
|| (a.flags & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED) != 0;
|| (a.flags & (FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_ALLOW_WHILE_IDLE)) != 0;
}
@VisibleForTesting
@@ -3368,6 +3608,11 @@ public class AlarmManagerService extends SystemService {
MATCH_SYSTEM_ONLY, USER_SYSTEM);
}
IAppOpsService getAppOpsService() {
return IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
}
ClockReceiver getClockReceiver(AlarmManagerService service) {
return service.new ClockReceiver();
}
@@ -3566,6 +3811,7 @@ public class AlarmManagerService extends SystemService {
public static final int APP_STANDBY_BUCKET_CHANGED = 5;
public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_CANCELED = 7;
public static final int REMOVE_EXACT_ALARMS = 8;
AlarmHandler() {
super(Looper.myLooper());
@@ -3645,6 +3891,14 @@ public class AlarmManagerService extends SystemService {
}
break;
case REMOVE_EXACT_ALARMS:
final int uid = msg.arg1;
final String packageName = (String) msg.obj;
synchronized (mLock) {
removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
}
break;
default:
// nope, just ignore it
break;
@@ -3720,7 +3974,7 @@ public class AlarmManagerService extends SystemService {
setImpl(ELAPSED_REALTIME, mInjector.getElapsedRealtime() + tickEventDelay, 0,
0, null, mTimeTickTrigger, TIME_TICK_TAG, flags, workSource, null,
Process.myUid(), "android");
Process.myUid(), "android", null);
// Finally, remember when we set the tick alarm
synchronized (mLock) {
@@ -3740,7 +3994,7 @@ public class AlarmManagerService extends SystemService {
final WorkSource workSource = null; // Let system take blame for date change events.
setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
AlarmManager.FLAG_STANDALONE, workSource, null,
Process.myUid(), "android");
Process.myUid(), "android", null);
}
}
@@ -4112,7 +4366,7 @@ public class AlarmManagerService extends SystemService {
* Deliver an alarm and set up the post-delivery handling appropriately
*/
@GuardedBy("mLock")
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
public void deliverLocked(Alarm alarm, long nowELAPSED) {
final long workSourceToken = ThreadLocalWorkSource.setUid(
getAlarmAttributionUid(alarm));
try {
@@ -4122,10 +4376,8 @@ public class AlarmManagerService extends SystemService {
try {
alarm.operation.send(getContext(), 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
mDeliveryTracker, mHandler, null,
allowWhileIdle ? mIdleOptions : null);
mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
} catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
@@ -4194,7 +4446,7 @@ public class AlarmManagerService extends SystemService {
if (inflight.isBroadcast()) {
notifyBroadcastAlarmPendingLocked(alarm.uid);
}
if (allowWhileIdle) {
if (isAllowedWhileIdleRestricted(alarm)) {
final boolean doze = (mPendingIdleUntil != null);
final boolean batterySaver = (mAppStateTracker != null
&& mAppStateTracker.isForceAllAppsStandbyEnabled());
@@ -4204,8 +4456,7 @@ public class AlarmManagerService extends SystemService {
mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
mAlarmStore.updateAlarmDeliveries(a -> {
if (a.creatorUid != alarm.creatorUid
|| (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {
return false;
}
return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))

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

View File

@@ -795,7 +795,8 @@ public class AppOpsManager {
// when adding one of these:
// - increment _NUM_OP
// - define an OPSTR_* constant (marked as @SystemApi)
// - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
// - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset,
// sOpRestrictions, sOpAllowSystemRestrictionBypass
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -1174,13 +1175,19 @@ public class AppOpsManager {
*
* @hide
*/
// TODO: Add as AppProtoEnums
public static final int OP_RECORD_AUDIO_OUTPUT = 106;
public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
/**
* App can schedule exact alarm to perform timing based background work
*
* @hide
*/
public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int _NUM_OP = 107;
public static final int _NUM_OP = 108;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1553,6 +1560,13 @@ public class AppOpsManager {
*/
public static final String OPSTR_RECORD_AUDIO_OUTPUT = "android:record_audio_output";
/**
* App can schedule exact alarm to perform timing based background work.
*
* @hide
*/
public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm";
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1633,6 +1647,7 @@ public class AppOpsManager {
OP_LOADER_USAGE_STATS,
OP_MANAGE_ONGOING_CALLS,
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OP_SCHEDULE_EXACT_ALARM,
};
/**
@@ -1751,6 +1766,7 @@ public class AppOpsManager {
OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT
OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM
};
/**
@@ -1864,6 +1880,7 @@ public class AppOpsManager {
OPSTR_MANAGE_CREDENTIALS,
OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OPSTR_RECORD_AUDIO_OUTPUT,
OPSTR_SCHEDULE_EXACT_ALARM,
};
/**
@@ -1978,6 +1995,7 @@ public class AppOpsManager {
"MANAGE_CREDENTIALS",
"USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
"RECORD_AUDIO_OUTPUT",
"SCHEDULE_EXACT_ALARM",
};
/**
@@ -2093,6 +2111,7 @@ public class AppOpsManager {
null, // no permission for OP_MANAGE_CREDENTIALS
Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
null, // no permission for OP_RECORD_AUDIO_OUTPUT
Manifest.permission.SCHEDULE_EXACT_ALARM,
};
/**
@@ -2208,6 +2227,7 @@ public class AppOpsManager {
null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT
null, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2322,6 +2342,7 @@ public class AppOpsManager {
null, // MANAGE_CREDENTIALS
null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
null, // RECORD_AUDIO_OUTPUT
null, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2435,6 +2456,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
};
/**
@@ -2552,6 +2574,7 @@ public class AppOpsManager {
false, // MANAGE_CREDENTIALS
true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
false, // RECORD_AUDIO_OUTPUT
false, // SCHEDULE_EXACT_ALARM
};
/**

View File

@@ -3905,6 +3905,12 @@
<permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
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
in it.
<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_WAKEUP;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT;
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.FLAG_IDLE_UNTIL;
import static android.app.AlarmManager.FLAG_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_WAKEUP;
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_FREQUENT;
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_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.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.CHARGING_STATUS_CHANGED;
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_WHITELIST_DURATION;
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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
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.never;
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -102,9 +116,12 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.MockedVoidMethod;
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.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal;
@@ -125,6 +142,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.LongConsumer;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -134,14 +152,21 @@ public class AlarmManagerServiceTest {
private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
private long mAppStandbyWindow;
private long mAllowWhileIdleWindow;
private AlarmManagerService mService;
private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener;
private AlarmManagerService.ChargingReceiver mChargingReceiver;
private IAppOpsCallback mIAppOpsCallback;
private IAlarmManager mBinder;
@Mock
private Context mMockContext;
@Mock
private IActivityManager mIActivityManager;
@Mock
private IAppOpsService mIAppOpsService;
@Mock
private DeviceIdleInternal mDeviceIdleInternal;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Mock
private AppStandbyInternal mAppStandbyInternal;
@@ -280,6 +305,11 @@ public class AlarmManagerServiceTest {
// "IllegalStateException: Querying activity state off main thread is not allowed."
// when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener().
}
@Override
IAppOpsService getAppOpsService() {
return mIAppOpsService;
}
}
@Before
@@ -287,15 +317,20 @@ public class AlarmManagerServiceTest {
mMockingSession = mockitoSession()
.initMocks(this)
.spyStatic(ActivityManager.class)
.mockStatic(CompatChanges.class)
.spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
.spyStatic(Looper.class)
.mockStatic(PermissionChecker.class)
.mockStatic(Settings.Global.class)
.mockStatic(ServiceManager.class)
.spyStatic(UserHandle.class)
.strictness(Strictness.WARN)
.startMocking();
doReturn(mIActivityManager).when(ActivityManager::getService);
doReturn(mDeviceIdleInternal).when(
() -> LocalServices.getService(DeviceIdleInternal.class));
doReturn(mActivityManagerInternal).when(
() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
@@ -330,6 +365,9 @@ public class AlarmManagerServiceTest {
() -> DeviceConfig.getProperties(
eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any()));
when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(
mock(AppOpsManager.class));
mInjector = new Injector(mMockContext);
mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService);
@@ -346,6 +384,7 @@ public class AlarmManagerServiceTest {
// Other boot phases don't matter
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW;
ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor =
ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
verify(mAppStandbyInternal).addListener(captor.capture());
@@ -358,6 +397,20 @@ public class AlarmManagerServiceTest {
&& filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
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();
}
@@ -389,13 +442,18 @@ public class AlarmManagerServiceTest {
private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
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,
null, null, callingUid, TEST_CALLING_PACKAGE);
null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions);
}
private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
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_MAX_INTERVAL, 15);
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_LISTENER_TIMEOUT, 35);
assertEquals(5, mService.mConstants.MIN_FUTURITY);
assertEquals(10, mService.mConstants.MIN_INTERVAL);
assertEquals(15, mService.mConstants.MAX_INTERVAL);
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(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
@@ -559,56 +630,45 @@ public class AlarmManagerServiceTest {
assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed());
}
private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
final int quota = mService.getQuotaForBucketLocked(standbyBucket);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
private void testQuotasDeferralOnSet(LongConsumer alarmSetter, int quota, long window)
throws Exception {
final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent());
alarmSetter.accept(firstTrigger + i);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// This one should get deferred on set
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent());
final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
alarmSetter.accept(firstTrigger + quota);
final long expectedNextTrigger = firstTrigger + window;
assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
}
private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
final int quota = mService.getQuotaForBucketLocked(standbyBucket);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
private void testQuotasDeferralOnExpiration(LongConsumer alarmSetter, int quota, long window)
throws Exception {
final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent());
alarmSetter.accept(firstTrigger + i);
}
// This one should get deferred after the latest alarm expires
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent());
// This one should get deferred after the latest alarm expires.
alarmSetter.accept(firstTrigger + quota);
for (int i = 0; i < quota; i++) {
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
final long expectedNextTrigger = firstTrigger + mAppStandbyWindow;
final long expectedNextTrigger = firstTrigger + window;
assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
}
private void testQuotasNoDeferral(int standbyBucket) throws Exception {
final int quota = mService.getQuotaForBucketLocked(standbyBucket);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(standbyBucket);
private void testQuotasNoDeferral(LongConsumer alarmSetter, int quota, long window)
throws Exception {
final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
getNewMockPendingIntent());
alarmSetter.accept(firstTrigger + i);
}
// This delivery time maintains the quota invariant. Should not be deferred.
final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
final long expectedNextTrigger = firstTrigger + window + 5;
alarmSetter.accept(expectedNextTrigger);
for (int i = 0; i < quota; i++) {
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
@@ -616,64 +676,88 @@ public class AlarmManagerServiceTest {
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
public void testActiveQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
}
@Test
public void testActiveQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
}
@Test
public void testActiveQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
testStandbyQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
}
@Test
public void testWorkingQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
}
@Test
public void testWorkingQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
}
@Test
public void testWorkingQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
testStandbyQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
}
@Test
public void testFrequentQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
}
@Test
public void testFrequentQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
}
@Test
public void testFrequentQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
testStandbyQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
}
@Test
public void testRareQuota_deferredOnSet() throws Exception {
testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
}
@Test
public void testRareQuota_deferredOnExpiration() throws Exception {
testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
}
@Test
public void testRareQuota_notDeferred() throws Exception {
testQuotasNoDeferral(STANDBY_BUCKET_RARE);
testStandbyQuotasNoDeferral(STANDBY_BUCKET_RARE);
}
@Test
@@ -731,7 +815,7 @@ public class AlarmManagerServiceTest {
}
@Test
public void testQuotaDowngrade() throws Exception {
public void testStandbyQuotaDowngrade() throws Exception {
final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
@@ -758,7 +842,7 @@ public class AlarmManagerServiceTest {
}
@Test
public void testQuotaUpgrade() throws Exception {
public void testStandbyQuotaUpgrade() throws Exception {
final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
@@ -1308,7 +1392,7 @@ public class AlarmManagerServiceTest {
public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception {
doReturn(0).when(mService).fuzzForDuration(anyLong());
setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000,
setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + mAllowWhileIdleWindow + 1000,
getNewMockPendingIntent());
assertNotNull(mService.mPendingIdleUntil);
@@ -1323,7 +1407,7 @@ public class AlarmManagerServiceTest {
// This one should get deferred on set.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent(), false);
final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
mTestTimer.getElapsed());
@@ -1449,61 +1533,24 @@ public class AlarmManagerServiceTest {
when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(true);
when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
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());
testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
// Refresh the state
mService.removeLocked(TEST_CALLING_UID);
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
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());
testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
// Refresh the state
mService.removeLocked(TEST_CALLING_UID);
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
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());
testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
}
@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
public void alarmStoreMigration() {
setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);

View File

@@ -69,12 +69,12 @@ public class AlarmStoreTest {
mock(PendingIntent.class));
return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0,
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) {
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) {

View File

@@ -17,10 +17,17 @@
package com.android.server.alarm;
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.NUM_POLICIES;
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_UID;
@@ -28,7 +35,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.platform.test.annotations.Presubmit;
@@ -43,15 +52,29 @@ import java.util.Random;
@RunWith(AndroidJUnit4.class)
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,
mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID,
TEST_CALLING_PACKAGE);
createAlarmSender(), null, null, null, flags, null, TEST_CALLING_UID,
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
public void initSetsOnlyRequesterPolicy() {
final Alarm a = createDefaultAlarm(4567, 2);
final Alarm a = createDefaultAlarm(4567, 2, 0);
for (int i = 0; i < NUM_POLICIES; i++) {
if (i == REQUESTER_POLICY_INDEX) {
@@ -86,7 +109,7 @@ public class AlarmTest {
@Test
public void whenElapsed() {
final Alarm a = createDefaultAlarm(0, 0);
final Alarm a = createDefaultAlarm(0, 0, 0);
final long[][] uniqueData = generatePolicyTestMatrix(NUM_POLICIES);
for (int i = 0; i < NUM_POLICIES; i++) {
@@ -104,7 +127,7 @@ public class AlarmTest {
@Test
public void maxWhenElapsed() {
final Alarm a = createDefaultAlarm(10, 12);
final Alarm a = createDefaultAlarm(10, 12, 0);
assertEquals(22, a.getMaxWhenElapsed());
a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15);
@@ -128,7 +151,7 @@ public class AlarmTest {
@Test
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(APP_STANDBY_POLICY_INDEX, 10));
@@ -143,7 +166,7 @@ public class AlarmTest {
@Test
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(APP_STANDBY_POLICY_INDEX, 10));
@@ -154,4 +177,20 @@ public class AlarmTest {
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(
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;
}