Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 447a3469 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Allow power-allowlisted apps to schedule alarm-clocks" into sc-dev am: d1d3358b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15146444

Change-Id: I5f44701b0581c73cd504e69c079435025dfacfc8
parents eceaa9b1 d1d3358b
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -1294,22 +1294,31 @@ public class AlarmManager {

    /**
     * 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>
     * Apps targeting {@link Build.VERSION_CODES#S} or higher can schedule exact alarms if they
     * have the {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission. These apps can also
     * 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 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
     * request this from the user.
     * request this permission from the user.
     * <p>
     * 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 #setExact(int, long, PendingIntent)
     * @see #setExactAndAllowWhileIdle(int, long, PendingIntent)
     * @see #setAlarmClock(AlarmClockInfo, PendingIntent)
     * @see android.os.PowerManager#isIgnoringBatteryOptimizations(String)
     */
    public boolean canScheduleExactAlarms() {
        return hasScheduleExactAlarm(mContext.getOpPackageName(), mContext.getUserId());
        try {
            return mService.canScheduleExactAlarms(mContext.getOpPackageName());
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
+1 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ interface IAlarmManager {
    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
    AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
    long currentNetworkTimeMillis();
    boolean canScheduleExactAlarms(String packageName);
    boolean hasScheduleExactAlarm(String packageName, int userId);
    int getConfigVersion();
}
+34 −17
Original line number Diff line number Diff line
@@ -1740,7 +1740,7 @@ public class AlarmManagerService extends SystemService {
                    if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) {
                        return false;
                    }
                    return a.alarmClock != null || !isExemptFromExactAlarmPermission(a.uid);
                    return !isExemptFromExactAlarmPermission(a.uid);
                };
                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,
     * allow-while-idle alarms.
     * Note: It is ok to call this method without the lock {@link #mLock} held.
     */
    boolean isExemptFromExactAlarmPermission(int uid) {
        return (UserHandle.isSameApp(mSystemUiUid, uid)
@@ -2515,7 +2516,7 @@ public class AlarmManagerService extends SystemService {
                    idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
                }
                if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid)) {
                    if (alarmClock != null || !isExemptFromExactAlarmPermission(callingUid)) {
                    if (!isExemptFromExactAlarmPermission(callingUid)) {
                        final String errorMessage = "Caller " + callingPackage + " needs to hold "
                                + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
                                + "exact alarms.";
@@ -2527,10 +2528,16 @@ public class AlarmManagerService extends SystemService {
                    } else {
                        allowListed = true;
                    }
                    // If the app is on the full system power allow-list (not except-idle), or we're
                    // in a soft failure mode, we still allow the alarms.
                    // We give temporary allowlist to allow-while-idle alarms but without FGS
                    // capability. Note that apps that are in the power allow-list do not need it.
                    // If the app is on the full system power allow-list (not except-idle), or the
                    // user-elected allow-list, or we're in a soft failure mode, we still allow the
                    // alarms.
                    // 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;
                    lowerQuota = allowWhileIdle;
                }
@@ -2560,6 +2567,22 @@ public class AlarmManagerService extends SystemService {
                    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
        public boolean hasScheduleExactAlarm(String packageName, int userId) {
            final int callingUid = mInjector.getCallingUid();
@@ -2572,9 +2595,6 @@ public class AlarmManagerService extends SystemService {
                throw new SecurityException("Uid " + callingUid
                        + " cannot query hasScheduleExactAlarm for uid " + uid);
            }
            if (!isExactAlarmChangeEnabled(packageName, userId)) {
                return true;
            }
            return (uid > 0) ? hasScheduleExactAlarmInternal(packageName, uid) : false;
        }

@@ -3577,17 +3597,14 @@ public class AlarmManagerService extends SystemService {
     * This is not expected to get called frequently.
     */
    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
        Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!");
        if (!isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
        if (isExemptFromExactAlarmPermission(uid)
                || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
            return;
        }
        Slog.w(TAG, "Package " + packageName + ", uid " + uid + " lost SCHEDULE_EXACT_ALARM!");

        final Predicate<Alarm> whichAlarms = a -> {
            if (a.uid == uid && a.packageName.equals(packageName) && a.windowLength == 0) {
                return a.alarmClock != null || !isExemptFromExactAlarmPermission(uid);
            }
            return false;
        };
        final Predicate<Alarm> whichAlarms = a -> (a.uid == uid && a.packageName.equals(packageName)
                && a.windowLength == 0);
        removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);

        if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
+57 −18
Original line number Diff line number Diff line
@@ -1910,17 +1910,6 @@ public class AlarmManagerServiceTest {
        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) {
        doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                any(UserHandle.class)));
@@ -1940,6 +1929,53 @@ public class AlarmManagerServiceTest {
        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
    public void noPermissionCheckWhenChangeDisabled() throws RemoteException {
        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
@@ -2086,14 +2122,17 @@ public class AlarmManagerServiceTest {

        final PendingIntent alarmPi = getNewMockPendingIntent();
        final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
        try {
        mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
                    alarmPi, null, null, null, alarmClock);
            fail("alarm clock binder call succeeded without permission");
        } catch (SecurityException se) {
            // Expected.
        }
        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());

        // Correct permission checks are invoked.
        verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
        verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID));

        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