Global app standby flag

Disable app standby features if app_standby_enabled
is set to 0. This allows for UI and experiments to
control the feature.

Bug: 70655630
Test: adb shell settings put global app_standby_enabled 0
      adb shell dumpsys usagestats
      adb shell am get-standby-bucket <packagename>

Change-Id: Id6c62b078e52040767100f2997832cc586bb0806
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index abc16ce..1ad1527 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9584,9 +9584,10 @@
         /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
-         *
+         * <p>
          * "idle_duration=5000,parole_interval=4500"
-         *
+         * <p>
+         * All durations are in millis.
          * The following keys are supported:
          *
          * <pre>
@@ -9736,6 +9737,15 @@
         public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
 
         /**
+         * Whether or not App Standby feature is enabled. This controls throttling of apps
+         * based on usage patterns and predictions.
+         * Type: int (0 for false, 1 for true)
+         * Default: 1
+         * @hide
+         */
+        public static final java.lang.String APP_STANDBY_ENABLED = "app_standby_enabled";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 907a1b8..d9b1e3a 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -106,6 +106,7 @@
                     Settings.Global.APN_DB_UPDATE_CONTENT_URL,
                     Settings.Global.APN_DB_UPDATE_METADATA_URL,
                     Settings.Global.APP_IDLE_CONSTANTS,
+                    Settings.Global.APP_STANDBY_ENABLED,
                     Settings.Global.ASSISTED_GPS_ENABLED,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index 620922a..a1f1810 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,6 +16,15 @@
 
 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.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;
 import android.util.ArrayMap;
@@ -197,14 +206,14 @@
                 + (elapsedRealtime - mElapsedSnapshot);
         appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
         appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
-        if (appUsageHistory.currentBucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
             if (DEBUG) {
                 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                         + ", reason=" + appUsageHistory.bucketingReason);
             }
         }
-        appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
+        appUsageHistory.bucketingReason = REASON_USAGE;
 
         return appUsageHistory.currentBucket;
     }
@@ -213,15 +222,15 @@
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
-        if (appUsageHistory.currentBucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
             if (DEBUG) {
                 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                         + ", reason=" + appUsageHistory.bucketingReason);
             }
         }
         // TODO: Should this be a different reason for partial usage?
-        appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
+        appUsageHistory.bucketingReason = REASON_USAGE;
 
         return appUsageHistory.currentBucket;
     }
@@ -279,8 +288,8 @@
             appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
             appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
             appUsageHistory.lastPredictedTime = getElapsedTime(0);
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
-            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
+            appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
+            appUsageHistory.bucketingReason = REASON_DEFAULT;
             appUsageHistory.lastInformedBucket = -1;
             userHistory.put(packageName, appUsageHistory);
         }
@@ -298,7 +307,7 @@
         if (appUsageHistory == null) {
             return false; // Default to not idle
         } else {
-            return appUsageHistory.currentBucket >= UsageStatsManager.STANDBY_BUCKET_RARE;
+            return appUsageHistory.currentBucket >= STANDBY_BUCKET_RARE;
             // Whether or not it's passed will now be externally calculated and the
             // bucket will be pushed to the history using setAppStandbyBucket()
             //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
@@ -320,7 +329,7 @@
                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
         appUsageHistory.currentBucket = bucket;
         appUsageHistory.bucketingReason = reason;
-        if (reason.startsWith(UsageStatsManager.REASON_PREDICTED)) {
+        if (reason.startsWith(REASON_PREDICTED)) {
             appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime);
         }
         if (DEBUG) {
@@ -336,12 +345,14 @@
         return appUsageHistory.currentBucket;
     }
 
-    public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) {
+    public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime,
+            boolean appIdleEnabled) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         int size = userHistory.size();
         HashMap<String, Integer> buckets = new HashMap<>(size);
         for (int i = 0; i < size; i++) {
-            buckets.put(userHistory.keyAt(i), userHistory.valueAt(i).currentBucket);
+            buckets.put(userHistory.keyAt(i),
+                    appIdleEnabled ? userHistory.valueAt(i).currentBucket : STANDBY_BUCKET_ACTIVE);
         }
         return buckets;
     }
