Flag to mark foreground jobs, fix data saver.

When a job will eventually run in the foreground, the internal
scheduling needs to ignore any background network restrictions when
satisfying constraints.  This also means the job should ignore the
current device doze state, since the requesting app could get the
same behavior by starting their own foreground service.

Always dispatch network policy changes to ConnectivityService first
to ensure that it has up-to-date information.  Fix bugs around data
saver that were causing networks to not be marked as BLOCKED for
background apps; before this fix apps would have been spinning in
internal connectivity loops, thinking that the network was actually
connected when the kernel was actually blocking their traffic.

Offer new ConnectivityService method overloads to ignore the blocked
state for a specific UID.

Print unsatisfied job constraints to aid debugging.

Bug: 26571724
Change-Id: Iaaa17933e6dc1bf6d3dff26d0bfc12222e51e241
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index ecfc527..c475575 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -16,6 +16,8 @@
 
 package android.app.job;
 
+import static android.util.TimeUtils.formatDuration;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -24,7 +26,6 @@
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.util.Log;
-import static android.util.TimeUtils.formatDuration;
 
 import java.util.ArrayList;
 
@@ -154,6 +155,20 @@
      */
     public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
 
+    /**
+     * Indicates that the implementation of this job will be using
+     * {@link JobService#startForeground(int, android.app.Notification)} to run
+     * in the foreground.
+     * <p>
+     * When set, the internal scheduling of this job will ignore any background
+     * network restrictions for the requesting app. Note that this flag alone
+     * doesn't actually place your {@link JobService} in the foreground; you
+     * still need to post the notification yourself.
+     *
+     * @hide
+     */
+    public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
+
     private final int jobId;
     private final PersistableBundle extras;
     private final ComponentName service;
@@ -174,6 +189,7 @@
     private final long initialBackoffMillis;
     private final int backoffPolicy;
     private final int priority;
+    private final int flags;
 
     /**
      * Unique job id associated with this class. This is assigned to your job by the scheduler.
@@ -201,6 +217,11 @@
         return priority;
     }
 
+    /** @hide */
+    public int getFlags() {
+        return flags;
+    }
+
     /**
      * Whether this job needs the device to be plugged in.
      */
@@ -356,6 +377,7 @@
         hasEarlyConstraint = in.readInt() == 1;
         hasLateConstraint = in.readInt() == 1;
         priority = in.readInt();
+        flags = in.readInt();
     }
 
     private JobInfo(JobInfo.Builder b) {
@@ -381,6 +403,7 @@
         hasEarlyConstraint = b.mHasEarlyConstraint;
         hasLateConstraint = b.mHasLateConstraint;
         priority = b.mPriority;
+        flags = b.mFlags;
     }
 
     @Override
@@ -410,6 +433,7 @@
         out.writeInt(hasEarlyConstraint ? 1 : 0);
         out.writeInt(hasLateConstraint ? 1 : 0);
         out.writeInt(priority);
+        out.writeInt(this.flags);
     }
 
     public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
@@ -504,6 +528,7 @@
         private PersistableBundle mExtras = PersistableBundle.EMPTY;
         private ComponentName mJobService;
         private int mPriority = PRIORITY_DEFAULT;
+        private int mFlags;
         // Requirements.
         private boolean mRequiresCharging;
         private boolean mRequiresDeviceIdle;
@@ -539,14 +564,18 @@
             mJobId = jobId;
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
         public Builder setPriority(int priority) {
             mPriority = priority;
             return this;
         }
 
