diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 88f3df8fa2c4b..9ea6f7946fcff 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -208,10 +208,10 @@ public class AlarmManager { public static final int FLAG_PRIORITIZE = 1 << 6; /** - * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs - * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and - * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new - * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} + * For apps targeting {@link Build.VERSION_CODES#S} or above, any APIs setting exact alarms, + * e.g. {@link #setExact(int, long, PendingIntent)}, + * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} and others will require holding a new + * permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} * * @hide */ @@ -219,6 +219,21 @@ public class AlarmManager { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L; + /** + * For apps targeting {@link Build.VERSION_CODES#S} or above, all inexact alarms will require + * to have a minimum window size, expected to be on the order of a few minutes. + * + * Practically, any alarms requiring smaller windows are the same as exact alarms and should use + * the corresponding APIs provided, like {@link #setExact(int, long, PendingIntent)}, et al. + * + * Inexact alarm with shorter windows specified will have their windows elongated by the system. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + public static final long ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS = 185199076L; + @UnsupportedAppUsage private final IAlarmManager mService; private final Context mContext; @@ -483,6 +498,11 @@ public class AlarmManager { * modest timeliness requirements for its alarms. * *
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, the system will ensure that the window + * specified is at least a few minutes, as smaller windows are considered practically exact + * and should use the other APIs provided for exact alarms. + * + *
* This method can also be used to achieve strict ordering guarantees among * multiple alarms by ensuring that the windows requested for each alarm do * not intersect. @@ -532,6 +552,13 @@ public class AlarmManager { * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be * invoked via the specified target Handler, or on the application's main looper * if {@code null} is passed as the {@code targetHandler} parameter. + * + *
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, the system will ensure that the window + * specified is at least a few minutes, as smaller windows are considered practically exact + * and should use the other APIs provided for exact alarms. + * + * @see #setWindow(int, long, long, PendingIntent) */ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, String tag, OnAlarmListener listener, Handler targetHandler) { 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 7a3614172dff9..31da2017bdefc 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -452,7 +452,8 @@ public class AlarmManagerService extends SystemService { private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_MAX_INTERVAL = 365 * INTERVAL_DAY; - private static final long DEFAULT_MIN_WINDOW = 10_000; + // TODO (b/185199076): Tune based on breakage reports. + private static final long DEFAULT_MIN_WINDOW = 30 * 60 * 1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000; private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000; private static final int DEFAULT_MAX_ALARMS_PER_UID = 500; @@ -1688,12 +1689,22 @@ public class AlarmManagerService extends SystemService { windowLength = AlarmManager.WINDOW_EXACT; } - // Sanity check the window length. This will catch people mistakenly - // trying to pass an end-of-window timestamp rather than a duration. - if (windowLength > AlarmManager.INTERVAL_HALF_DAY) { + // Snap the window to reasonable limits. + if (windowLength > INTERVAL_DAY) { Slog.w(TAG, "Window length " + windowLength - + "ms suspiciously long; limiting to 1 hour"); - windowLength = AlarmManager.INTERVAL_HOUR; + + "ms suspiciously long; limiting to 1 day"); + windowLength = INTERVAL_DAY; + } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW) { + if (CompatChanges.isChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, + callingPackage, UserHandle.getUserHandleForUid(callingUid))) { + Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to " + + mConstants.MIN_WINDOW + "ms."); + windowLength = mConstants.MIN_WINDOW; + } else { + // TODO (b/185199076): Remove log once we have some data about what apps will break + Slog.wtf(TAG, "Short window " + windowLength + "ms specified by " + + callingPackage); + } } // Sanity check the recurrence interval. This will catch people who supply @@ -1737,7 +1748,6 @@ public class AlarmManagerService extends SystemService { // Fix this window in place, so that as time approaches we don't collapse it. windowLength = maxElapsed - triggerElapsed; } else { - windowLength = Math.max(windowLength, mConstants.MIN_WINDOW); maxElapsed = triggerElapsed + windowLength; } synchronized (mLock) { 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 edfc21d477673..7654093f9e7ec 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2223,7 +2223,11 @@ public class AlarmManagerServiceTest { } @Test - public void minWindow() { + public void minWindowChangeEnabled() { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled( + eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), + anyString(), any(UserHandle.class))); final long minWindow = 73; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); @@ -2238,6 +2242,26 @@ public class AlarmManagerServiceTest { } } + @Test + public void minWindowChangeDisabled() { + doReturn(false).when( + () -> CompatChanges.isChangeEnabled( + eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), + anyString(), any(UserHandle.class))); + final long minWindow = 73; + setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); + + // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. + for (int window = 1; window <= minWindow; window++) { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null); + + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(window, a.windowLength); + } + } + @Test public void denyListPackagesAdded() { mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});