Added runtime flag for forced-app-standby

Added a global setting which can be configured to get metrics on the
battery savings from this feature.

Test: atest \
FrameworksServicesTests:\
com.android.server.job.BackgroundRestrictionsTest

Fixes: 70579515
Change-Id: I541f2aac39847f9bf372240cafc4e0f0e5ebdc86
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2ec4906..66d1bba 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9802,6 +9802,14 @@
         public static final java.lang.String APP_STANDBY_ENABLED = "app_standby_enabled";
 
         /**
+         * Feature flag to enable or disable the Forced App Standby feature.
+         * Type: int (0 for false, 1 for true)
+         * Default: 1
+         * @hide
+         */
+        public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled";
+
+        /**
          * Whether or not Network Watchlist feature is enabled.
          * Type: int (0 for false, 1 for true)
          * Default: 0
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 7cfedc8..68789f3 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -211,6 +211,7 @@
                     Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
                     Settings.Global.FANCY_IME_ANIMATIONS,
                     Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
+                    Settings.Global.FORCED_APP_STANDBY_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/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a024d5a..86063c3 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1162,12 +1162,12 @@
             // ignored; both services live in system_server
         }
         publishBinderService(Context.ALARM_SERVICE, mService);
-        mForceAppStandbyTracker.start();
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            mForceAppStandbyTracker.start();
             mConstants.start(getContext().getContentResolver());
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
             mLocalDeviceIdleController
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 61d3833..8776f3a 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -33,8 +34,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 
@@ -59,15 +62,16 @@
  * - Global "force all apps standby" mode enforced by battery saver.
  *
  * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
- *    each callback. For example, even when an UID comes into the foreground, if it wasn't
- *    originally restricted, then there's no need to send an event.
- *    Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ * each callback. For example, even when an UID comes into the foreground, if it wasn't
+ * originally restricted, then there's no need to send an event.
+ * Doing this would be error-prone, so we punt it for now, but we should revisit it later.
  *
  * Test:
-   atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+ * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
  */
 public class ForceAppStandbyTracker {
     private static final String TAG = "ForceAppStandbyTracker";
+    private static final boolean DEBUG = false;
 
     @GuardedBy("ForceAppStandbyTracker.class")
     private static ForceAppStandbyTracker sInstance;
@@ -107,7 +111,43 @@
     boolean mStarted;
 
     @GuardedBy("mLock")
-    boolean mForceAllAppsStandby;
+    boolean mForceAllAppsStandby;   // True if device is in extreme battery saver mode
+
+    @GuardedBy("mLock")
+    boolean mForcedAppStandbyEnabled;   // True if the forced app standby feature is enabled
+
+    private class FeatureFlagObserver extends ContentObserver {
+        FeatureFlagObserver() {
+            super(null);
+        }
+
+        void register() {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED),
+                    false, this);
+        }
+
+        boolean isForcedAppStandbyEnabled() {
+            return Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            final boolean enabled = isForcedAppStandbyEnabled();
+            synchronized (mLock) {
+                if (mForcedAppStandbyEnabled == enabled) {
+                    return;
+                }
+                mForcedAppStandbyEnabled = enabled;
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled);
+                }
+            }
+            mHandler.notifyFeatureFlagChanged();
+        }
+    }
 
     public static abstract class Listener {
         /**
@@ -246,6 +286,9 @@
             mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
             mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
             mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
+            final FeatureFlagObserver flagObserver = new FeatureFlagObserver();
+            flagObserver.register();
+            mForcedAppStandbyEnabled = flagObserver.isForcedAppStandbyEnabled();
 
             try {
                 mIActivityManager.registerUidObserver(new UidObserver(),
@@ -364,7 +407,7 @@
      */
     boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
             boolean restricted) {
-        final int index =  findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
+        final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
         final boolean wasRestricted = index >= 0;
         if (wasRestricted == restricted) {
             return false;
@@ -418,25 +461,30 @@
     }
 
     private final class UidObserver extends IUidObserver.Stub {
-        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+        @Override
+        public void onUidStateChanged(int uid, int procState, long procStateSeq) {
         }
 
-        @Override public void onUidGone(int uid, boolean disabled) {
+        @Override
+        public void onUidGone(int uid, boolean disabled) {
             uidToBackground(uid, /*remove=*/ true);
         }
 
-        @Override public void onUidActive(int uid) {
+        @Override
+        public void onUidActive(int uid) {
             uidToForeground(uid);
         }
 
-        @Override public void onUidIdle(int uid, boolean disabled) {
+        @Override
+        public void onUidIdle(int uid, boolean disabled) {
             // Just to avoid excessive memcpy, don't remove from the array in this case.
             uidToBackground(uid, /*remove=*/ false);
         }
 
-        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        @Override
+        public void onUidCachedChanged(int uid, boolean cached) {
         }
-    };
+    }
 
     private final class AppOpsWatcher extends IAppOpsCallback.Stub {
         @Override
@@ -481,8 +529,8 @@
         private static final int MSG_ALL_WHITELIST_CHANGED = 4;
         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;
 
         public MyHandler(Looper looper) {
             super(looper);
@@ -491,6 +539,7 @@
         public void notifyUidForegroundStateChanged(int uid) {
             obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
         }
+
         public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
             obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
         }
@@ -511,12 +560,16 @@
             obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
         }
 
+        public void notifyFeatureFlagChanged() {
+            obtainMessage(MSG_FEATURE_FLAG_CHANGED).sendToTarget();
+        }
+
         public void doUserRemoved(int userId) {
             obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
         }
 
         @Override
-        public void dispatchMessage(Message msg) {
+        public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_USER_REMOVED:
                     handleUserRemoved(msg.arg1);
@@ -562,6 +615,19 @@
                         l.onForceAllAppsStandbyChanged(sender);
                     }
                     return;
+                case MSG_FEATURE_FLAG_CHANGED:
+                    // Feature flag for forced app standby changed.
+                    final boolean unblockAlarms;
+                    synchronized (mLock) {
+                        unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby;
+                    }
+                    for (Listener l: cloneListeners()) {
+                        l.updateAllJobs();
+                        if (unblockAlarms) {
+                            l.unblockAllUnrestrictedAlarms();
+                        }
+                    }
+                    return;
                 case MSG_USER_REMOVED:
                     handleUserRemoved(msg.arg1);
                     return;
@@ -701,7 +767,7 @@
                 return true;
             }
 
-            return isRunAnyRestrictedLocked(uid, packageName);
+            return mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName);
         }
     }
 
