From 75e1da7bebf476b7a71fd6ec69dc9a6e61712ba1 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Wed, 26 Feb 2020 17:18:03 -0800 Subject: [PATCH] Unrestrict updated buggy apps. If an app is put into the RESTRICTED bucket because the system thinks it's buggy, we can bring it out of the bucket when the app is updated, assuming that the bug is fixed in the new version. If there are other reasons that the app is restricted, then we don't bring it out. Bug: 149507105 Test: atest FrameworksServicesTests:AppIdleHistoryTests Test: atest FrameworksServicesTests:AppStandbyControllerTests Change-Id: Ie4538dde3b367d32f4260e8301afa0556b845b2a --- .../server/usage/AppStandbyController.java | 51 ++++++++++--- .../android/app/usage/UsageStatsManager.java | 18 +++++ .../usage/AppStandbyControllerTests.java | 74 +++++++++++++++++++ 3 files changed, 134 insertions(+), 9 deletions(-) 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)); }