Loading services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +84 −0 Original line number Diff line number Diff line Loading @@ -308,6 +308,7 @@ public class AppStandbyControllerTests { private void reportEvent(AppStandbyController controller, int eventType, long elapsedTime) { // Back to ACTIVE on event mInjector.mElapsedRealtime = elapsedTime; UsageEvents.Event ev = new UsageEvents.Event(); ev.mPackage = PACKAGE_1; ev.mEventType = eventType; Loading Loading @@ -486,6 +487,89 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_FREQUENT); } @Test public void testCascadingTimeouts() throws Exception { setChargingState(mController, false); reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); reportEvent(mController, NOTIFICATION_SEEN, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_PREDICTED, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); assertBucket(STANDBY_BUCKET_WORKING_SET); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); assertBucket(STANDBY_BUCKET_FREQUENT); } @Test public void testOverlappingTimeouts() throws Exception { setChargingState(mController, false); reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); reportEvent(mController, NOTIFICATION_SEEN, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); // Overlapping USER_INTERACTION before previous one times out reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000); assertBucket(STANDBY_BUCKET_ACTIVE); // Still in ACTIVE after first USER_INTERACTION times out mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // Both timed out, so NOTIFICATION_SEEN timeout should be effective mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_WORKING_SET); mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_RARE); } @Test public void testPredictionNotOverridden() throws Exception { setChargingState(mController, false); reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000; reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // Falls back to WORKING_SET mInjector.mElapsedRealtime += 5000; mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_WORKING_SET); // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // CheckIdleStates should not change the prediction mInjector.mElapsedRealtime += 1000; mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_ACTIVE); } @Test public void testAddActiveDeviceAdmin() { assertActiveAdmins(USER_ID, (String[]) null); Loading services/usage/java/com/android/server/usage/AppIdleHistory.java +48 −18 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_USAGE; 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; Loading Loading @@ -87,7 +88,9 @@ public class AppIdleHistory { // 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"; 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"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration Loading Loading @@ -117,11 +120,15 @@ public class AppIdleHistory { 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 // 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 bucketTimeoutTime; 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; } AppIdleHistory(File storageDir, long elapsedRealtime) { Loading Loading @@ -208,11 +215,28 @@ public class AppIdleHistory { * @param packageName name of the app being updated, for logging purposes * @param newBucket 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 * @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 */ public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int newBucket, long elapsedRealtime, long timeout) { // Set the timeout if applicable if (timeout > elapsedRealtime) { // 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); } } if (elapsedRealtime != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); Loading @@ -226,12 +250,6 @@ public class AppIdleHistory { .currentBucket + ", reason=" + appUsageHistory.bucketingReason); } if (timeout > elapsedRealtime) { // Convert to elapsed timebase appUsageHistory.bucketTimeoutTime = Math.max(appUsageHistory.bucketTimeoutTime, mElapsedDuration + (timeout - mElapsedSnapshot)); } } appUsageHistory.bucketingReason = REASON_USAGE; Loading @@ -247,7 +265,8 @@ public class AppIdleHistory { * @param userId * @param newBucket 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 * @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 */ public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, Loading Loading @@ -504,8 +523,10 @@ public class AppIdleHistory { 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); appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); if (appUsageHistory.bucketingReason == null) { appUsageHistory.bucketingReason = REASON_DEFAULT; } Loading Loading @@ -557,9 +578,13 @@ public class AppIdleHistory { 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.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 Loading Loading @@ -593,14 +618,19 @@ public class AppIdleHistory { continue; } idpw.print("package=" + packageName); idpw.print(" userId=" + userId); idpw.print(" lastUsedElapsed="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); idpw.print(" lastUsedScreenOn="); 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(" bucketActiveTimeoutTime="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketActiveTimeoutTime, idpw); idpw.print(" bucketWorkingSetTimeoutTime="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketWorkingSetTimeoutTime, idpw); idpw.print(" lastJobRunTime="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); Loading services/usage/java/com/android/server/usage/AppStandbyController.java +124 −55 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.usage.UsageStatsManager.StandbyBuckets; Loading Loading @@ -171,6 +172,8 @@ public class AppStandbyController { static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8; static final int MSG_PAROLE_STATE_CHANGED = 9; static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; /** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */ static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11; long mCheckIdleIntervalMillis; long mAppIdleParoleIntervalMillis; Loading Loading @@ -322,7 +325,7 @@ public class AppStandbyController { // Get sync adapters for the authority String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser( authority, userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); final long elapsedRealtime = mInjector.elapsedRealtime(); for (String packageName: packages) { // Only force the sync adapters to active if the provider is not in the same package and // the sync adapter is a system package. Loading Loading @@ -460,8 +463,31 @@ public class AppStandbyController { for (int p = 0; p < packageCount; p++) { final PackageInfo pi = packages.get(p); final String packageName = pi.packageName; checkAndUpdateStandbyState(packageName, userId, pi.applicationInfo.uid, elapsedRealtime); } } if (DEBUG) { Slog.d(TAG, "checkIdleStates took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } return true; } /** Check if we need to update the standby state of a specific app. */ private void checkAndUpdateStandbyState(String packageName, @UserIdInt int userId, int uid, long elapsedRealtime) { if (uid <= 0) { try { uid = mPackageManager.getPackageUidAsUser(packageName, userId); } catch (PackageManager.NameNotFoundException e) { // Not a valid package for this user, nothing to do // TODO: Remove any history of removed packages return; } } final boolean isSpecial = isAppSpecial(packageName, UserHandle.getAppId(pi.applicationInfo.uid), UserHandle.getAppId(uid), userId); if (DEBUG) { Slog.d(TAG, " Checking idle state for " + packageName + " special=" + Loading @@ -476,45 +502,61 @@ public class AppStandbyController { STANDBY_BUCKET_EXEMPTED, false); } else { synchronized (mAppIdleLock) { AppIdleHistory.AppUsageHistory app = final AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime); // 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; String reason = app.bucketingReason; // If the bucket was forced by the user/developer, leave it alone. // A usage event will be the only way to bring it out of this forced state if (REASON_FORCED.equals(app.bucketingReason)) { return; } final int oldBucket = app.currentBucket; int newBucket = Math.max(oldBucket, STANDBY_BUCKET_ACTIVE); // Undo EXEMPTED boolean predictionLate = false; // If the bucket was moved up due to usage, let the timeouts apply. // Compute age-based bucket if (REASON_DEFAULT.equals(app.bucketingReason) || REASON_USAGE.equals(app.bucketingReason) || REASON_TIMEOUT.equals(app.bucketingReason) || (predictionLate = predictionTimedOut(app, elapsedRealtime))) { int oldBucket = app.currentBucket; int newBucket = getBucketForLocked(packageName, userId, newBucket = getBucketForLocked(packageName, userId, elapsedRealtime); if (DEBUG) { Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); } reason = REASON_TIMEOUT; } // Check if the app is within one of the timeouts for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); if (newBucket >= STANDBY_BUCKET_ACTIVE && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { newBucket = STANDBY_BUCKET_ACTIVE; reason = REASON_USAGE; 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; reason = REASON_USAGE; if (DEBUG) { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } if (DEBUG) { Slog.d(TAG, " Old bucket=" + oldBucket + ", newBucket=" + newBucket); } if (oldBucket < newBucket || predictionLate) { mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, REASON_TIMEOUT); elapsedRealtime, newBucket, reason); maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, false); } } } } } } if (DEBUG) { Slog.d(TAG, "checkIdleStates took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } return true; } private boolean predictionTimedOut(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) { return app.bucketingReason != null Loading @@ -526,7 +568,9 @@ public class AppStandbyController { private boolean hasBucketTimeoutPassed(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) { return app.bucketTimeoutTime < mAppIdleHistory.getElapsedTime(elapsedRealtime); final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); return app.bucketActiveTimeoutTime < elapsedTimeAdjusted && app.bucketWorkingSetTimeoutTime < elapsedTimeAdjusted; } private void maybeInformListeners(String packageName, int userId, Loading Loading @@ -631,16 +675,22 @@ public class AppStandbyController { event.mPackage, userId, elapsedRealtime); final int prevBucket = appHistory.currentBucket; final String prevBucketReason = appHistory.bucketingReason; final long nextCheckTime; if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) { // Mild usage elevates to WORKING_SET but doesn't change usage time. mAppIdleHistory.reportUsage(appHistory, event.mPackage, STANDBY_BUCKET_WORKING_SET, elapsedRealtime, elapsedRealtime + mNotificationSeenTimeoutMillis); 0, elapsedRealtime + mNotificationSeenTimeoutMillis); nextCheckTime = mNotificationSeenTimeoutMillis; } else { mAppIdleHistory.reportUsage(event.mPackage, userId, mAppIdleHistory.reportUsage(appHistory, event.mPackage, STANDBY_BUCKET_ACTIVE, elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); nextCheckTime = mStrongUsageTimeoutMillis; } mHandler.sendMessageDelayed(mHandler.obtainMessage (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, event.mPackage), nextCheckTime); final boolean userStartedInteracting = appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && prevBucket != appHistory.currentBucket && Loading Loading @@ -932,9 +982,24 @@ public class AppStandbyController { // 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; if (predicted) { // Check if the app is within one of the timeouts for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); if (newBucket > STANDBY_BUCKET_ACTIVE && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { newBucket = STANDBY_BUCKET_ACTIVE; reason = REASON_USAGE; 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; reason = REASON_USAGE; if (DEBUG) { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } } mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, Loading Loading @@ -1347,6 +1412,10 @@ public class AppStandbyController { + ", Charging state:" + mCharging); informParoleStateChanged(); break; case MSG_CHECK_PACKAGE_IDLE_STATE: checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2, mInjector.elapsedRealtime()); break; default: super.handleMessage(msg); break; Loading Loading
services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +84 −0 Original line number Diff line number Diff line Loading @@ -308,6 +308,7 @@ public class AppStandbyControllerTests { private void reportEvent(AppStandbyController controller, int eventType, long elapsedTime) { // Back to ACTIVE on event mInjector.mElapsedRealtime = elapsedTime; UsageEvents.Event ev = new UsageEvents.Event(); ev.mPackage = PACKAGE_1; ev.mEventType = eventType; Loading Loading @@ -486,6 +487,89 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_FREQUENT); } @Test public void testCascadingTimeouts() throws Exception { setChargingState(mController, false); reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); reportEvent(mController, NOTIFICATION_SEEN, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, REASON_PREDICTED, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); assertBucket(STANDBY_BUCKET_WORKING_SET); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); assertBucket(STANDBY_BUCKET_FREQUENT); } @Test public void testOverlappingTimeouts() throws Exception { setChargingState(mController, false); reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); reportEvent(mController, NOTIFICATION_SEEN, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); // Overlapping USER_INTERACTION before previous one times out reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000); assertBucket(STANDBY_BUCKET_ACTIVE); // Still in ACTIVE after first USER_INTERACTION times out mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // Both timed out, so NOTIFICATION_SEEN timeout should be effective mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_WORKING_SET); mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_RARE); } @Test public void testPredictionNotOverridden() throws Exception { setChargingState(mController, false); reportEvent(mController, USER_INTERACTION, 0); assertBucket(STANDBY_BUCKET_ACTIVE); mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000; reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // Falls back to WORKING_SET mInjector.mElapsedRealtime += 5000; mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_WORKING_SET); // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // CheckIdleStates should not change the prediction mInjector.mElapsedRealtime += 1000; mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_ACTIVE); } @Test public void testAddActiveDeviceAdmin() { assertActiveAdmins(USER_ID, (String[]) null); Loading
services/usage/java/com/android/server/usage/AppIdleHistory.java +48 −18 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_USAGE; 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; Loading Loading @@ -87,7 +88,9 @@ public class AppIdleHistory { // 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"; 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"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration Loading Loading @@ -117,11 +120,15 @@ public class AppIdleHistory { 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 // 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 bucketTimeoutTime; 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; } AppIdleHistory(File storageDir, long elapsedRealtime) { Loading Loading @@ -208,11 +215,28 @@ public class AppIdleHistory { * @param packageName name of the app being updated, for logging purposes * @param newBucket 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 * @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 */ public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int newBucket, long elapsedRealtime, long timeout) { // Set the timeout if applicable if (timeout > elapsedRealtime) { // 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); } } if (elapsedRealtime != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); Loading @@ -226,12 +250,6 @@ public class AppIdleHistory { .currentBucket + ", reason=" + appUsageHistory.bucketingReason); } if (timeout > elapsedRealtime) { // Convert to elapsed timebase appUsageHistory.bucketTimeoutTime = Math.max(appUsageHistory.bucketTimeoutTime, mElapsedDuration + (timeout - mElapsedSnapshot)); } } appUsageHistory.bucketingReason = REASON_USAGE; Loading @@ -247,7 +265,8 @@ public class AppIdleHistory { * @param userId * @param newBucket 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 * @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 */ public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, Loading Loading @@ -504,8 +523,10 @@ public class AppIdleHistory { 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); appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L); appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L); if (appUsageHistory.bucketingReason == null) { appUsageHistory.bucketingReason = REASON_DEFAULT; } Loading Loading @@ -557,9 +578,13 @@ public class AppIdleHistory { 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.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 Loading Loading @@ -593,14 +618,19 @@ public class AppIdleHistory { continue; } idpw.print("package=" + packageName); idpw.print(" userId=" + userId); idpw.print(" lastUsedElapsed="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); idpw.print(" lastUsedScreenOn="); 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(" bucketActiveTimeoutTime="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketActiveTimeoutTime, idpw); idpw.print(" bucketWorkingSetTimeoutTime="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketWorkingSetTimeoutTime, idpw); idpw.print(" lastJobRunTime="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); Loading
services/usage/java/com/android/server/usage/AppStandbyController.java +124 −55 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.usage.UsageStatsManager.StandbyBuckets; Loading Loading @@ -171,6 +172,8 @@ public class AppStandbyController { static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8; static final int MSG_PAROLE_STATE_CHANGED = 9; static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; /** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */ static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11; long mCheckIdleIntervalMillis; long mAppIdleParoleIntervalMillis; Loading Loading @@ -322,7 +325,7 @@ public class AppStandbyController { // Get sync adapters for the authority String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser( authority, userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); final long elapsedRealtime = mInjector.elapsedRealtime(); for (String packageName: packages) { // Only force the sync adapters to active if the provider is not in the same package and // the sync adapter is a system package. Loading Loading @@ -460,8 +463,31 @@ public class AppStandbyController { for (int p = 0; p < packageCount; p++) { final PackageInfo pi = packages.get(p); final String packageName = pi.packageName; checkAndUpdateStandbyState(packageName, userId, pi.applicationInfo.uid, elapsedRealtime); } } if (DEBUG) { Slog.d(TAG, "checkIdleStates took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } return true; } /** Check if we need to update the standby state of a specific app. */ private void checkAndUpdateStandbyState(String packageName, @UserIdInt int userId, int uid, long elapsedRealtime) { if (uid <= 0) { try { uid = mPackageManager.getPackageUidAsUser(packageName, userId); } catch (PackageManager.NameNotFoundException e) { // Not a valid package for this user, nothing to do // TODO: Remove any history of removed packages return; } } final boolean isSpecial = isAppSpecial(packageName, UserHandle.getAppId(pi.applicationInfo.uid), UserHandle.getAppId(uid), userId); if (DEBUG) { Slog.d(TAG, " Checking idle state for " + packageName + " special=" + Loading @@ -476,45 +502,61 @@ public class AppStandbyController { STANDBY_BUCKET_EXEMPTED, false); } else { synchronized (mAppIdleLock) { AppIdleHistory.AppUsageHistory app = final AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime); // 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; String reason = app.bucketingReason; // If the bucket was forced by the user/developer, leave it alone. // A usage event will be the only way to bring it out of this forced state if (REASON_FORCED.equals(app.bucketingReason)) { return; } final int oldBucket = app.currentBucket; int newBucket = Math.max(oldBucket, STANDBY_BUCKET_ACTIVE); // Undo EXEMPTED boolean predictionLate = false; // If the bucket was moved up due to usage, let the timeouts apply. // Compute age-based bucket if (REASON_DEFAULT.equals(app.bucketingReason) || REASON_USAGE.equals(app.bucketingReason) || REASON_TIMEOUT.equals(app.bucketingReason) || (predictionLate = predictionTimedOut(app, elapsedRealtime))) { int oldBucket = app.currentBucket; int newBucket = getBucketForLocked(packageName, userId, newBucket = getBucketForLocked(packageName, userId, elapsedRealtime); if (DEBUG) { Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); } reason = REASON_TIMEOUT; } // Check if the app is within one of the timeouts for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); if (newBucket >= STANDBY_BUCKET_ACTIVE && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { newBucket = STANDBY_BUCKET_ACTIVE; reason = REASON_USAGE; 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; reason = REASON_USAGE; if (DEBUG) { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } if (DEBUG) { Slog.d(TAG, " Old bucket=" + oldBucket + ", newBucket=" + newBucket); } if (oldBucket < newBucket || predictionLate) { mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, REASON_TIMEOUT); elapsedRealtime, newBucket, reason); maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, false); } } } } } } if (DEBUG) { Slog.d(TAG, "checkIdleStates took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } return true; } private boolean predictionTimedOut(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) { return app.bucketingReason != null Loading @@ -526,7 +568,9 @@ public class AppStandbyController { private boolean hasBucketTimeoutPassed(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) { return app.bucketTimeoutTime < mAppIdleHistory.getElapsedTime(elapsedRealtime); final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); return app.bucketActiveTimeoutTime < elapsedTimeAdjusted && app.bucketWorkingSetTimeoutTime < elapsedTimeAdjusted; } private void maybeInformListeners(String packageName, int userId, Loading Loading @@ -631,16 +675,22 @@ public class AppStandbyController { event.mPackage, userId, elapsedRealtime); final int prevBucket = appHistory.currentBucket; final String prevBucketReason = appHistory.bucketingReason; final long nextCheckTime; if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) { // Mild usage elevates to WORKING_SET but doesn't change usage time. mAppIdleHistory.reportUsage(appHistory, event.mPackage, STANDBY_BUCKET_WORKING_SET, elapsedRealtime, elapsedRealtime + mNotificationSeenTimeoutMillis); 0, elapsedRealtime + mNotificationSeenTimeoutMillis); nextCheckTime = mNotificationSeenTimeoutMillis; } else { mAppIdleHistory.reportUsage(event.mPackage, userId, mAppIdleHistory.reportUsage(appHistory, event.mPackage, STANDBY_BUCKET_ACTIVE, elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); nextCheckTime = mStrongUsageTimeoutMillis; } mHandler.sendMessageDelayed(mHandler.obtainMessage (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, event.mPackage), nextCheckTime); final boolean userStartedInteracting = appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && prevBucket != appHistory.currentBucket && Loading Loading @@ -932,9 +982,24 @@ public class AppStandbyController { // 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; if (predicted) { // Check if the app is within one of the timeouts for forced bucket elevation final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); if (newBucket > STANDBY_BUCKET_ACTIVE && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { newBucket = STANDBY_BUCKET_ACTIVE; reason = REASON_USAGE; 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; reason = REASON_USAGE; if (DEBUG) { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } } mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, Loading Loading @@ -1347,6 +1412,10 @@ public class AppStandbyController { + ", Charging state:" + mCharging); informParoleStateChanged(); break; case MSG_CHECK_PACKAGE_IDLE_STATE: checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2, mInjector.elapsedRealtime()); break; default: super.handleMessage(msg); break; Loading