diff options
5 files changed, 206 insertions, 32 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8c2a8ae9080a..c9e86216fd16 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9896,6 +9896,15 @@ public final class Settings { public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled"; /** + * Whether or not to enable Forced App Standby on small battery devices. + * Type: int (0 for false, 1 for true) + * Default: 0 + * @hide + */ + public static final String FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED + = "forced_app_standby_for_small_battery_enabled"; + + /** * Whether or not Network Watchlist feature is enabled. * Type: int (0 for false, 1 for true) * Default: 0 diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto index 8753bf768321..c9f7d52ae83f 100644 --- a/core/proto/android/server/forceappstandbytracker.proto +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -41,4 +41,13 @@ message ForceAppStandbyTrackerProto { // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND. repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5; + + // Whether device is a small battery device + optional bool is_small_battery_device = 6; + + // Whether force app standby for small battery device setting is enabled + optional bool force_all_apps_standby_for_small_battery = 7; + + // Whether device is charging + optional bool is_charging = 8; } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 37b318065918..1482bafb3e7a 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -213,6 +213,7 @@ public class SettingsBackupTest { Settings.Global.FANCY_IME_ANIMATIONS, Settings.Global.FORCE_ALLOW_ON_EXTERNAL, Settings.Global.FORCED_APP_STANDBY_ENABLED, + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, Settings.Global.FSTRIM_MANDATORY_INTERVAL, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, Settings.Global.GLOBAL_HTTP_PROXY_HOST, diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java index a75a3675f7f9..0b3bc7249847 100644 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java @@ -26,6 +26,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; +import android.net.Uri; +import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -89,6 +91,9 @@ public class ForceAppStandbyTracker { private final MyHandler mHandler; + @VisibleForTesting + FeatureFlagsObserver mFlagsObserver; + /** * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed. */ @@ -113,14 +118,36 @@ public class ForceAppStandbyTracker { @GuardedBy("mLock") boolean mStarted; + /** + * Only used for small battery use-case. + */ + @GuardedBy("mLock") + boolean mIsPluggedIn; + + @GuardedBy("mLock") + boolean mBatterySaverEnabled; + + /** + * True if the forced app standby is currently enabled + */ + @GuardedBy("mLock") + boolean mForceAllAppsStandby; + + /** + * True if the forced app standby for small battery devices feature is enabled in settings + */ @GuardedBy("mLock") - boolean mForceAllAppsStandby; // True if device is in extreme battery saver mode + boolean mForceAllAppStandbyForSmallBattery; + /** + * True if the forced app standby feature is enabled in settings + */ @GuardedBy("mLock") - boolean mForcedAppStandbyEnabled; // True if the forced app standby feature is enabled + boolean mForcedAppStandbyEnabled; - private class FeatureFlagObserver extends ContentObserver { - FeatureFlagObserver() { + @VisibleForTesting + class FeatureFlagsObserver extends ContentObserver { + FeatureFlagsObserver() { super(null); } @@ -128,6 +155,9 @@ public class ForceAppStandbyTracker { mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED), false, this); + + mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this); } boolean isForcedAppStandbyEnabled() { @@ -135,20 +165,43 @@ public class ForceAppStandbyTracker { Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1; } + boolean isForcedAppStandbyForSmallBatteryEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1; + } + @Override - public void onChange(boolean selfChange) { - final boolean enabled = isForcedAppStandbyEnabled(); - synchronized (mLock) { - if (mForcedAppStandbyEnabled == enabled) { - return; + public void onChange(boolean selfChange, Uri uri) { + if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) { + final boolean enabled = isForcedAppStandbyEnabled(); + synchronized (mLock) { + if (mForcedAppStandbyEnabled == enabled) { + return; + } + mForcedAppStandbyEnabled = enabled; + if (DEBUG) { + Slog.d(TAG,"Forced app standby feature flag changed: " + + mForcedAppStandbyEnabled); + } } - mForcedAppStandbyEnabled = enabled; - if (DEBUG) { - Slog.d(TAG, - "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled); + mHandler.notifyForcedAppStandbyFeatureFlagChanged(); + } else if (Settings.Global.getUriFor( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) { + final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled(); + synchronized (mLock) { + if (mForceAllAppStandbyForSmallBattery == enabled) { + return; + } + mForceAllAppStandbyForSmallBattery = enabled; + if (DEBUG) { + Slog.d(TAG, "Forced app standby for small battery feature flag changed: " + + mForceAllAppStandbyForSmallBattery); + } + updateForceAllAppStandbyState(); } + } else { + Slog.w(TAG, "Unexpected feature flag uri encountered: " + uri); } - mHandler.notifyFeatureFlagChanged(); } } @@ -289,9 +342,11 @@ public class ForceAppStandbyTracker { mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager()); mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService()); mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal()); - final FeatureFlagObserver flagObserver = new FeatureFlagObserver(); - flagObserver.register(); - mForcedAppStandbyEnabled = flagObserver.isForcedAppStandbyEnabled(); + mFlagsObserver = new FeatureFlagsObserver(); + mFlagsObserver.register(); + mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled(); + mForceAllAppStandbyForSmallBattery = + mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled(); try { mIActivityManager.registerUidObserver(new UidObserver(), @@ -306,16 +361,24 @@ public class ForceAppStandbyTracker { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); mContext.registerReceiver(new MyReceiver(), filter); refreshForcedAppStandbyUidPackagesLocked(); mPowerManagerInternal.registerLowPowerModeObserver( ServiceType.FORCE_ALL_APPS_STANDBY, - (state) -> updateForceAllAppsStandby(state.batterySaverEnabled)); + (state) -> { + synchronized (mLock) { + mBatterySaverEnabled = state.batterySaverEnabled; + updateForceAllAppStandbyState(); + } + }); + + mBatterySaverEnabled = mPowerManagerInternal.getLowPowerState( + ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled; - updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState( - ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled); + updateForceAllAppStandbyState(); } } @@ -340,6 +403,11 @@ public class ForceAppStandbyTracker { return LocalServices.getService(PowerManagerInternal.class); } + @VisibleForTesting + boolean isSmallBatteryDevice() { + return ActivityManager.isSmallBatteryDevice(); + } + /** * Update {@link #mRunAnyRestrictedPackages} with the current app ops state. */ @@ -369,18 +437,26 @@ public class ForceAppStandbyTracker { } } - /** - * Update {@link #mForceAllAppsStandby} and notifies the listeners. - */ - void updateForceAllAppsStandby(boolean enable) { + private void updateForceAllAppStandbyState() { synchronized (mLock) { - if (enable == mForceAllAppsStandby) { - return; + if (mForceAllAppStandbyForSmallBattery && isSmallBatteryDevice()) { + toggleForceAllAppsStandbyLocked(!mIsPluggedIn); + } else { + toggleForceAllAppsStandbyLocked(mBatterySaverEnabled); } - mForceAllAppsStandby = enable; + } + } - mHandler.notifyForceAllAppsStandbyChanged(); + /** + * Update {@link #mForceAllAppsStandby} and notifies the listeners. + */ + private void toggleForceAllAppsStandbyLocked(boolean enable) { + if (enable == mForceAllAppsStandby) { + return; } + mForceAllAppsStandby = enable; + + mHandler.notifyForceAllAppsStandbyChanged(); } private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { @@ -515,6 +591,11 @@ public class ForceAppStandbyTracker { if (userId > 0) { mHandler.doUserRemoved(userId); } + } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + synchronized (mLock) { + mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); + } + updateForceAllAppStandbyState(); } } } @@ -533,7 +614,7 @@ public class ForceAppStandbyTracker { private static final int MSG_TEMP_WHITELIST_CHANGED = 5; private static final int MSG_FORCE_ALL_CHANGED = 6; private static final int MSG_USER_REMOVED = 7; - private static final int MSG_FEATURE_FLAG_CHANGED = 8; + private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8; public MyHandler(Looper looper) { super(looper); @@ -563,8 +644,8 @@ public class ForceAppStandbyTracker { obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); } - public void notifyFeatureFlagChanged() { - obtainMessage(MSG_FEATURE_FLAG_CHANGED).sendToTarget(); + public void notifyForcedAppStandbyFeatureFlagChanged() { + obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget(); } public void doUserRemoved(int userId) { @@ -618,7 +699,7 @@ public class ForceAppStandbyTracker { l.onForceAllAppsStandbyChanged(sender); } return; - case MSG_FEATURE_FLAG_CHANGED: + case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED: // Feature flag for forced app standby changed. final boolean unblockAlarms; synchronized (mLock) { @@ -842,6 +923,18 @@ public class ForceAppStandbyTracker { pw.println(isForceAllAppsStandbyEnabled()); pw.print(indent); + pw.print("Small Battery Device: "); + pw.println(isSmallBatteryDevice()); + + pw.print(indent); + pw.print("Force all apps standby for small battery device: "); + pw.println(mForceAllAppStandbyForSmallBattery); + + pw.print(indent); + pw.print("Plugged In: "); + pw.println(mIsPluggedIn); + + pw.print(indent); pw.print("Foreground uids: ["); String sep = ""; @@ -880,6 +973,11 @@ public class ForceAppStandbyTracker { final long token = proto.start(fieldId); proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby); + proto.write(ForceAppStandbyTrackerProto.IS_SMALL_BATTERY_DEVICE, + isSmallBatteryDevice()); + proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY, + mForceAllAppStandbyForSmallBattery); + proto.write(ForceAppStandbyTrackerProto.IS_CHARGING, mIsPluggedIn); for (int i = 0; i < mForegroundUids.size(); i++) { if (mForegroundUids.valueAt(i)) { diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java index 429dd8fd1d3d..de54e52d4d0a 100644 --- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java @@ -42,6 +42,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; import android.os.PowerManager.ServiceType; @@ -51,6 +52,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; +import android.provider.Settings.Global; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContentResolver; @@ -105,6 +107,9 @@ public class ForceAppStandbyTrackerTest { PowerManagerInternal injectPowerManagerInternal() { return mMockPowerManagerInternal; } + + @Override + boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; }; } private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1; @@ -144,6 +149,7 @@ public class ForceAppStandbyTrackerTest { private FakeSettingsProvider mFakeSettingsProvider; private boolean mPowerSaveMode; + private boolean mIsSmallBatteryDevice; private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet(); @@ -232,6 +238,7 @@ public class ForceAppStandbyTrackerTest { assertNotNull(mAppOpsCallback); assertNotNull(mPowerSaveObserver); assertNotNull(mReceiver); + assertNotNull(instance.mFlagsObserver); } private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException { @@ -852,6 +859,56 @@ public class ForceAppStandbyTrackerTest { assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); } + @Test + public void testSmallBatteryAndPluggedIn() throws Exception { + // This is a small battery device + mIsSmallBatteryDevice = true; + + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + // Setting/experiment for all app standby for small battery is enabled + Global.putInt(mMockContentResolver, Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 1); + instance.mFlagsObserver.onChange(true, + Global.getUriFor(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery is plugged in, force app standby is disabled + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); + mReceiver.onReceive(mMockContext, intent); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + // When battery stops plugged in, force app standby is enabled + mReceiver.onReceive(mMockContext, new Intent(Intent.ACTION_BATTERY_CHANGED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + } + + @Test + public void testNotSmallBatteryAndPluggedIn() throws Exception { + // Not a small battery device, so plugged in status should not affect forced app standby + mIsSmallBatteryDevice = false; + + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery is plugged in, force app standby is unaffected + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); + mReceiver.onReceive(mMockContext, intent); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery stops plugged in, force app standby is unaffected + mReceiver.onReceive(mMockContext, new Intent(Intent.ACTION_BATTERY_CHANGED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + } + static int[] array(int... appIds) { Arrays.sort(appIds); return appIds; |