diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index e14ca990a2e50..b6f85b2d06bfd 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -23,6 +23,8 @@ 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_DEFAULT_APP_UPDATE; +import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY; import static android.app.usage.UsageStatsManager.REASON_SUB_MASK; import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT; @@ -73,7 +75,6 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.database.ContentObserver; import android.hardware.display.DisplayManager; -import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.os.BatteryManager; import android.os.BatteryStats; @@ -304,10 +305,7 @@ public class AppStandbyController implements AppStandbyInternal { private final AppStandbyHandler mHandler; private final Context mContext; - // TODO: Provide a mechanism to set an external bucketing service - private AppWidgetManager mAppWidgetManager; - private ConnectivityManager mConnectivityManager; private PackageManager mPackageManager; Injector mInjector; @@ -411,7 +409,6 @@ public class AppStandbyController implements AppStandbyInternal { settingsObserver.updateSettings(); mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class); - mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); mInjector.registerDisplayListener(mDisplayListener, mHandler); synchronized (mAppIdleLock) { @@ -1519,6 +1516,38 @@ public class AppStandbyController implements AppStandbyInternal { } } + /** + * Remove an app from the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} + * bucket if it was forced into the bucket by the system because it was buggy. + */ + @VisibleForTesting + void maybeUnrestrictBuggyApp(String packageName, int userId) { + synchronized (mAppIdleLock) { + final long elapsedRealtime = mInjector.elapsedRealtime(); + final AppIdleHistory.AppUsageHistory app = + mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime); + if (app.currentBucket != STANDBY_BUCKET_RESTRICTED + || (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_FORCED_BY_SYSTEM) { + return; + } + + final int newBucket; + final int newReason; + if ((app.bucketingReason & REASON_SUB_MASK) == REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY) { + // If bugginess was the only reason the app should be restricted, then lift it out. + newBucket = STANDBY_BUCKET_RARE; + newReason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_UPDATE; + } else { + // There's another reason the app was restricted. Remove the buggy bit and call + // it a day. + newBucket = STANDBY_BUCKET_RESTRICTED; + newReason = app.bucketingReason & ~REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY; + } + mAppIdleHistory.setAppStandbyBucket( + packageName, userId, elapsedRealtime, newBucket, newReason); + } + } + private class PackageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -1528,10 +1557,14 @@ public class AppStandbyController implements AppStandbyInternal { clearCarrierPrivilegedApps(); } if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) || - Intent.ACTION_PACKAGE_ADDED.equals(action)) - && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(), - getSendingUserId()); + Intent.ACTION_PACKAGE_ADDED.equals(action))) { + final String pkgName = intent.getData().getSchemeSpecificPart(); + final int userId = getSendingUserId(); + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + maybeUnrestrictBuggyApp(pkgName, userId); + } else { + clearAppIdleForPackage(pkgName, userId); + } } } } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 0d66198db0388..0a67802fe92bf 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -203,6 +203,16 @@ public final class UsageStatsManager { /** @hide */ public static final int REASON_SUB_MASK = 0x00FF; + /** + * The reason for using the default main reason is unknown or undefined. + * @hide + */ + public static final int REASON_SUB_DEFAULT_UNDEFINED = 0x0000; + /** + * The app was updated. + * @hide + */ + public static final int REASON_SUB_DEFAULT_APP_UPDATE = 0x0001; /** * The app was interacted with in some way by the system. * @hide @@ -1069,6 +1079,14 @@ public final class UsageStatsManager { switch (standbyReason & REASON_MAIN_MASK) { case REASON_MAIN_DEFAULT: sb.append("d"); + switch (subReason) { + case REASON_SUB_DEFAULT_UNDEFINED: + // Historically, undefined didn't have a string, so don't add anything here. + break; + case REASON_SUB_DEFAULT_APP_UPDATE: + sb.append("-au"); + break; + } break; case REASON_MAIN_FORCED_BY_SYSTEM: sb.append("s"); 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 f070bff6f9069..05cd26dfd7376 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -1208,6 +1208,80 @@ public class AppStandbyControllerTests { STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); } + public void testAppUpdateOnRestrictedBucketStatus() { + // Updates shouldn't change bucket if the app timed out. + // Way past all timeouts. App times out into RESTRICTED bucket. + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + // Updates shouldn't change bucket if the app was forced by the system for a non-buggy + // reason. + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM + | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE); + + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + // Updates should change bucket if the app was forced by the system for a buggy reason. + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); + + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); + assertNotEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1)); + + // Updates shouldn't change bucket if the app was forced by the system for more than just + // a buggy reason. + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE + | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); + + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, + getStandbyBucketReason(PACKAGE_1)); + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + + // Updates shouldn't change bucket if the app was forced by the user. + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4; + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, + REASON_MAIN_FORCED_BY_USER); + + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2); + assertBucket(STANDBY_BUCKET_RESTRICTED); + mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); + } + private String getAdminAppsStr(int userId) { return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId)); }