+        /** @hide */
+        public Builder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+
         /**
          * Set optional extras. This is persisted, so we only allow primitive types.
          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index faf5c64..933dddf 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -774,8 +774,13 @@
      * @hide
      */
     public Network getActiveNetworkForUid(int uid) {
+        return getActiveNetworkForUid(uid, false);
+    }
+
+    /** {@hide} */
+    public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) {
         try {
-            return mService.getActiveNetworkForUid(uid);
+            return mService.getActiveNetworkForUid(uid, ignoreBlocked);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -836,8 +841,13 @@
      * {@hide}
      */
     public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+        return getActiveNetworkInfoForUid(uid, false);
+    }
+
+    /** {@hide} */
+    public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
         try {
-            return mService.getActiveNetworkInfoForUid(uid);
+            return mService.getActiveNetworkInfoForUid(uid, ignoreBlocked);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -880,8 +890,13 @@
      *        is not valid.
      */
     public NetworkInfo getNetworkInfo(Network network) {
+        return getNetworkInfoForUid(network, Process.myUid(), false);
+    }
+
+    /** {@hide} */
+    public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) {
         try {
-            return mService.getNetworkInfoForNetwork(network);
+            return mService.getNetworkInfoForUid(network, uid, ignoreBlocked);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index c897c45..aec6b3e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -44,11 +44,11 @@
 interface IConnectivityManager
 {
     Network getActiveNetwork();
-    Network getActiveNetworkForUid(int uid);
+    Network getActiveNetworkForUid(int uid, boolean ignoreBlocked);
     NetworkInfo getActiveNetworkInfo();
-    NetworkInfo getActiveNetworkInfoForUid(int uid);
+    NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked);
     NetworkInfo getNetworkInfo(int networkType);
-    NetworkInfo getNetworkInfoForNetwork(in Network network);
+    NetworkInfo getNetworkInfoForUid(in Network network, int uid, boolean ignoreBlocked);
     NetworkInfo[] getAllNetworkInfo();
     Network getNetworkForType(int networkType);
     Network[] getAllNetworks();
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 2b8b28d..224ff5b 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -38,6 +38,9 @@
 
     boolean isUidForeground(int uid);
 
+    /** Higher priority listener before general event dispatch */
+    void setConnectivityListener(INetworkPolicyListener listener);
+
     void registerListener(INetworkPolicyListener listener);
     void unregisterListener(INetworkPolicyListener listener);
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d85827e..f5e9d19 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -29,8 +29,12 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
+import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
+
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
 import android.app.Notification;
@@ -96,8 +100,10 @@
 import android.security.KeyStore;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.LocalLog.ReadOnlyLocalLog;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -121,9 +127,9 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.KeepaliveTracker;
-import com.android.server.connectivity.NetworkDiagnostics;
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkAgentInfo;
+import com.android.server.connectivity.NetworkDiagnostics;
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.PacManager;
 import com.android.server.connectivity.PermissionMonitor;
@@ -131,8 +137,8 @@
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
+
 import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -152,11 +158,11 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.SortedSet;
-import java.util.TreeSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
 /**
  * @hide
@@ -202,9 +208,14 @@
     /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
     private Object mRulesLock = new Object();
     /** Currently active network rules by UID. */
+    @GuardedBy("mRulesLock")
     private SparseIntArray mUidRules = new SparseIntArray();
     /** Set of ifaces that are costly. */
-    private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+    @GuardedBy("mRulesLock")
+    private ArraySet<String> mMeteredIfaces = new ArraySet<>();
+    /** Flag indicating if background data is restricted. */
+    @GuardedBy("mRulesLock")
+    private boolean mRestrictBackground;
 
     final private Context mContext;
     private int mNetworkPreference;
@@ -651,7 +662,8 @@
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
         try {
-            mPolicyManager.registerListener(mPolicyListener);
+            mPolicyManager.setConnectivityListener(mPolicyListener);
+            mRestrictBackground = mPolicyManager.getRestrictBackground();
         } catch (RemoteException e) {
             // ouch, no rules updates means some processes may never get network
             loge("unable to register INetworkPolicyListener" + e.toString());
@@ -819,7 +831,7 @@
         throw new IllegalStateException("No free netIds");
     }
 
-    private NetworkState getFilteredNetworkState(int networkType, int uid) {
+    private NetworkState getFilteredNetworkState(int networkType, int uid, boolean ignoreBlocked) {
         if (mLegacyTypeTracker.isTypeSupported(networkType)) {
             final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
             final NetworkState state;
@@ -834,7 +846,7 @@
                 state = new NetworkState(info, new LinkProperties(), new NetworkCapabilities(),
                         null, null, null);
             }
-            filterNetworkStateForUid(state, uid);
+            filterNetworkStateForUid(state, uid, ignoreBlocked);
             return state;
         } else {
             return NetworkState.EMPTY;
@@ -890,22 +902,36 @@
     /**
      * Check if UID should be blocked from using the network with the given LinkProperties.
      */
-    private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid) {
-        final boolean networkCostly;
+    private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid,
+            boolean ignoreBlocked) {
+        // Networks aren't blocked when ignoring blocked status
+        if (ignoreBlocked) return false;
+        // Networks are never blocked for system services
+        if (uid < Process.FIRST_APPLICATION_UID) return false;
+
+        final boolean networkMetered;
         final int uidRules;
 
         final String iface = (lp == null ? "" : lp.getInterfaceName());
         synchronized (mRulesLock) {
-            networkCostly = mMeteredIfaces.contains(iface);
-            uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+            networkMetered = mMeteredIfaces.contains(iface);
+            uidRules = mUidRules.get(uid, RULE_UNKNOWN);
         }
 
-        if (uidRules == RULE_REJECT_ALL) {
-            return true;
-        } else if ((uidRules == RULE_REJECT_METERED) && networkCostly) {
-            return true;
-        } else {
-            return false;
+        switch (uidRules) {
+            case RULE_ALLOW_ALL:
+            case RULE_ALLOW_METERED:
+            case RULE_TEMPORARY_ALLOW_METERED:
+                return false;
+            case RULE_REJECT_METERED:
+                return networkMetered;
+            case RULE_REJECT_ALL:
+                return true;
+            case RULE_UNKNOWN:
+            default:
+                // When background data is restricted device-wide, the default
+                // behavior for apps should be like RULE_REJECT_METERED
+                return mRestrictBackground ? networkMetered : false;
         }
     }
 
@@ -930,10 +956,10 @@
      * on {@link #isNetworkWithLinkPropertiesBlocked}, or
      * {@link NetworkInfo#isMetered()} based on network policies.
      */
-    private void filterNetworkStateForUid(NetworkState state, int uid) {
+    private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) {
         if (state == null || state.networkInfo == null || state.linkProperties == null) return;
 
-        if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid)) {
+        if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) {
             state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
         if (mLockdownTracker != null) {
@@ -962,7 +988,7 @@
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
         final NetworkState state = getUnfilteredActiveNetworkState(uid);
-        filterNetworkStateForUid(state, uid);
+        filterNetworkStateForUid(state, uid, false);
         maybeLogBlockedNetworkInfo(state.networkInfo, uid);
         return state.networkInfo;
     }
@@ -970,16 +996,16 @@
     @Override
     public Network getActiveNetwork() {
         enforceAccessPermission();
-        return getActiveNetworkForUidInternal(Binder.getCallingUid());
+        return getActiveNetworkForUidInternal(Binder.getCallingUid(), false);
     }
 
     @Override
-    public Network getActiveNetworkForUid(int uid) {
+    public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) {
         enforceConnectivityInternalPermission();
-        return getActiveNetworkForUidInternal(uid);
+        return getActiveNetworkForUidInternal(uid, ignoreBlocked);
     }
 
-    private Network getActiveNetworkForUidInternal(final int uid) {
+    private Network getActiveNetworkForUidInternal(final int uid, boolean ignoreBlocked) {
         final int user = UserHandle.getUserId(uid);
         int vpnNetId = NETID_UNSET;
         synchronized (mVpns) {
@@ -994,7 +1020,10 @@
             if (nai != null) return nai.network;
         }
         nai = getDefaultNetwork();
-        if (nai != null && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) nai = null;
+        if (nai != null
+                && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, ignoreBlocked)) {
+            nai = null;
+        }
         return nai != null ? nai.network : null;
     }
 
@@ -1006,10 +1035,10 @@
     }
 
     @Override
-    public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+    public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
         enforceConnectivityInternalPermission();
         final NetworkState state = getUnfilteredActiveNetworkState(uid);
-        filterNetworkStateForUid(state, uid);
+        filterNetworkStateForUid(state, uid, ignoreBlocked);
         return state.networkInfo;
     }
 
@@ -1023,22 +1052,21 @@
             // getUnfilteredActiveNetworkState.
             final NetworkState state = getUnfilteredActiveNetworkState(uid);
             if (state.networkInfo != null && state.networkInfo.getType() == networkType) {
-                filterNetworkStateForUid(state, uid);
+                filterNetworkStateForUid(state, uid, false);
                 return state.networkInfo;
             }
         }
-        final NetworkState state = getFilteredNetworkState(networkType, uid);
+        final NetworkState state = getFilteredNetworkState(networkType, uid, false);
         return state.networkInfo;
     }
 
     @Override
-    public NetworkInfo getNetworkInfoForNetwork(Network network) {
+    public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) {
         enforceAccessPermission();
-        final int uid = Binder.getCallingUid();
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
         if (nai != null) {
             final NetworkState state = nai.getNetworkState();
-            filterNetworkStateForUid(state, uid);
+            filterNetworkStateForUid(state, uid, ignoreBlocked);
             return state.networkInfo;
         } else {
             return null;
@@ -1063,8 +1091,8 @@
     public Network getNetworkForType(int networkType) {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        NetworkState state = getFilteredNetworkState(networkType, uid);
-        if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid)) {
+        NetworkState state = getFilteredNetworkState(networkType, uid, false);
+        if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, false)) {
             return state.network;
         }
         return null;
@@ -1381,6 +1409,11 @@
             if (LOGD_RULES) {
                 log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
             }
+
+            synchronized (mRulesLock) {
+                mRestrictBackground = restrictBackground;
+            }
+
             if (restrictBackground) {
                 log("onRestrictBackgroundChanged(true): disabling tethering");
                 mTethering.untetherAll();
@@ -1824,6 +1857,10 @@
         pw.decreaseIndent();
         pw.println();
 
+        pw.print("Restrict background: ");
+        pw.println(mRestrictBackground);
+        pw.println();
+
         pw.println("Network Requests:");
         pw.increaseIndent();
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
@@ -2765,7 +2802,7 @@
             // which isn't meant to work on uncreated networks.
             if (!nai.created) return;
 
-            if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) return;
+            if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, false)) return;
 
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
         }
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 5ad988a..1f7d312 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -333,6 +333,7 @@
             out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
             out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
             out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
