package android.app.usage;
+import android.annotation.UserIdInt;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
* @param eventType The event that occurred. Valid values can be found at
* {@link UsageEvents}
*/
- public abstract void reportEvent(ComponentName component, int userId, int eventType);
+ public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType);
/**
* Reports an event to the UsageStatsManager.
* @param eventType The event that occurred. Valid values can be found at
* {@link UsageEvents}
*/
- public abstract void reportEvent(String packageName, int userId, int eventType);
+ public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType);
/**
* Reports a configuration change to the UsageStatsManager.
*
* @param config The new device configuration.
*/
- public abstract void reportConfigurationChange(Configuration config, int userId);
+ public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);
/**
* Reports that an action equivalent to a ShortcutInfo is taken by the user.
*
* @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
*/
- public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId);
+ public abstract void reportShortcutUsage(String packageName, String shortcutId,
+ @UserIdInt int userId);
/**
* Reports that a content provider has been accessed by a foreground app.
* @param pkgName The package name of the content provider
* @param userId The user in which the content provider was accessed.
*/
- public abstract void reportContentProviderUsage(String name, String pkgName, int userId);
+ public abstract void reportContentProviderUsage(String name, String pkgName,
+ @UserIdInt int userId);
/**
* Prepares the UsageStatsService for shutdown.
* @param userId
* @return
*/
- public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
+ public abstract boolean isAppIdle(String packageName, int uidForAppId, @UserIdInt int userId);
/**
* Returns the app standby bucket that the app is currently in. This accessor does
* @return the AppStandby bucket code the app currently resides in. If the app is
* unknown in the given user, STANDBY_BUCKET_NEVER is returned.
*/
- @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId,
- long nowElapsed);
+ @StandbyBuckets public abstract int getAppStandbyBucket(String packageName,
+ @UserIdInt int userId, long nowElapsed);
/**
* Returns all of the uids for a given user where all packages associating with that uid
* are in the app idle state -- there are no associated apps that are not idle. This means
* all of the returned uids can be safely considered app idle.
*/
- public abstract int[] getIdleUidsForUser(int userId);
+ public abstract int[] getIdleUidsForUser(@UserIdInt int userId);
/**
* @return True if currently app idle parole mode is on. This means all idle apps are allow to
public static abstract class AppIdleStateChangeListener {
/** Callback to inform listeners that the idle state has changed to a new bucket. */
- public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
- int bucket);
+ public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
+ boolean idle, int bucket);
/**
* Callback to inform listeners that the parole state has changed. This means apps are
public abstract void onParoleStateChanged(boolean isParoleOn);
}
- /* Backup/Restore API */
- public abstract byte[] getBackupPayload(int user, String key);
+ /** Backup/Restore API */
+ public abstract byte[] getBackupPayload(@UserIdInt int userId, String key);
- public abstract void applyRestoredPayload(int user, String key, byte[] payload);
+ /**
+ * ?
+ * @param userId
+ * @param key
+ * @param payload
+ */
+ public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload);
/**
* Return usage stats.
* @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
* result.
*/
- public abstract List<UsageStats> queryUsageStatsForUser(
- int userId, int interval, long beginTime, long endTime, boolean obfuscateInstantApps);
+ public abstract List<UsageStats> queryUsageStatsForUser(@UserIdInt int userId, int interval,
+ long beginTime, long endTime, boolean obfuscateInstantApps);
+
+ /**
+ * Used to persist the last time a job was run for this app, in order to make decisions later
+ * whether a job should be deferred until later. The time passed in should be in elapsed
+ * realtime since boot.
+ * @param packageName the app that executed a job.
+ * @param userId the user associated with the job.
+ * @param elapsedRealtime the time when the job was executed, in elapsed realtime millis since
+ * boot.
+ */
+ public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId,
+ long elapsedRealtime);
+
+ /**
+ * Returns the time in millis since a job was executed for this app, in elapsed realtime
+ * timebase. This value can be larger than the current elapsed realtime if the job was executed
+ * before the device was rebooted. The default value is {@link Long#MAX_VALUE}.
+ * @param packageName the app you're asking about.
+ * @param userId the user associated with the job.
+ * @return the time in millis since a job was last executed for the app, provided it was
+ * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}.
+ */
+ public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId);
}
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import android.app.usage.UsageStatsManager;
import android.os.FileUtils;
import android.test.AndroidTestCase;
assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_FREQUENT));
}
+
+ public void testJobRunTime() throws Exception {
+ AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
+
+ aih.setLastJobRunTime(PACKAGE_1, USER_ID, 2000);
+ assertEquals(Long.MAX_VALUE, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 0));
+ assertEquals(4000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 6000));
+
+ aih.setLastJobRunTime(PACKAGE_2, USER_ID, 6000);
+ assertEquals(1000, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 7000));
+ assertEquals(5000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 7000));
+ }
}
\ No newline at end of file
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.view.Display;
* Unit test for AppStandbyController.
*/
@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
public class AppStandbyControllerTests {
private static final String PACKAGE_1 = "com.example.foo";
true);
}
+ private void assertBucket(int bucket) {
+ assertEquals(bucket, getStandbyBucket(mController));
+ }
+
@Test
public void testBuckets() throws Exception {
assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
REASON_PREDICTED, 2 * HOUR_MS);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
}
+
+ @Test
+ public void testTimeout() throws Exception {
+ setChargingState(mController, false);
+
+ reportEvent(mController, USER_INTERACTION, 0);
+ assertBucket(STANDBY_BUCKET_ACTIVE);
+
+ mInjector.mElapsedRealtime = 2000;
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+ REASON_PREDICTED, mInjector.mElapsedRealtime);
+ assertBucket(STANDBY_BUCKET_ACTIVE);
+
+ // bucketing works after timeout
+ mInjector.mElapsedRealtime = FREQUENT_THRESHOLD - 100;
+ mController.checkIdleStates(USER_ID);
+ assertBucket(STANDBY_BUCKET_WORKING_SET);
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+ REASON_PREDICTED, mInjector.mElapsedRealtime);
+ assertBucket(STANDBY_BUCKET_FREQUENT);
+
+ }
}
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
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_WORKING_SET;
import android.app.usage.UsageStatsManager;
import android.os.SystemClock;
// History for all users and all packages
private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
- private long mLastPeriod = 0;
private static final long ONE_MINUTE = 60 * 1000;
- private static final int HISTORY_SIZE = 100;
- private static final int FLAG_LAST_STATE = 2;
- private static final int FLAG_PARTIAL_ACTIVE = 1;
- private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE
- : 60 * ONE_MINUTE;
@VisibleForTesting
static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
// The reason the app was put in the above bucket
private static final String ATTR_BUCKETING_REASON = "bucketReason";
+ // The last time a job was run for this app
+ private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime";
+ // The time when the forced active state can be overridden.
+ private static final String ATTR_BUCKET_TIMEOUT_TIME = "bucketTimeoutTime";
// device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
private boolean mScreenOn;
static class AppUsageHistory {
- // Debug
- final byte[] recent = new byte[HISTORY_SIZE];
// Last used time using elapsed timebase
long lastUsedElapsedTime;
// Last used time using screen_on timebase
String bucketingReason;
// In-memory only, last bucket for which the listeners were informed
int lastInformedBucket;
+ // The last time a job was run for this app, using elapsed timebase
+ long lastJobRunTime;
+ // When should the bucket 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 bucketTimeoutTime;
}
AppIdleHistory(File storageDir, long elapsedRealtime) {
writeScreenOnTime();
}
- public int reportUsage(String packageName, int userId, long elapsedRealtime) {
+ /**
+ * Mark the app as used and update the bucket if necessary. If there is a timeout 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
+ * elevated bucket for a while until some important task gets to run.
+ * @param packageName
+ * @param userId
+ * @param bucket the bucket to set the app to
+ * @param elapsedRealtime mark as used time if non-zero
+ * @param timeout set the timeout of the specified bucket, if non-zero
+ * @return
+ */
+ public int reportUsage(String packageName, int userId, int bucket, long elapsedRealtime,
+ long timeout) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
- shiftHistoryToNow(userHistory, elapsedRealtime);
-
- appUsageHistory.lastUsedElapsedTime = mElapsedDuration
- + (elapsedRealtime - mElapsedSnapshot);
- appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
- appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
- if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
- appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
- if (DEBUG) {
- Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
- + ", reason=" + appUsageHistory.bucketingReason);
- }
+ if (elapsedRealtime != 0) {
+ appUsageHistory.lastUsedElapsedTime = mElapsedDuration
+ + (elapsedRealtime - mElapsedSnapshot);
+ appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
}
- appUsageHistory.bucketingReason = REASON_USAGE;
-
- return appUsageHistory.currentBucket;
- }
- public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
- ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
- AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
- elapsedRealtime, true);
- if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
- appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
+ if (appUsageHistory.currentBucket > bucket) {
+ appUsageHistory.currentBucket = bucket;
if (DEBUG) {
- Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory
+ .currentBucket
+ ", reason=" + appUsageHistory.bucketingReason);
}
+ if (timeout > elapsedRealtime) {
+ // Convert to elapsed timebase
+ appUsageHistory.bucketTimeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
+ }
}
- // TODO: Should this be a different reason for partial usage?
appUsageHistory.bucketingReason = REASON_USAGE;
return appUsageHistory.currentBucket;
}
- public void setIdle(String packageName, int userId, long elapsedRealtime) {
- ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
- AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
- elapsedRealtime, true);
-
- shiftHistoryToNow(userHistory, elapsedRealtime);
-
- appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
- }
-
- private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
- long elapsedRealtime) {
- long thisPeriod = elapsedRealtime / PERIOD_DURATION;
- // Has the period switched over? Slide all users' package histories
- if (mLastPeriod != 0 && mLastPeriod < thisPeriod
- && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) {
- int diff = (int) (thisPeriod - mLastPeriod);
- final int NUSERS = mIdleHistory.size();
- for (int u = 0; u < NUSERS; u++) {
- userHistory = mIdleHistory.valueAt(u);
- for (AppUsageHistory idleState : userHistory.values()) {
- // Shift left
- System.arraycopy(idleState.recent, diff, idleState.recent, 0,
- HISTORY_SIZE - diff);
- // Replicate last state across the diff
- for (int i = 0; i < diff; i++) {
- idleState.recent[HISTORY_SIZE - i - 1] =
- (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
- }
- }
- }
- }
- mLastPeriod = thisPeriod;
- }
-
private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
if (userHistory == null) {
appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
appUsageHistory.bucketingReason = REASON_DEFAULT;
appUsageHistory.lastInformedBucket = -1;
+ appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago
userHistory.put(packageName, appUsageHistory);
}
return appUsageHistory;
}
}
+ /**
+ * Marks the last time a job was run, with the given elapsedRealtime. The time stored is
+ * based on the elapsed timebase.
+ * @param packageName
+ * @param userId
+ * @param elapsedRealtime
+ */
+ public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory =
+ getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+ appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime);
+ }
+
+ /**
+ * Returns the time since the last job was run for this app. This can be larger than the
+ * current elapsedRealtime, in case it happened before boot or a really large value if no jobs
+ * were ever run.
+ * @param packageName
+ * @param userId
+ * @param elapsedRealtime
+ * @return
+ */
+ public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory =
+ getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+ // Don't adjust the default, else it'll wrap around to a positive value
+ if (appUsageHistory.lastJobRunTime == Long.MIN_VALUE) return Long.MAX_VALUE;
+ return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime;
+ }
+
public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory =
Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
appUsageHistory.lastUsedScreenTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
- String lastPredictedTimeString = parser.getAttributeValue(null,
- ATTR_LAST_PREDICTED_TIME);
- if (lastPredictedTimeString != null) {
- appUsageHistory.lastPredictedTime =
- Long.parseLong(lastPredictedTimeString);
- }
+ appUsageHistory.lastPredictedTime = getLongValue(parser,
+ ATTR_LAST_PREDICTED_TIME, 0L);
String currentBucketString = parser.getAttributeValue(null,
ATTR_CURRENT_BUCKET);
appUsageHistory.currentBucket = currentBucketString == null
: Integer.parseInt(currentBucketString);
appUsageHistory.bucketingReason =
parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
+ appUsageHistory.lastJobRunTime = getLongValue(parser,
+ ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
+ appUsageHistory.bucketTimeoutTime = getLongValue(parser,
+ ATTR_BUCKET_TIMEOUT_TIME, 0L);
if (appUsageHistory.bucketingReason == null) {
appUsageHistory.bucketingReason = REASON_DEFAULT;
}
}
}
+ private long getLongValue(XmlPullParser parser, String attrName, long defValue) {
+ String value = parser.getAttributeValue(null, attrName);
+ if (value == null) return defValue;
+ return Long.parseLong(value);
+ }
+
public void writeAppIdleTimes(int userId) {
FileOutputStream fos = null;
AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
xml.attribute(null, ATTR_CURRENT_BUCKET,
Integer.toString(history.currentBucket));
xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
+ if (history.bucketTimeoutTime > 0) {
+ xml.attribute(null, ATTR_BUCKET_TIMEOUT_TIME, Long.toString(history
+ .bucketTimeoutTime));
+ }
+ if (history.lastJobRunTime != Long.MIN_VALUE) {
+ xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
+ .lastJobRunTime));
+ }
xml.endTag(null, TAG_PACKAGE);
}
TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
idpw.print(" lastPredictedTime=");
TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
+ idpw.print(" bucketTimeoutTime=");
+ TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketTimeoutTime, idpw);
+ idpw.print(" lastJobRunTime=");
+ TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
idpw.print(" bucket=" + appUsageHistory.currentBucket
+ " reason=" + appUsageHistory.bucketingReason);
idpw.println();
idpw.decreaseIndent();
}
-
- public void dumpHistory(IndentingPrintWriter idpw, int userId) {
- ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- if (userHistory == null) return;
- final int P = userHistory.size();
- for (int p = 0; p < P; p++) {
- final String packageName = userHistory.keyAt(p);
- final byte[] history = userHistory.valueAt(p).recent;
- for (int i = 0; i < HISTORY_SIZE; i++) {
- idpw.print(history[i] == 0 ? '.' : 'A');
- }
- idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
- idpw.print(" " + packageName);
- idpw.println();
- }
- }
}
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
}
if (!packageName.equals(providerPkgName)) {
synchronized (mAppIdleLock) {
- int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId,
- elapsedRealtime);
+ int newBucket = mAppIdleHistory.reportUsage(packageName, userId,
+ STANDBY_BUCKET_ACTIVE, elapsedRealtime,
+ elapsedRealtime + 2 * ONE_HOUR);
maybeInformListeners(packageName, userId, elapsedRealtime,
newBucket);
}
AppIdleHistory.AppUsageHistory app =
mAppIdleHistory.getAppUsageHistory(packageName,
userId, elapsedRealtime);
- // If the bucket was forced by the developer, leave it alone
- if (REASON_FORCED.equals(app.bucketingReason)) {
+ // If the bucket was forced by the developer or the app is within the
+ // temporary active period, leave it alone.
+ if (REASON_FORCED.equals(app.bucketingReason)
+ || !hasBucketTimeoutPassed(app, elapsedRealtime)) {
continue;
}
boolean predictionLate = false;
- app.lastPredictedTime > PREDICTION_TIMEOUT;
}
+ private boolean hasBucketTimeoutPassed(AppIdleHistory.AppUsageHistory app,
+ long elapsedRealtime) {
+ return app.bucketTimeoutTime < mAppIdleHistory.getElapsedTime(elapsedRealtime);
+ }
+
private void maybeInformListeners(String packageName, int userId,
long elapsedRealtime, int bucket) {
synchronized (mAppIdleLock) {
final int newBucket;
if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
- newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId,
- elapsedRealtime);
+ newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
+ STANDBY_BUCKET_WORKING_SET,
+ elapsedRealtime, elapsedRealtime + 2 * ONE_HOUR);
} else {
newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
- elapsedRealtime);
+ STANDBY_BUCKET_ACTIVE,
+ elapsedRealtime, elapsedRealtime + 2 * ONE_HOUR);
}
maybeInformListeners(event.mPackage, userId, elapsedRealtime,
}
}
+ public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.setLastJobRunTime(packageName, userId, elapsedRealtime);
+ }
+ }
+
+ public long getTimeSinceLastJobRun(String packageName, int userId) {
+ final long elapsedRealtime = mInjector.elapsedRealtime();
+ synchronized (mAppIdleLock) {
+ return mAppIdleHistory.getTimeSinceLastJobRun(packageName, userId, elapsedRealtime);
+ }
+ }
+
public void onUserRemoved(int userId) {
synchronized (mAppIdleLock) {
mAppIdleHistory.onUserRemoved(userId);
AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName,
userId, elapsedRealtime);
boolean predicted = reason != null && reason.startsWith(REASON_PREDICTED);
+
// Don't allow changing bucket if higher than ACTIVE
if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
- // Don't allow prediction to change from or to NEVER
+
+ // Don't allow prediction to change from/to NEVER
if ((app.currentBucket == STANDBY_BUCKET_NEVER
|| newBucket == STANDBY_BUCKET_NEVER)
&& predicted) {
return;
}
+
// If the bucket was forced, don't allow prediction to override
if (app.bucketingReason.equals(REASON_FORCED) && predicted) return;
+
+ // If the bucket is required to stay in a higher state for a specified duration, don't
+ // override unless the duration has passed
+ if (predicted && app.currentBucket < newBucket
+ && !hasBucketTimeoutPassed(app, elapsedRealtime)) {
+ return;
+ }
+
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
reason);
}
final PackageInfo pi = packages.get(i);
String packageName = pi.packageName;
if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
- mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
+ // Mark app as used for 4 hours. After that it can timeout to whatever the
+ // past usage pattern was.
+ mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE, 0,
+ elapsedRealtime + 4 * ONE_HOUR);
if (isAppSpecial(packageName, UserHandle.getAppId(pi.applicationInfo.uid),
userId)) {
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime,
.sendToTarget();
}
- void dumpHistory(IndentingPrintWriter idpw, int userId) {
- synchronized (mAppIdleLock) {
- mAppIdleHistory.dumpHistory(idpw, userId);
- }
- }
-
void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
synchronized (mAppIdleLock) {
mAppIdleHistory.dump(idpw, userId, pkg);
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
boolean checkin = false;
- boolean history = false;
String pkg = null;
if (args != null) {
String arg = args[i];
if ("--checkin".equals(arg)) {
checkin = true;
- } else if ("--history".equals(arg)) {
- history = true;
- } else if ("history".equals(arg)) {
- history = true;
- break;
} else if ("flush".equals(arg)) {
flushToDiskLocked();
pw.println("Flushed stats to disk");
} else {
mUserState.valueAt(i).dump(idpw, pkg);
idpw.println();
- if (history) {
- mAppStandby.dumpHistory(idpw, userId);
- }
}
mAppStandby.dumpUser(idpw, userId, pkg);
idpw.decreaseIndent();
return UsageStatsService.this.queryUsageStats(
userId, intervalType, beginTime, endTime, obfuscateInstantApps);
}
+
+ @Override
+ public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
+ mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
+ }
+
+ @Override
+ public long getTimeSinceLastJobRun(String packageName, int userId) {
+ return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
+ }
}
}