Merge "Allow power-allowlisted apps to schedule alarm-clocks" into sc-dev am: d1d3358be9
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15146444 Change-Id: I5f44701b0581c73cd504e69c079435025dfacfc8
This commit is contained in:
@@ -1294,22 +1294,31 @@ public class AlarmManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to check if the caller can schedule exact alarms.
|
* Called to check if the caller can schedule exact alarms.
|
||||||
|
* Your app schedules exact alarms when it calls any of the {@code setExact...} or
|
||||||
|
* {@link #setAlarmClock(AlarmClockInfo, PendingIntent) setAlarmClock} API methods.
|
||||||
* <p>
|
* <p>
|
||||||
* Apps targeting {@link Build.VERSION_CODES#S} or higher can schedule exact alarms if they
|
* Apps targeting {@link Build.VERSION_CODES#S} or higher can schedule exact alarms only if they
|
||||||
* have the {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission. These apps can also
|
* have the {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission or they are on the
|
||||||
|
* device's power-save exemption list.
|
||||||
|
* These apps can also
|
||||||
* start {@link android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM} to
|
* start {@link android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM} to
|
||||||
* request this from the user.
|
* request this permission from the user.
|
||||||
* <p>
|
* <p>
|
||||||
* Apps targeting lower sdk versions, can always schedule exact alarms.
|
* Apps targeting lower sdk versions, can always schedule exact alarms.
|
||||||
*
|
*
|
||||||
* @return {@code true} if the caller can schedule exact alarms.
|
* @return {@code true} if the caller can schedule exact alarms, {@code false} otherwise.
|
||||||
* @see android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
* @see android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
||||||
* @see #setExact(int, long, PendingIntent)
|
* @see #setExact(int, long, PendingIntent)
|
||||||
* @see #setExactAndAllowWhileIdle(int, long, PendingIntent)
|
* @see #setExactAndAllowWhileIdle(int, long, PendingIntent)
|
||||||
* @see #setAlarmClock(AlarmClockInfo, PendingIntent)
|
* @see #setAlarmClock(AlarmClockInfo, PendingIntent)
|
||||||
|
* @see android.os.PowerManager#isIgnoringBatteryOptimizations(String)
|
||||||
*/
|
*/
|
||||||
public boolean canScheduleExactAlarms() {
|
public boolean canScheduleExactAlarms() {
|
||||||
return hasScheduleExactAlarm(mContext.getOpPackageName(), mContext.getUserId());
|
try {
|
||||||
|
return mService.canScheduleExactAlarms(mContext.getOpPackageName());
|
||||||
|
} catch (RemoteException re) {
|
||||||
|
throw re.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ 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(String packageName);
|
||||||
boolean hasScheduleExactAlarm(String packageName, int userId);
|
boolean hasScheduleExactAlarm(String packageName, int userId);
|
||||||
int getConfigVersion();
|
int getConfigVersion();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1740,7 +1740,7 @@ public class AlarmManagerService extends SystemService {
|
|||||||
if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) {
|
if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return a.alarmClock != null || !isExemptFromExactAlarmPermission(a.uid);
|
return !isExemptFromExactAlarmPermission(a.uid);
|
||||||
};
|
};
|
||||||
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
|
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
|
||||||
}
|
}
|
||||||
@@ -2414,6 +2414,7 @@ public class AlarmManagerService extends SystemService {
|
|||||||
/**
|
/**
|
||||||
* Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact,
|
* Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact,
|
||||||
* allow-while-idle alarms.
|
* allow-while-idle alarms.
|
||||||
|
* Note: It is ok to call this method without the lock {@link #mLock} held.
|
||||||
*/
|
*/
|
||||||
boolean isExemptFromExactAlarmPermission(int uid) {
|
boolean isExemptFromExactAlarmPermission(int uid) {
|
||||||
return (UserHandle.isSameApp(mSystemUiUid, uid)
|
return (UserHandle.isSameApp(mSystemUiUid, uid)
|
||||||
@@ -2515,7 +2516,7 @@ public class AlarmManagerService extends SystemService {
|
|||||||
idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
|
idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
|
||||||
}
|
}
|
||||||
if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid)) {
|
if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid)) {
|
||||||
if (alarmClock != null || !isExemptFromExactAlarmPermission(callingUid)) {
|
if (!isExemptFromExactAlarmPermission(callingUid)) {
|
||||||
final String errorMessage = "Caller " + callingPackage + " needs to hold "
|
final String errorMessage = "Caller " + callingPackage + " needs to hold "
|
||||||
+ Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
|
+ Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
|
||||||
+ "exact alarms.";
|
+ "exact alarms.";
|
||||||
@@ -2527,10 +2528,16 @@ public class AlarmManagerService extends SystemService {
|
|||||||
} else {
|
} else {
|
||||||
allowListed = true;
|
allowListed = true;
|
||||||
}
|
}
|
||||||
// If the app is on the full system power allow-list (not except-idle), or we're
|
// If the app is on the full system power allow-list (not except-idle), or the
|
||||||
// in a soft failure mode, we still allow the alarms.
|
// user-elected allow-list, or we're in a soft failure mode, we still allow the
|
||||||
// We give temporary allowlist to allow-while-idle alarms but without FGS
|
// alarms.
|
||||||
// capability. Note that apps that are in the power allow-list do not need it.
|
// In both cases, ALLOW_WHILE_IDLE alarms get a lower quota equivalent to what
|
||||||
|
// pre-S apps got. Note that user-allow-listed apps don't use the flag
|
||||||
|
// ALLOW_WHILE_IDLE.
|
||||||
|
// We grant temporary allow-list to allow-while-idle alarms but without FGS
|
||||||
|
// capability. AlarmClock alarms do not get the temporary allow-list. This is
|
||||||
|
// consistent with pre-S behavior. Note that apps that are in either of the
|
||||||
|
// power-save allow-lists do not need it.
|
||||||
idleOptions = allowWhileIdle ? mOptsWithoutFgs.toBundle() : null;
|
idleOptions = allowWhileIdle ? mOptsWithoutFgs.toBundle() : null;
|
||||||
lowerQuota = allowWhileIdle;
|
lowerQuota = allowWhileIdle;
|
||||||
}
|
}
|
||||||
@@ -2560,6 +2567,22 @@ public class AlarmManagerService extends SystemService {
|
|||||||
idleOptions, exactAllowReason);
|
idleOptions, exactAllowReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canScheduleExactAlarms(String packageName) {
|
||||||
|
final int callingUid = mInjector.getCallingUid();
|
||||||
|
final int userId = UserHandle.getUserId(callingUid);
|
||||||
|
final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
|
||||||
|
if (callingUid != packageUid) {
|
||||||
|
throw new SecurityException("Uid " + callingUid
|
||||||
|
+ " cannot query canScheduleExactAlarms for package " + packageName);
|
||||||
|
}
|
||||||
|
if (!isExactAlarmChangeEnabled(packageName, userId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isExemptFromExactAlarmPermission(packageUid)
|
||||||
|
|| hasScheduleExactAlarmInternal(packageName, packageUid);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasScheduleExactAlarm(String packageName, int userId) {
|
public boolean hasScheduleExactAlarm(String packageName, int userId) {
|
||||||
final int callingUid = mInjector.getCallingUid();
|
final int callingUid = mInjector.getCallingUid();
|
||||||
@@ -2572,9 +2595,6 @@ public class AlarmManagerService extends SystemService {
|
|||||||
throw new SecurityException("Uid " + callingUid
|
throw new SecurityException("Uid " + callingUid
|
||||||
+ " cannot query hasScheduleExactAlarm for uid " + uid);
|
+ " cannot query hasScheduleExactAlarm for uid " + uid);
|
||||||
}
|
}
|
||||||
if (!isExactAlarmChangeEnabled(packageName, userId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return (uid > 0) ? hasScheduleExactAlarmInternal(packageName, uid) : false;
|
return (uid > 0) ? hasScheduleExactAlarmInternal(packageName, uid) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3577,17 +3597,14 @@ public class AlarmManagerService extends SystemService {
|
|||||||
* This is not expected to get called frequently.
|
* This is not expected to get called frequently.
|
||||||
*/
|
*/
|
||||||
void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
|
void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
|
||||||
Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!");
|
if (isExemptFromExactAlarmPermission(uid)
|
||||||
if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
|
|| !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!");
|
||||||
|
|
||||||
final Predicate<Alarm> whichAlarms = a -> {
|
final Predicate<Alarm> whichAlarms = a -> (a.uid == uid && a.packageName.equals(packageName)
|
||||||
if (a.uid == uid && a.packageName.equals(packageName) && a.windowLength == 0) {
|
&& a.windowLength == 0);
|
||||||
return a.alarmClock != null || !isExemptFromExactAlarmPermission(uid);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
|
removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
|
||||||
|
|
||||||
if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
|
if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
|
||||||
|
|||||||
@@ -1910,17 +1910,6 @@ public class AlarmManagerServiceTest {
|
|||||||
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
|
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void hasScheduleExactAlarmBinderCallChangeDisabled() throws RemoteException {
|
|
||||||
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
|
|
||||||
|
|
||||||
mockExactAlarmPermissionGrant(false, true, MODE_DEFAULT);
|
|
||||||
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
|
|
||||||
|
|
||||||
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
|
|
||||||
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mockChangeEnabled(long changeId, boolean enabled) {
|
private void mockChangeEnabled(long changeId, boolean enabled) {
|
||||||
doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
|
doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
|
||||||
any(UserHandle.class)));
|
any(UserHandle.class)));
|
||||||
@@ -1940,6 +1929,53 @@ public class AlarmManagerServiceTest {
|
|||||||
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
|
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canScheduleExactAlarmsBinderCallChangeDisabled() throws RemoteException {
|
||||||
|
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
|
||||||
|
|
||||||
|
mockExactAlarmPermissionGrant(false, true, MODE_DEFAULT);
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canScheduleExactAlarmsBinderCall() throws RemoteException {
|
||||||
|
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
|
||||||
|
|
||||||
|
// No permission, no exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, true, MODE_DEFAULT);
|
||||||
|
assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
// No permission, no exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
|
||||||
|
assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
// Permission, no exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT);
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
// Permission, no exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, true, MODE_ALLOWED);
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
// No permission, exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
|
||||||
|
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
// No permission, exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
|
||||||
|
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
|
||||||
|
doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
|
||||||
|
// Both permission and exemption.
|
||||||
|
mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
|
||||||
|
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
|
public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
|
||||||
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
|
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
|
||||||
@@ -2086,14 +2122,17 @@ public class AlarmManagerServiceTest {
|
|||||||
|
|
||||||
final PendingIntent alarmPi = getNewMockPendingIntent();
|
final PendingIntent alarmPi = getNewMockPendingIntent();
|
||||||
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
|
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
|
||||||
try {
|
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
|
||||||
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
|
|
||||||
alarmPi, null, null, null, alarmClock);
|
alarmPi, null, null, null, alarmClock);
|
||||||
fail("alarm clock binder call succeeded without permission");
|
|
||||||
} catch (SecurityException se) {
|
// Correct permission checks are invoked.
|
||||||
// Expected.
|
verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
|
||||||
}
|
verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID));
|
||||||
verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
|
|
||||||
|
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(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE),
|
||||||
|
isNull(), eq(EXACT_ALLOW_REASON_ALLOW_LIST));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user