Exempt some broadcasts from FGS start restriction.

1. Exempt broadcasts ACTION_TIMEZONE_CHANGED, ACTION_TIME_CHANGED, ACTION_LOCALE_CHANGED
from FGS start restriction.
2. Add test case AlarmManagerServiceTest#setTimeZoneImpl.

Bug: 184111171
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
Change-Id: I80ce830d1bd98d69f35fdc6bcc041de173083fba
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index f96fc83..b62ece6 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -236,7 +236,21 @@
      * @hide
      */
     public static final int REASON_BLUETOOTH_BROADCAST = 203;
-
+    /**
+     * Broadcast {@link android.content.Intent#ACTION_TIMEZONE_CHANGED}
+     * @hide
+     */
+    public static final int REASON_TIMEZONE_CHANGED = 204;
+    /**
+     * Broadcast {@link android.content.Intent#ACTION_TIME_CHANGED}
+     * @hide
+     */
+    public static final int REASON_TIME_CHANGED = 205;
+    /**
+     * Broadcast {@link android.content.Intent#ACTION_LOCALE_CHANGED}
+     * @hide
+     */
+    public static final int REASON_LOCALE_CHANGED = 206;
     /* Reason code range 300-399 are reserved for other internal reasons */
     /**
      * Device idle system allow list, including EXCEPT-IDLE
@@ -369,6 +383,9 @@
             REASON_PRE_BOOT_COMPLETED,
             REASON_LOCKED_BOOT_COMPLETED,
             REASON_BLUETOOTH_BROADCAST,
+            REASON_TIMEZONE_CHANGED,
+            REASON_TIME_CHANGED,
+            REASON_LOCALE_CHANGED,
             REASON_SYSTEM_ALLOW_LISTED,
             REASON_ALARM_MANAGER_ALARM_CLOCK,
             REASON_ALARM_MANAGER_WHILE_IDLE,
@@ -641,6 +658,12 @@
                 return "LOCKED_BOOT_COMPLETED";
             case REASON_BLUETOOTH_BROADCAST:
                 return "BLUETOOTH_BROADCAST";
+            case REASON_TIMEZONE_CHANGED:
+                return "TIMEZONE_CHANGED";
+            case REASON_TIME_CHANGED:
+                return "TIME_CHANGED";
+            case REASON_LOCALE_CHANGED:
+                return "LOCALE_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 0e36275..d035341 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.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;
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
@@ -81,6 +82,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelableException;
+import android.os.PowerExemptionManager;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -293,6 +295,7 @@
 
     BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
     BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
+    BroadcastOptions mOptsTimeBroadcast = BroadcastOptions.makeBasic();
 
     // TODO(b/172085676): Move inside alarm store.
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -1745,7 +1748,12 @@
                     | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                     | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             intent.putExtra(Intent.EXTRA_TIMEZONE, zone.getID());
-            getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+            mOptsTimeBroadcast.setTemporaryAppAllowlist(
+                    mActivityManagerInternal.getBootTimeTempAllowListDuration(),
+                    TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                    PowerExemptionManager.REASON_TIMEZONE_CHANGED, "");
+            getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                    null /* receiverPermission */, mOptsTimeBroadcast.toBundle());
         }
     }
 
@@ -3899,8 +3907,12 @@
                                 | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-                        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
-
+                        mOptsTimeBroadcast.setTemporaryAppAllowlist(
+                                mActivityManagerInternal.getBootTimeTempAllowListDuration(),
+                                TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                                PowerExemptionManager.REASON_TIME_CHANGED, "");
+                        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                                null /* receiverPermission */, mOptsTimeBroadcast.toBundle());
                         // The world has changed on us, so we need to re-evaluate alarms
                         // regardless of whether the kernel has told us one went off.
                         result |= IS_WAKEUP_MASK;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9aedf15..8001cfc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15831,8 +15831,12 @@
                     if (initLocale || !mProcessesReady) {
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                     }
+                    final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
+                    bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
+                            TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                            PowerExemptionManager.REASON_LOCALE_CHANGED, "");
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
-                            null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                            null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, SYSTEM_UID,
                             Binder.getCallingUid(), Binder.getCallingPid(),
                             UserHandle.USER_ALL);
                 }
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 64dad7f..9f2a1c0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -127,9 +127,11 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerExemptionManager;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -367,6 +369,7 @@
                 .mockStatic(MetricsHelper.class)
                 .mockStatic(Settings.Global.class)
                 .mockStatic(ServiceManager.class)
+                .mockStatic(SystemProperties.class)
                 .spyStatic(UserHandle.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
@@ -2536,6 +2539,24 @@
         verify(() -> MetricsHelper.pushAlarmBatchDelivered(10, 5));
     }
 
+    @Test
+    public void setTimeZoneImpl() {
+        final long durationMs = 20000L;
+        when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
+        mService.setTimeZoneImpl("UTC");
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL),
+                isNull(), bundleCaptor.capture());
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, intentCaptor.getValue().getAction());
+        final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
+        assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration());
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                bOptions.getTemporaryAppAllowlistType());
+        assertEquals(PowerExemptionManager.REASON_TIMEZONE_CHANGED,
+                bOptions.getTemporaryAppAllowlistReasonCode());
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {