Extreme battery saver: AlarmManager

- AlarmManagerService now uses ForceAppStandbyTracker.

- Now AlarmManagerService uses the system + user power-save whitelist,
rather than just the user whitelist.

Test: Manual test
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
Bug 68769804

Change-Id: Ie2bd17fe0c3cb8b09ec4c4a78f9254277be16926
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 3904fc9..4330f5d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -46,7 +46,6 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -55,7 +54,6 @@
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -81,6 +79,7 @@
 import java.util.Random;
 import java.util.TimeZone;
 import java.util.TreeSet;
+import java.util.function.Predicate;
 
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.RTC;
@@ -88,11 +87,17 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
 
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
 class AlarmManagerService extends SystemService {
     private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
     private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +136,10 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
-    IAppOpsService mAppOpsService;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
-    ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
-    SparseBooleanArray mForegroundUids = new SparseBooleanArray();
     // List of alarms per uid deferred due to user applied background restrictions on the source app
     SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
     long mNativeData;
@@ -184,12 +186,6 @@
     int mSystemUiUid;
 
     /**
-     * The current set of user whitelisted apps for device idle mode, meaning these are allowed
-     * to freely schedule alarms.
-     */
-    int[] mDeviceIdleUserWhitelist = new int[0];
-
-    /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
      * used to determine the earliest we can dispatch the next such alarm. Times are in the
      * 'elapsed' timebase.
@@ -223,6 +219,8 @@
     private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
             new SparseArray<>();
 
+    private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
@@ -757,6 +755,9 @@
     public AlarmManagerService(Context context) {
         super(context);
         mConstants = new Constants(mHandler);
+
+        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+        mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
     }
 
     static long convertToElapsed(long when, int type) {
@@ -894,17 +895,48 @@
         deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
     }
 
-    void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+    /**
+     * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+     * restricted.
+     *
+     * This is only called when the global "force all apps-standby" flag changes or when the
+     * power save whitelist changes, so it's okay to be slow.
+     */
+    void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
         final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
-        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
-            final int uid = mPendingBackgroundAlarms.keyAt(i);
-            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
-            if (UserHandle.getAppId(uid) == appId) {
-                alarmsToDeliver.addAll(alarmsForUid);
-                mPendingBackgroundAlarms.removeAt(i);
+
+        findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+                mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+        if (alarmsToDeliver.size() > 0) {
+            deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+        }
+    }
+
+    @VisibleForTesting
+    static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+            SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+            Predicate<Alarm> isBackgroundRestricted) {
+
+        for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+            final int uid = pendingAlarms.keyAt(uidIndex);
+            final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+            for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+                final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+                if (isBackgroundRestricted.test(alarm)) {
+                    continue;
+                }
+
+                unrestrictedAlarms.add(alarm);
+                alarmsForUid.remove(alarmIndex);
+            }
+
+            if (alarmsForUid.size() == 0) {
+                pendingAlarms.removeAt(uidIndex);
             }
         }
-        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
     }
 
     private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1266,8 @@
         } catch (RemoteException e) {
             // ignored; both services live in system_server
         }
-        mAppOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
         publishBinderService(Context.ALARM_SERVICE, mService);
-        publishLocalService(LocalService.class, new LocalService());
+        mForceAppStandbyTracker.start();
     }
 
     @Override
@@ -1247,13 +1277,6 @@
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
             mLocalDeviceIdleController
                     = LocalServices.getService(DeviceIdleController.LocalService.class);
-            try {
-                mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
-                        new AppOpsWatcher());
-            } catch (RemoteException rexc) {
-                // Shouldn't happen as they are in the same process.
-                Slog.e(TAG, "AppOps service not reachable", rexc);
-            }
         }
     }
 
@@ -1582,8 +1605,7 @@
             // timing restrictions.
             } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                     || callingUid == mSystemUiUid
-                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
-                            UserHandle.getAppId(callingUid)) >= 0)) {
+                    || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
                 flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
             }
@@ -1660,24 +1682,14 @@
         }
     };
 
-    public final class LocalService {
-        public void setDeviceIdleUserWhitelist(int[] appids) {
-            setDeviceIdleUserWhitelistImpl(appids);
-        }
-    }
-
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
             mConstants.dump(pw);
             pw.println();
 
