Merge "Added API to whitelist apps for background restriction."
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 7f5f377..06aa616 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -52,6 +52,11 @@
     void setRestrictBackground(boolean restrictBackground);
     boolean getRestrictBackground();
 
+    /** Control which applications can be exempt from background data restrictions */
+    void addRestrictBackgroundWhitelistedUid(int uid);
+    void removeRestrictBackgroundWhitelistedUid(int uid);
+    int[] getRestrictBackgroundWhitelistedUids();
+
     void setDeviceIdleMode(boolean enabled);
 
     NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4764300..38ebc80 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,7 @@
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
@@ -153,6 +154,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
@@ -213,6 +215,8 @@
     private static final String TAG_NETWORK_POLICY = "network-policy";
     private static final String TAG_UID_POLICY = "uid-policy";
     private static final String TAG_APP_POLICY = "app-policy";
+    private static final String TAG_WHITELIST = "whitelist";
+    private static final String TAG_RESTRICT_BACKGROUND = "restrict-background";
 
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
@@ -304,6 +308,11 @@
 
     private final SparseBooleanArray mPowerSaveTempWhitelistAppIds = new SparseBooleanArray();
 
+    /**
+     * UIDs that have been white-listed to avoid restricted background.
+     */
+    private final SparseBooleanArray mRestrictBackgroundWhitelistUids = new SparseBooleanArray();
+
     /** Set of ifaces that are metered. */
     private ArraySet<String> mMeteredIfaces = new ArraySet<>();
     /** Set of over-limit templates that have been notified. */
@@ -324,6 +333,8 @@
 
     private final AppOpsManager mAppOps;
 
+    private final MyPackageMonitor mPackageMonitor;
+
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
 
@@ -363,6 +374,8 @@
         mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
 
         mAppOps = context.getSystemService(AppOpsManager.class);
+
+        mPackageMonitor = new MyPackageMonitor();
     }
 
     public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -431,6 +444,8 @@
 
         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
 
+        mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
+
         synchronized (mRulesLock) {
             updatePowerSaveWhitelistLocked();
             mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -1127,7 +1142,7 @@
         // If we are in restrict power mode, we want to treat all interfaces
         // as metered, to restrict access to the network by uid.  However, we
         // will not have a bandwidth limit.  Also only do this if restrict
-        // background data use is *not* enabled, since that takes precendence
+        // background data use is *not* enabled, since that takes precedence
         // use over those networks can have a cost associated with it).
         final boolean powerSave = mRestrictPower && !mRestrictBackground;
 
@@ -1339,6 +1354,7 @@
 
             int type;
             int version = VERSION_INIT;
+            boolean insideWhitelist = false;
             while ((type = in.next()) != END_DOCUMENT) {
                 final String tag = in.getName();
                 if (type == START_TAG) {
@@ -1431,7 +1447,17 @@
                         } else {
                             Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
                         }
+                    } else if (TAG_WHITELIST.equals(tag)) {
+                        insideWhitelist = true;
+                    } else if (TAG_RESTRICT_BACKGROUND.equals(tag) && insideWhitelist) {
+                        final int uid = readIntAttribute(in, ATTR_UID);
+                        mRestrictBackgroundWhitelistUids.put(uid, true);
                     }
+                } else if (type == END_TAG) {
+                    if (TAG_WHITELIST.equals(tag)) {
+                        insideWhitelist = false;
+                    }
+
                 }
             }
 
@@ -1519,6 +1545,21 @@
             }
 
             out.endTag(null, TAG_POLICY_LIST);
+
+            // write all whitelists
+            out.startTag(null, TAG_WHITELIST);
+
+            // restrict background whitelist
+            final int size = mRestrictBackgroundWhitelistUids.size();
+            for (int i = 0; i < size; i++) {
+                final int uid = mRestrictBackgroundWhitelistUids.keyAt(i);
+                out.startTag(null, TAG_RESTRICT_BACKGROUND);
+                writeIntAttribute(out, ATTR_UID, uid);
+                out.endTag(null, TAG_RESTRICT_BACKGROUND);
+            }
+
+            out.endTag(null, TAG_WHITELIST);
+
             out.endDocument();
 
             mPolicyFile.finishWrite(fos);
