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

Commit be22a43c authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Adding a normal permission for exact alarms

Apps that have this permission can freely schedule exact alarms without
needing to ask the user.

Test: atest AlarmManagerServiceTest
atest CtsAlarmManagerTestCases

Bug: 218533173
Change-Id: I664bcf8be9520ad8864ae561f1e1eb2a4260d53f
parent 5e729109
Loading
Loading
Loading
Loading
+46 −6
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
@@ -1839,6 +1840,9 @@ public class AlarmManagerService extends SystemService {
                    if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) {
                        return false;
                    }
                    if (hasUseExactAlarmPermission(a.packageName, a.uid)) {
                        return false;
                    }
                    return !isExemptFromExactAlarmPermission(a.uid);
                };
                removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
@@ -1900,6 +1904,9 @@ public class AlarmManagerService extends SystemService {
                                        || !isExactAlarmChangeEnabled(packageName, userId)) {
                                    return;
                                }
                                if (hasUseExactAlarmPermission(packageName, uid)) {
                                    return;
                                }

                                final boolean requested = mExactAlarmCandidates.contains(
                                        UserHandle.getAppId(uid));
@@ -2534,6 +2541,8 @@ public class AlarmManagerService extends SystemService {

    private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed,
            int appOpMode) {
        // This does not account for the state of the USE_EXACT_ALARM permission.
        // The caller should do that separately.
        if (!requested) {
            return false;
        }
@@ -2543,7 +2552,16 @@ public class AlarmManagerService extends SystemService {
        return appOpMode == AppOpsManager.MODE_ALLOWED;
    }

    boolean hasUseExactAlarmPermission(String packageName, int uid) {
        return PermissionChecker.checkPermissionForPreflight(getContext(),
                Manifest.permission.USE_EXACT_ALARM, PermissionChecker.PID_UNKNOWN, uid,
                packageName) == PermissionChecker.PERMISSION_GRANTED;
    }

    boolean hasScheduleExactAlarmInternal(String packageName, int uid) {
        if (hasUseExactAlarmPermission(packageName, uid)) {
            return true;
        }
        // Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService.
        // Not using #mLastOpScheduleExactAlarm as it may contain stale values.
        // No locking needed as all internal containers being queried are immutable.
@@ -3759,6 +3777,9 @@ public class AlarmManagerService extends SystemService {
                if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
                    continue;
                }
                if (hasUseExactAlarmPermission(changedPackage, uid)) {
                    continue;
                }
                final int appOpMode;
                synchronized (mLock) {
                    appOpMode = mLastOpScheduleExactAlarm.get(uid,
@@ -3778,7 +3799,8 @@ public class AlarmManagerService extends SystemService {
                }
                if (added) {
                    synchronized (mLock) {
                        removeExactAlarmsOnPermissionRevokedLocked(uid, changedPackage);
                        removeExactAlarmsOnPermissionRevokedLocked(uid,
                                changedPackage, /*killUid = */ true);
                    }
                } else {
                    sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
@@ -3794,7 +3816,7 @@ public class AlarmManagerService extends SystemService {
     * This is not expected to get called frequently.
     */
    @GuardedBy("mLock")
    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
    void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName, boolean killUid) {
        if (isExemptFromExactAlarmPermission(uid)
                || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) {
            return;
@@ -3805,7 +3827,7 @@ public class AlarmManagerService extends SystemService {
                && a.windowLength == 0);
        removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);

        if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
        if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
            PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
                    "schedule_exact_alarm revoked");
        }
@@ -4617,6 +4639,7 @@ public class AlarmManagerService extends SystemService {
        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
        public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
        public static final int TARE_AFFORDABILITY_CHANGED = 12;
        public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;

        AlarmHandler() {
            super(Looper.myLooper());
@@ -4715,10 +4738,11 @@ public class AlarmManagerService extends SystemService {
                    break;

                case REMOVE_EXACT_ALARMS:
                    final int uid = msg.arg1;
                    final String packageName = (String) msg.obj;
                    int uid = msg.arg1;
                    String packageName = (String) msg.obj;
                    synchronized (mLock) {
                        removeExactAlarmsOnPermissionRevokedLocked(uid, packageName);
                        removeExactAlarmsOnPermissionRevokedLocked(uid, packageName, /*killUid = */
                                true);
                    }
                    break;
                case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
@@ -4730,6 +4754,16 @@ public class AlarmManagerService extends SystemService {
                case REFRESH_EXACT_ALARM_CANDIDATES:
                    refreshExactAlarmCandidates();
                    break;
                case CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE:
                    packageName = (String) msg.obj;
                    uid = msg.arg1;
                    if (!hasScheduleExactAlarmInternal(packageName, uid)) {
                        synchronized (mLock) {
                            removeExactAlarmsOnPermissionRevokedLocked(uid,
                                    packageName, /*killUid = */false);
                        }
                    }
                    break;
                default:
                    // nope, just ignore it
                    break;
@@ -4914,6 +4948,12 @@ public class AlarmManagerService extends SystemService {
                        }
                        break;
                    case Intent.ACTION_PACKAGE_ADDED:
                        if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                            final String packageUpdated = intent.getData().getSchemeSpecificPart();
                            mHandler.obtainMessage(
                                    AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE, uid, -1,
                                    packageUpdated).sendToTarget();
                        }
                        mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES);
                        return;
                }
+1 −0
Original line number Diff line number Diff line
@@ -192,6 +192,7 @@ package android {
    field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
    field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
    field public static final String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC";
    field public static final String USE_EXACT_ALARM = "android.permission.USE_EXACT_ALARM";
    field @Deprecated public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
    field public static final String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT";
    field public static final String USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER";
+10 −0
Original line number Diff line number Diff line
@@ -4384,6 +4384,16 @@
    <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
        android:protectionLevel="normal|appop"/>

    <!-- Allows apps to use exact alarms just like with SCHEDULE_EXACT_ALARM but without needing
        to request this permission from the user.
        <p><b>This is only for apps that rely on exact alarms for their core functionality.</b>
        App stores may enforce policies to audit and review the use of this permission. Any app that
        requests this but is found to not require exact alarms for its primary function may be
        removed from the app store.
    -->
    <permission android:name="android.permission.USE_EXACT_ALARM"
                android:protectionLevel="normal"/>

    <!-- Allows an application to query tablet mode state and monitor changes
         in it.
         <p>Not for use by third-party applications.
+26 −8
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;

import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
@@ -124,6 +125,7 @@ import android.app.usage.UsageStatsManagerInternal;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.net.Uri;
@@ -389,6 +391,7 @@ public class AlarmManagerServiceTest {
                .mockStatic(LocalServices.class)
                .spyStatic(Looper.class)
                .mockStatic(MetricsHelper.class)
                .mockStatic(PermissionChecker.class)
                .mockStatic(PermissionManagerService.class)
                .mockStatic(ServiceManager.class)
                .mockStatic(Settings.Global.class)
@@ -445,6 +448,10 @@ public class AlarmManagerServiceTest {
        doReturn(true)
                .when(() -> DateFormat.is24HourFormat(eq(mMockContext), anyInt()));

        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
                () -> PermissionChecker.checkPermissionForPreflight(any(),
                        eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString()));

        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);

        registerAppIds(new String[]{TEST_CALLING_PACKAGE},
@@ -2158,6 +2165,7 @@ public class AlarmManagerServiceTest {

    @Test
    public void canScheduleExactAlarmsBinderCall() throws RemoteException {
        // Policy permission is denied in setUp().
        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);

        // No permission, no exemption.
@@ -2168,6 +2176,14 @@ public class AlarmManagerServiceTest {
        mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
        assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));

        // Policy permission only, no exemption.
        mockExactAlarmPermissionGrant(true, false, MODE_ERRORED);
        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
                () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext),
                        eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID),
                        eq(TEST_CALLING_PACKAGE)));
        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));

        // Permission, no exemption.
        mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT);
        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