-            pw.print("  Foreground uids: [");
-            for (int i = 0; i < mForegroundUids.size(); i++) {
-                if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
-            }
-            pw.println("]");
-            pw.println("  Forced app standby packages: " + mForcedAppStandbyPackages);
+            mForceAppStandbyTracker.dump(pw, "  ");
+
             final long nowRTC = System.currentTimeMillis();
             final long nowELAPSED = SystemClock.elapsedRealtime();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1729,6 @@
             pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
             pw.println();
             pw.print("  Num time change events: "); pw.println(mNumTimeChanged);
-            pw.println("  mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
 
             pw.println();
             pw.println("  Next alarm clock information: ");
@@ -1990,15 +2001,8 @@
 
             mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
 
-            final int foregroundUidsSize = mForegroundUids.size();
-            for (int i = 0; i < foregroundUidsSize; i++) {
-                if (mForegroundUids.valueAt(i)) {
-                    proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
-                }
-            }
-            for (String pkg : mForcedAppStandbyPackages) {
-                proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
-            }
+            mForceAppStandbyTracker.dumpProto(proto,
+                    AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
 
             proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
             if (!mInteractive) {
@@ -2022,9 +2026,6 @@
             proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
                     nowElapsed - mLastWakeupSet);
             proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
-            for (int i : mDeviceIdleUserWhitelist) {
-                proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
-            }
 
             final TreeSet<Integer> users = new TreeSet<>();
             final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2267,6 @@
         }
     }
 
-    void setDeviceIdleUserWhitelistImpl(int[] appids) {
-        synchronized (mLock) {
-            // appids are sorted, just send pending alarms for any new appids added to the whitelist
-            int i = 0, j = 0;
-            while (i < appids.length) {
-                while (j < mDeviceIdleUserWhitelist.length
-                        && mDeviceIdleUserWhitelist[j] < appids[i]) {
-                    j++;
-                }
-                if (j < mDeviceIdleUserWhitelist.length
-                        && appids[i] != mDeviceIdleUserWhitelist[j]) {
-                    if (DEBUG_BG_LIMIT) {
-                        Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
-                    }
-                    sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
-                }
-                i++;
-            }
-            mDeviceIdleUserWhitelist = appids;
-        }
-    }
-
     AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
         synchronized (mLock) {
             return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2689,7 @@
         final String sourcePackage =
                 (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
         final int sourceUid = alarm.creatorUid;
-        return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
-                && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
-                < 0;
+        return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
     }
 
     private native long init();
@@ -2859,7 +2836,8 @@
         }
     }
 
-    private static class Alarm {
+    @VisibleForTesting
+    static class Alarm {
         public final int type;
         public final long origWhen;
         public final boolean wakeup;
@@ -3476,17 +3454,10 @@
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
-                mForegroundUids.delete(uid);
             }
         }
 
         @Override public void onUidActive(int uid) {
-            synchronized (mLock) {
-                if (!mForegroundUids.get(uid)) {
-                    mForegroundUids.put(uid, true);
-                    sendPendingBackgroundAlarmsLocked(uid, null);
-                }
-            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3465,6 @@
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
-                mForegroundUids.delete(uid);
             }
         }
 
@@ -3502,27 +3472,29 @@
         }
     };
 
-    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+    private final Listener mForceAppStandbyListener = new Listener() {
         @Override
-        public void opChanged(int op, int uid, String packageName) throws RemoteException {
+        public void unblockAllUnrestrictedAlarms() {
             synchronized (mLock) {
-                final int mode = mAppOpsService.checkOperation(op, uid, packageName);
-                if (DEBUG_BG_LIMIT) {
-                    Slog.d(TAG,
-                            "Appop changed for " + uid + ", " + packageName + " to " + mode);
-                }
-                final boolean changed;
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    changed = mForcedAppStandbyPackages.add(packageName);
-                } else {
-                    changed = mForcedAppStandbyPackages.remove(packageName);
-                }
-                if (changed && mode == AppOpsManager.MODE_ALLOWED) {
-                    sendPendingBackgroundAlarmsLocked(uid, packageName);
-                }
+                sendAllUnrestrictedPendingBackgroundAlarmsLocked();
             }
         }
-    }
+
+        @Override
+        public void unblockAlarmsForUid(int uid) {
+            synchronized (mLock) {
+                sendPendingBackgroundAlarmsLocked(uid, null);
+            }
+        }
+
+        @Override
+        public void unblockAlarmsForUidPackage(int uid, String packageName) {
+            synchronized (mLock) {
+                sendPendingBackgroundAlarmsLocked(uid, packageName);
+            }
+        }
+    };
 
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 0921a00..d7aeb8c 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@
     private PowerManagerInternal mLocalPowerManager;
     private PowerManager mPowerManager;
     private ConnectivityService mConnectivityService;