@@ -363,12 +374,12 @@
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
         if (idle) {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_RARE;
-            appUsageHistory.bucketingReason = UsageStatsManager.REASON_FORCED;
+            appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
+            appUsageHistory.bucketingReason = REASON_FORCED;
         } else {
-            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
             // This is to pretend that the app was just used, don't freeze the state anymore.
-            appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
+            appUsageHistory.bucketingReason = REASON_USAGE;
         }
         return appUsageHistory.currentBucket;
     }
@@ -471,12 +482,12 @@
                         String currentBucketString = parser.getAttributeValue(null,
                                 ATTR_CURRENT_BUCKET);
                         appUsageHistory.currentBucket = currentBucketString == null
-                                ? UsageStatsManager.STANDBY_BUCKET_ACTIVE
+                                ? STANDBY_BUCKET_ACTIVE
                                 : Integer.parseInt(currentBucketString);
                         appUsageHistory.bucketingReason =
                                 parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
                         if (appUsageHistory.bucketingReason == null) {
-                            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
+                            appUsageHistory.bucketingReason = REASON_DEFAULT;
                         }
                         appUsageHistory.lastInformedBucket = -1;
                         userHistory.put(packageName, appUsageHistory);
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index cc0259d..9b588fa 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -162,12 +162,14 @@
     long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
     long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
 
-    boolean mAppIdleEnabled;
+    volatile boolean mAppIdleEnabled;
     boolean mAppIdleTempParoled;
     boolean mCharging;
     private long mLastAppIdleParoledTime;
     private boolean mSystemServicesReady = false;
 
+    private final DeviceStateReceiver mDeviceStateReceiver;
+
     private volatile boolean mPendingOneTimeCheckIdleStates;
 
     private final AppStandbyHandler mHandler;
@@ -178,7 +180,7 @@
     private AppWidgetManager mAppWidgetManager;
     private PowerManager mPowerManager;
     private PackageManager mPackageManager;
-    private Injector mInjector;
+    Injector mInjector;
 
 
     AppStandbyController(Context context, Looper looper) {
@@ -190,14 +192,13 @@
         mContext = mInjector.getContext();
         mHandler = new AppStandbyHandler(mInjector.getLooper());
         mPackageManager = mContext.getPackageManager();
-        mAppIdleEnabled = mInjector.isAppIdleEnabled();
+        mDeviceStateReceiver = new DeviceStateReceiver();
 
-        if (mAppIdleEnabled) {
-            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
-            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
-        }
+        IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+        deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(mDeviceStateReceiver, deviceStates);
+
         synchronized (mAppIdleLock) {
             mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
                     mInjector.elapsedRealtime());
@@ -213,9 +214,14 @@
                 null, mHandler);
     }
 
+    void setAppIdleEnabled(boolean enabled) {
+        mAppIdleEnabled = enabled;
+    }
+
     public void onBootPhase(int phase) {
         mInjector.onBootPhase(phase);
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            setAppIdleEnabled(mInjector.isAppIdleEnabled());
             // Observe changes to the threshold
             SettingsObserver settingsObserver = new SettingsObserver(mHandler);
             settingsObserver.registerObserver();
@@ -240,6 +246,8 @@
     }
 
     void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
+        if (!mAppIdleEnabled) return;
+
         // Get sync adapters for the authority
         String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
                 authority, userId);
