diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 01f31e43989ec..b096537821caa 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.NonNull; 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 class AlarmManager { 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. + * + *
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. + * + *
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. + * + *
Note: + * 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. + * + *
This broadcast will be sent to both runtime receivers and manifest receivers. + * + *
This broadcast is sent as a foreground broadcast. + * See {@link android.content.Intent#FLAG_RECEIVER_FOREGROUND}. + * + *
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 b62ece6759c60..9dd1296d681c0 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -251,6 +251,12 @@ public class PowerExemptionManager {
* @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 @@ public class PowerExemptionManager {
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 @@ public class PowerExemptionManager {
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 b3396c5c827c7..0eb260986472a 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.INTERVAL_HOUR;
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 @@ public class AlarmManagerService extends SystemService {
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 @@ public class AlarmManagerService extends SystemService {
}
}
+ /**
+ * 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 de92fd1af6ffe..5f3eeb1ed1911 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4404,6 +4404,7 @@ package android.app {
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 4e7dd913b260f..ffb8d1daac906 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -696,6 +696,8 @@