@@ -766,6 +832,9 @@
     public void dump(PrintWriter pw, String indent) {
         synchronized (mLock) {
             pw.print(indent);
+            pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled);
+
+            pw.print(indent);
             pw.print("Force all apps standby: ");
             pw.println(isForceAllAppsStandbyEnabled());
 
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index 467b47a..14b118e 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -37,6 +37,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -50,7 +51,6 @@
 import org.junit.runner.RunWith;
 
 /**
- * TODO: Also add a test for temp power whitelist
  * Tests that background restrictions on jobs work as expected.
  * This test requires test-apps/JobTestApp to be installed on the device.
  * To run this test from root of checkout:
@@ -144,15 +144,29 @@
                 awaitJobStop(DEFAULT_WAIT_TIMEOUT));
     }
 
+    @Test
+    public void testFeatureFlag() throws Exception {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.FORCED_APP_STANDBY_ENABLED, 0);
+        scheduleAndAssertJobStarted();
+        setAppOpsModeAllowed(false);
+        mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
+        assertFalse("Job stopped even when feature flag was disabled",
+                awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
     @After
     public void tearDown() throws Exception {
-        Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
+        final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
         cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
         cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(cancelJobsIntent);
         mContext.unregisterReceiver(mJobStateChangeReceiver);
+        Thread.sleep(500); // To avoid race with register in the next setUp
         setAppOpsModeAllowed(true);
         setPowerWhiteListed(false);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.FORCED_APP_STANDBY_ENABLED, 1);
     }
 
     private void setPowerWhiteListed(boolean whitelist) throws RemoteException {