Merge "Add a metrics reason code for USE_EXACT_ALARM" into tm-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index f5167b7..2e6b8bd 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -88,6 +88,10 @@
      * Change wasn't enable for the caller due to compat reasons.
      */
     static final int EXACT_ALLOW_REASON_COMPAT = 2;
+    /**
+     * Caller had USE_EXACT_ALARM permission.
+     */
+    static final int EXACT_ALLOW_REASON_POLICY_PERMISSION = 3;
 
     public final int type;
     /**
@@ -275,6 +279,8 @@
                 return "compat";
             case EXACT_ALLOW_REASON_PERMISSION:
                 return "permission";
+            case EXACT_ALLOW_REASON_POLICY_PERMISSION:
+                return "policy_permission";
             case EXACT_ALLOW_REASON_NOT_APPLICABLE:
                 return "N/A";
             default:
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 4aa9e84..7baf805 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -46,6 +46,7 @@
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION;
+import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_POLICY_PERMISSION;
 import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.TARE_POLICY_INDEX;
 import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_ALARM_CANCELLED;
@@ -2698,8 +2699,7 @@
 
             // Make sure the caller is allowed to use the requested kind of alarm, and also
             // decide what quota and broadcast options to use.
-            boolean allowListed = false;    // For logging the reason.
-            boolean changeDisabled = false; // For logging the reason.
+            int exactAllowReason = EXACT_ALLOW_REASON_NOT_APPLICABLE;
             Bundle idleOptions = null;
             if ((flags & FLAG_PRIORITIZE) != 0) {
                 getContext().enforcePermission(
@@ -2721,57 +2721,56 @@
                         idleOptions = mOptsWithoutFgs.toBundle();
                     }
                 } else {
-                    changeDisabled = true;
                     needsPermission = false;
                     lowerQuota = allowWhileIdle;
                     idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
-                }
-                if (needsPermission && !hasScheduleExactAlarmInternal(callingPackage, callingUid)
-                        && !hasUseExactAlarmInternal(callingPackage, callingUid)) {
-                    if (!isExemptFromExactAlarmPermissionNoLock(callingUid)) {
-                        final String errorMessage = "Caller " + callingPackage + " needs to hold "
-                                + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
-                                + "exact alarms.";
-                        if (mConstants.CRASH_NON_CLOCK_APPS) {
-                            throw new SecurityException(errorMessage);
-                        } else {
-                            Slog.wtf(TAG, errorMessage);
-                        }
-                    } else {
-                        allowListed = true;
+                    if (exact) {
+                        exactAllowReason = EXACT_ALLOW_REASON_COMPAT;
                     }
-                    // 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;
+                }
+                if (needsPermission) {
+                    if (hasUseExactAlarmInternal(callingPackage, callingUid)) {
+                        exactAllowReason = EXACT_ALLOW_REASON_POLICY_PERMISSION;
+                    } else if (hasScheduleExactAlarmInternal(callingPackage, callingUid)) {
+                        exactAllowReason = EXACT_ALLOW_REASON_PERMISSION;
+                    } else {
+                        if (isExemptFromExactAlarmPermissionNoLock(callingUid)) {
+                            exactAllowReason = EXACT_ALLOW_REASON_ALLOW_LIST;
+                        } else {
+                            final String errorMessage =
+                                    "Caller " + callingPackage + " needs to hold "
+                                            + Manifest.permission.SCHEDULE_EXACT_ALARM + " or "
+                                            + Manifest.permission.USE_EXACT_ALARM + " to set "
+                                            + "exact alarms.";
+                            if (mConstants.CRASH_NON_CLOCK_APPS) {
+                                throw new SecurityException(errorMessage);
+                            } else {
+                                Slog.wtf(TAG, errorMessage);
+                            }
+                        }
+                        // 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;
+                    }
                 }
                 if (lowerQuota) {
                     flags &= ~FLAG_ALLOW_WHILE_IDLE;
                     flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
                 }
             }
-            final int exactAllowReason;
             if (exact) {
                 // If this is an exact time alarm, then it can't be batched with other alarms.
                 flags |= AlarmManager.FLAG_STANDALONE;
 
-                if (changeDisabled) {
-                    exactAllowReason = EXACT_ALLOW_REASON_COMPAT;
-                } else if (allowListed) {
-                    exactAllowReason = EXACT_ALLOW_REASON_ALLOW_LIST;
-                } else {
-                    exactAllowReason = EXACT_ALLOW_REASON_PERMISSION;
-                }
-            } else {
-                exactAllowReason = EXACT_ALLOW_REASON_NOT_APPLICABLE;
             }
 
             setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index 4c2f8d1..75ed616 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__CHANGE_DISABLED;
 import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__NOT_APPLICABLE;
 import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__POLICY_PERMISSION;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 
 import android.app.ActivityManager;
@@ -89,6 +90,8 @@
                 return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__PERMISSION;
             case Alarm.EXACT_ALLOW_REASON_COMPAT:
                 return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__CHANGE_DISABLED;
+            case Alarm.EXACT_ALLOW_REASON_POLICY_PERMISSION:
+                return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__POLICY_PERMISSION;
             default:
                 return ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__NOT_APPLICABLE;
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 17b4226..cdc9d71 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -56,6 +56,7 @@
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION;
+import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_POLICY_PERMISSION;
 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;
@@ -2337,7 +2338,7 @@
     }
 
     @Test
-    public void alarmClockBinderCall() throws RemoteException {
+    public void alarmClockBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
@@ -2362,6 +2363,34 @@
         assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
     }
 
+    @Test
+    public void alarmClockBinderCallWithUEAPermission() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+
+        mockUseExactAlarmState(true);
+        mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+
+        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(mService).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
+        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(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE),
+                bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_POLICY_PERMISSION));
+
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
     private void mockScheduleExactAlarmState(boolean declared, boolean denyList, int mode) {
         String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
         when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
@@ -2410,7 +2439,7 @@
     }
 
     @Test
-    public void exactBinderCallWithPermission() throws RemoteException {
+    public void exactBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
@@ -2434,6 +2463,32 @@
     }
 
     @Test
+    public void exactBinderCallWithUEAPermission() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+
+        mockUseExactAlarmState(true);
+        mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                0, alarmPi, null, null, null, null);
+
+        verify(mService).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
+        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_STANDALONE), isNull(), isNull(),
+                eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(),
+                eq(EXACT_ALLOW_REASON_POLICY_PERMISSION));
+
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
     public void exactBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.
@@ -2454,7 +2509,7 @@
     }
 
     @Test
-    public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException {
+    public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
 
         mockScheduleExactAlarmState(true, false, MODE_ALLOWED);
@@ -2462,6 +2517,7 @@
         mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
                 FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
 
+        verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
         verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
 
         final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -2477,6 +2533,32 @@
     }
 
     @Test
+    public void exactAllowWhileIdleBinderCallWithUEAPermission() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+
+        mockUseExactAlarmState(true);
+        mockScheduleExactAlarmState(false, false, MODE_ERRORED);
+        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(mService).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
+        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(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(),
+                eq(EXACT_ALLOW_REASON_POLICY_PERMISSION));
+
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
     public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
         mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
         // If permission is denied, only then allowlist will be checked.