@@ -1789,6 +1830,49 @@
     }
 
     @Override
+    public void addRestrictBackgroundWhitelistedUid(int uid) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist");
+        synchronized (mRulesLock) {
+            mRestrictBackgroundWhitelistUids.append(uid, true);
+            writePolicyLocked();
+            // TODO: call other update methods like updateNetworkRulesLocked?
+        }
+    }
+
+    @Override
+    public void removeRestrictBackgroundWhitelistedUid(int uid) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist");
+        synchronized (mRulesLock) {
+            removeRestrictBackgroundWhitelistedUidLocked(uid);
+        }
+    }
+
+    private void removeRestrictBackgroundWhitelistedUidLocked(int uid) {
+        mRestrictBackgroundWhitelistUids.delete(uid);
+        writePolicyLocked();
+        // TODO: call other update methods like updateNetworkRulesLocked?
+    }
+
+    @Override
+    public int[] getRestrictBackgroundWhitelistedUids() {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        synchronized (mRulesLock) {
+            final int size = mRestrictBackgroundWhitelistUids.size();
+            final int[] whitelist = new int[size];
+            for (int i = 0; i < size; i++) {
+                whitelist[i] = mRestrictBackgroundWhitelistUids.keyAt(i);
+            }
+            if (LOGV) {
+                Slog.v(TAG, "getRestrictBackgroundWhitelistedUids(): "
+                        + mRestrictBackgroundWhitelistUids);
+            }
+            return whitelist;
+        }
+    }
+
+    @Override
     public boolean getRestrictBackground() {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
@@ -1978,6 +2062,18 @@
                 fout.decreaseIndent();
             }
 
+            size = mRestrictBackgroundWhitelistUids.size();
+            if (size > 0) {
+                fout.println("Restrict background whitelist uids:");
+                fout.increaseIndent();
+                for (int i = 0; i < size; i++) {
+                    fout.print("UID=");
+                    fout.print(mRestrictBackgroundWhitelistUids.keyAt(i));
+                    fout.println();
+                }
+                fout.decreaseIndent();
+            }
+
             final SparseBooleanArray knownUids = new SparseBooleanArray();
             collectKeys(mUidState, knownUids);
             collectKeys(mUidRules, knownUids);
@@ -2279,8 +2375,11 @@
             uidRules = RULE_REJECT_METERED;
         } else if (mRestrictBackground) {
             if (!uidForeground) {
-                // uid in background, and global background disabled
-                uidRules = RULE_REJECT_METERED;
+                // uid in background, global background disabled, and this uid is not on the white
+                // list of those allowed background access while global background is disabled
+                if (!mRestrictBackgroundWhitelistUids.get(uid)) {
+                    uidRules = RULE_REJECT_METERED;
+                }
             }
         } else if (mRestrictPower) {
             final boolean whitelisted = mPowerSaveWhitelistExceptIdleAppIds.get(appId)
@@ -2642,4 +2741,23 @@
             }
         }
     }
+
+    private class MyPackageMonitor extends PackageMonitor {
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            if (LOGV) Slog.v(TAG, "onPackageRemoved: " + packageName + " ->" + uid);
+            synchronized (mRulesLock) {
+                removeRestrictBackgroundWhitelistedUidLocked(uid);
+            }
+        }
+
+        @Override
+        public void onPackageRemovedAllUsers(String packageName, int uid) {
+            if (LOGV) Slog.v(TAG, "onPackageRemovedAllUsers: " + packageName + " ->" + uid);
+            synchronized (mRulesLock) {
+                removeRestrictBackgroundWhitelistedUidLocked(uid);
+            }
+        }
+    }
 }