Fix issue #22989030: Separate battery whitelists

We now have a new whitelist you can put apps in, which
opts them out of the old battery saver mode and new app idle,
but doesn't keep them from going in to doze.  This is for a few
special cases that we had previously whitelisted for battery saver,
and inherited to the new modes...  ultimately we should figure out
how to get these apps out of the whitelist completely, but this
will help for now.

Apps in this new whitelist are not shown in the UI, because they
are still significantly restricted by not being able to operate
normally in doze.  This also means they are still visible in the
list of all apps for the user to be able to put them on/off the
complete whitelist if that is what they really want.

In the course of doing this, I needed to clean up code in the
network policy manager to better separate management of the
two firewall rules that now have different whitelists applied
to them.  This also hopefully just generally simplifies and cleans
up that code.  Hopefully!

Change-Id: I92e15f2f85899571dd8b049b5e3eb1354f55f353
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 7561c7d..e678bbc 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -113,6 +113,7 @@
     private Display mCurDisplay;
     private AnyMotionDetector mAnyMotionDetector;
     private boolean mEnabled;
+    private boolean mForceIdle;
     private boolean mScreenOn;
     private boolean mCharging;
     private boolean mSigMotionActive;
@@ -151,7 +152,14 @@
     public final AtomicFile mConfigFile;
 
     /**
-     * Package names the system has white-listed to opt out of power save restrictions.
+     * Package names the system has white-listed to opt out of power save restrictions,
+     * except for device idle mode.
+     */
+    private final ArrayMap<String, Integer> mPowerSaveWhitelistAppsExceptIdle = new ArrayMap<>();
+
+    /**
+     * Package names the system has white-listed to opt out of power save restrictions for
+     * all modes.
      */
     private final ArrayMap<String, Integer> mPowerSaveWhitelistApps = new ArrayMap<>();
 
@@ -161,11 +169,30 @@
     private final ArrayMap<String, Integer> mPowerSaveWhitelistUserApps = new ArrayMap<>();
 
     /**
+     * App IDs of built-in system apps that have been white-listed except for idle modes.
+     */
+    private final SparseBooleanArray mPowerSaveWhitelistSystemAppIdsExceptIdle
+            = new SparseBooleanArray();
+
+    /**
      * App IDs of built-in system apps that have been white-listed.
      */
     private final SparseBooleanArray mPowerSaveWhitelistSystemAppIds = new SparseBooleanArray();
 
     /**
+     * App IDs that have been white-listed to opt out of power save restrictions, except
+     * for device idle modes.
+     */
+    private final SparseBooleanArray mPowerSaveWhitelistExceptIdleAppIds = new SparseBooleanArray();
+
+    /**
+     * Current app IDs that are in the complete power save white list, but shouldn't be
+     * excluded from idle modes.  This array can be shared with others because it will not be
+     * modified once set.
+     */
+    private int[] mPowerSaveWhitelistExceptIdleAppIdArray = new int[0];
+
+    /**
      * App IDs that have been white-listed to opt out of power save restrictions.
      */
     private final SparseBooleanArray mPowerSaveWhitelistAllAppIds = new SparseBooleanArray();
@@ -583,14 +610,26 @@
             removePowerSaveWhitelistAppInternal(name);
         }
 
+        @Override public String[] getSystemPowerWhitelistExceptIdle() {
+            return getSystemPowerWhitelistExceptIdleInternal();
+        }
+
         @Override public String[] getSystemPowerWhitelist() {
             return getSystemPowerWhitelistInternal();
         }
 
+        @Override public String[] getFullPowerWhitelistExceptIdle() {
+            return getFullPowerWhitelistExceptIdleInternal();
+        }
+
         @Override public String[] getFullPowerWhitelist() {
             return getFullPowerWhitelistInternal();
         }
 
+        @Override public int[] getAppIdWhitelistExceptIdle() {
+            return getAppIdWhitelistExceptIdleInternal();
+        }
+
         @Override public int[] getAppIdWhitelist() {
             return getAppIdWhitelistInternal();
         }
@@ -599,6 +638,10 @@
             return getAppIdTempWhitelistInternal();
         }
 
+        @Override public boolean isPowerSaveWhitelistExceptIdleApp(String name) {
+            return isPowerSaveWhitelistExceptIdleAppInternal(name);
+        }
+
         @Override public boolean isPowerSaveWhitelistApp(String name) {
             return isPowerSaveWhitelistAppInternal(name);
         }
