am 0385e3e9: am 8e63bbfd: am efa4dee1: am 977d421a: am f0e5501e: Merge "Fix issue #22989030: Separate battery whitelists" into mnc-dev

* commit '0385e3e908fb9988bb2d2d0d15e32cb6a819b34e':
  Fix issue #22989030: Separate battery whitelists
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 9113426..948ea1e 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -77,6 +77,13 @@
     public abstract boolean isAppIdle(String packageName, int userId);
 
     /**
+     * Returns all of the uids for a given user where all packages associating with that uid
+     * are in the app idle state -- there are no associated apps that are not idle.  This means
+     * all of the returned uids can be safely considered app idle.
+     */
+    public abstract int[] getIdleUidsForUser(int userId);
+
+    /**
      * @return True if currently app idle parole mode is on.  This means all idle apps are allow to
      * run for a short period of time.
      */
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index d3eec1e..f55883a 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -22,10 +22,14 @@
 interface IDeviceIdleController {
     void addPowerSaveWhitelistApp(String name);
     void removePowerSaveWhitelistApp(String name);
+    String[] getSystemPowerWhitelistExceptIdle();
     String[] getSystemPowerWhitelist();
+    String[] getFullPowerWhitelistExceptIdle();
     String[] getFullPowerWhitelist();
+    int[] getAppIdWhitelistExceptIdle();
     int[] getAppIdWhitelist();
     int[] getAppIdTempWhitelist();
+    boolean isPowerSaveWhitelistExceptIdleApp(String name);
     boolean isPowerSaveWhitelistApp(String name);
     void addPowerSaveTempWhitelistApp(String name, long duration, int userId, String reason);
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index 2b85a21..e5c729d 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -184,6 +184,14 @@
     }
 
     /**
+     * Directly set the value at a particular index.
+     * @hide
+     */
+    public void setValueAt(int index, int value) {
+        mValues[index] = value;
+    }
+
+    /**
      * Returns the index for which {@link #keyAt} would return the
      * specified key, or a negative number if the specified
      * key is not mapped.
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 579d2df..350310c 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -141,6 +141,6 @@
 
     <!-- These are the standard packages that are white-listed to always have internet
          access while in power save mode, even if they aren't in the foreground. -->
-    <allow-in-power-save package="com.android.providers.downloads" />
+    <allow-in-power-save-except-idle package="com.android.providers.downloads" />
 
 </permissions>
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 7334f5b..7d16a35 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;
@@ -282,6 +282,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
      */
@@ -300,9 +307,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<>();
 
@@ -363,7 +367,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) {
@@ -423,7 +434,6 @@
                         if (mRestrictPower != enabled) {
                             mRestrictPower = enabled;
                             updateRulesForGlobalChangeLocked(true);
-                            updateRulesForTempWhitelistChangeLocked();
                         }
                     }
                 }
@@ -435,9 +445,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();
             }
         }
 
@@ -1905,7 +1918,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++) {
@@ -1929,6 +1941,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:");
@@ -1958,7 +1984,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);
@@ -1986,7 +2012,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;
     }
 
     /**
@@ -2022,8 +2048,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);
         }
@@ -2047,7 +2073,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);
             }
@@ -2067,9 +2093,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);
@@ -2087,6 +2115,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);
@@ -2099,14 +2166,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();
@@ -2136,10 +2197,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);
             }
         }
     }
@@ -2186,16 +2246,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) {
@@ -2204,7 +2260,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) {
@@ -2230,13 +2286,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();
@@ -2258,7 +2307,7 @@
             try {
                 int uid = mContext.getPackageManager().getPackageUid(packageName, userId);
                 synchronized (mRulesLock) {
-                    updateRulesForUidLocked(uid);
+                    updateRuleForAppIdleLocked(uid);
                 }
             } catch (NameNotFoundException nnfe) {
             }
@@ -2420,10 +2469,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) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index eba9e1e..8e475e6 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -35,6 +35,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
@@ -65,6 +66,7 @@
 import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.view.Display;
 
@@ -799,7 +801,10 @@
         }
         if (packageName.equals("android")) return false;
         try {
-            if (mDeviceIdleController.isPowerSaveWhitelistApp(packageName)) {
+            // We allow all whitelisted apps, including those that don't want to be whitelisted
+            // for idle mode, because app idle (aka app standby) is really not as big an issue
+            // for controlling who participates vs. doze mode.
+            if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
                 return false;
             }
         } catch (RemoteException re) {
@@ -825,6 +830,72 @@
         return isAppIdleUnfiltered(packageName, userService, timeNow, screenOnTime);
     }
 
+    int[] getIdleUidsForUser(int userId) {
+        if (!mAppIdleEnabled) {
+            return new int[0];
+        }
+
+        final long timeNow;
+        final UserUsageStatsService userService;
+        final long screenOnTime;
+        synchronized (mLock) {
+            timeNow = checkAndGetTimeLocked();
+            userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+            screenOnTime = getScreenOnTimeLocked(timeNow);
+        }
+
+        List<ApplicationInfo> apps;
+        try {
+            ParceledListSlice<ApplicationInfo> slice
+                    = AppGlobals.getPackageManager().getInstalledApplications(0, userId);
+            apps = slice.getList();
+        } catch (RemoteException e) {
+            return new int[0];
+        }
+
+        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
+        // associated with that uid, upper 16 bits is the number of those apps that is idle.
+        SparseIntArray uidStates = new SparseIntArray();
+
+        // Now resolve all app state.  Iterating over all apps, keeping track of how many
+        // we find for each uid and how many of those are idle.
+        for (int i = apps.size()-1; i >= 0; i--) {
+            ApplicationInfo ai = apps.get(i);
+
+            // Check whether this app is idle.
+            boolean idle = isAppIdleFiltered(ai.packageName, userId, userService, timeNow,
+                    screenOnTime);
+
+            int index = uidStates.indexOfKey(ai.uid);
+            if (index < 0) {
+                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
+            } else {
+                int value = uidStates.valueAt(index);
+                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
+            }
+        }
+
+        int numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                numIdle++;
+            }
+        }
+
+        int[] res = new int[numIdle];
+        numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                res[numIdle] = uidStates.keyAt(i);
+                numIdle++;
+            }
+        }
+
+        return res;
+    }
+
     void setAppIdle(String packageName, boolean idle, int userId) {
         if (packageName == null) return;
 
@@ -1284,6 +1355,11 @@
         }
 
         @Override
+        public int[] getIdleUidsForUser(int userId) {
+            return UsageStatsService.this.getIdleUidsForUser(userId);
+        }
+
+        @Override
         public boolean isAppIdleParoleOn() {
             return mAppIdleParoled;
         }