Let apps timeout into the RESTRICTED bucket.

Let apps that haven't been used in 4 days time out into the RESTRICTED
bucket (after being in the RARE bucket). Apps that timed out into the
RESTRICTED bucket can be pulled out of the bucket via prediction.

Bug: 145551233
Test: atest FrameworksServicesTests:AppStandbyControllerTests
Change-Id: I646b5c4e3d3b45bd0ba8404b5a0c3d26186737c0
This commit is contained in:
Kweku Adams
2020-02-06 15:56:12 -08:00
parent bf395db018
commit 4297b5da1c
2 changed files with 93 additions and 24 deletions

View File

@@ -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;

View File

@@ -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);