@@ -679,6 +722,19 @@
             mEnabled = getContext().getResources().getBoolean(
                     com.android.internal.R.bool.config_enableAutoPowerModes);
             SystemConfig sysConfig = SystemConfig.getInstance();
+            ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
+            for (int i=0; i<allowPowerExceptIdle.size(); i++) {
+                String pkg = allowPowerExceptIdle.valueAt(i);
+                try {
+                    ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
+                    if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        int appid = UserHandle.getAppId(ai.uid);
+                        mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
+                        mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                }
+            }
             ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
             for (int i=0; i<allowPower.size(); i++) {
                 String pkg = allowPower.valueAt(i);
@@ -686,6 +742,10 @@
                     ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
                     if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
                         int appid = UserHandle.getAppId(ai.uid);
+                        // These apps are on both the whitelist-except-idle as well
+                        // as the full whitelist, so they apply in all cases.
+                        mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
+                        mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
                         mPowerSaveWhitelistApps.put(ai.packageName, appid);
                         mPowerSaveWhitelistSystemAppIds.put(appid, true);
                     }
@@ -783,17 +843,45 @@
         return false;
     }
 
+    public String[] getSystemPowerWhitelistExceptIdleInternal() {
+        synchronized (this) {
+            int size = mPowerSaveWhitelistAppsExceptIdle.size();
+            String[] apps = new String[size];
+            for (int i = 0; i < size; i++) {
+                apps[i] = mPowerSaveWhitelistAppsExceptIdle.keyAt(i);
+            }
+            return apps;
+        }
+    }
+
     public String[] getSystemPowerWhitelistInternal() {
         synchronized (this) {
             int size = mPowerSaveWhitelistApps.size();
             String[] apps = new String[size];
-            for (int i = 0; i < mPowerSaveWhitelistApps.size(); i++) {
+            for (int i = 0; i < size; i++) {
                 apps[i] = mPowerSaveWhitelistApps.keyAt(i);
             }
             return apps;
         }
     }
 
+    public String[] getFullPowerWhitelistExceptIdleInternal() {
+        synchronized (this) {
+            int size = mPowerSaveWhitelistAppsExceptIdle.size() + mPowerSaveWhitelistUserApps.size();
+            String[] apps = new String[size];
+            int cur = 0;
+            for (int i = 0; i < mPowerSaveWhitelistAppsExceptIdle.size(); i++) {
+                apps[cur] = mPowerSaveWhitelistAppsExceptIdle.keyAt(i);
+                cur++;
+            }
+            for (int i = 0; i < mPowerSaveWhitelistUserApps.size(); i++) {
+                apps[cur] = mPowerSaveWhitelistUserApps.keyAt(i);
+                cur++;
+            }
+            return apps;
+        }
+    }
+
     public String[] getFullPowerWhitelistInternal() {
         synchronized (this) {
             int size = mPowerSaveWhitelistApps.size() + mPowerSaveWhitelistUserApps.size();
@@ -811,6 +899,13 @@
         }
     }
 
+    public boolean isPowerSaveWhitelistExceptIdleAppInternal(String packageName) {
+        synchronized (this) {
+            return mPowerSaveWhitelistAppsExceptIdle.containsKey(packageName)
+                    || mPowerSaveWhitelistUserApps.containsKey(packageName);
+        }
+    }
+
     public boolean isPowerSaveWhitelistAppInternal(String packageName) {
         synchronized (this) {
             return mPowerSaveWhitelistApps.containsKey(packageName)
@@ -818,6 +913,12 @@
         }
     }
 
+    public int[] getAppIdWhitelistExceptIdleInternal() {
+        synchronized (this) {
+            return mPowerSaveWhitelistExceptIdleAppIdArray;
+        }
+    }
+
     public int[] getAppIdWhitelistInternal() {
         synchronized (this) {
             return mPowerSaveWhitelistAllAppIdArray;
@@ -921,6 +1022,9 @@
                     Slog.d(TAG, "Removing UID " + uid + " from temp whitelist");
                 }
                 updateTempWhitelistAppIdsLocked();
+                if (mNetworkPolicyTempWhitelistCallback != null) {
+                    mHandler.post(mNetworkPolicyTempWhitelistCallback);
+                }
                 reportTempWhitelistChangedLocked();
                 try {
                     mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_TEMP_WHITELIST_FINISH,
@@ -949,10 +1053,14 @@
         if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
         if (!screenOn && mScreenOn) {
             mScreenOn = false;
-            becomeInactiveIfAppropriateLocked();
+            if (!mForceIdle) {
+                becomeInactiveIfAppropriateLocked();
+            }
         } else if (screenOn) {
             mScreenOn = true;
-            becomeActiveLocked("screen", Process.myUid());
+            if (!mForceIdle) {
+                becomeActiveLocked("screen", Process.myUid());
+            }
         }
     }
 
@@ -960,10 +1068,14 @@
         if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging);
         if (!charging && mCharging) {
             mCharging = false;
-            becomeInactiveIfAppropriateLocked();
+            if (!mForceIdle) {
+                becomeInactiveIfAppropriateLocked();
+            }
         } else if (charging) {
             mCharging = charging;
-            becomeActiveLocked("charging", Process.myUid());
+            if (!mForceIdle) {
+                becomeActiveLocked("charging", Process.myUid());
+            }
         }
     }
 