+            out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
         }
 
         private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
@@ -543,6 +544,10 @@
                 if (val != null) {
                     jobBuilder.setPriority(Integer.parseInt(val));
                 }
+                val = parser.getAttributeValue(null, "flags");
+                if (val != null) {
+                    jobBuilder.setFlags(Integer.parseInt(val));
+                }
                 val = parser.getAttributeValue(null, "sourceUserId");
                 sourceUserId = val == null ? -1 : Integer.parseInt(val);
             } catch (NumberFormatException e) {
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 88cf322..f5aac08 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job.controllers;
 
+import android.app.job.JobInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -97,7 +98,9 @@
     }
 
     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
-        final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobStatus.getSourceUid());
+        final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+        final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobStatus.getSourceUid(),
+                ignoreBlocked);
         final boolean connected = (info != null) && info.isConnected();
         final boolean unmetered = connected && !info.isMetered();
         final boolean notRoaming = connected && !info.isRoaming();
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 590d075..19bede9 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -297,6 +297,10 @@
         return job.getPriority();
     }
 
+    public int getFlags() {
+        return job.getFlags();
+    }
+
     public boolean hasConnectivityConstraint() {
         return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
     }
@@ -416,12 +420,12 @@
         // satisfied).
         // AppNotIdle implicit constraint must be satisfied
         // DeviceNotDozing implicit constraint must be satisfied
