summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2021-05-14 21:15:53 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-05-14 21:15:53 +0000
commit60b32709f0b9cedf4d22c55dd371e944effdf79d (patch)
treeb0e9579772f8ba05705a1a29056a99d9905584e6
parent9e22972568f11a7741d2afc6952703d736e6d218 (diff)
parent0d3bf483bc8f288a1c9d231ac1980c0a203322f6 (diff)
Merge "Relaxing minimum alarm window requirements" into sc-dev
-rw-r--r--apex/jobscheduler/framework/java/android/app/AlarmManager.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java55
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java34
3 files changed, 73 insertions, 30 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 8b824e8a9a72..9fa7810ab155 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -532,9 +532,10 @@ public class AlarmManager {
* modest timeliness requirements for its alarms.
*
* <p>
- * 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.
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
*
* <p>
* This method can also be used to achieve strict ordering guarantees among
@@ -588,9 +589,10 @@ public class AlarmManager {
* if {@code null} is passed as the {@code targetHandler} parameter.
*
* <p>
- * 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.
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
*
* @see #setWindow(int, long, long, PendingIntent)
*/
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 0eb260986472..5365218203d9 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -530,8 +530,7 @@ 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;
- // TODO (b/185199076): Tune based on breakage reports.
- private static final long DEFAULT_MIN_WINDOW = 30 * 60 * 1000;
+ private static final long DEFAULT_MIN_WINDOW = 10 * 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;
@@ -1147,6 +1146,18 @@ public class AlarmManagerService extends SystemService {
return when;
}
+ /**
+ * This is the minimum window that can be requested for the given alarm. Windows smaller than
+ * this value will be elongated to match it.
+ * Current heuristic is similar to {@link #maxTriggerTime(long, long, long)}, the minimum
+ * allowed window is either {@link Constants#MIN_WINDOW} or 75% of the alarm's futurity,
+ * whichever is smaller.
+ */
+ long getMinimumAllowedWindow(long nowElapsed, long triggerElapsed) {
+ final long futurity = triggerElapsed - nowElapsed;
+ return Math.min((long) (futurity * 0.75), mConstants.MIN_WINDOW);
+ }
+
// Apply a heuristic to { recurrence interval, futurity of the trigger time } to
// calculate the end of our nominal delivery window for the alarm.
static long maxTriggerTime(long now, long triggerAtTime, long interval) {
@@ -1833,25 +1844,6 @@ public class AlarmManagerService extends SystemService {
}
}
- // Snap the window to reasonable limits.
- if (windowLength > INTERVAL_DAY) {
- Slog.w(TAG, "Window length " + windowLength
- + "ms suspiciously long; limiting to 1 day");
- windowLength = INTERVAL_DAY;
- } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW
- && (flags & FLAG_PRIORITIZE) == 0) {
- 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
// seconds when the API expects milliseconds, or apps trying shenanigans
// around intentional period overflow, etc.
@@ -1883,7 +1875,7 @@ public class AlarmManagerService extends SystemService {
// Try to prevent spamming by making sure apps aren't firing alarms in the immediate future
final long minTrigger = nowElapsed
+ (UserHandle.isCore(callingUid) ? 0L : mConstants.MIN_FUTURITY);
- final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger;
+ final long triggerElapsed = Math.max(minTrigger, nominalTrigger);
final long maxElapsed;
if (windowLength == 0) {
@@ -1893,6 +1885,25 @@ public class AlarmManagerService extends SystemService {
// Fix this window in place, so that as time approaches we don't collapse it.
windowLength = maxElapsed - triggerElapsed;
} else {
+ // The window was explicitly requested. Snap it to allowable limits.
+ final long minAllowedWindow = getMinimumAllowedWindow(nowElapsed, triggerElapsed);
+ if (windowLength > INTERVAL_DAY) {
+ Slog.w(TAG, "Window length " + windowLength + "ms too long; limiting to 1 day");
+ windowLength = INTERVAL_DAY;
+ } else if ((flags & FLAG_PRIORITIZE) == 0 && windowLength < minAllowedWindow) {
+ // Prioritized alarms are exempt from minimum window limits.
+ 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 "
+ + minAllowedWindow + "ms.");
+ windowLength = minAllowedWindow;
+ } else {
+ // TODO (b/185199076): Remove temporary log to catch breaking apps.
+ Slog.wtf(TAG, "Short window " + windowLength + "ms specified by "
+ + callingPackage);
+ }
+ }
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 5222511cc479..d5e1cd6e0415 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -175,6 +175,7 @@ import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.LongConsumer;
@@ -2269,18 +2270,47 @@ public class AlarmManagerServiceTest {
() -> CompatChanges.isChangeEnabled(
eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
anyString(), any(UserHandle.class)));
- final long minWindow = 73;
+ final int minWindow = 73;
setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
+ final Random random = new Random(42);
+
// 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);
+ final long futurity = random.nextInt(minWindow);
+
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0,
+ TEST_CALLING_UID, null);
+
+ final long minAllowed = (long) (futurity * 0.75); // This will always be <= minWindow.
+
+ assertEquals(1, mService.mAlarmStore.size());
+ final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+ assertEquals(Math.max(minAllowed, window), a.windowLength);
+ }
+
+ for (int window = 1; window <= minWindow; window++) {
+ final PendingIntent pi = getNewMockPendingIntent();
+ final long futurity = 2 * minWindow + window; // implies (0.75 * futurity) > minWindow
+
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0,
+ TEST_CALLING_UID, null);
assertEquals(1, mService.mAlarmStore.size());
final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
assertEquals(minWindow, a.windowLength);
}
+
+ for (int i = 0; i < 20; i++) {
+ final long window = minWindow + random.nextInt(100);
+ 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