Add a metrics reason code for USE_EXACT_ALARM

Updating the reason code logging to better differentiate between usage
of both the permissions.
The only functional changes expected from this change are the values of
exactAllowReason in an alarm:

- Prioritized alarms change from "Permission" to "Not applicable"
- When crashing is turned off, reason code changes from "Permission" to
  "Not applicable"
- When the caller has USE_EXACT_ALARM, reason code changes from
  "Permission" to "Policy Permission".

Also updating the error message to include USE_EXACT_ALARM.

Test: atest FrameworksMockingServicesTests:AlarmManagerServiceTest
atest CtsStatsdAtomHostTestCases:AlarmStatsTest

Existing tests should pass:
atest CtsAlarmManagerTestCases

Manually test output of `./out/host/linux-x86/bin/statsd_testdrive 368`
and `adb shell dumpsys alarm`

Bug: 231661615
Change-Id: I13239c300cfb468e7fc2df21fccb494fdf8c80ac
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.