-        return (isConstraintsSatisfied()
-                || (!job.isPeriodic()
-                && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0)
-                )
-                && (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0
-                && (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0;
+        final boolean deadlineSatisfied = (!job.isPeriodic() && hasDeadlineConstraint()
+                && (satisfiedConstraints & CONSTRAINT_DEADLINE) != 0);
+        final boolean notIdle = (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0;
+        final boolean notDozing = (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0
+                || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
+        return (isConstraintsSatisfied() || deadlineSatisfied) && notIdle && notDozing;
     }
 
     static final int CONSTRAINTS_OF_INTEREST =
@@ -561,6 +565,10 @@
             if (job.getPriority() != 0) {
                 pw.print(prefix); pw.print("  Priority: "); pw.println(job.getPriority());
             }
+            if (job.getFlags() != 0) {
+                pw.print(prefix); pw.print("  Flags: ");
+                pw.println(Integer.toHexString(job.getFlags()));
+            }
             pw.print(prefix); pw.print("  Requires: charging=");
             pw.print(job.isRequireCharging()); pw.print(" deviceIdle=");
             pw.println(job.isRequireDeviceIdle());
@@ -613,6 +621,9 @@
             pw.print(prefix); pw.print("Satisfied constraints:");
             dumpConstraints(pw, satisfiedConstraints);
             pw.println();
+            pw.print(prefix); pw.print("Unsatisfied constraints:");
+            dumpConstraints(pw, (requiredConstraints & ~satisfiedConstraints));
+            pw.println();
         }
         if (changedAuthorities != null) {
             pw.print(prefix); pw.println("Changed authorities:");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 30aab8c..50d9368 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -48,7 +48,6 @@
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
@@ -349,6 +348,9 @@
     /** Foreground at UID granularity. */
     final SparseIntArray mUidState = new SparseIntArray();
 
+    /** Higher priority listener before general event dispatch */
+    private INetworkPolicyListener mConnectivityListener;
+
     private final RemoteCallbackList<INetworkPolicyListener>
             mListeners = new RemoteCallbackList<>();
 
@@ -1391,6 +1393,7 @@
                 final String tag = in.getName();
                 if (type == START_TAG) {
                     if (TAG_POLICY_LIST.equals(tag)) {
+                        final boolean oldValue = mRestrictBackground;
                         version = readIntAttribute(in, ATTR_VERSION);
                         if (version >= VERSION_ADDED_RESTRICT_BACKGROUND) {
                             mRestrictBackground = readBooleanAttribute(
@@ -1398,6 +1401,12 @@
                         } else {
                             mRestrictBackground = false;
                         }
+                        if (mRestrictBackground != oldValue) {
+                            // Some early services may have read the default value,
+                            // so notify them that it's changed
+                            mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED,
+                                    mRestrictBackground ? 1 : 0, 0).sendToTarget();
+                        }
 
                     } else if (TAG_NETWORK_POLICY.equals(tag)) {
                         final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
@@ -1766,20 +1775,25 @@
     }
 
     @Override
+    public void setConnectivityListener(INetworkPolicyListener listener) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        if (mConnectivityListener != null) {
+            throw new IllegalStateException("Connectivity listener already registered");
+        }
+        mConnectivityListener = listener;
+    }
+
+    @Override
     public void registerListener(INetworkPolicyListener listener) {
         // TODO: create permission for observing network policy
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
         mListeners.register(listener);
-
-        // TODO: consider dispatching existing rules to new listeners
     }
 
     @Override
     public void unregisterListener(INetworkPolicyListener listener) {
         // TODO: create permission for observing network policy
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
         mListeners.unregister(listener);
     }
 
@@ -2754,8 +2768,8 @@
         final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
         final boolean isWhitelisted = mRestrictBackgroundWhitelistUids.get(uid);
 
-        int newRule = RULE_ALLOW_ALL;
-        final int oldRule = mUidRules.get(uid);
+        int newRule = RULE_UNKNOWN;
+        final int oldRule = mUidRules.get(uid, RULE_UNKNOWN);
 
         // First step: define the new rule based on user restrictions and foreground state.
         if (isForeground) {
@@ -2777,8 +2791,7 @@
                     + ", oldRule: " + ruleToString(oldRule));
         }
 
-
-        if (newRule == RULE_ALLOW_ALL) {
+        if (newRule == RULE_UNKNOWN) {
             mUidRules.delete(uid);
         } else {
             mUidRules.put(uid, newRule);
@@ -2858,6 +2871,45 @@
         }
     }
 
+    private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
+        if (listener != null) {
+            try {
+                listener.onUidRulesChanged(uid, uidRules);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void dispatchMeteredIfacesChanged(INetworkPolicyListener listener,
+            String[] meteredIfaces) {
+        if (listener != null) {
+            try {
+                listener.onMeteredIfacesChanged(meteredIfaces);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void dispatchRestrictBackgroundChanged(INetworkPolicyListener listener,
+            boolean restrictBackground) {
+        if (listener != null) {
+            try {
+                listener.onRestrictBackgroundChanged(restrictBackground);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void dispatchRestrictBackgroundWhitelistChanged(INetworkPolicyListener listener,
+            int uid, boolean whitelisted) {
+        if (listener != null) {
+            try {
+                listener.onRestrictBackgroundWhitelistChanged(uid, whitelisted);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
     private Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
         public boolean handleMessage(Message msg) {
@@ -2865,30 +2917,22 @@
                 case MSG_RULES_CHANGED: {
                     final int uid = msg.arg1;
                     final int uidRules = msg.arg2;
+                    dispatchUidRulesChanged(mConnectivityListener, uid, uidRules);
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-                        if (listener != null) {
-                            try {
-                                listener.onUidRulesChanged(uid, uidRules);
-                            } catch (RemoteException e) {
-                            }
-                        }
+                        dispatchUidRulesChanged(listener, uid, uidRules);
                     }
                     mListeners.finishBroadcast();
                     return true;
                 }
                 case MSG_METERED_IFACES_CHANGED: {
                     final String[] meteredIfaces = (String[]) msg.obj;
+                    dispatchMeteredIfacesChanged(mConnectivityListener, meteredIfaces);
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-                        if (listener != null) {
-                            try {
-                                listener.onMeteredIfacesChanged(meteredIfaces);
-                            } catch (RemoteException e) {
-                            }
-                        }
+                        dispatchMeteredIfacesChanged(listener, meteredIfaces);
                     }
                     mListeners.finishBroadcast();
                     return true;
@@ -2915,15 +2959,11 @@
                 }
                 case MSG_RESTRICT_BACKGROUND_CHANGED: {
                     final boolean restrictBackground = msg.arg1 != 0;
+                    dispatchRestrictBackgroundChanged(mConnectivityListener, restrictBackground);
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-                        if (listener != null) {
-                            try {
-                                listener.onRestrictBackgroundChanged(restrictBackground);
-                            } catch (RemoteException e) {
-                            }
-                        }
+                        dispatchRestrictBackgroundChanged(listener, restrictBackground);
                     }
                     mListeners.finishBroadcast();
                     final Intent intent =
@@ -2947,18 +2987,16 @@
                     final boolean changed = msg.arg2 == 1;
                     final Boolean whitelisted = (Boolean) msg.obj;
 
+                    // First notify internal listeners...
                     if (whitelisted != null) {
+                        final boolean whitelistedBool = whitelisted.booleanValue();
+                        dispatchRestrictBackgroundWhitelistChanged(mConnectivityListener, uid,
+                                whitelistedBool);
                         final int length = mListeners.beginBroadcast();
                         for (int i = 0; i < length; i++) {
-                            // First notify internal listeners...
                             final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-                            if (listener != null) {
-                                try {
-                                    listener.onRestrictBackgroundWhitelistChanged(uid,
-                                            whitelisted.booleanValue());
-                                } catch (RemoteException e) {
-                                }
-                            }
+                            dispatchRestrictBackgroundWhitelistChanged(listener, uid,
+                                    whitelistedBool);
                         }
                         mListeners.finishBroadcast();
                     }