@@ -989,7 +1101,7 @@
 
     void becomeInactiveIfAppropriateLocked() {
         if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
-        if (!mScreenOn && !mCharging && mEnabled && mState == STATE_ACTIVE) {
+        if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {
             // Screen has turned off; we are now going to become inactive and start
             // waiting to see if we will ultimately go idle.
             mState = STATE_INACTIVE;
@@ -1010,6 +1122,15 @@
         becomeInactiveIfAppropriateLocked();
     }
 
+    void exitForceIdleLocked() {
+        if (mForceIdle) {
+            mForceIdle = false;
+            if (mScreenOn || mCharging) {
+                becomeActiveLocked("exit-force-idle", Process.myUid());
+            }
+        }
+    }
+
     void stepIdleStateLocked() {
         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
         EventLogTags.writeDeviceIdleStep();
@@ -1138,20 +1259,28 @@
           mNextAlarmTime, mSensingAlarmIntent);
     }
 
-    private void updateWhitelistAppIdsLocked() {
-        mPowerSaveWhitelistAllAppIds.clear();
-        for (int i=0; i<mPowerSaveWhitelistApps.size(); i++) {
-            mPowerSaveWhitelistAllAppIds.put(mPowerSaveWhitelistApps.valueAt(i), true);
+    private static int[] buildAppIdArray(ArrayMap<String, Integer> systemApps,
+            ArrayMap<String, Integer> userApps, SparseBooleanArray outAppIds) {
+        outAppIds.clear();
+        for (int i=0; i<systemApps.size(); i++) {
+            outAppIds.put(systemApps.valueAt(i), true);
         }
-        for (int i=0; i<mPowerSaveWhitelistUserApps.size(); i++) {
-            mPowerSaveWhitelistAllAppIds.put(mPowerSaveWhitelistUserApps.valueAt(i), true);
+        for (int i=0; i<userApps.size(); i++) {
+            outAppIds.put(userApps.valueAt(i), true);
         }
-        int size = mPowerSaveWhitelistAllAppIds.size();
+        int size = outAppIds.size();
         int[] appids = new int[size];
         for (int i = 0; i < size; i++) {
-            appids[i] = mPowerSaveWhitelistAllAppIds.keyAt(i);
+            appids[i] = outAppIds.keyAt(i);
         }
-        mPowerSaveWhitelistAllAppIdArray = appids;
+        return appids;
+    }
+
+    private void updateWhitelistAppIdsLocked() {
+        mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
+                mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
+        mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
+                mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
         if (mLocalPowerManager != null) {
             if (DEBUG) {
                 Slog.d(TAG, "Setting wakelock whitelist to "
@@ -1320,6 +1449,9 @@
         pw.println("Commands:");
         pw.println("  step");
         pw.println("    Immediately step to next state, without waiting for alarm.");
+        pw.println("  force-idle");
+        pw.println("    Force directly into idle mode, regardless of other device state.");
+        pw.println("    Use \"step\" to get out.");
         pw.println("  disable");
         pw.println("    Completely disable device idle mode.");
         pw.println("  enable");
@@ -1362,6 +1494,7 @@
                     synchronized (this) {
                         long token = Binder.clearCallingIdentity();
                         try {
+                            exitForceIdleLocked();
                             stepIdleStateLocked();
                             pw.print("Stepped to: "); pw.println(stateToString(mState));
                         } finally {
@@ -1369,6 +1502,33 @@
                         }
                     }
                     return;
+                } else if ("force-idle".equals(arg)) {
+                    synchronized (this) {
+                        long token = Binder.clearCallingIdentity();
+                        try {
+                            if (!mEnabled) {
+                                pw.println("Unable to go idle; not enabled");
+                                return;
+                            }
+                            mForceIdle = true;
+                            becomeInactiveIfAppropriateLocked();
+                            int curState = mState;
+                            while (curState != STATE_IDLE) {
+                                stepIdleStateLocked();
+                                if (curState == mState) {
+                                    pw.print("Unable to go idle; stopped at ");
+                                    pw.println(stateToString(mState));
+                                    exitForceIdleLocked();
+                                    return;
+                                }
+                                curState = mState;
+                            }
+                            pw.println("Now forced in to idle mode");
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    }
+                    return;
                 } else if ("disable".equals(arg)) {
                     synchronized (this) {
                         long token = Binder.clearCallingIdentity();
@@ -1387,6 +1547,7 @@
                     synchronized (this) {
                         long token = Binder.clearCallingIdentity();
                         try {
+                            exitForceIdleLocked();
                             if (!mEnabled) {
                                 mEnabled = true;
                                 becomeInactiveIfAppropriateLocked();
@@ -1431,6 +1592,12 @@
                             }
                         } else {
                             synchronized (this) {
+                                for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
+                                    pw.print("system-excidle,");
+                                    pw.print(mPowerSaveWhitelistAppsExceptIdle.keyAt(j));
+                                    pw.print(",");
+                                    pw.println(mPowerSaveWhitelistAppsExceptIdle.valueAt(j));
+                                }
                                 for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
                                     pw.print("system,");
                                     pw.print(mPowerSaveWhitelistApps.keyAt(j));
@@ -1481,7 +1648,15 @@
         synchronized (this) {
             mConstants.dump(pw);
 
-            int size = mPowerSaveWhitelistApps.size();
+            int size = mPowerSaveWhitelistAppsExceptIdle.size();
+            if (size > 0) {
+                pw.println("  Whitelist (except idle) system apps:");
+                for (int i = 0; i < size; i++) {
+                    pw.print("    ");
+                    pw.println(mPowerSaveWhitelistAppsExceptIdle.keyAt(i));
+                }
+            }
+            size = mPowerSaveWhitelistApps.size();
             if (size > 0) {
                 pw.println("  Whitelist system apps:");
                 for (int i = 0; i < size; i++) {
@@ -1497,6 +1672,15 @@
                     pw.println(mPowerSaveWhitelistUserApps.keyAt(i));
                 }
             }
+            size = mPowerSaveWhitelistExceptIdleAppIds.size();
+            if (size > 0) {
+                pw.println("  Whitelist (except idle) all app ids:");
+                for (int i = 0; i < size; i++) {
+                    pw.print("    ");
+                    pw.print(mPowerSaveWhitelistExceptIdleAppIds.keyAt(i));
+                    pw.println();
+                }
+            }
             size = mPowerSaveWhitelistAllAppIds.size();
             if (size > 0) {
                 pw.println("  Whitelist all app ids:");
@@ -1531,6 +1715,7 @@
             }
 
             pw.print("  mEnabled="); pw.println(mEnabled);
+            pw.print("  mForceIdle="); pw.println(mForceIdle);
             pw.print("  mSigMotionSensor="); pw.println(mSigMotionSensor);
             pw.print("  mCurDisplay="); pw.println(mCurDisplay);
             pw.print("  mScreenOn="); pw.println(mScreenOn);
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index ad5406c..cd61347 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -84,6 +84,11 @@
     final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
 
     // These are the packages that are white-listed to be able to run in the
+    // background while in power save mode (but not whitelisted from device idle modes),
+    // as read from the configuration files.
+    final ArraySet<String> mAllowInPowerSaveExceptIdle = new ArraySet<>();
+
+    // These are the packages that are white-listed to be able to run in the
     // background while in power save mode, as read from the configuration files.
     final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
 
@@ -123,6 +128,10 @@
         return mPermissions;
     }
 
+    public ArraySet<String> getAllowInPowerSaveExceptIdle() {
+        return mAllowInPowerSaveExceptIdle;
+    }
+
     public ArraySet<String> getAllowInPowerSave() {
         return mAllowInPowerSave;
     }
@@ -329,6 +338,17 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
+                } else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) {
+                    String pkgname = parser.getAttributeValue(null, "package");
+                    if (pkgname == null) {
+                        Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
+                                + permFile + " at " + parser.getPositionDescription());
+                    } else {
+                        mAllowInPowerSaveExceptIdle.add(pkgname);
+                    }
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+
                 } else if ("allow-in-power-save".equals(name) && !onlyFeatures) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46bda8c..c0d0d13 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -39,6 +39,7 @@
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_BACKGROUND_BATTERY_SAVE;
@@ -46,7 +47,6 @@
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
-import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
@@ -284,6 +284,13 @@
 
     /**
      * UIDs that have been white-listed to always be able to have network access
+     * in power save mode, except device idle (doze) still applies.
+     * TODO: An int array might be sufficient
+     */
+    private final SparseBooleanArray mPowerSaveWhitelistExceptIdleAppIds = new SparseBooleanArray();
+
+    /**
+     * UIDs that have been white-listed to always be able to have network access
      * in power save mode.
      * TODO: An int array might be sufficient
      */
@@ -302,9 +309,6 @@
     /** Foreground at UID granularity. */
     final SparseIntArray mUidState = new SparseIntArray();
 
-    /** The current maximum process state that we are considering to be foreground. */
-    private int mCurForegroundState = ActivityManager.PROCESS_STATE_TOP;
-
     private final RemoteCallbackList<INetworkPolicyListener>
             mListeners = new RemoteCallbackList<>();
 
@@ -365,7 +369,14 @@
 
     void updatePowerSaveWhitelistLocked() {
         try {
-            final int[] whitelist = mDeviceIdleController.getAppIdWhitelist();
+            int[] whitelist = mDeviceIdleController.getAppIdWhitelistExceptIdle();
+            mPowerSaveWhitelistExceptIdleAppIds.clear();
+            if (whitelist != null) {
+                for (int uid : whitelist) {
+                    mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
+                }
+            }
+            whitelist = mDeviceIdleController.getAppIdWhitelist();
             mPowerSaveWhitelistAppIds.clear();
             if (whitelist != null) {
                 for (int uid : whitelist) {
@@ -425,7 +436,6 @@
                         if (mRestrictPower != enabled) {
                             mRestrictPower = enabled;
                             updateRulesForGlobalChangeLocked(true);
-                            updateRulesForTempWhitelistChangeLocked();
                         }
                     }
                 }
@@ -437,9 +447,12 @@
             readPolicyLocked();
 
             if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) {
-                updateRulesForGlobalChangeLocked(true);
-                updateRulesForTempWhitelistChangeLocked();
+                updateRulesForGlobalChangeLocked(false);
                 updateNotificationsLocked();
+            } else {
+                // If we are not in any special mode, we just need to make sure the current
+                // app idle state is updated.
+                updateRulesForAppIdleLocked();
             }
         }
 
@@ -1907,7 +1920,6 @@
             fout.print("Restrict background: "); fout.println(mRestrictBackground);
             fout.print("Restrict power: "); fout.println(mRestrictPower);
             fout.print("Device idle: "); fout.println(mDeviceIdleMode);
-            fout.print("Current foreground state: "); fout.println(mCurForegroundState);
             fout.println("Network policies:");
             fout.increaseIndent();
             for (int i = 0; i < mNetworkPolicy.size(); i++) {
@@ -1931,6 +1943,20 @@
             }
             fout.decreaseIndent();
 
+            size = mPowerSaveWhitelistExceptIdleAppIds.size();
+            if (size > 0) {
+                fout.println("Power save whitelist (except idle) app ids:");
+                fout.increaseIndent();
+                for (int i = 0; i < size; i++) {
+                    fout.print("UID=");
+                    fout.print(mPowerSaveWhitelistExceptIdleAppIds.keyAt(i));
+                    fout.print(": ");
+                    fout.print(mPowerSaveWhitelistExceptIdleAppIds.valueAt(i));
+                    fout.println();
+                }
+                fout.decreaseIndent();
+            }
+
             size = mPowerSaveWhitelistAppIds.size();
             if (size > 0) {
                 fout.println("Power save whitelist app ids:");
@@ -1960,7 +1986,7 @@
                 int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
                 fout.print(" state=");
                 fout.print(state);
-                fout.print(state <= mCurForegroundState ? " (fg)" : " (bg)");
+                fout.print(state <= ActivityManager.PROCESS_STATE_TOP ? " (fg)" : " (bg)");
 
                 fout.print(" rules=");
                 final int rulesIndex = mUidRules.indexOfKey(uid);
@@ -1988,7 +2014,7 @@
     boolean isUidForegroundLocked(int uid) {
         // only really in foreground when screen is also on
         return mScreenOn && mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY)
-                <= mCurForegroundState;
+                <= ActivityManager.PROCESS_STATE_TOP;
     }
 
     /**
@@ -2024,8 +2050,8 @@
     }
 
     void updateRulesForUidStateChangeLocked(int uid, int oldUidState, int newUidState) {
-        final boolean oldForeground = oldUidState <= mCurForegroundState;
-        final boolean newForeground = newUidState <= mCurForegroundState;
+        final boolean oldForeground = oldUidState <= ActivityManager.PROCESS_STATE_TOP;
+        final boolean newForeground = newUidState <= ActivityManager.PROCESS_STATE_TOP;
         if (oldForeground != newForeground) {
             updateRulesForUidLocked(uid);
         }
@@ -2049,7 +2075,7 @@
         // only update rules for anyone with foreground activities
         final int size = mUidState.size();
         for (int i = 0; i < size; i++) {
-            if (mUidState.valueAt(i) <= mCurForegroundState) {
+            if (mUidState.valueAt(i) <= ActivityManager.PROCESS_STATE_TOP) {
                 final int uid = mUidState.keyAt(i);
                 updateRulesForUidLocked(uid);
             }
@@ -2069,9 +2095,11 @@
             for (int ui = users.size() - 1; ui >= 0; ui--) {
                 UserInfo user = users.get(ui);
                 for (int i = mPowerSaveTempWhitelistAppIds.size() - 1; i >= 0; i--) {
-                    int appId = mPowerSaveTempWhitelistAppIds.keyAt(i);
-                    int uid = UserHandle.getUid(user.id, appId);
-                    uidRules.put(uid, FIREWALL_RULE_ALLOW);
+                    if (mPowerSaveTempWhitelistAppIds.valueAt(i)) {
+                        int appId = mPowerSaveTempWhitelistAppIds.keyAt(i);
+                        int uid = UserHandle.getUid(user.id, appId);
+                        uidRules.put(uid, FIREWALL_RULE_ALLOW);
+                    }
                 }
                 for (int i = mPowerSaveWhitelistAppIds.size() - 1; i >= 0; i--) {
                     int appId = mPowerSaveWhitelistAppIds.keyAt(i);
@@ -2089,6 +2117,45 @@
         enableFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mDeviceIdleMode);
     }
 
+    void updateRuleForDeviceIdleLocked(int uid) {
+        if (mDeviceIdleMode) {
+            int appId = UserHandle.getAppId(uid);
+            if (mPowerSaveTempWhitelistAppIds.get(appId) || mPowerSaveWhitelistAppIds.get(appId)
+                    || isProcStateAllowedWhileIdle(mUidState.get(uid))) {
+                setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, uid, FIREWALL_RULE_ALLOW);
+            } else {
+                setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, uid, FIREWALL_RULE_DEFAULT);
+            }
+        }
+    }
+
+    void updateRulesForAppIdleLocked() {
+        // Fully update the app idle firewall chain.
+        SparseIntArray uidRules = new SparseIntArray();
+        final List<UserInfo> users = mUserManager.getUsers();
+        for (int ui = users.size() - 1; ui >= 0; ui--) {
+            UserInfo user = users.get(ui);
+            int[] idleUids = mUsageStats.getIdleUidsForUser(user.id);
+            for (int uid : idleUids) {
+                if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) {
+                    uidRules.put(uid, FIREWALL_RULE_DENY);
+                }
+            }
+        }
+        setUidFirewallRules(FIREWALL_CHAIN_STANDBY, uidRules);
+    }
+
+    void updateRuleForAppIdleLocked(int uid) {
+        if (!isUidValidForRules(uid)) return;
+
+        int appId = UserHandle.getAppId(uid);
+        if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)) {
+            setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
+        } else {
+            setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+        }
+    }
+
     void updateRulesForAppIdleParoleLocked() {
         boolean enableChain = !mUsageStats.isAppIdleParoleOn();
         enableFirewallChainLocked(FIREWALL_CHAIN_STANDBY, enableChain);
@@ -2101,14 +2168,8 @@
     void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
         final PackageManager pm = mContext.getPackageManager();
 
-        // If we are in restrict power mode, we allow all important apps
-        // to have data access.  Otherwise, we restrict data access to only
-        // the top apps.
-        mCurForegroundState = (!mRestrictBackground && mRestrictPower)
-                ? ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-                : ActivityManager.PROCESS_STATE_TOP;
-
         updateRulesForDeviceIdleLocked();
+        updateRulesForAppIdleLocked();
 
         // update rules for all installed applications
         final List<UserInfo> users = mUserManager.getUsers();
@@ -2138,10 +2199,9 @@
         for (UserInfo user : users) {
             for (int i = mPowerSaveTempWhitelistAppIds.size() - 1; i >= 0; i--) {
                 int appId = mPowerSaveTempWhitelistAppIds.keyAt(i);
-                boolean isAllow = mPowerSaveTempWhitelistAppIds.valueAt(i);
                 int uid = UserHandle.getUid(user.id, appId);
-                updateRulesForUidLocked(uid);
-                setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, uid, !isAllow);
+                updateRuleForAppIdleLocked(uid);
+                updateRuleForDeviceIdleLocked(uid);
             }
         }
     }
@@ -2188,16 +2248,12 @@
 
         final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
         final boolean uidForeground = isUidForegroundLocked(uid);
-        final boolean uidIdle = isUidIdle(uid);
 
         // derive active rules based on policy and active state
 
         int appId = UserHandle.getAppId(uid);
         int uidRules = RULE_ALLOW_ALL;
-        if (uidIdle && !mPowerSaveWhitelistAppIds.get(appId)
-                && !mPowerSaveTempWhitelistAppIds.get(appId)) {
-            uidRules = RULE_REJECT_ALL;
-        } else if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
+        if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
             // uid in background, and policy says to block metered data
             uidRules = RULE_REJECT_METERED;
         } else if (mRestrictBackground) {
@@ -2206,7 +2262,7 @@
                 uidRules = RULE_REJECT_METERED;
             }
         } else if (mRestrictPower) {
-            final boolean whitelisted = mPowerSaveWhitelistAppIds.get(appId)
+            final boolean whitelisted = mPowerSaveWhitelistExceptIdleAppIds.get(appId)
                     || mPowerSaveTempWhitelistAppIds.get(appId);
             if (!whitelisted && !uidForeground
                     && (uidPolicy & POLICY_ALLOW_BACKGROUND_BATTERY_SAVE) == 0) {
@@ -2232,13 +2288,6 @@
             setUidNetworkRules(uid, rejectMetered);
         }
 
-        // Update firewall rules if necessary
-        final boolean oldFirewallReject = (oldRules & RULE_REJECT_ALL) != 0;
-        final boolean firewallReject = (uidRules & RULE_REJECT_ALL) != 0;
-        if (oldFirewallReject != firewallReject) {
-            setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, firewallReject);
-        }
-
         // dispatch changed rule to existing listeners
         if (oldRules != uidRules) {
             mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules).sendToTarget();
@@ -2260,7 +2309,7 @@
             try {
                 int uid = mContext.getPackageManager().getPackageUid(packageName, userId);
                 synchronized (mRulesLock) {
-                    updateRulesForUidLocked(uid);
+                    updateRuleForAppIdleLocked(uid);
                 }
             } catch (NameNotFoundException nnfe) {
             }
@@ -2422,10 +2471,9 @@
     /**
      * Add or remove a uid to the firewall blacklist for all network ifaces.
      */
-    private void setUidFirewallRule(int chain, int uid, boolean rejectOnAll) {
+    private void setUidFirewallRule(int chain, int uid, int rule) {
         try {
-            mNetworkManager.setFirewallUidRule(chain, uid,
-                    rejectOnAll ? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW);
+            mNetworkManager.setFirewallUidRule(chain, uid, rule);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting firewall uid rules", e);
         } catch (RemoteException e) {