@@ -295,6 +303,7 @@
     }
 
     boolean isParoledOrCharging() {
+        if (!mAppIdleEnabled) return true;
         synchronized (mAppIdleLock) {
             return mAppIdleTempParoled || mCharging;
         }
@@ -520,6 +529,7 @@
     }
 
     void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+        if (!mAppIdleEnabled) return;
         synchronized (mAppIdleLock) {
             // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
             // about apps that are on some kind of whitelist anyway.
@@ -559,6 +569,8 @@
      * required.
      */
     void forceIdleState(String packageName, int userId, boolean idle) {
+        if (!mAppIdleEnabled) return;
+
         final int appId = getAppId(packageName);
         if (appId < 0) return;
         final long elapsedRealtime = mInjector.elapsedRealtime();
@@ -763,7 +775,7 @@
     }
 
     void setAppIdleAsync(String packageName, boolean idle, int userId) {
-        if (packageName == null) return;
+        if (packageName == null || !mAppIdleEnabled) return;
 
         mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
                 .sendToTarget();
@@ -771,8 +783,8 @@
 
     @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
             long elapsedRealtime, boolean shouldObfuscateInstantApps) {
-        if (shouldObfuscateInstantApps &&
-                mInjector.isPackageEphemeral(userId, packageName)) {
+        if (!mAppIdleEnabled || (shouldObfuscateInstantApps
+                && mInjector.isPackageEphemeral(userId, packageName))) {
             return STANDBY_BUCKET_ACTIVE;
         }
 
@@ -783,7 +795,7 @@
 
     public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) {
         synchronized (mAppIdleLock) {
-            return mAppIdleHistory.getAppStandbyBuckets(userId, elapsedRealtime);
+            return mAppIdleHistory.getAppStandbyBuckets(userId, elapsedRealtime, mAppIdleEnabled);
         }
     }
 
@@ -1058,8 +1070,11 @@
         }
 
         boolean isAppIdleEnabled() {
-            return mContext.getResources().getBoolean(
+            final boolean buildFlag = mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_enableAutoPowerModes);
+            final boolean runtimeFlag = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.APP_STANDBY_ENABLED, 1) == 1;
+            return buildFlag && runtimeFlag;
         }
 
         boolean isCharging() {
@@ -1130,7 +1145,7 @@
                     break;
 
                 case MSG_CHECK_IDLE_STATES:
-                    if (checkIdleStates(msg.arg1)) {
+                    if (checkIdleStates(msg.arg1) && mAppIdleEnabled) {
                         mHandler.sendMessageDelayed(mHandler.obtainMessage(
                                 MSG_CHECK_IDLE_STATES, msg.arg1, 0),
                                 mCheckIdleIntervalMillis);
@@ -1229,6 +1244,8 @@
         void registerObserver() {
             mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.APP_IDLE_CONSTANTS), false, this);
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.APP_STANDBY_ENABLED), false, this);
         }
 
         @Override
@@ -1238,15 +1255,27 @@
         }
 
         void updateSettings() {
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "appidle=" + Settings.Global.getString(mContext.getContentResolver(),
+                                Settings.Global.APP_STANDBY_ENABLED));
+                Slog.d(TAG, "appidleconstants=" + Settings.Global.getString(
+                        mContext.getContentResolver(),
+                        Settings.Global.APP_IDLE_CONSTANTS));
+            }
+            // Check if app_idle_enabled has changed
+            setAppIdleEnabled(mInjector.isAppIdleEnabled());
+
+            // Look at global settings for this.
+            // TODO: Maybe apply different thresholds for different users.
+            try {
+                mParser.setString(mInjector.getAppIdleSettings());
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+                // fallthrough, mParser is empty and all defaults will be returned.
+            }
+
             synchronized (mAppIdleLock) {
-                // Look at global settings for this.
-                // TODO: Maybe apply different thresholds for different users.
-                try {
-                    mParser.setString(mInjector.getAppIdleSettings());
-                } catch (IllegalArgumentException e) {
-                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
-                    // fallthrough, mParser is empty and all defaults will be returned.
-                }
 
                 // Default: 24 hours between paroles
                 mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,