diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index b1b8fba78ab9d..6e05bc0cf4d96 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -136,25 +136,59 @@ public class AppStandbyController implements AppStandbyInternal { private static final long ONE_HOUR = ONE_MINUTE * 60; private static final long ONE_DAY = ONE_HOUR * 24; - static final long[] SCREEN_TIME_THRESHOLDS = { + /** + * The minimum amount of time the screen must have been on before an app can time out from its + * current bucket to the next bucket. + */ + private static final long[] SCREEN_TIME_THRESHOLDS = { 0, 0, - COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR, - COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR + COMPRESS_TIME ? 2 * ONE_MINUTE : 1 * ONE_HOUR, + COMPRESS_TIME ? 4 * ONE_MINUTE : 2 * ONE_HOUR, + COMPRESS_TIME ? 8 * ONE_MINUTE : 6 * ONE_HOUR }; - static final long[] ELAPSED_TIME_THRESHOLDS = { + /** The minimum allowed values for each index in {@link #SCREEN_TIME_THRESHOLDS}. */ + private static final long[] MINIMUM_SCREEN_TIME_THRESHOLDS = COMPRESS_TIME + ? new long[SCREEN_TIME_THRESHOLDS.length] + : new long[]{ + 0, + 0, + 0, + 30 * ONE_MINUTE, + ONE_HOUR + }; + + /** + * The minimum amount of elapsed time that must have passed before an app can time out from its + * current bucket to the next bucket. + */ + private static final long[] ELAPSED_TIME_THRESHOLDS = { 0, COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR, COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR, - COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR + COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR, + // TODO(149050681): increase timeout to 30+ days + COMPRESS_TIME ? 32 * ONE_MINUTE : 4 * ONE_DAY }; - static final int[] THRESHOLD_BUCKETS = { + /** The minimum allowed values for each index in {@link #ELAPSED_TIME_THRESHOLDS}. */ + private static final long[] MINIMUM_ELAPSED_TIME_THRESHOLDS = COMPRESS_TIME + ? new long[ELAPSED_TIME_THRESHOLDS.length] + : new long[]{ + 0, + ONE_HOUR, + ONE_HOUR, + 2 * ONE_HOUR, + 4 * ONE_DAY + }; + + private static final int[] THRESHOLD_BUCKETS = { STANDBY_BUCKET_ACTIVE, STANDBY_BUCKET_WORKING_SET, STANDBY_BUCKET_FREQUENT, - STANDBY_BUCKET_RARE + STANDBY_BUCKET_RARE, + STANDBY_BUCKET_RESTRICTED }; /** Default expiration time for bucket prediction. After this, use thresholds to downgrade. */ @@ -204,7 +238,15 @@ public class AppStandbyController implements AppStandbyInternal { static final int MSG_REPORT_EXEMPTED_SYNC_START = 13; long mCheckIdleIntervalMillis; + /** + * The minimum amount of time the screen must have been on before an app can time out from its + * current bucket to the next bucket. + */ long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS; + /** + * The minimum amount of elapsed time that must have passed before an app can time out from its + * current bucket to the next bucket. + */ long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS; /** Minimum time a strong usage event should keep the bucket elevated. */ long mStrongUsageTimeoutMillis; @@ -1147,9 +1189,11 @@ public class AppStandbyController implements AppStandbyInternal { final boolean isForcedByUser = (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER; - // If the current bucket is RESTRICTED, only user force or usage should bring it out. + // If the current bucket is RESTRICTED, only user force or usage should bring it out, + // unless the app was put into the bucket due to timing out. if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason) - && !isForcedByUser) { + && !isForcedByUser + && (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_TIMEOUT) { return; } @@ -1829,12 +1873,12 @@ public class AppStandbyController implements AppStandbyInternal { String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null); mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue, - SCREEN_TIME_THRESHOLDS); + SCREEN_TIME_THRESHOLDS, MINIMUM_SCREEN_TIME_THRESHOLDS); String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null); mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue, - ELAPSED_TIME_THRESHOLDS); + ELAPSED_TIME_THRESHOLDS, MINIMUM_ELAPSED_TIME_THRESHOLDS); mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4, COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours mStrongUsageTimeoutMillis = mParser.getDurationMillis( @@ -1870,8 +1914,8 @@ public class AppStandbyController implements AppStandbyInternal { mUnexemptedSyncScheduledTimeoutMillis = mParser.getDurationMillis( KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION, - COMPRESS_TIME ? ONE_MINUTE - : DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT); // TODO + COMPRESS_TIME + ? ONE_MINUTE : DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT); mSystemInteractionTimeoutMillis = mParser.getDurationMillis( KEY_SYSTEM_INTERACTION_HOLD_DURATION, @@ -1888,7 +1932,7 @@ public class AppStandbyController implements AppStandbyInternal { setAppIdleEnabled(mInjector.isAppIdleEnabled()); } - long[] parseLongArray(String values, long[] defaults) { + long[] parseLongArray(String values, long[] defaults, long[] minValues) { if (values == null) return defaults; if (values.isEmpty()) { // Reset to defaults @@ -1896,13 +1940,19 @@ public class AppStandbyController implements AppStandbyInternal { } else { String[] thresholds = values.split("/"); if (thresholds.length == THRESHOLD_BUCKETS.length) { + if (minValues.length != THRESHOLD_BUCKETS.length) { + Slog.wtf(TAG, "minValues array is the wrong size"); + // Use zeroes as the minimums. + minValues = new long[THRESHOLD_BUCKETS.length]; + } long[] array = new long[THRESHOLD_BUCKETS.length]; for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) { try { if (thresholds[i].startsWith("P") || thresholds[i].startsWith("p")) { - array[i] = Duration.parse(thresholds[i]).toMillis(); + array[i] = Math.max(minValues[i], + Duration.parse(thresholds[i]).toMillis()); } else { - array[i] = Long.parseLong(thresholds[i]); + array[i] = Math.max(minValues[i], Long.parseLong(thresholds[i])); } } catch (NumberFormatException|DateTimeParseException e) { return defaults; diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 03dc21370e24d..290683ddb7a13 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -115,6 +115,7 @@ public class AppStandbyControllerTests { private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS; private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS; private static final long RARE_THRESHOLD = 48 * HOUR_MS; + private static final long RESTRICTED_THRESHOLD = 96 * HOUR_MS; /** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */ private static boolean isPackageInstalled = true; @@ -232,9 +233,8 @@ public class AppStandbyControllerTests { @Override String getAppIdleSettings() { return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/" - + WORKING_SET_THRESHOLD + "/" - + FREQUENT_THRESHOLD + "/" - + RARE_THRESHOLD; + + WORKING_SET_THRESHOLD + "/" + FREQUENT_THRESHOLD + "/" + RARE_THRESHOLD + + "/" + RESTRICTED_THRESHOLD; } @Override @@ -372,12 +372,15 @@ public class AppStandbyControllerTests { // RARE bucket assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_RARE); + // RESTRICTED bucket + assertTimeout(mController, RESTRICTED_THRESHOLD + 1, STANDBY_BUCKET_RESTRICTED); + reportEvent(mController, USER_INTERACTION, RARE_THRESHOLD + 1, PACKAGE_1); assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_ACTIVE); - // RARE bucket - assertTimeout(mController, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE); + // RESTRICTED bucket + assertTimeout(mController, RESTRICTED_THRESHOLD * 2 + 2, STANDBY_BUCKET_RESTRICTED); } @Test @@ -437,7 +440,7 @@ public class AppStandbyControllerTests { assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); mInjector.setDisplayOn(true); - assertTimeout(mController, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE); + assertTimeout(mController, RARE_THRESHOLD + 2 * HOUR_MS + 1, STANDBY_BUCKET_RARE); } @Test @@ -642,7 +645,7 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_FREQUENT); // Way past prediction timeout, use system thresholds - mInjector.mElapsedRealtime = RARE_THRESHOLD * 4; + mInjector.mElapsedRealtime = RARE_THRESHOLD; mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_RARE); } @@ -669,7 +672,7 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_RESTRICTED); // Way past all timeouts. Make sure timeout processing doesn't raise bucket. - mInjector.mElapsedRealtime += RARE_THRESHOLD * 4; + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_RESTRICTED); } @@ -696,6 +699,22 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_RESTRICTED); } + @Test + public void testPredictionRaiseFromRestrictedTimeout() { + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + + // Way past all timeouts. App times out into RESTRICTED bucket. + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + // Since the app timed out into RESTRICTED, prediction should be able to remove from the + // bucket. + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, + REASON_MAIN_PREDICTED); + } + @Test public void testCascadingTimeouts() throws Exception { reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);