-    private AlarmManagerService.LocalService mLocalAlarmManager;
     private INetworkPolicyManager mNetworkPolicyManager;
     private SensorManager mSensorManager;
     private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@
                 mGoingIdleWakeLock.setReferenceCounted(true);
                 mConnectivityService = (ConnectivityService)ServiceManager.getService(
                         Context.CONNECTIVITY_SERVICE);
-                mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
                 mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                         ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
                 mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@
 
                 mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
-                mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
 
+                passWhiteListToForceAppStandbyTrackerLocked();
                 updateInteractivityLocked();
             }
             updateConnectivityState(null);
@@ -2477,13 +2475,7 @@
             }
             mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
         }
-        if (mLocalAlarmManager != null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Setting alarm whitelist to "
-                        + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
-            }
-            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
-        }
+        passWhiteListToForceAppStandbyTrackerLocked();
     }
 
     private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@
             }
             mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
         }
+        passWhiteListToForceAppStandbyTrackerLocked();
     }
 
     private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@
         getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
     }
 
+    private void passWhiteListToForceAppStandbyTrackerLocked() {
+        ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+                mPowerSaveWhitelistAllAppIdArray,
+                mTempWhitelistAppIdArray);
+    }
+
     void readConfigFileLocked() {
         if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
         mPowerSaveWhitelistUserApps.clear();
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 5dd3ee0..61d3833 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -16,13 +16,18 @@
 package com.android.server;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
 import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
@@ -30,21 +35,36 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Pair;
-import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
 
+import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
 
 /**
- * Class to track OP_RUN_ANY_IN_BACKGROUND, UID foreground state and "force all app standby".
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
  *
- * TODO Clean up cache when a user is deleted.
- * TODO Add unit tests. b/68769804.
+ * 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.
+ *
+ * Test:
+   atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
  */
 public class ForceAppStandbyTracker {
     private static final String TAG = "ForceAppStandbyTracker";
@@ -55,22 +75,32 @@
     private final Object mLock = new Object();
     private final Context mContext;
 
+    @VisibleForTesting
+    static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+    IActivityManager mIActivityManager;
     AppOpsManager mAppOpsManager;
     IAppOpsService mAppOpsService;
     PowerManagerInternal mPowerManagerInternal;
 
-    private final Handler mCallbackHandler;
+    private final MyHandler mHandler;
 
     /**
      * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
      */
     @GuardedBy("mLock")
-    final ArraySet<Pair<Integer, String>> mForcedAppStandbyUidPackages = new ArraySet<>();
+    final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
 
     @GuardedBy("mLock")
     final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
 
     @GuardedBy("mLock")
+    private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+    @GuardedBy("mLock")
+    private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+    @GuardedBy("mLock")
     final ArraySet<Listener> mListeners = new ArraySet<>();
 
     @GuardedBy("mLock")
@@ -80,16 +110,116 @@
     boolean mForceAllAppsStandby;
 
     public static abstract class Listener {
-        public void onRestrictionChanged(int uid, @Nullable String packageName) {
+        /**
+         * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+         */
+        private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+                int uid, @NonNull String packageName) {
+            updateJobsForUidPackage(uid, packageName);
+
+            if (!sender.areAlarmsRestricted(uid, packageName)) {
+                unblockAlarmsForUidPackage(uid, packageName);
+            }
         }
 
-        public void onGlobalRestrictionChanged() {
+        /**
+         * This is called when the foreground state changed for a UID.
+         */
+        private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+            updateJobsForUid(uid);
+
+            if (sender.isInForeground(uid)) {
+                unblockAlarmsForUid(uid);
+            }
+        }
+
+        /**
+         * This is called when an app-id(s) is removed from the power save whitelist.
+         */
+        private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+            unblockAllUnrestrictedAlarms();
+        }
+
+        /**
+         * This is called when the power save whitelist changes, excluding the
+         * {@link #onPowerSaveUnwhitelisted} case.
+         */
+        private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+        }
+
+        /**
+         * This is called when the temp whitelist changes.
+         */
+        private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+            // TODO This case happens rather frequently; consider optimizing and update jobs
+            // only for affected app-ids.
+
+            updateAllJobs();
+        }
+
+        /**
+         * This is called when the global "force all apps standby" flag changes.
+         */
+        private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+
+            if (!sender.isForceAllAppsStandbyEnabled()) {
+                unblockAllUnrestrictedAlarms();
+            }
+        }
+
+        /**
+         * Called when the job restrictions for multiple UIDs might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateAllJobs() {
+        }
+
+        /**
+         * Called when the job restrictions for a UID might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateJobsForUid(int uid) {
+        }
+
+        /**
+         * Called when the job restrictions for a UID - package might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateJobsForUidPackage(int uid, String packageName) {
+        }
+
+        /**
+         * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+         * manager should re-evaluate all restrictions for all blocked jobs.
+         */
+        public void unblockAllUnrestrictedAlarms() {
+        }
+
+        /**
+         * Called when all jobs for a specific UID are unblocked.
+         */
+        public void unblockAlarmsForUid(int uid) {
+        }
+
+        /**
+         * Called when all alarms for a specific UID - package are unblocked.
+         */
+        public void unblockAlarmsForUidPackage(int uid, String packageName) {
         }
     }
 
