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,