Add a knob to change the impact of the notification-seen event.
Currently, notification-seen event will promote an app to Working set bucket. This change adds a knob to allow changing the bucket that the app gets promoted to on a notification-seen event. This change also updates AppStandbyController logic to allow setting timeouts for buckets other than Active and Working set. Bug: 206518483 Test: atest --test-mapping apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING Change-Id: Ic818c3871a34107b86b97c983285214beea220a5
This commit is contained in:
@@ -42,6 +42,7 @@ import android.util.AtomicFile;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseLongArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.Xml;
|
||||
|
||||
@@ -49,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
@@ -80,7 +82,7 @@ public class AppIdleHistory {
|
||||
private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
|
||||
private static final long ONE_MINUTE = 60 * 1000;
|
||||
|
||||
private static final int STANDBY_BUCKET_UNKNOWN = -1;
|
||||
static final int STANDBY_BUCKET_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
* The bucket beyond which apps are considered idle. Any apps in this bucket or lower are
|
||||
@@ -88,10 +90,35 @@ public class AppIdleHistory {
|
||||
*/
|
||||
static final int IDLE_BUCKET_CUTOFF = STANDBY_BUCKET_RARE;
|
||||
|
||||
/** Initial version of the xml containing the app idle stats ({@link #APP_IDLE_FILENAME}). */
|
||||
private static final int XML_VERSION_INITIAL = 0;
|
||||
/**
|
||||
* Allowed writing expiry times for any standby bucket instead of only active and working set.
|
||||
* In previous version, we used to specify expiry times for active and working set as
|
||||
* attributes:
|
||||
* <pre>
|
||||
* <package activeTimeoutTime="..." workingSetTimeoutTime="..." />
|
||||
* </pre>
|
||||
* In this version, it is changed to:
|
||||
* <pre>
|
||||
* <package>
|
||||
* <expiryTimes>
|
||||
* <item bucket="..." expiry="..." />
|
||||
* <item bucket="..." expiry="..." />
|
||||
* </expiryTimes>
|
||||
* </package>
|
||||
* </pre>
|
||||
*/
|
||||
private static final int XML_VERSION_ADD_BUCKET_EXPIRY_TIMES = 1;
|
||||
/** Current version */
|
||||
private static final int XML_VERSION_CURRENT = XML_VERSION_ADD_BUCKET_EXPIRY_TIMES;
|
||||
|
||||
@VisibleForTesting
|
||||
static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
|
||||
private static final String TAG_PACKAGES = "packages";
|
||||
private static final String TAG_PACKAGE = "package";
|
||||
private static final String TAG_BUCKET_EXPIRY_TIMES = "expiryTimes";
|
||||
private static final String TAG_ITEM = "item";
|
||||
private static final String ATTR_NAME = "name";
|
||||
// Screen on timebase time when app was last used
|
||||
private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
|
||||
@@ -111,6 +138,10 @@ public class AppIdleHistory {
|
||||
private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime";
|
||||
// The time when the forced working_set state can be overridden.
|
||||
private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime";
|
||||
// The standby bucket value
|
||||
private static final String ATTR_BUCKET = "bucket";
|
||||
// The time when the forced bucket state can be overridde.
|
||||
private static final String ATTR_EXPIRY_TIME = "expiry";
|
||||
// Elapsed timebase time when the app was last marked for restriction.
|
||||
private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED =
|
||||
"lastRestrictionAttemptElapsedTime";
|
||||
@@ -119,6 +150,8 @@ public class AppIdleHistory {
|
||||
"lastRestrictionAttemptReason";
|
||||
// The next estimated launch time of the app, in ms since epoch.
|
||||
private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime";
|
||||
// Version of the xml file.
|
||||
private static final String ATTR_VERSION = "version";
|
||||
|
||||
// device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
|
||||
private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
|
||||
@@ -158,15 +191,10 @@ public class AppIdleHistory {
|
||||
// The estimated time the app will be launched next, in milliseconds since epoch.
|
||||
@CurrentTimeMillisLong
|
||||
long nextEstimatedLaunchTime;
|
||||
// When should the bucket active state timeout, in elapsed timebase, if greater than
|
||||
// lastUsedElapsedTime.
|
||||
// This is used to keep the app in a high bucket regardless of other timeouts and
|
||||
// predictions.
|
||||
long bucketActiveTimeoutTime;
|
||||
// If there's a forced working_set state, this is when it times out. This can be sitting
|
||||
// under any active state timeout, so that it becomes applicable after the active state
|
||||
// timeout expires.
|
||||
long bucketWorkingSetTimeoutTime;
|
||||
// Contains standby buckets that apps were forced into and the corresponding expiry times
|
||||
// (in elapsed timebase) for each bucket state. App will stay in the highest bucket until
|
||||
// it's expiry time is elapsed and will be moved to the next highest bucket.
|
||||
SparseLongArray bucketExpiryTimesMs;
|
||||
// The last time an agent attempted to put the app into the RESTRICTED bucket.
|
||||
long lastRestrictAttemptElapsedTime;
|
||||
// The last reason the app was marked to be put into the RESTRICTED bucket.
|
||||
@@ -249,21 +277,24 @@ public class AppIdleHistory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the app as used and update the bucket if necessary. If there is a timeout specified
|
||||
* Mark the app as used and update the bucket if necessary. If there is a expiry time specified
|
||||
* that's in the future, then the usage event is temporary and keeps the app in the specified
|
||||
* bucket at least until the timeout is reached. This can be used to keep the app in an
|
||||
* bucket at least until the expiry time is reached. This can be used to keep the app in an
|
||||
* elevated bucket for a while until some important task gets to run.
|
||||
*
|
||||
* @param appUsageHistory the usage record for the app being updated
|
||||
* @param packageName name of the app being updated, for logging purposes
|
||||
* @param newBucket the bucket to set the app to
|
||||
* @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_*
|
||||
* @param elapsedRealtime mark as used time if non-zero
|
||||
* @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
|
||||
* with bucket values of ACTIVE and WORKING_SET.
|
||||
* @param nowElapsedRealtimeMs mark as used time if non-zero (in
|
||||
* {@link SystemClock#elapsedRealtime()} time base)
|
||||
* @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in
|
||||
* {@link SystemClock#elapsedRealtime()} time base)
|
||||
* @return {@code appUsageHistory}
|
||||
*/
|
||||
AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId,
|
||||
int newBucket, int usageReason, long elapsedRealtime, long timeout) {
|
||||
int newBucket, int usageReason,
|
||||
long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) {
|
||||
int bucketingReason = REASON_MAIN_USAGE | usageReason;
|
||||
final boolean isUserUsage = isUserUsage(bucketingReason);
|
||||
|
||||
@@ -274,30 +305,27 @@ public class AppIdleHistory {
|
||||
newBucket = STANDBY_BUCKET_RESTRICTED;
|
||||
bucketingReason = appUsageHistory.bucketingReason;
|
||||
} else {
|
||||
// Set the timeout if applicable
|
||||
if (timeout > elapsedRealtime) {
|
||||
// Set the expiry time if applicable
|
||||
if (expiryElapsedRealtimeMs > nowElapsedRealtimeMs) {
|
||||
// Convert to elapsed timebase
|
||||
final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
|
||||
if (newBucket == STANDBY_BUCKET_ACTIVE) {
|
||||
appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
|
||||
appUsageHistory.bucketActiveTimeoutTime);
|
||||
} else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
|
||||
appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
|
||||
appUsageHistory.bucketWorkingSetTimeoutTime);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot set a timeout on bucket="
|
||||
+ newBucket);
|
||||
final long expiryTimeMs = getElapsedTime(expiryElapsedRealtimeMs);
|
||||
if (appUsageHistory.bucketExpiryTimesMs == null) {
|
||||
appUsageHistory.bucketExpiryTimesMs = new SparseLongArray();
|
||||
}
|
||||
final long currentExpiryTimeMs = appUsageHistory.bucketExpiryTimesMs.get(newBucket);
|
||||
appUsageHistory.bucketExpiryTimesMs.put(newBucket,
|
||||
Math.max(expiryTimeMs, currentExpiryTimeMs));
|
||||
removeElapsedExpiryTimes(appUsageHistory, getElapsedTime(nowElapsedRealtimeMs));
|
||||
}
|
||||
}
|
||||
|
||||
if (elapsedRealtime != 0) {
|
||||
if (nowElapsedRealtimeMs != 0) {
|
||||
appUsageHistory.lastUsedElapsedTime = mElapsedDuration
|
||||
+ (elapsedRealtime - mElapsedSnapshot);
|
||||
+ (nowElapsedRealtimeMs - mElapsedSnapshot);
|
||||
if (isUserUsage) {
|
||||
appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime;
|
||||
}
|
||||
appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
|
||||
appUsageHistory.lastUsedScreenTime = getScreenOnTime(nowElapsedRealtimeMs);
|
||||
}
|
||||
|
||||
if (appUsageHistory.currentBucket > newBucket) {
|
||||
@@ -309,26 +337,41 @@ public class AppIdleHistory {
|
||||
return appUsageHistory;
|
||||
}
|
||||
|
||||
private void removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs) {
|
||||
if (appUsageHistory.bucketExpiryTimesMs == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = appUsageHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) {
|
||||
if (appUsageHistory.bucketExpiryTimesMs.valueAt(i) < elapsedTimeMs) {
|
||||
appUsageHistory.bucketExpiryTimesMs.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the app as used and update the bucket if necessary. If there is a timeout specified
|
||||
* Mark the app as used and update the bucket if necessary. If there is a expiry time specified
|
||||
* that's in the future, then the usage event is temporary and keeps the app in the specified
|
||||
* bucket at least until the timeout is reached. This can be used to keep the app in an
|
||||
* bucket at least until the expiry time is reached. This can be used to keep the app in an
|
||||
* elevated bucket for a while until some important task gets to run.
|
||||
* @param packageName
|
||||
* @param userId
|
||||
*
|
||||
* @param packageName package name of the app the usage is reported for
|
||||
* @param userId user that the app is running in
|
||||
* @param newBucket the bucket to set the app to
|
||||
* @param usageReason sub reason for usage
|
||||
* @param nowElapsed mark as used time if non-zero
|
||||
* @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
|
||||
* with bucket values of ACTIVE and WORKING_SET.
|
||||
* @return
|
||||
* @param nowElapsedRealtimeMs mark as used time if non-zero (in
|
||||
* {@link SystemClock#elapsedRealtime()} time base).
|
||||
* @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in
|
||||
* {@link SystemClock#elapsedRealtime()} time base).
|
||||
* @return the {@link AppUsageHistory} corresponding to the {@code packageName}
|
||||
* and {@code userId}.
|
||||
*/
|
||||
public AppUsageHistory reportUsage(String packageName, int userId, int newBucket,
|
||||
int usageReason, long nowElapsed, long timeout) {
|
||||
int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) {
|
||||
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
|
||||
AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true);
|
||||
return reportUsage(history, packageName, userId, newBucket, usageReason, nowElapsed,
|
||||
timeout);
|
||||
AppUsageHistory history = getPackageHistory(userHistory, packageName,
|
||||
nowElapsedRealtimeMs, true);
|
||||
return reportUsage(history, packageName, userId, newBucket, usageReason,
|
||||
nowElapsedRealtimeMs, expiryElapsedRealtimeMs);
|
||||
}
|
||||
|
||||
private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
|
||||
@@ -383,7 +426,7 @@ public class AppIdleHistory {
|
||||
}
|
||||
|
||||
public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
|
||||
int bucket, int reason, boolean resetTimeout) {
|
||||
int bucket, int reason, boolean resetExpiryTimes) {
|
||||
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
|
||||
AppUsageHistory appUsageHistory =
|
||||
getPackageHistory(userHistory, packageName, elapsedRealtime, true);
|
||||
@@ -397,9 +440,8 @@ public class AppIdleHistory {
|
||||
appUsageHistory.lastPredictedTime = elapsed;
|
||||
appUsageHistory.lastPredictedBucket = bucket;
|
||||
}
|
||||
if (resetTimeout) {
|
||||
appUsageHistory.bucketActiveTimeoutTime = elapsed;
|
||||
appUsageHistory.bucketWorkingSetTimeoutTime = elapsed;
|
||||
if (resetExpiryTimes && appUsageHistory.bucketExpiryTimesMs != null) {
|
||||
appUsageHistory.bucketExpiryTimesMs.clear();
|
||||
}
|
||||
if (changed) {
|
||||
logAppStandbyBucketChanged(packageName, userId, bucket, reason);
|
||||
@@ -621,6 +663,17 @@ public class AppIdleHistory {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
long getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs) {
|
||||
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
|
||||
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
|
||||
elapsedRealtimeMs, true);
|
||||
if (appUsageHistory.bucketExpiryTimesMs == null) {
|
||||
return 0;
|
||||
}
|
||||
return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
File getUserFile(int userId) {
|
||||
return new File(new File(new File(mStorageDir, "users"),
|
||||
@@ -657,6 +710,7 @@ public class AppIdleHistory {
|
||||
if (!parser.getName().equals(TAG_PACKAGES)) {
|
||||
return;
|
||||
}
|
||||
final int version = getIntValue(parser, ATTR_VERSION, XML_VERSION_INITIAL);
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (type == XmlPullParser.START_TAG) {
|
||||
final String name = parser.getName();
|
||||
@@ -681,10 +735,6 @@ public class AppIdleHistory {
|
||||
parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
|
||||
appUsageHistory.lastJobRunTime = getLongValue(parser,
|
||||
ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
|
||||
appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser,
|
||||
ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L);
|
||||
appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser,
|
||||
ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L);
|
||||
appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
|
||||
if (bucketingReason != null) {
|
||||
try {
|
||||
@@ -710,6 +760,26 @@ public class AppIdleHistory {
|
||||
ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0);
|
||||
appUsageHistory.lastInformedBucket = -1;
|
||||
userHistory.put(packageName, appUsageHistory);
|
||||
|
||||
if (version >= XML_VERSION_ADD_BUCKET_EXPIRY_TIMES) {
|
||||
final int outerDepth = parser.getDepth();
|
||||
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
|
||||
if (TAG_BUCKET_EXPIRY_TIMES.equals(parser.getName())) {
|
||||
readBucketExpiryTimes(parser, appUsageHistory);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final long bucketActiveTimeoutTime = getLongValue(parser,
|
||||
ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L);
|
||||
final long bucketWorkingSetTimeoutTime = getLongValue(parser,
|
||||
ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L);
|
||||
if (bucketActiveTimeoutTime != 0 || bucketWorkingSetTimeoutTime != 0) {
|
||||
insertBucketExpiryTime(appUsageHistory,
|
||||
STANDBY_BUCKET_ACTIVE, bucketActiveTimeoutTime);
|
||||
insertBucketExpiryTime(appUsageHistory,
|
||||
STANDBY_BUCKET_WORKING_SET, bucketWorkingSetTimeoutTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -720,21 +790,53 @@ public class AppIdleHistory {
|
||||
}
|
||||
}
|
||||
|
||||
private void readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory)
|
||||
throws IOException, XmlPullParserException {
|
||||
final int depth = parser.getDepth();
|
||||
while (XmlUtils.nextElementWithin(parser, depth)) {
|
||||
if (TAG_ITEM.equals(parser.getName())) {
|
||||
final int bucket = getIntValue(parser, ATTR_BUCKET, STANDBY_BUCKET_UNKNOWN);
|
||||
if (bucket == STANDBY_BUCKET_UNKNOWN) {
|
||||
Slog.e(TAG, "Error reading the buckets expiry times");
|
||||
continue;
|
||||
}
|
||||
final long expiryTimeMs = getLongValue(parser, ATTR_EXPIRY_TIME, 0 /* default */);
|
||||
insertBucketExpiryTime(appUsageHistory, bucket, expiryTimeMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insertBucketExpiryTime(AppUsageHistory appUsageHistory,
|
||||
int bucket, long expiryTimeMs) {
|
||||
if (expiryTimeMs == 0) {
|
||||
return;
|
||||
}
|
||||
if (appUsageHistory.bucketExpiryTimesMs == null) {
|
||||
appUsageHistory.bucketExpiryTimesMs = new SparseLongArray();
|
||||
}
|
||||
appUsageHistory.bucketExpiryTimesMs.put(bucket, expiryTimeMs);
|
||||
}
|
||||
|
||||
private long getLongValue(XmlPullParser parser, String attrName, long defValue) {
|
||||
String value = parser.getAttributeValue(null, attrName);
|
||||
if (value == null) return defValue;
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
|
||||
private int getIntValue(XmlPullParser parser, String attrName, int defValue) {
|
||||
String value = parser.getAttributeValue(null, attrName);
|
||||
if (value == null) return defValue;
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
public void writeAppIdleTimes() {
|
||||
public void writeAppIdleTimes(long elapsedRealtimeMs) {
|
||||
final int size = mIdleHistory.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
writeAppIdleTimes(mIdleHistory.keyAt(i));
|
||||
writeAppIdleTimes(mIdleHistory.keyAt(i), elapsedRealtimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeAppIdleTimes(int userId) {
|
||||
public void writeAppIdleTimes(int userId, long elapsedRealtimeMs) {
|
||||
FileOutputStream fos = null;
|
||||
AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
|
||||
try {
|
||||
@@ -747,7 +849,9 @@ public class AppIdleHistory {
|
||||
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
|
||||
xml.startTag(null, TAG_PACKAGES);
|
||||
xml.attribute(null, ATTR_VERSION, String.valueOf(XML_VERSION_CURRENT));
|
||||
|
||||
final long elapsedTimeMs = getElapsedTime(elapsedRealtimeMs);
|
||||
ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
|
||||
final int N = userHistory.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
@@ -772,14 +876,6 @@ public class AppIdleHistory {
|
||||
Integer.toString(history.currentBucket));
|
||||
xml.attribute(null, ATTR_BUCKETING_REASON,
|
||||
Integer.toHexString(history.bucketingReason));
|
||||
if (history.bucketActiveTimeoutTime > 0) {
|
||||
xml.attribute(null, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, Long.toString(history
|
||||
.bucketActiveTimeoutTime));
|
||||
}
|
||||
if (history.bucketWorkingSetTimeoutTime > 0) {
|
||||
xml.attribute(null, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, Long.toString(history
|
||||
.bucketWorkingSetTimeoutTime));
|
||||
}
|
||||
if (history.lastJobRunTime != Long.MIN_VALUE) {
|
||||
xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
|
||||
.lastJobRunTime));
|
||||
@@ -794,6 +890,22 @@ public class AppIdleHistory {
|
||||
xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME,
|
||||
Long.toString(history.nextEstimatedLaunchTime));
|
||||
}
|
||||
if (history.bucketExpiryTimesMs != null) {
|
||||
xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES);
|
||||
for (int j = 0; j < history.bucketExpiryTimesMs.size(); ++j) {
|
||||
final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j);
|
||||
// Skip writing to disk if the expiry time already elapsed.
|
||||
if (expiryTimeMs < elapsedTimeMs) {
|
||||
continue;
|
||||
}
|
||||
final int bucket = history.bucketExpiryTimesMs.keyAt(j);
|
||||
xml.startTag(null, TAG_ITEM);
|
||||
xml.attribute(null, ATTR_BUCKET, String.valueOf(bucket));
|
||||
xml.attribute(null, ATTR_EXPIRY_TIME, String.valueOf(expiryTimeMs));
|
||||
xml.endTag(null, TAG_ITEM);
|
||||
}
|
||||
xml.endTag(null, TAG_BUCKET_EXPIRY_TIMES);
|
||||
}
|
||||
xml.endTag(null, TAG_PACKAGE);
|
||||
}
|
||||
|
||||
@@ -846,12 +958,7 @@ public class AppIdleHistory {
|
||||
TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
|
||||
idpw.print(" lastPred=");
|
||||
TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
|
||||
idpw.print(" activeLeft=");
|
||||
TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime,
|
||||
idpw);
|
||||
idpw.print(" wsLeft=");
|
||||
TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime,
|
||||
idpw);
|
||||
dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime);
|
||||
idpw.print(" lastJob=");
|
||||
TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
|
||||
if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
|
||||
@@ -877,4 +984,25 @@ public class AppIdleHistory {
|
||||
idpw.println();
|
||||
idpw.decreaseIndent();
|
||||
}
|
||||
|
||||
private void dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory,
|
||||
long totalElapsedTimeMs) {
|
||||
idpw.print(" expiryTimes=");
|
||||
if (appUsageHistory.bucketExpiryTimesMs == null
|
||||
|| appUsageHistory.bucketExpiryTimesMs.size() == 0) {
|
||||
idpw.print("<none>");
|
||||
return;
|
||||
}
|
||||
idpw.print("(");
|
||||
for (int i = 0; i < appUsageHistory.bucketExpiryTimesMs.size(); ++i) {
|
||||
final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i);
|
||||
final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i);
|
||||
if (i != 0) {
|
||||
idpw.print(",");
|
||||
}
|
||||
idpw.print(bucket + ":");
|
||||
TimeUtils.formatDuration(totalElapsedTimeMs - expiryTimeMs, idpw);
|
||||
}
|
||||
idpw.print(")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +49,12 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
|
||||
import static android.app.usage.UsageStatsManager.standbyBucketToString;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
|
||||
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||
import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN;
|
||||
|
||||
import android.annotation.CurrentTimeMillisLong;
|
||||
import android.annotation.NonNull;
|
||||
@@ -298,6 +300,9 @@ public class AppStandbyController
|
||||
long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_STRONG_USAGE_TIMEOUT;
|
||||
/** Minimum time a notification seen event should keep the bucket elevated. */
|
||||
long mNotificationSeenTimeoutMillis = ConstantsObserver.DEFAULT_NOTIFICATION_TIMEOUT;
|
||||
/** The standby bucket that an app will be promoted on a notification-seen event */
|
||||
int mNotificationSeenPromotedBucket =
|
||||
ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET;
|
||||
/** Minimum time a system update event should keep the buckets elevated. */
|
||||
long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT;
|
||||
/** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
|
||||
@@ -773,7 +778,7 @@ public class AppStandbyController
|
||||
userId);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Checking idle state for " + packageName
|
||||
+ " minBucket=" + minBucket);
|
||||
+ " minBucket=" + standbyBucketToString(minBucket));
|
||||
}
|
||||
if (minBucket <= STANDBY_BUCKET_ACTIVE) {
|
||||
// No extra processing needed for ACTIVE or higher since apps can't drop into lower
|
||||
@@ -815,36 +820,34 @@ public class AppStandbyController
|
||||
newBucket = app.lastPredictedBucket;
|
||||
reason = REASON_MAIN_PREDICTED | REASON_SUB_PREDICTED_RESTORED;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Restored predicted newBucket = " + newBucket);
|
||||
Slog.d(TAG, "Restored predicted newBucket = "
|
||||
+ standbyBucketToString(newBucket));
|
||||
}
|
||||
} else {
|
||||
newBucket = getBucketForLocked(packageName, userId,
|
||||
elapsedRealtime);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket);
|
||||
Slog.d(TAG, "Evaluated AOSP newBucket = "
|
||||
+ standbyBucketToString(newBucket));
|
||||
}
|
||||
reason = REASON_MAIN_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the app is within one of the timeouts for forced bucket elevation
|
||||
// Check if the app is within one of the expiry times for forced bucket elevation
|
||||
final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime);
|
||||
if (newBucket >= STANDBY_BUCKET_ACTIVE
|
||||
&& app.bucketActiveTimeoutTime > elapsedTimeAdjusted) {
|
||||
newBucket = STANDBY_BUCKET_ACTIVE;
|
||||
reason = app.bucketingReason;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Keeping at ACTIVE due to min timeout");
|
||||
final int bucketWithValidExpiryTime = getMinBucketWithValidExpiryTime(app,
|
||||
newBucket, elapsedTimeAdjusted);
|
||||
if (bucketWithValidExpiryTime != STANDBY_BUCKET_UNKNOWN) {
|
||||
newBucket = bucketWithValidExpiryTime;
|
||||
if (newBucket == STANDBY_BUCKET_ACTIVE || app.currentBucket == newBucket) {
|
||||
reason = app.bucketingReason;
|
||||
} else {
|
||||
reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
|
||||
}
|
||||
} else if (newBucket >= STANDBY_BUCKET_WORKING_SET
|
||||
&& app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) {
|
||||
newBucket = STANDBY_BUCKET_WORKING_SET;
|
||||
// If it was already there, keep the reason, else assume timeout to WS
|
||||
reason = (newBucket == oldBucket)
|
||||
? app.bucketingReason
|
||||
: REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Keeping at WORKING_SET due to min timeout");
|
||||
Slog.d(TAG, " Keeping at " + standbyBucketToString(newBucket)
|
||||
+ " due to min timeout");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,13 +871,14 @@ public class AppStandbyController
|
||||
newBucket = minBucket;
|
||||
// Leave the reason alone.
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Bringing up from " + newBucket + " to " + minBucket
|
||||
Slog.d(TAG, "Bringing up from " + standbyBucketToString(newBucket)
|
||||
+ " to " + standbyBucketToString(minBucket)
|
||||
+ " due to min bucketing");
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Old bucket=" + oldBucket
|
||||
+ ", newBucket=" + newBucket);
|
||||
Slog.d(TAG, " Old bucket=" + standbyBucketToString(oldBucket)
|
||||
+ ", newBucket=" + standbyBucketToString(newBucket));
|
||||
}
|
||||
if (oldBucket != newBucket || predictionLate) {
|
||||
mAppIdleHistory.setAppStandbyBucket(packageName, userId,
|
||||
@@ -967,6 +971,7 @@ public class AppStandbyController
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mAppIdleLock")
|
||||
private void reportEventLocked(String pkg, int eventType, long elapsedRealtime, int userId) {
|
||||
// TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
|
||||
// about apps that are on some kind of whitelist anyway.
|
||||
@@ -980,12 +985,20 @@ public class AppStandbyController
|
||||
final long nextCheckDelay;
|
||||
final int subReason = usageEventToSubReason(eventType);
|
||||
final int reason = REASON_MAIN_USAGE | subReason;
|
||||
if (eventType == UsageEvents.Event.NOTIFICATION_SEEN
|
||||
|| eventType == UsageEvents.Event.SLICE_PINNED) {
|
||||
if (eventType == UsageEvents.Event.NOTIFICATION_SEEN) {
|
||||
// Notification-seen elevates to a higher bucket (depending on
|
||||
// {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't
|
||||
// change usage time.
|
||||
mAppIdleHistory.reportUsage(appHistory, pkg, userId,
|
||||
mNotificationSeenPromotedBucket, subReason,
|
||||
0, elapsedRealtime + mNotificationSeenTimeoutMillis);
|
||||
nextCheckDelay = mNotificationSeenTimeoutMillis;
|
||||
} else if (eventType == UsageEvents.Event.SLICE_PINNED) {
|
||||
// Mild usage elevates to WORKING_SET but doesn't change usage time.
|
||||
mAppIdleHistory.reportUsage(appHistory, pkg, userId,
|
||||
STANDBY_BUCKET_WORKING_SET, subReason,
|
||||
0, elapsedRealtime + mNotificationSeenTimeoutMillis);
|
||||
// TODO: Add a separate setting to control the timeout for SLICE_PINNED event.
|
||||
nextCheckDelay = mNotificationSeenTimeoutMillis;
|
||||
} else if (eventType == UsageEvents.Event.SYSTEM_INTERACTION) {
|
||||
mAppIdleHistory.reportUsage(appHistory, pkg, userId,
|
||||
@@ -1021,6 +1034,29 @@ public class AppStandbyController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lowest standby bucket that is better than {@code targetBucket} and has an
|
||||
* valid expiry time (i.e. the expiry time is not yet elapsed).
|
||||
*/
|
||||
private int getMinBucketWithValidExpiryTime(AppUsageHistory usageHistory,
|
||||
int targetBucket, long elapsedTimeMs) {
|
||||
if (usageHistory.bucketExpiryTimesMs == null) {
|
||||
return STANDBY_BUCKET_UNKNOWN;
|
||||
}
|
||||
final int size = usageHistory.bucketExpiryTimesMs.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
final int bucket = usageHistory.bucketExpiryTimesMs.keyAt(i);
|
||||
if (targetBucket <= bucket) {
|
||||
break;
|
||||
}
|
||||
final long expiryTimeMs = usageHistory.bucketExpiryTimesMs.valueAt(i);
|
||||
if (expiryTimeMs > elapsedTimeMs) {
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
return STANDBY_BUCKET_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: don't call this with the lock held since it makes calls to other system services.
|
||||
*/
|
||||
@@ -1564,23 +1600,18 @@ public class AppStandbyController
|
||||
// ACTIVE or WORKING_SET timeout.
|
||||
mAppIdleHistory.updateLastPrediction(app, elapsedTimeAdjusted, newBucket);
|
||||
|
||||
if (newBucket > STANDBY_BUCKET_ACTIVE
|
||||
&& app.bucketActiveTimeoutTime > elapsedTimeAdjusted) {
|
||||
newBucket = STANDBY_BUCKET_ACTIVE;
|
||||
reason = app.bucketingReason;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Keeping at ACTIVE due to min timeout");
|
||||
}
|
||||
} else if (newBucket > STANDBY_BUCKET_WORKING_SET
|
||||
&& app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) {
|
||||
newBucket = STANDBY_BUCKET_WORKING_SET;
|
||||
if (app.currentBucket != newBucket) {
|
||||
reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
|
||||
} else {
|
||||
final int bucketWithValidExpiryTime = getMinBucketWithValidExpiryTime(app,
|
||||
newBucket, elapsedTimeAdjusted);
|
||||
if (bucketWithValidExpiryTime != STANDBY_BUCKET_UNKNOWN) {
|
||||
newBucket = bucketWithValidExpiryTime;
|
||||
if (newBucket == STANDBY_BUCKET_ACTIVE || app.currentBucket == newBucket) {
|
||||
reason = app.bucketingReason;
|
||||
} else {
|
||||
reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Keeping at WORKING_SET due to min timeout");
|
||||
Slog.d(TAG, " Keeping at " + standbyBucketToString(newBucket)
|
||||
+ " due to min timeout");
|
||||
}
|
||||
} else if (newBucket == STANDBY_BUCKET_RARE
|
||||
&& mAllowRestrictedBucket
|
||||
@@ -1746,7 +1777,7 @@ public class AppStandbyController
|
||||
@Override
|
||||
public void flushToDisk() {
|
||||
synchronized (mAppIdleLock) {
|
||||
mAppIdleHistory.writeAppIdleTimes();
|
||||
mAppIdleHistory.writeAppIdleTimes(mInjector.elapsedRealtime());
|
||||
mAppIdleHistory.writeAppIdleDurations();
|
||||
}
|
||||
}
|
||||
@@ -1897,7 +1928,7 @@ public class AppStandbyController
|
||||
}
|
||||
}
|
||||
// Immediately persist defaults to disk
|
||||
mAppIdleHistory.writeAppIdleTimes(userId);
|
||||
mAppIdleHistory.writeAppIdleTimes(userId, elapsedRealtime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1964,6 +1995,9 @@ public class AppStandbyController
|
||||
pw.print(" mNotificationSeenTimeoutMillis=");
|
||||
TimeUtils.formatDuration(mNotificationSeenTimeoutMillis, pw);
|
||||
pw.println();
|
||||
pw.print(" mNotificationSeenPromotedBucket=");
|
||||
pw.print(standbyBucketToString(mNotificationSeenPromotedBucket));
|
||||
pw.println();
|
||||
pw.print(" mSyncAdapterTimeoutMillis=");
|
||||
TimeUtils.formatDuration(mSyncAdapterTimeoutMillis, pw);
|
||||
pw.println();
|
||||
@@ -2386,6 +2420,8 @@ public class AppStandbyController
|
||||
private static final String KEY_STRONG_USAGE_HOLD_DURATION = "strong_usage_duration";
|
||||
private static final String KEY_NOTIFICATION_SEEN_HOLD_DURATION =
|
||||
"notification_seen_duration";
|
||||
private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET =
|
||||
"notification_seen_promoted_bucket";
|
||||
private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION =
|
||||
"system_update_usage_duration";
|
||||
private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout";
|
||||
@@ -2428,6 +2464,8 @@ public class AppStandbyController
|
||||
COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR;
|
||||
public static final long DEFAULT_NOTIFICATION_TIMEOUT =
|
||||
COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
|
||||
public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET =
|
||||
STANDBY_BUCKET_WORKING_SET;
|
||||
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT =
|
||||
COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR;
|
||||
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT =
|
||||
@@ -2513,6 +2551,11 @@ public class AppStandbyController
|
||||
KEY_NOTIFICATION_SEEN_HOLD_DURATION,
|
||||
DEFAULT_NOTIFICATION_TIMEOUT);
|
||||
break;
|
||||
case KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET:
|
||||
mNotificationSeenPromotedBucket = properties.getInt(
|
||||
KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET,
|
||||
DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET);
|
||||
break;
|
||||
case KEY_STRONG_USAGE_HOLD_DURATION:
|
||||
mStrongUsageTimeoutMillis = properties.getLong(
|
||||
KEY_STRONG_USAGE_HOLD_DURATION, DEFAULT_STRONG_USAGE_TIMEOUT);
|
||||
|
||||
@@ -1277,6 +1277,28 @@ public final class UsageStatsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static String standbyBucketToString(int standbyBucket) {
|
||||
switch (standbyBucket) {
|
||||
case STANDBY_BUCKET_EXEMPTED:
|
||||
return "EXEMPTED";
|
||||
case STANDBY_BUCKET_ACTIVE:
|
||||
return "ACTIVE";
|
||||
case STANDBY_BUCKET_WORKING_SET:
|
||||
return "WORKING_SET";
|
||||
case STANDBY_BUCKET_FREQUENT:
|
||||
return "FREQUENT";
|
||||
case STANDBY_BUCKET_RARE:
|
||||
return "RARE";
|
||||
case STANDBY_BUCKET_RESTRICTED:
|
||||
return "RESTRICTED";
|
||||
case STANDBY_BUCKET_NEVER:
|
||||
return "NEVER";
|
||||
default:
|
||||
return String.valueOf(standbyBucket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
* Temporarily allowlist the specified app for a short duration. This is to allow an app
|
||||
|
||||
@@ -22,16 +22,20 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
|
||||
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SLICE_PINNED;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
|
||||
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
|
||||
import static android.app.usage.UsageStatsManager.standbyBucketToString;
|
||||
|
||||
import android.os.FileUtils;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public class AppIdleHistoryTests extends AndroidTestCase {
|
||||
|
||||
@@ -65,7 +69,7 @@ public class AppIdleHistoryTests extends AndroidTestCase {
|
||||
// Screen On time file should be written right away
|
||||
assertTrue(aih.getScreenOnTimeFile().exists());
|
||||
|
||||
aih.writeAppIdleTimes(USER_ID);
|
||||
aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 2000);
|
||||
// stats file should be written now
|
||||
assertTrue(new File(new File(mStorageDir, "users/" + USER_ID),
|
||||
AppIdleHistory.APP_IDLE_FILENAME).exists());
|
||||
@@ -128,7 +132,7 @@ public class AppIdleHistoryTests extends AndroidTestCase {
|
||||
|
||||
// Check persistence
|
||||
aih.writeAppIdleDurations();
|
||||
aih.writeAppIdleTimes(USER_ID);
|
||||
aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 3000);
|
||||
aih = new AppIdleHistory(mStorageDir, 4000);
|
||||
assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
|
||||
assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
|
||||
@@ -165,7 +169,7 @@ public class AppIdleHistoryTests extends AndroidTestCase {
|
||||
aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000));
|
||||
aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 4000, STANDBY_BUCKET_WORKING_SET,
|
||||
REASON_MAIN_TIMEOUT);
|
||||
aih.writeAppIdleTimes(USER_ID);
|
||||
aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 4000);
|
||||
|
||||
aih = new AppIdleHistory(mStorageDir, 5000);
|
||||
assertEquals(REASON_MAIN_TIMEOUT, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000));
|
||||
@@ -180,11 +184,63 @@ public class AppIdleHistoryTests extends AndroidTestCase {
|
||||
aih.reportUsage(null, USER_ID, STANDBY_BUCKET_ACTIVE,
|
||||
REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0);
|
||||
// Persist data
|
||||
aih.writeAppIdleTimes(USER_ID);
|
||||
aih.writeAppIdleTimes(USER_ID, /* elapsedRealtime= */ 2000);
|
||||
// Recover data from disk
|
||||
aih = new AppIdleHistory(mStorageDir, 5000);
|
||||
// Verify data is intact
|
||||
assertEquals(REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND,
|
||||
aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000));
|
||||
}
|
||||
|
||||
public void testBucketExpiryTimes() throws Exception {
|
||||
AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000 /* elapsedRealtime */);
|
||||
aih.reportUsage(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
|
||||
REASON_SUB_USAGE_SLICE_PINNED,
|
||||
2000 /* elapsedRealtime */, 6000 /* expiryRealtime */);
|
||||
assertEquals(5000 /* expectedExpiryTimeMs */, aih.getBucketExpiryTimeMs(PACKAGE_1, USER_ID,
|
||||
STANDBY_BUCKET_WORKING_SET, 2000 /* elapsedRealtime */));
|
||||
aih.reportUsage(PACKAGE_2, USER_ID, STANDBY_BUCKET_FREQUENT,
|
||||
REASON_SUB_USAGE_NOTIFICATION_SEEN,
|
||||
2000 /* elapsedRealtime */, 3000 /* expiryRealtime */);
|
||||
assertEquals(2000 /* expectedExpiryTimeMs */, aih.getBucketExpiryTimeMs(PACKAGE_2, USER_ID,
|
||||
STANDBY_BUCKET_FREQUENT, 2000 /* elapsedRealtime */));
|
||||
aih.writeAppIdleTimes(USER_ID, 4000 /* elapsedRealtime */);
|
||||
|
||||
// Persist data
|
||||
aih = new AppIdleHistory(mStorageDir, 5000 /* elapsedRealtime */);
|
||||
final Map<Integer, Long> expectedExpiryTimes1 = Map.of(
|
||||
STANDBY_BUCKET_ACTIVE, 0L,
|
||||
STANDBY_BUCKET_WORKING_SET, 5000L,
|
||||
STANDBY_BUCKET_FREQUENT, 0L,
|
||||
STANDBY_BUCKET_RARE, 0L,
|
||||
STANDBY_BUCKET_RESTRICTED, 0L
|
||||
);
|
||||
// For PACKAGE_1, only WORKING_SET bucket should have an expiry time.
|
||||
verifyBucketExpiryTimes(aih, PACKAGE_1, USER_ID, 5000 /* elapsedRealtime */,
|
||||
expectedExpiryTimes1);
|
||||
final Map<Integer, Long> expectedExpiryTimes2 = Map.of(
|
||||
STANDBY_BUCKET_ACTIVE, 0L,
|
||||
STANDBY_BUCKET_WORKING_SET, 0L,
|
||||
STANDBY_BUCKET_FREQUENT, 0L,
|
||||
STANDBY_BUCKET_RARE, 0L,
|
||||
STANDBY_BUCKET_RESTRICTED, 0L
|
||||
);
|
||||
// For PACKAGE_2, there shouldn't be any expiry time since the one set earlier would have
|
||||
// elapsed by the time the data was persisted to disk
|
||||
verifyBucketExpiryTimes(aih, PACKAGE_2, USER_ID, 5000 /* elapsedRealtime */,
|
||||
expectedExpiryTimes2);
|
||||
}
|
||||
|
||||
private void verifyBucketExpiryTimes(AppIdleHistory aih, String packageName, int userId,
|
||||
long elapsedRealtimeMs, Map<Integer, Long> expectedExpiryTimesMs) throws Exception {
|
||||
for (Map.Entry<Integer, Long> entry : expectedExpiryTimesMs.entrySet()) {
|
||||
final int bucket = entry.getKey();
|
||||
final long expectedExpiryTimeMs = entry.getValue();
|
||||
final long actualExpiryTimeMs = aih.getBucketExpiryTimeMs(packageName, userId, bucket,
|
||||
elapsedRealtimeMs);
|
||||
assertEquals("Unexpected expiry time for pkg=" + packageName + ", userId=" + userId
|
||||
+ ", bucket=" + standbyBucketToString(bucket),
|
||||
expectedExpiryTimeMs, actualExpiryTimeMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,6 @@ import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -828,7 +827,6 @@ public class AppStandbyControllerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@FlakyTest(bugId = 185169504)
|
||||
public void testNotificationEvent() throws Exception {
|
||||
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
|
||||
@@ -841,6 +839,22 @@ public class AppStandbyControllerTests {
|
||||
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationEvent_changePromotedBucket() throws Exception {
|
||||
mController.forceIdleState(PACKAGE_1, USER_ID, true);
|
||||
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
|
||||
|
||||
// TODO: Avoid hardcoding these string constants.
|
||||
mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket",
|
||||
STANDBY_BUCKET_FREQUENT);
|
||||
mInjector.mPropertiesChangedListener.onPropertiesChanged(
|
||||
mInjector.getDeviceConfigProperties());
|
||||
mController.forceIdleState(PACKAGE_1, USER_ID, true);
|
||||
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@FlakyTest(bugId = 185169504)
|
||||
public void testSlicePinnedEvent() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user