-    private ForceAppStandbyTracker(Context context) {
+    @VisibleForTesting
+    ForceAppStandbyTracker(Context context, Looper looper) {
         mContext = context;
-        mCallbackHandler = FgThread.getHandler();
+        mHandler = new MyHandler(looper);
+    }
+
+    private ForceAppStandbyTracker(Context context) {
+        this(context, FgThread.get().getLooper());
     }
 
     /**
@@ -112,45 +242,65 @@
             }
             mStarted = true;
 
-            mAppOpsManager = Preconditions.checkNotNull(
-                    mContext.getSystemService(AppOpsManager.class));
-            mAppOpsService = Preconditions.checkNotNull(
-                    IAppOpsService.Stub.asInterface(
-                            ServiceManager.getService(Context.APP_OPS_SERVICE)));
-            mPowerManagerInternal = Preconditions.checkNotNull(
-                    LocalServices.getService(PowerManagerInternal.class));
+            mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+            mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+            mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+            mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
 
             try {
-                ActivityManager.getService().registerUidObserver(new UidObserver(),
+                mIActivityManager.registerUidObserver(new UidObserver(),
                         ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
                                 | ActivityManager.UID_OBSERVER_ACTIVE,
                         ActivityManager.PROCESS_STATE_UNKNOWN, null);
-                mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
+                mAppOpsService.startWatchingMode(TARGET_OP, null,
                         new AppOpsWatcher());
             } catch (RemoteException e) {
                 // shouldn't happen.
             }
 
-            mPowerManagerInternal.registerLowPowerModeObserver(
-                    ServiceType.FORCE_ALL_APPS_STANDBY,
-                    state -> updateForceAllAppsStandby(state.batterySaverEnabled));
-
-            updateForceAllAppsStandby(
-                    mPowerManagerInternal.getLowPowerState(ServiceType.FORCE_ALL_APPS_STANDBY)
-                            .batterySaverEnabled);
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_USER_REMOVED);
+            mContext.registerReceiver(new MyReceiver(), filter);
 
             refreshForcedAppStandbyUidPackagesLocked();
+
+            mPowerManagerInternal.registerLowPowerModeObserver(
+                    ServiceType.FORCE_ALL_APPS_STANDBY,
+                    (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+            updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+                    ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
         }
     }
 
+    @VisibleForTesting
+    AppOpsManager injectAppOpsManager() {
+        return mContext.getSystemService(AppOpsManager.class);
+    }
+
+    @VisibleForTesting
+    IAppOpsService injectIAppOpsService() {
+        return IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
+    }
+
+    @VisibleForTesting
+    IActivityManager injectIActivityManager() {
+        return ActivityManager.getService();
+    }
+
+    @VisibleForTesting
+    PowerManagerInternal injectPowerManagerInternal() {
+        return LocalServices.getService(PowerManagerInternal.class);
+    }
+
     /**
-     * Update {@link #mForcedAppStandbyUidPackages} with the current app ops state.
+     * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
      */
     private void refreshForcedAppStandbyUidPackagesLocked() {
-        final int op = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
-
-        mForcedAppStandbyUidPackages.clear();
-        final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(new int[] {op});
+        mRunAnyRestrictedPackages.clear();
+        final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+                new int[] {TARGET_OP});
 
         if (ops == null) {
             return;
@@ -162,31 +312,38 @@
 
             for (int j = 0; j < entries.size(); j++) {
                 AppOpsManager.OpEntry ent = entries.get(j);
-                if (ent.getOp() != op) {
+                if (ent.getOp() != TARGET_OP) {
                     continue;
                 }
                 if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
-                    mForcedAppStandbyUidPackages.add(Pair.create(
+                    mRunAnyRestrictedPackages.add(Pair.create(
                             pkg.getUid(), pkg.getPackageName()));
                 }
             }
         }
     }
 
-    boolean isRunAnyInBackgroundAppOpRestricted(int uid, @NonNull String packageName) {
-        try {
-            return mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                    uid, packageName) != AppOpsManager.MODE_ALLOWED;
-        } catch (RemoteException e) {
-            return false; // shouldn't happen.
+    /**
+     * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+     */
+    void updateForceAllAppsStandby(boolean enable) {
+        synchronized (mLock) {
+            if (enable == mForceAllAppsStandby) {
+                return;
+            }
+            mForceAllAppsStandby = enable;
+
+            mHandler.notifyForceAllAppsStandbyChanged();
         }
     }
 
     private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
-        // TODO Maybe we should switch to indexOf(Pair.create()) if the array size is too big.
-        final int size = mForcedAppStandbyUidPackages.size();
+        final int size = mRunAnyRestrictedPackages.size();
+        if (size > 8) {
+            return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+        }
         for (int i = 0; i < size; i++) {
-            final Pair<Integer, String> pair = mForcedAppStandbyUidPackages.valueAt(i);
+            final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
 
             if ((pair.first == uid) && packageName.equals(pair.second)) {
                 return i;
@@ -196,13 +353,16 @@
     }
 
     /**
-     * @return whether a uid package-name pair is in mForcedAppStandbyUidPackages.
+     * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
      */
-    boolean isUidPackageRestrictedLocked(int uid, @NonNull String packageName) {
+    boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
         return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
     }
 
-    boolean updateRestrictedUidPackageLocked(int uid, @NonNull String packageName,
+    /**
+     * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+     */
+    boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
             boolean restricted) {
         final int index =  findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
         final boolean wasRestricted = index >= 0;
@@ -210,13 +370,16 @@
             return false;
         }
         if (restricted) {
-            mForcedAppStandbyUidPackages.add(Pair.create(uid, packageName));
+            mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
         } else {
-            mForcedAppStandbyUidPackages.removeAt(index);
+            mRunAnyRestrictedPackages.removeAt(index);
         }
         return true;
     }
 
+    /**
+     * Puts a UID to {@link #mForegroundUids}.
+     */
     void uidToForeground(int uid) {
         synchronized (mLock) {
             if (!UserHandle.isApp(uid)) {
@@ -228,10 +391,13 @@
                 return;
             }
             mForegroundUids.put(uid, true);
-            notifyForUidPackage(uid, null);
+            mHandler.notifyUidForegroundStateChanged(uid);
         }
     }
 
+    /**
+     * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+     */
     void uidToBackground(int uid, boolean remove) {
         synchronized (mLock) {
             if (!UserHandle.isApp(uid)) {
@@ -247,13 +413,11 @@
             } else {
                 mForegroundUids.put(uid, false);
             }
-            notifyForUidPackage(uid, null);
+            mHandler.notifyUidForegroundStateChanged(uid);
         }
     }
 
-    // Event handlers
-
-    final class UidObserver extends IUidObserver.Stub {
+    private final class UidObserver extends IUidObserver.Stub {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
         }
 
@@ -277,11 +441,28 @@
     private final class AppOpsWatcher extends IAppOpsCallback.Stub {
         @Override
         public void opChanged(int op, int uid, String packageName) throws RemoteException {
+            boolean restricted = false;
+            try {
+                restricted = mAppOpsService.checkOperation(TARGET_OP,
+                        uid, packageName) != AppOpsManager.MODE_ALLOWED;
+            } catch (RemoteException e) {
+                // Shouldn't happen
+            }
             synchronized (mLock) {
-                final boolean restricted = isRunAnyInBackgroundAppOpRestricted(uid, packageName);
+                if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+                    mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+                }
+            }
+        }
+    }
 
-                if (updateRestrictedUidPackageLocked(uid, packageName, restricted)) {
-                    notifyForUidPackage(uid, packageName);
+    private final class MyReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId > 0) {
+                    mHandler.doUserRemoved(userId);
                 }
             }
         }
@@ -293,31 +474,183 @@
         }
     }
 