@@ -2699,7 +2715,8 @@ public class AlarmManagerServiceTest {
        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);

        // No permission revoked.
        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString());
        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString(),
                anyBoolean());

        // Permission got granted only for (appId1, userId2).
        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -2754,14 +2771,14 @@ public class AlarmManagerServiceTest {

        // Permission got revoked only for (appId1, userId2)
        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]));
                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]));
                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
        verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(
                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]));
                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));

        verify(mService).removeExactAlarmsOnPermissionRevokedLocked(
                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]));
                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
    }

    @Test
@@ -2774,7 +2791,7 @@ public class AlarmManagerServiceTest {
        mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
        assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
        verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID,
                TEST_CALLING_PACKAGE);
                TEST_CALLING_PACKAGE, true);
    }

    @Test
@@ -2859,7 +2876,8 @@ public class AlarmManagerServiceTest {
                null);
        assertEquals(6, mService.mAlarmStore.size());

        mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE);
        mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE,
                true);

        final ArrayList<Alarm> remaining = mService.mAlarmStore.asList();
        assertEquals(3, remaining.size());
@@ -3080,7 +3098,7 @@ public class AlarmManagerServiceTest {
                SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters);

        final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED)
                .setPackage(TEST_CALLING_PACKAGE)
                .setData(Uri.fromParts("package", TEST_CALLING_PACKAGE, null))
                .putExtra(Intent.EXTRA_REPLACING, true);
        mPackageChangesReceiver.onReceive(mMockContext, packageAdded);