From 119be9a5fc4033eba570ec94b94862401ee84570 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Sun, 18 Feb 2018 22:23:00 -0800 Subject: [PATCH] Improve reporting of bucketing reason Keep track of main and sub reason for bucket change Bug: 73178753 Test: atest AppIdleHistoryTests Change-Id: I4936281ac06046bb5ffed9f3306efa24c7fd47ab --- core/java/android/app/usage/UsageEvents.java | 28 ++- .../android/app/usage/UsageStatsManager.java | 96 +++++++++-- .../app/usage/UsageStatsManagerInternal.java | 2 +- .../android/server/AlarmManagerService.java | 2 +- .../com/android/server/AppStateTracker.java | 2 +- .../server/am/ActivityManagerConstants.java | 2 +- .../server/job/JobSchedulerService.java | 2 +- .../job/controllers/AppIdleController.java | 3 +- .../net/NetworkPolicyManagerService.java | 3 +- .../android/server/AppStateTrackerTest.java | 13 +- .../server/usage/AppIdleHistoryTests.java | 29 +++- .../usage/AppStandbyControllerTests.java | 46 ++--- .../android/server/usage/AppIdleHistory.java | 85 ++++++---- .../server/usage/AppStandbyController.java | 159 +++++++++++------- .../server/usage/UsageStatsService.java | 16 +- .../android/server/usage/UsageStatsXmlV1.java | 9 +- .../server/usage/UserUsageStatsService.java | 3 +- 17 files changed, 327 insertions(+), 173 deletions(-) diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 6b573e99f1fab..521ab4edc4c56 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -109,7 +109,8 @@ public final class UsageEvents implements Parcelable { public static final int NOTIFICATION_SEEN = 10; /** - * An event type denoting a change in App Standby Bucket. + * An event type denoting a change in App Standby Bucket. Additional bucket information + * is contained in mBucketAndReason. * @hide */ @SystemApi @@ -180,11 +181,12 @@ public final class UsageEvents implements Parcelable { public String[] mContentAnnotations; /** - * The app standby bucket assigned. + * The app standby bucket assigned and reason. Bucket is the high order 16 bits, reason + * is the low order 16 bits. * Only present for {@link #STANDBY_BUCKET_CHANGED} event types * {@hide} */ - public int mBucket; + public int mBucketAndReason; /** @hide */ @EventFlags @@ -205,7 +207,7 @@ public final class UsageEvents implements Parcelable { mContentType = orig.mContentType; mContentAnnotations = orig.mContentAnnotations; mFlags = orig.mFlags; - mBucket = orig.mBucket; + mBucketAndReason = orig.mBucketAndReason; } /** @@ -268,7 +270,19 @@ public final class UsageEvents implements Parcelable { */ @SystemApi public int getStandbyBucket() { - return mBucket; + return (mBucketAndReason & 0xFFFF0000) >>> 16; + } + + /** + * Returns the reason for the bucketing, if the event is of type + * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include + * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there + * are sub-reasons for the main reason, such as REASON_SUB_USAGE_* when the main reason + * is REASON_MAIN_USAGE. + * @hide + */ + public int getStandbyReason() { + return mBucketAndReason & 0x0000FFFF; } /** @hide */ @@ -428,7 +442,7 @@ public final class UsageEvents implements Parcelable { p.writeStringArray(event.mContentAnnotations); break; case Event.STANDBY_BUCKET_CHANGED: - p.writeInt(event.mBucket); + p.writeInt(event.mBucketAndReason); break; } } @@ -474,7 +488,7 @@ public final class UsageEvents implements Parcelable { eventOut.mContentAnnotations = p.createStringArray(); break; case Event.STANDBY_BUCKET_CHANGED: - eventOut.mBucket = p.readInt(); + eventOut.mBucketAndReason = p.readInt(); break; } } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 9a0bb1d405b2d..5a57b067f2c43 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -132,24 +132,37 @@ public final class UsageStatsManager { @SystemApi public static final int STANDBY_BUCKET_NEVER = 50; - /** {@hide} Reason for bucketing -- default initial state */ - public static final String REASON_DEFAULT = "default"; + /** @hide */ + public static final int REASON_MAIN_MASK = 0xFF00; + /** @hide */ + public static final int REASON_MAIN_DEFAULT = 0x0100; + /** @hide */ + public static final int REASON_MAIN_TIMEOUT = 0x0200; + /** @hide */ + public static final int REASON_MAIN_USAGE = 0x0300; + /** @hide */ + public static final int REASON_MAIN_FORCED = 0x0400; + /** @hide */ + public static final int REASON_MAIN_PREDICTED = 0x0500; - /** {@hide} Reason for bucketing -- timeout */ - public static final String REASON_TIMEOUT = "timeout"; - - /** {@hide} Reason for bucketing -- usage */ - public static final String REASON_USAGE = "usage"; - - /** {@hide} Reason for bucketing -- forced by user / shell command */ - public static final String REASON_FORCED = "forced"; - - /** - * {@hide} - * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will - * be appended. - */ - public static final String REASON_PREDICTED = "predicted"; + /** @hide */ + public static final int REASON_SUB_MASK = 0x00FF; + /** @hide */ + public static final int REASON_SUB_USAGE_SYSTEM_INTERACTION = 0x0001; + /** @hide */ + public static final int REASON_SUB_USAGE_NOTIFICATION_SEEN = 0x0002; + /** @hide */ + public static final int REASON_SUB_USAGE_USER_INTERACTION = 0x0003; + /** @hide */ + public static final int REASON_SUB_USAGE_MOVE_TO_FOREGROUND = 0x0004; + /** @hide */ + public static final int REASON_SUB_USAGE_MOVE_TO_BACKGROUND = 0x0005; + /** @hide */ + public static final int REASON_SUB_USAGE_SYSTEM_UPDATE = 0x0006; + /** @hide */ + public static final int REASON_SUB_USAGE_ACTIVE_TIMEOUT = 0x0007; + /** @hide */ + public static final int REASON_SUB_USAGE_SYNC_ADAPTER = 0x0008; /** @hide */ @IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = { @@ -427,6 +440,55 @@ public final class UsageStatsManager { } } + /** @hide */ + public static String reasonToString(int standbyReason) { + StringBuilder sb = new StringBuilder(); + switch (standbyReason & REASON_MAIN_MASK) { + case REASON_MAIN_DEFAULT: + sb.append("d"); + break; + case REASON_MAIN_FORCED: + sb.append("f"); + break; + case REASON_MAIN_PREDICTED: + sb.append("p"); + break; + case REASON_MAIN_TIMEOUT: + sb.append("t"); + break; + case REASON_MAIN_USAGE: + sb.append("u-"); + switch (standbyReason & REASON_SUB_MASK) { + case REASON_SUB_USAGE_SYSTEM_INTERACTION: + sb.append("si"); + break; + case REASON_SUB_USAGE_NOTIFICATION_SEEN: + sb.append("ns"); + break; + case REASON_SUB_USAGE_USER_INTERACTION: + sb.append("ui"); + break; + case REASON_SUB_USAGE_MOVE_TO_FOREGROUND: + sb.append("mf"); + break; + case REASON_SUB_USAGE_MOVE_TO_BACKGROUND: + sb.append("mb"); + break; + case REASON_SUB_USAGE_SYSTEM_UPDATE: + sb.append("su"); + break; + case REASON_SUB_USAGE_ACTIVE_TIMEOUT: + sb.append("at"); + break; + case REASON_SUB_USAGE_SYNC_ADAPTER: + sb.append("sa"); + break; + } + break; + } + return sb.toString(); + } + /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 5d6a989ffef84..b62b1ee0492b0 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -139,7 +139,7 @@ public abstract class UsageStatsManagerInternal { /** Callback to inform listeners that the idle state has changed to a new bucket. */ public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId, - boolean idle, int bucket); + boolean idle, int bucket, int reason); /** * Callback to inform listeners that the parole state has changed. This means apps are diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 9cd3621eb7373..c93f405012ca5 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -3860,7 +3860,7 @@ class AlarmManagerService extends SystemService { final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener { @Override public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, - boolean idle, int bucket) { + boolean idle, int bucket, int reason) { if (DEBUG_STANDBY) { Slog.d(TAG, "Package " + packageName + " for user " + userId + " now in bucket " + bucket); diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java index fc4d4638491fe..a6b71b772d15f 100644 --- a/services/core/java/com/android/server/AppStateTracker.java +++ b/services/core/java/com/android/server/AppStateTracker.java @@ -678,7 +678,7 @@ public class AppStateTracker { final class StandbyTracker extends AppIdleStateChangeListener { @Override public void onAppIdleStateChanged(String packageName, int userId, boolean idle, - int bucket) { + int bucket, int reason) { if (DEBUG) { Slog.d(TAG,"onAppIdleStateChanged: " + packageName + " u" + userId + (idle ? " idle" : " active") + " " + bucket); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 0d6d2bde48c8b..6550d06b8a128 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -85,7 +85,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final int DEFAULT_POWER_CHECK_MAX_CPU_3 = 10; private static final int DEFAULT_POWER_CHECK_MAX_CPU_4 = 2; private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME = 30*60*1000; - private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL = 24*60*60*1000L; + private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL = 2*60*60*1000L; private static final long DEFAULT_SERVICE_RESTART_DURATION = 1*1000; private static final long DEFAULT_SERVICE_RESET_RUN_DURATION = 60*1000; private static final int DEFAULT_SERVICE_RESTART_DURATION_FACTOR = 4; diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 740866c9aec33..017fadaf86203 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -2240,7 +2240,7 @@ public final class JobSchedulerService extends com.android.server.SystemService @Override public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, - boolean idle, int bucket) { + boolean idle, int bucket, int reason) { final int uid = mLocalPM.getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); if (uid < 0) { diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java index bd8fe289ddada..ed29a4c4e252a 100644 --- a/services/core/java/com/android/server/job/controllers/AppIdleController.java +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -186,7 +186,8 @@ public final class AppIdleController extends StateController { private final class AppIdleStateChangeListener extends UsageStatsManagerInternal.AppIdleStateChangeListener { @Override - public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) { + public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket, + int reason) { boolean changed = false; synchronized (mLock) { if (mAppIdleParoleOn) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index f29e0bba8ad5a..ab555532a102a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -3947,7 +3947,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { extends UsageStatsManagerInternal.AppIdleStateChangeListener { @Override - public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) { + public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket, + int reason) { try { final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); diff --git a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java index 90db2a328c6fa..796d36453870c 100644 --- a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java @@ -15,6 +15,9 @@ */ package com.android.server; +import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; +import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; + import static com.android.server.AppStateTracker.TARGET_OP; import static org.junit.Assert.assertEquals; @@ -612,7 +615,7 @@ public class AppStateTrackerTest { // Exempt package 2 on user-10. mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT); areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); @@ -624,7 +627,7 @@ public class AppStateTrackerTest { // Exempt package 1 on user-0. mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT); areRestricted(instance, UID_1, PACKAGE_1, NONE); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); @@ -632,7 +635,7 @@ public class AppStateTrackerTest { // Unexempt package 2 on user-10. mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false, - UsageStatsManager.STANDBY_BUCKET_ACTIVE); + UsageStatsManager.STANDBY_BUCKET_ACTIVE, REASON_MAIN_USAGE); areRestricted(instance, UID_1, PACKAGE_1, NONE); areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); @@ -644,9 +647,9 @@ public class AppStateTrackerTest { mPowerSaveObserver.accept(getPowerSaveState()); mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT); mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 0, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT); setAppOps(UID_1, PACKAGE_1, true); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java index 7b0664844df1c..36504ac7ec656 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java @@ -16,10 +16,13 @@ package com.android.server.usage; -import static android.app.usage.UsageStatsManager.REASON_TIMEOUT; +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_USAGE_MOVE_TO_FOREGROUND; 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_WORKING_SET; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -92,18 +95,18 @@ public class AppIdleHistoryTests extends AndroidTestCase { AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE, - UsageStatsManager.REASON_USAGE); + REASON_MAIN_USAGE); // ACTIVE means not idle assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000)); aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE, - UsageStatsManager.REASON_USAGE); + REASON_MAIN_USAGE); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE, - REASON_TIMEOUT); + REASON_MAIN_TIMEOUT); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE); - assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT); + assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_MAIN_TIMEOUT); // RARE is considered idle assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000)); @@ -115,7 +118,7 @@ public class AppIdleHistoryTests extends AndroidTestCase { 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); - assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT); + assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_MAIN_TIMEOUT); assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); @@ -133,4 +136,18 @@ public class AppIdleHistoryTests extends AndroidTestCase { assertEquals(1000, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 7000)); assertEquals(5000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 7000)); } + + public void testReason() throws Exception { + AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000); + aih.reportUsage(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, + REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0); + assertEquals(REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND, + 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 = new AppIdleHistory(mStorageDir, 5000); + assertEquals(REASON_MAIN_TIMEOUT, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000)); + } } \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index cbbdca6b394ff..edf1f746fb4bd 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -18,11 +18,11 @@ package com.android.server.usage; import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN; import static android.app.usage.UsageEvents.Event.USER_INTERACTION; -import static android.app.usage.UsageStatsManager.REASON_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_FORCED; -import static android.app.usage.UsageStatsManager.REASON_PREDICTED; -import static android.app.usage.UsageStatsManager.REASON_TIMEOUT; -import static android.app.usage.UsageStatsManager.REASON_USAGE; +import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; +import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; @@ -410,11 +410,11 @@ public class AppStandbyControllerTests { setChargingState(mController, false); // Set it to timeout or usage, so that prediction can override it mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_TIMEOUT, 1 * HOUR_MS); + REASON_MAIN_TIMEOUT, 1 * HOUR_MS); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_PREDICTED + ":CTS", 1 * HOUR_MS); + REASON_MAIN_PREDICTED, 1 * HOUR_MS); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController)); // Fast forward 12 hours @@ -440,28 +440,28 @@ public class AppStandbyControllerTests { setChargingState(mController, false); // Can force to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED, 1 * HOUR_MS); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController)); // Prediction can't override FORCED reason mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED, 1 * HOUR_MS); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_PREDICTED, 1 * HOUR_MS); + REASON_MAIN_PREDICTED, 1 * HOUR_MS); assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController)); // Prediction can't override NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_DEFAULT, 2 * HOUR_MS); + REASON_MAIN_DEFAULT, 2 * HOUR_MS); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED, 2 * HOUR_MS); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController)); // Prediction can't set to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_USAGE, 2 * HOUR_MS); + REASON_MAIN_USAGE, 2 * HOUR_MS); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED, 2 * HOUR_MS); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController)); } @@ -474,7 +474,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // bucketing works after timeout @@ -483,7 +483,7 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_WORKING_SET); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_FREQUENT); } @@ -498,15 +498,15 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_PREDICTED, 1000); + REASON_MAIN_PREDICTED, 1000); assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); + REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); assertBucket(STANDBY_BUCKET_WORKING_SET); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); + REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); assertBucket(STANDBY_BUCKET_FREQUENT); } @@ -527,18 +527,18 @@ public class AppStandbyControllerTests { // 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); + REASON_MAIN_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); + REASON_MAIN_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); + REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_RARE); } @@ -561,7 +561,7 @@ public class AppStandbyControllerTests { // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_ACTIVE); // CheckIdleStates should not change the prediction diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java index 8e5a4180e8abb..fd28b65137f14 100644 --- a/services/usage/java/com/android/server/usage/AppIdleHistory.java +++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java @@ -16,10 +16,12 @@ package com.android.server.usage; -import static android.app.usage.UsageStatsManager.REASON_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_FORCED; -import static android.app.usage.UsageStatsManager.REASON_PREDICTED; -import static android.app.usage.UsageStatsManager.REASON_USAGE; +import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; +import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; 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; @@ -112,8 +114,10 @@ public class AppIdleHistory { // Standby bucket @UsageStatsManager.StandbyBuckets int currentBucket; - // Reason for setting the standby bucket. TODO: Switch to int. - String bucketingReason; + // Reason for setting the standby bucket. The value here is a combination of + // one of UsageStatsManager.REASON_MAIN_* and one (or none) of + // UsageStatsManager.REASON_SUB_*. Also see REASON_MAIN_MASK and REASON_SUB_MASK. + int 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 @@ -212,13 +216,14 @@ public class AppIdleHistory { * @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. * @return */ public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, - int newBucket, long elapsedRealtime, long timeout) { + int newBucket, int usageReason, long elapsedRealtime, long timeout) { // Set the timeout if applicable if (timeout > elapsedRealtime) { // Convert to elapsed timebase @@ -246,10 +251,10 @@ public class AppIdleHistory { if (DEBUG) { Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory .currentBucket - + ", reason=" + appUsageHistory.bucketingReason); + + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); } } - appUsageHistory.bucketingReason = REASON_USAGE; + appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason; return appUsageHistory; } @@ -262,16 +267,17 @@ public class AppIdleHistory { * @param packageName * @param userId * @param newBucket the bucket to set the app to - * @param elapsedRealtime mark as used time if non-zero + * @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 */ public AppUsageHistory reportUsage(String packageName, int userId, int newBucket, - long nowElapsed, long timeout) { + int usageReason, long nowElapsed, long timeout) { ArrayMap userHistory = getUserHistory(userId); AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true); - return reportUsage(history, packageName, newBucket, nowElapsed, timeout); + return reportUsage(history, packageName, newBucket, usageReason, nowElapsed, timeout); } private ArrayMap getUserHistory(int userId) { @@ -293,7 +299,7 @@ public class AppIdleHistory { appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); appUsageHistory.lastPredictedTime = getElapsedTime(0); appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER; - appUsageHistory.bucketingReason = REASON_DEFAULT; + appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; appUsageHistory.lastInformedBucket = -1; appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago userHistory.put(packageName, appUsageHistory); @@ -328,18 +334,18 @@ public class AppIdleHistory { } public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, - int bucket, String reason) { + int bucket, int reason) { ArrayMap userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); appUsageHistory.currentBucket = bucket; appUsageHistory.bucketingReason = reason; - if (reason.startsWith(REASON_PREDICTED)) { + if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) { appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime); } if (DEBUG) { Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket - + ", reason=" + appUsageHistory.bucketingReason); + + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); } } @@ -393,11 +399,11 @@ public class AppIdleHistory { return buckets; } - public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { + public int getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { ArrayMap userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, false); - return appUsageHistory != null ? appUsageHistory.bucketingReason : null; + return appUsageHistory != null ? appUsageHistory.bucketingReason : 0; } public long getElapsedTime(long elapsedRealtime) { @@ -411,11 +417,11 @@ public class AppIdleHistory { elapsedRealtime, true); if (idle) { appUsageHistory.currentBucket = STANDBY_BUCKET_RARE; - appUsageHistory.bucketingReason = REASON_FORCED; + appUsageHistory.bucketingReason = REASON_MAIN_FORCED; } else { appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; // This is to pretend that the app was just used, don't freeze the state anymore. - appUsageHistory.bucketingReason = REASON_USAGE; + appUsageHistory.bucketingReason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION; } return appUsageHistory.currentBucket; } @@ -516,7 +522,7 @@ public class AppIdleHistory { appUsageHistory.currentBucket = currentBucketString == null ? STANDBY_BUCKET_ACTIVE : Integer.parseInt(currentBucketString); - appUsageHistory.bucketingReason = + String bucketingReason = parser.getAttributeValue(null, ATTR_BUCKETING_REASON); appUsageHistory.lastJobRunTime = getLongValue(parser, ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE); @@ -524,8 +530,13 @@ public class AppIdleHistory { 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; + appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT; + if (bucketingReason != null) { + try { + appUsageHistory.bucketingReason = + Integer.parseInt(bucketingReason, 16); + } catch (NumberFormatException nfe) { + } } appUsageHistory.lastInformedBucket = -1; userHistory.put(packageName, appUsageHistory); @@ -574,7 +585,8 @@ public class AppIdleHistory { Long.toString(history.lastPredictedTime)); xml.attribute(null, ATTR_CURRENT_BUCKET, Integer.toString(history.currentBucket)); - xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason); + 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)); @@ -600,7 +612,7 @@ public class AppIdleHistory { } public void dump(IndentingPrintWriter idpw, int userId, String pkg) { - idpw.println("Package idle stats:"); + idpw.println("App Standby States:"); idpw.increaseIndent(); ArrayMap userHistory = mIdleHistory.get(userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -615,24 +627,25 @@ public class AppIdleHistory { continue; } idpw.print("package=" + packageName); - idpw.print(" userId=" + userId); - idpw.print(" lastUsedElapsed="); + idpw.print(" u=" + userId); + idpw.print(" bucket=" + appUsageHistory.currentBucket + + " reason=" + + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason)); + idpw.print(" used="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); - idpw.print(" lastUsedScreenOn="); + idpw.print(" usedScr="); TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); - idpw.print(" lastPredictedTime="); + idpw.print(" lastPred="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw); - idpw.print(" bucketActiveTimeoutTime="); - TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketActiveTimeoutTime, + idpw.print(" activeLeft="); + TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime, idpw); - idpw.print(" bucketWorkingSetTimeoutTime="); - TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketWorkingSetTimeoutTime, + idpw.print(" wsLeft="); + TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime, idpw); - idpw.print(" lastJobRunTime="); + idpw.print(" lastJob="); 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.println(); diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index f40aa5b7ac5c4..e836677e38fc5 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -16,11 +16,20 @@ package com.android.server.usage; -import static android.app.usage.UsageStatsManager.REASON_DEFAULT; -import static android.app.usage.UsageStatsManager.REASON_FORCED; -import static android.app.usage.UsageStatsManager.REASON_PREDICTED; -import static android.app.usage.UsageStatsManager.REASON_TIMEOUT; -import static android.app.usage.UsageStatsManager.REASON_USAGE; +import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT; +import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED; +import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; +import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; +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_USAGE_ACTIVE_TIMEOUT; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND; +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_SYNC_ADAPTER; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE; +import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; @@ -129,8 +138,8 @@ public class AppStandbyController { STANDBY_BUCKET_RARE }; - // Expiration time for predicted bucket - private static final long PREDICTION_TIMEOUT = 12 * ONE_HOUR; + /** Default expiration time for bucket prediction. After this, use thresholds to downgrade. */ + private static final long DEFAULT_PREDICTION_TIMEOUT = 12 * ONE_HOUR; /** * Indicates the maximum wait time for admin data to be available; @@ -187,6 +196,8 @@ public class AppStandbyController { long mNotificationSeenTimeoutMillis; /** Minimum time a system update event should keep the buckets elevated. */ long mSystemUpdateUsageTimeoutMillis; + /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */ + long mPredictionTimeoutMillis; volatile boolean mAppIdleEnabled; boolean mAppIdleTempParoled; @@ -223,24 +234,30 @@ public class AppStandbyController { // Whether the bucket change is because the user has started interacting with the app boolean isUserInteraction; - StandbyUpdateRecord(String pkgName, int userId, int bucket, boolean isInteraction) { + // Reason for bucket change + int reason; + + StandbyUpdateRecord(String pkgName, int userId, int bucket, int reason, + boolean isInteraction) { this.packageName = pkgName; this.userId = userId; this.bucket = bucket; + this.reason = reason; this.isUserInteraction = isInteraction; } public static StandbyUpdateRecord obtain(String pkgName, int userId, - int bucket, boolean isInteraction) { + int bucket, int reason, boolean isInteraction) { synchronized (sStandbyUpdatePool) { final int size = sStandbyUpdatePool.size(); if (size < 1) { - return new StandbyUpdateRecord(pkgName, userId, bucket, isInteraction); + return new StandbyUpdateRecord(pkgName, userId, bucket, reason, isInteraction); } StandbyUpdateRecord r = sStandbyUpdatePool.remove(size - 1); r.packageName = pkgName; r.userId = userId; r.bucket = bucket; + r.reason = reason; r.isUserInteraction = isInteraction; return r; } @@ -339,10 +356,11 @@ public class AppStandbyController { if (!packageName.equals(providerPkgName)) { synchronized (mAppIdleLock) { AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId, - STANDBY_BUCKET_ACTIVE, elapsedRealtime, + STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_SYNC_ADAPTER, + elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); maybeInformListeners(packageName, userId, elapsedRealtime, - appUsage.currentBucket, false); + appUsage.currentBucket, appUsage.bucketingReason, false); } } } catch (PackageManager.NameNotFoundException e) { @@ -497,50 +515,54 @@ public class AppStandbyController { if (isSpecial) { synchronized (mAppIdleLock) { mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, - STANDBY_BUCKET_EXEMPTED, REASON_DEFAULT); + STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT); } maybeInformListeners(packageName, userId, elapsedRealtime, - STANDBY_BUCKET_EXEMPTED, false); + STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT, false); } else { synchronized (mAppIdleLock) { final AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime); - String reason = app.bucketingReason; + int reason = app.bucketingReason; + final int oldMainReason = reason & REASON_MAIN_MASK; // 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)) { + if (oldMainReason == REASON_MAIN_FORCED) { return; } final int oldBucket = app.currentBucket; int newBucket = Math.max(oldBucket, STANDBY_BUCKET_ACTIVE); // Undo EXEMPTED boolean predictionLate = false; // Compute age-based bucket - if (REASON_DEFAULT.equals(app.bucketingReason) - || REASON_USAGE.equals(app.bucketingReason) - || REASON_TIMEOUT.equals(app.bucketingReason) + if (oldMainReason == REASON_MAIN_DEFAULT + || oldMainReason == REASON_MAIN_USAGE + || oldMainReason == REASON_MAIN_TIMEOUT || (predictionLate = predictionTimedOut(app, elapsedRealtime))) { newBucket = getBucketForLocked(packageName, userId, elapsedRealtime); if (DEBUG) { Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket); } - reason = REASON_TIMEOUT; + reason = REASON_MAIN_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; + 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; - reason = REASON_USAGE; + // 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"); } @@ -553,43 +575,41 @@ public class AppStandbyController { mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason); maybeInformListeners(packageName, userId, elapsedRealtime, - newBucket, false); + newBucket, reason, false); } } } } + /** Returns true if there hasn't been a prediction for the app in a while. */ private boolean predictionTimedOut(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) { - return app.bucketingReason != null - && app.bucketingReason.startsWith(REASON_PREDICTED) + return (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED && app.lastPredictedTime > 0 && mAppIdleHistory.getElapsedTime(elapsedRealtime) - - app.lastPredictedTime > PREDICTION_TIMEOUT; - } - - private boolean hasBucketTimeoutPassed(AppIdleHistory.AppUsageHistory app, - long elapsedRealtime) { - final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime); - return app.bucketActiveTimeoutTime < elapsedTimeAdjusted - && app.bucketWorkingSetTimeoutTime < elapsedTimeAdjusted; + - app.lastPredictedTime > mPredictionTimeoutMillis; } + /** Inform listeners if the bucket has changed since it was last reported to listeners */ private void maybeInformListeners(String packageName, int userId, - long elapsedRealtime, int bucket, boolean userStartedInteracting) { + long elapsedRealtime, int bucket, int reason, boolean userStartedInteracting) { synchronized (mAppIdleLock) { - // TODO: fold these into one call + lookup for efficiency if needed if (mAppIdleHistory.shouldInformListeners(packageName, userId, elapsedRealtime, bucket)) { - StandbyUpdateRecord r = StandbyUpdateRecord.obtain(packageName, userId, - bucket, userStartedInteracting); + final StandbyUpdateRecord r = StandbyUpdateRecord.obtain(packageName, userId, + bucket, reason, userStartedInteracting); if (DEBUG) Slog.d(TAG, "Standby bucket for " + packageName + "=" + bucket); - mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, - StandbyUpdateRecord.obtain(packageName, userId, - bucket, userStartedInteracting))); + mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, r)); } } } + /** + * Evaluates next bucket based on time since last used and the bucketing thresholds. + * @param packageName the app + * @param userId the user + * @param elapsedRealtime as the name suggests, current elapsed time + * @return the bucket for the app, based on time since last used + */ @GuardedBy("mAppIdleLock") @StandbyBuckets int getBucketForLocked(String packageName, int userId, long elapsedRealtime) { @@ -675,17 +695,20 @@ public class AppStandbyController { final AppUsageHistory appHistory = mAppIdleHistory.getAppUsageHistory( event.mPackage, userId, elapsedRealtime); final int prevBucket = appHistory.currentBucket; - final String prevBucketReason = appHistory.bucketingReason; + final int prevBucketReason = appHistory.bucketingReason; final long nextCheckTime; + final int subReason = usageEventToSubReason(event.mEventType); + final int reason = REASON_MAIN_USAGE | subReason; 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, + STANDBY_BUCKET_WORKING_SET, subReason, 0, elapsedRealtime + mNotificationSeenTimeoutMillis); nextCheckTime = mNotificationSeenTimeoutMillis; + } else { mAppIdleHistory.reportUsage(appHistory, event.mPackage, - STANDBY_BUCKET_ACTIVE, + STANDBY_BUCKET_ACTIVE, subReason, elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); nextCheckTime = mStrongUsageTimeoutMillis; } @@ -695,9 +718,9 @@ public class AppStandbyController { final boolean userStartedInteracting = appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && prevBucket != appHistory.currentBucket && - prevBucketReason != REASON_USAGE; + (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; maybeInformListeners(event.mPackage, userId, elapsedRealtime, - appHistory.currentBucket, userStartedInteracting); + appHistory.currentBucket, reason, userStartedInteracting); if (previouslyIdle) { notifyBatteryStats(event.mPackage, userId, false); @@ -706,6 +729,17 @@ public class AppStandbyController { } } + private int usageEventToSubReason(int eventType) { + switch (eventType) { + case UsageEvents.Event.MOVE_TO_FOREGROUND: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND; + case UsageEvents.Event.MOVE_TO_BACKGROUND: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND; + case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION; + case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION; + case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN; + default: return 0; + } + } + /** * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle, * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind @@ -731,7 +765,8 @@ public class AppStandbyController { userId, elapsedRealtime); // Inform listeners if necessary if (previouslyIdle != stillIdle) { - maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, false); + maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, + REASON_MAIN_FORCED, false); if (!stillIdle) { notifyBatteryStats(packageName, userId, idle); } @@ -962,11 +997,11 @@ public class AppStandbyController { } void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - String reason, long elapsedRealtime) { + int reason, long elapsedRealtime) { synchronized (mAppIdleLock) { AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime); - boolean predicted = reason != null && reason.startsWith(REASON_PREDICTED); + boolean predicted = (reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED; // Don't allow changing bucket if higher than ACTIVE if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return; @@ -979,7 +1014,7 @@ public class AppStandbyController { } // If the bucket was forced, don't allow prediction to override - if (app.bucketingReason.equals(REASON_FORCED) && predicted) return; + if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_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 @@ -989,14 +1024,18 @@ public class AppStandbyController { if (newBucket > STANDBY_BUCKET_ACTIVE && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) { newBucket = STANDBY_BUCKET_ACTIVE; - reason = REASON_USAGE; + 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; - reason = REASON_USAGE; + if (app.currentBucket != newBucket) { + reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT; + } else { + reason = app.bucketingReason; + } if (DEBUG) { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } @@ -1006,7 +1045,7 @@ public class AppStandbyController { mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason); } - maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, false); + maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, reason, false); } @VisibleForTesting @@ -1106,11 +1145,12 @@ public class AppStandbyController { return packageName != null && packageName.equals(activeScorer); } - void informListeners(String packageName, int userId, int bucket, boolean userInteraction) { + void informListeners(String packageName, int userId, int bucket, int reason, + boolean userInteraction) { final boolean idle = bucket >= STANDBY_BUCKET_RARE; synchronized (mPackageAccessListeners) { for (AppIdleStateChangeListener listener : mPackageAccessListeners) { - listener.onAppIdleStateChanged(packageName, userId, idle, bucket); + listener.onAppIdleStateChanged(packageName, userId, idle, bucket, reason); if (userInteraction) { listener.onUserInteractionStarted(packageName, userId); } @@ -1188,7 +1228,8 @@ public class AppStandbyController { if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) { // Mark app as used for 2 hours. After that it can timeout to whatever the // past usage pattern was. - mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE, 0, + mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE, + REASON_SUB_USAGE_SYSTEM_UPDATE, 0, elapsedRealtime + mSystemUpdateUsageTimeoutMillis); } } @@ -1369,7 +1410,8 @@ public class AppStandbyController { switch (msg.what) { case MSG_INFORM_LISTENERS: StandbyUpdateRecord r = (StandbyUpdateRecord) msg.obj; - informListeners(r.packageName, r.userId, r.bucket, r.isUserInteraction); + informListeners(r.packageName, r.userId, r.bucket, r.reason, + r.isUserInteraction); r.recycle(); break; @@ -1477,7 +1519,7 @@ public class AppStandbyController { "notification_seen_duration"; private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION = "system_update_usage_duration"; - + private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout"; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -1547,6 +1589,9 @@ public class AppStandbyController { mSystemUpdateUsageTimeoutMillis = mParser.getDurationMillis (KEY_SYSTEM_UPDATE_HOLD_DURATION, COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR); + mPredictionTimeoutMillis = mParser.getDurationMillis + (KEY_PREDICTION_TIMEOUT, + COMPRESS_TIME ? 10 * ONE_MINUTE : DEFAULT_PREDICTION_TIMEOUT); } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index dedf967c0ffab..43ac58a54169d 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -120,10 +120,10 @@ public class UsageStatsService extends SystemService implements new UsageStatsManagerInternal.AppIdleStateChangeListener() { @Override public void onAppIdleStateChanged(String packageName, int userId, boolean idle, - int bucket) { + int bucket, int reason) { Event event = new Event(); event.mEventType = Event.STANDBY_BUCKET_CHANGED; - event.mBucket = bucket; + event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF); event.mPackage = packageName; // This will later be converted to system time. event.mTimeStamp = SystemClock.elapsedRealtime(); @@ -741,9 +741,9 @@ public class UsageStatsService extends SystemService implements throw re.rethrowFromSystemServer(); } final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final String reason = shellCaller - ? UsageStatsManager.REASON_FORCED - : UsageStatsManager.REASON_PREDICTED + ":" + callingUid; + final int reason = shellCaller + ? UsageStatsManager.REASON_MAIN_FORCED + : UsageStatsManager.REASON_MAIN_PREDICTED; final long token = Binder.clearCallingIdentity(); try { // Caller cannot set their own standby state @@ -798,9 +798,9 @@ public class UsageStatsService extends SystemService implements throw re.rethrowFromSystemServer(); } final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final String reason = shellCaller - ? UsageStatsManager.REASON_FORCED - : UsageStatsManager.REASON_PREDICTED + ":" + callingUid; + final int reason = shellCaller + ? UsageStatsManager.REASON_MAIN_FORCED + : UsageStatsManager.REASON_MAIN_PREDICTED; final long token = Binder.clearCallingIdentity(); try { final long elapsedRealtime = SystemClock.elapsedRealtime(); diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index d1ed59921d4d3..5e7e80db410be 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -26,10 +26,7 @@ import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.content.res.Configuration; -import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Log; -import android.util.LogWriter; import java.io.IOException; import java.net.ProtocolException; @@ -175,7 +172,7 @@ final class UsageStatsXmlV1 { event.mShortcutId = (id != null) ? id.intern() : null; break; case UsageEvents.Event.STANDBY_BUCKET_CHANGED: - event.mBucket = XmlUtils.readIntAttribute(parser, STANDBY_BUCKET_ATTR, 0); + event.mBucketAndReason = XmlUtils.readIntAttribute(parser, STANDBY_BUCKET_ATTR, 0); break; } @@ -281,8 +278,8 @@ final class UsageStatsXmlV1 { } break; case UsageEvents.Event.STANDBY_BUCKET_CHANGED: - if (event.mBucket != 0) { - XmlUtils.writeIntAttribute(xml, STANDBY_BUCKET_ATTR, event.mBucket); + if (event.mBucketAndReason != 0) { + XmlUtils.writeIntAttribute(xml, STANDBY_BUCKET_ATTR, event.mBucketAndReason); } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 8afc5112d1d88..3fbcd8116d9d9 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -520,7 +520,8 @@ class UserUsageStatsService { pw.printPair("shortcutId", event.mShortcutId); } if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) { - pw.printPair("standbyBucket", event.mBucket); + pw.printPair("standbyBucket", event.getStandbyBucket()); + pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason())); } pw.printHexPair("flags", event.mFlags); pw.println();