-    void notifyForUidPackage(int uid, String packageName) {
-        mCallbackHandler.post(() -> {
-            for (Listener l : cloneListeners()) {
-                l.onRestrictionChanged(uid, packageName);
-            }
-        });
-    }
+    private class MyHandler extends Handler {
+        private static final int MSG_UID_STATE_CHANGED = 1;
+        private static final int MSG_RUN_ANY_CHANGED = 2;
+        private static final int MSG_ALL_UNWHITELISTED = 3;
+        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;
 
-    void notifyGlobal() {
-        mCallbackHandler.post(() -> {
-            for (Listener l : cloneListeners()) {
-                l.onGlobalRestrictionChanged();
-            }
-        });
-    }
+        private static final int MSG_USER_REMOVED = 7;
 
-    void updateForceAllAppsStandby(boolean forceAllAppsStandby) {
-        synchronized (mLock) {
-            if (mForceAllAppsStandby == forceAllAppsStandby) {
-                return;
-            }
-            mForceAllAppsStandby = forceAllAppsStandby;
-            Slog.i(TAG, "Force all app standby: " + mForceAllAppsStandby);
-            notifyGlobal();
+        public MyHandler(Looper looper) {
+            super(looper);
         }
+
+        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();
+        }
+
+        public void notifyAllUnwhitelisted() {
+            obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+        }
+
+        public void notifyAllWhitelistChanged() {
+            obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+        }
+
+        public void notifyTempWhitelistChanged() {
+            obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+        }
+
+        public void notifyForceAllAppsStandbyChanged() {
+            obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+        }
+
+        public void doUserRemoved(int userId) {
+            obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+        }
+
+        @Override
+        public void dispatchMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USER_REMOVED:
+                    handleUserRemoved(msg.arg1);
+                    return;
+            }
+
+            // Only notify the listeners when started.
+            synchronized (mLock) {
+                if (!mStarted) {
+                    return;
+                }
+            }
+            final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+            switch (msg.what) {
+                case MSG_UID_STATE_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onUidForegroundStateChanged(sender, msg.arg1);
+                    }
+                    return;
+                case MSG_RUN_ANY_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+                    }
+                    return;
+                case MSG_ALL_UNWHITELISTED:
+                    for (Listener l : cloneListeners()) {
+                        l.onPowerSaveUnwhitelisted(sender);
+                    }
+                    return;
+                case MSG_ALL_WHITELIST_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onPowerSaveWhitelistedChanged(sender);
+                    }
+                    return;
+                case MSG_TEMP_WHITELIST_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onTempPowerSaveWhitelistChanged(sender);
+                    }
+                    return;
+                case MSG_FORCE_ALL_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onForceAllAppsStandbyChanged(sender);
+                    }
+                    return;
+                case MSG_USER_REMOVED:
+                    handleUserRemoved(msg.arg1);
+                    return;
+            }
+        }
+    }
+
+    void handleUserRemoved(int removedUserId) {
+        synchronized (mLock) {
+            for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+                final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+                final int uid = pair.first;
+                final int userId = UserHandle.getUserId(uid);
+
+                if (userId == removedUserId) {
+                    mRunAnyRestrictedPackages.removeAt(i);
+                }
+            }
+            for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+                final int uid = mForegroundUids.keyAt(i);
+                final int userId = UserHandle.getUserId(uid);
+
+                if (userId == removedUserId) {
+                    mForegroundUids.removeAt(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by device idle controller to update the power save whitelists.
+     */
+    public void setPowerSaveWhitelistAppIds(
+            int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+        synchronized (mLock) {
+            final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+            final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+            mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+            mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+            if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+                mHandler.notifyAllUnwhitelisted();
+            } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+                mHandler.notifyAllWhitelistChanged();
+            }
+
+            if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+                mHandler.notifyTempWhitelistChanged();
+            }
+
+        }
+    }
+
+    /**
+     * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+     * that's not in a sorted app-id array {@code newArray}.
+     */
+    @VisibleForTesting
+    static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+        int i1 = 0;
+        int i2 = 0;
+        boolean prevFinished;
+        boolean newFinished;
+
+        for (;;) {
+            prevFinished = i1 >= prevArray.length;
+            newFinished = i2 >= newArray.length;
+            if (prevFinished || newFinished) {
+                break;
+            }
+            int a1 = prevArray[i1];
+            int a2 = newArray[i2];
+
+            if (a1 == a2) {
+                i1++;
+                i2++;
+                continue;
+            }
+            if (a1 < a2) {
+                // prevArray has an element that's not in a2.
+                return true;
+            }
+            i2++;
+        }
+        if (prevFinished) {
+            return false;
+        }
+        return newFinished;
     }
 
     // Public interface.
