Send a broadcast when an app is granted SCHEDULE_EXACT_ALARM

Bug: 187206399
Test: atest AlarmManagerServiceTest
Test: Manual test with a test app.
- Grant the permission to test app and make sure it can start FGS.
Log:
```
05-07 10:12:08.671  1000  1579  9475 I ActivityManager: Background started FGS: Allowed [callingPackage: com.google.omakoto.testapp; callingUid: 10294; uidState: RCVR; intent: Intent { act=fgs cmp=com.google.omakoto.testapp/.MyFgs }; code:(unknown:207); tempAllowListReason:<broadcast:1000:android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,reason:,reasonCode:(unknown:207),duration:10000,callingUid:1000>; targetSdkVersion:30; callerTargetSdkVersion:30; startForegroundCount:0]
```
- Grant the permission to a different app and make sure the test app
  won't receive the broadcast.

Test: CTS -- Incoming

Change-Id: Iefe3e12dcf51318d8433532ba3048caa69b1edcd
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 01f31e4..b096537 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -138,6 +139,36 @@
     public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED =
             "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
 
+    /**
+     * Broadcast Action: An app is granted the
+     * {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} permission.
+     *
+     * <p>When the user revokes the {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+     * permission, all alarms scheduled with
+     * {@link #setExact}, {@link #setExactAndAllowWhileIdle} and
+     * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will be deleted.
+     *
+     * <p>When the user grants the {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM},
+     * this broadcast will be sent. Applications can reschedule all the necessary alarms when
+     * receiving it.
+     *
+     * <p><em>Note:</em>
+     * Applications are still required to check {@link #canScheduleExactAlarms()}
+     * before using the above APIs after receiving this broadcast,
+     * because it's possible that the permission is already revoked again by the time
+     * applications receive this broadcast.
+     *
+     * <p>This broadcast will be sent to both runtime receivers and manifest receivers.
+     *
+     * <p>This broadcast is sent as a foreground broadcast.
+     * See {@link android.content.Intent#FLAG_RECEIVER_FOREGROUND}.
+     *
+     * <p>When an application receives this broadcast, it's allowed to start a foreground service.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED =
+            "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
+
     /** @hide */
     @UnsupportedAppUsage
     public static final long WINDOW_EXACT = 0;
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index b62ece6..9dd1296 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -251,6 +251,12 @@
      * @hide
      */
     public static final int REASON_LOCALE_CHANGED = 206;
+    /**
+     * Broadcast
+     * {@link android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED}
+     * @hide
+     */
+    public static final int REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
     /* Reason code range 300-399 are reserved for other internal reasons */
     /**
      * Device idle system allow list, including EXCEPT-IDLE
@@ -386,6 +392,7 @@
             REASON_TIMEZONE_CHANGED,
             REASON_TIME_CHANGED,
             REASON_LOCALE_CHANGED,
+            REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
             REASON_SYSTEM_ALLOW_LISTED,
             REASON_ALARM_MANAGER_ALARM_CLOCK,
             REASON_ALARM_MANAGER_WHILE_IDLE,
@@ -664,6 +671,8 @@
                 return "TIME_CHANGED";
             case REASON_LOCALE_CHANGED:
                 return "LOCALE_CHANGED";
+            case REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
+                return "REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
             case REASON_SYSTEM_ALLOW_LISTED:
                 return "SYSTEM_ALLOW_LISTED";
             case REASON_ALARM_MANAGER_ALARM_CLOCK:
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 b3396c5..0eb2609 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -30,6 +30,7 @@
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.PowerWhitelistManager.REASON_ALARM_MANAGER_WHILE_IDLE;
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -1704,6 +1705,11 @@
                                 if (!hasScheduleExactAlarmInternal(packageName, uid)) {
                                     mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
                                             uid, 0, packageName).sendToTarget();
+                                } else {
+                                    // TODO(b/187206399) Make sure this won't be sent, if the app
+                                    // already had the appop previously.
+                                    sendScheduleExactAlarmPermissionStateChangedBroadcast(
+                                            packageName, UserHandle.getUserId(uid));
                                 }
                             }
                         });
@@ -4816,6 +4822,30 @@
         }
     }
 
+    /**
+     * Send {@link AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED} to
+     * the app that is just granted the permission.
+     */
+    private void sendScheduleExactAlarmPermissionStateChangedBroadcast(
+            String packageName, int userId) {
+        final Intent i = new Intent(
+                AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED);
+        i.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        i.setPackage(packageName);
+
+        // We need to allow the app to start a foreground service.
+        // This broadcast is very rare, so we do not cache the BroadcastOptions.
+        final BroadcastOptions opts = BroadcastOptions.makeBasic();
+        opts.setTemporaryAppAllowlist(
+                mActivityManagerInternal.getBootTimeTempAllowListDuration(),
+                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, "");
+        getContext().sendBroadcastAsUser(i, UserHandle.of(userId), /*permission*/ null,
+                opts.toBundle());
+    }
+
     private void decrementAlarmCount(int uid, int decrement) {
         int oldCount = 0;
         final int uidIndex = mAlarmsPerUid.indexOfKey(uid);
diff --git a/core/api/current.txt b/core/api/current.txt
index de92fd1..5f3eeb1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4404,6 +4404,7 @@
     method public void setWindow(int, long, long, android.app.PendingIntent);
     method public void setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     field public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
+    field public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
     field public static final int ELAPSED_REALTIME = 3; // 0x3
     field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
     field public static final long INTERVAL_DAY = 86400000L; // 0x5265c00L
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4e7dd91..ffb8d1d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -696,6 +696,8 @@
     <protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
     <protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
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 7234281..5222511 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -2403,6 +2403,34 @@
     }
 
     @Test
+    public void opScheduleExactAlarmGranted() throws Exception {
+        final long durationMs = 20000L;
+        when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
+
+        mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
+        mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+
+        verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.SYSTEM),
+                isNull(), bundleCaptor.capture());
+
+        // Validate the intent.
+        assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
+                intentCaptor.getValue().getAction());
+        assertEquals(TEST_CALLING_PACKAGE, intentCaptor.getValue().getPackage());
+
+        // Validate the options.
+        final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                bOptions.getTemporaryAppAllowlistType());
+        assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration());
+        assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
+                bOptions.getTemporaryAppAllowlistReasonCode());
+    }
+
+    @Test
     public void removeExactAlarmsOnPermissionRevoked() {
         doReturn(true).when(
                 () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),