@@ -332,21 +665,51 @@
     }
 
     /**
-     * Whether force-app-standby is effective for a UID package-name.
+     * @return whether alarms should be restricted for a UID package-name.
      */
-    public boolean isRestricted(int uid, @NonNull String packageName) {
+    public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+    }
+
+    /**
+     * @return whether jobs should be restricted for a UID package-name.
+     */
+    public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+    }
+
+    /**
+     * @return whether force-app-standby is effective for a UID package-name.
+     */
+    private boolean isRestricted(int uid, @NonNull String packageName,
+            boolean useTempWhitelistToo) {
         if (isInForeground(uid)) {
             return false;
         }
         synchronized (mLock) {
+            // Whitelisted?
+            final int appId = UserHandle.getAppId(uid);
+            if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+                return false;
+            }
+            if (useTempWhitelistToo &&
+                    ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+                return false;
+            }
+
             if (mForceAllAppsStandby) {
                 return true;
             }
-            return isUidPackageRestrictedLocked(uid, packageName);
+
+            return isRunAnyRestrictedLocked(uid, packageName);
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
+    /**
+     * @return whether a UID is in the foreground or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
     public boolean isInForeground(int uid) {
         if (!UserHandle.isApp(uid)) {
             return true;
@@ -356,31 +719,120 @@
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
-    public boolean isForceAllAppsStandbyEnabled() {
+    /**
+     * @return whether force all apps standby is enabled or not.
+     *
+     * Note clients normally shouldn't need to access it.
+     */
+    boolean isForceAllAppsStandbyEnabled() {
         synchronized (mLock) {
             return mForceAllAppsStandby;
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
+    /**
+     * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
     public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
         synchronized (mLock) {
-            return !isUidPackageRestrictedLocked(uid, packageName);
+            return !isRunAnyRestrictedLocked(uid, packageName);
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
-    public SparseBooleanArray getForegroudUids() {
+    /**
+     * @return whether a UID is in the user / system defined power-save whitelist or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isUidPowerSaveWhitelisted(int uid) {
         synchronized (mLock) {
-            return mForegroundUids.clone();
+            return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
         }
     }
 
-    /** For dumpsys -- otherwise the callers don't need to know it. */
-    public ArraySet<Pair<Integer, String>> getRestrictedUidPackages() {
+    /**
+     * @return whether a UID is in the temp power-save whitelist or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isUidTempPowerSaveWhitelisted(int uid) {
         synchronized (mLock) {
-            return new ArraySet(mForcedAppStandbyUidPackages);
+            return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+        }
+    }
+
+    public void dump(PrintWriter pw, String indent) {
+        synchronized (mLock) {
+            pw.print(indent);
+            pw.print("Force all apps standby: ");
+            pw.println(isForceAllAppsStandbyEnabled());
+
+            pw.print(indent);
+            pw.print("Foreground uids: [");
+
+            String sep = "";
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    pw.print(sep);
+                    pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+                    sep = " ";
+                }
+            }
+            pw.println("]");
+
+            pw.print(indent);
+            pw.print("Whitelist appids: ");
+            pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+            pw.print(indent);
+            pw.print("Temp whitelist appids: ");
+            pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+            pw.print(indent);
+            pw.println("Restricted packages:");
+            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+                pw.print(indent);
+                pw.print("  ");
+                pw.print(UserHandle.formatUid(uidAndPackage.first));
+                pw.print(" ");
+                pw.print(uidAndPackage.second);
+                pw.println();
+            }
+        }
+    }
+
+    public void dumpProto(ProtoOutputStream proto, long fieldId) {
+        synchronized (mLock) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+                            mForegroundUids.keyAt(i));
+                }
+            }
+
+            for (int appId : mPowerWhitelistedAllAppIds) {
+                proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+            }
+
+            for (int appId : mTempWhitelistedAppIds) {
+                proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+            }
+
+            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+                final long token2 = proto.start(
+                        ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+                proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+                proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+                        uidAndPackage.second);
+                proto.end(token2);
+            }
+            proto.end(token);
         }
     }
 }
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index f67bd04..fc4015d 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,19 +16,12 @@
 
 package com.android.server.job.controllers;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseBooleanArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.ForceAppStandbyTracker;
@@ -37,7 +30,6 @@
 import com.android.server.job.JobStore;
 
 import java.io.PrintWriter;
-import java.util.function.Predicate;
 
 public final class BackgroundJobsController extends StateController {
 
@@ -51,9 +43,6 @@
     private final JobSchedulerService mJobSchedulerService;
     private final IDeviceIdleController mDeviceIdleController;
 
-    private int[] mPowerWhitelistedUserAppIds;
-    private int[] mTempWhitelistedAppIds;
-
     private final ForceAppStandbyTracker mForceAppStandbyTracker;
 
 
@@ -67,28 +56,6 @@
         }
     }
 
-    private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                try {
-                    switch (intent.getAction()) {
-                        case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
-                            mPowerWhitelistedUserAppIds =
-                                    mDeviceIdleController.getAppIdUserWhitelist();
-                            break;
-                        case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
-                            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
-                            break;
-                    }
-                } catch (RemoteException rexc) {
-                    Slog.e(LOG_TAG, "Device idle controller not reachable");
-                }
-                updateAllJobRestrictionsLocked();
-            }
-        }
-    };
-
     private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
         super(service, context, lock);
         mJobSchedulerService = service;
@@ -97,19 +64,6 @@
 
         mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
 
-        try {
-            mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
-            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
-        } catch (RemoteException rexc) {
-            // Shouldn't happen as they are in the same process.
-            Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
-        }
-        IntentFilter powerWhitelistFilter = new IntentFilter();
-        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
-        context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
-                null, null);
-
         mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
         mForceAppStandbyTracker.start();
     }
@@ -128,31 +82,7 @@
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("BackgroundJobsController");
 
-        pw.print("Force all apps standby: ");
-        pw.println(mForceAppStandbyTracker.isForceAllAppsStandbyEnabled());
-
-        pw.print("Foreground uids: [");
-        final SparseBooleanArray foregroundUids = mForceAppStandbyTracker.getForegroudUids();
-
-        String sep = "";
-        for (int i = 0; i < foregroundUids.size(); i++) {
-            if (foregroundUids.valueAt(i)) {
-                pw.print(sep);
-                pw.print(UserHandle.formatUid(foregroundUids.keyAt(i)));
-                sep = " ";
-            }
-        }
-        pw.println("]");
-
-        pw.println("Restricted packages:");
-        for (Pair<Integer, String> uidAndPackage
-                : mForceAppStandbyTracker.getRestrictedUidPackages()) {
-            pw.print("  ");
-            pw.print(UserHandle.formatUid(uidAndPackage.first));
-            pw.print(" ");
-            pw.print(uidAndPackage.second);
-            pw.println();
-        }
+        mForceAppStandbyTracker.dump(pw, "");
 
         pw.println("Job state:");
         mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -165,15 +95,17 @@
             pw.print(" from ");
             UserHandle.formatUid(pw, uid);
             pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
-            if (isWhitelistedLocked(uid)) {
+            if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+                    mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
                 pw.print(", whitelisted");
             }
             pw.print(": ");
             pw.print(jobStatus.getSourcePackageName());
 
-            pw.print(" [background restrictions ");
+            pw.print(" [RUN_ANY_IN_BACKGROUND ");
             pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
-                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) ? "off]" : "on]");
+                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+                    ? "allowed]" : "disallowed]");
 
             if ((jobStatus.satisfiedConstraints
                     & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
@@ -218,19 +150,12 @@
         }
     }
 
-    private boolean isWhitelistedLocked(int uid) {
-        final int appId = UserHandle.getAppId(uid);
-        return ArrayUtils.contains(mTempWhitelistedAppIds, appId)
-                || ArrayUtils.contains(mPowerWhitelistedUserAppIds, appId);
-    }
-
     boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
 
         final int uid = jobStatus.getSourceUid();
         final String packageName = jobStatus.getSourcePackageName();
 
-        final boolean canRun = isWhitelistedLocked(uid)
-                || !mForceAppStandbyTracker.isRestricted(uid, packageName);
+        final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
 
         return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
     }
@@ -261,13 +186,18 @@
 
     private final Listener mForceAppStandbyListener = new Listener() {
         @Override
-        public void onRestrictionChanged(int uid, String packageName) {
+        public void updateAllJobs() {
+            updateAllJobRestrictionsLocked();
+        }
+
+        @Override
+        public void updateJobsForUid(int uid) {
             updateJobRestrictionsForUidLocked(uid);
         }
 
         @Override
-        public void onGlobalRestrictionChanged() {
-            updateAllJobRestrictionsLocked();
+        public void updateJobsForUidPackage(int uid, String packageName) {
+            updateJobRestrictionsForUidLocked(uid);
         }
     };
 }