Merge "ConnectivityController tracks jobs by UID."
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 0c66c5b..6989c33 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -55,6 +55,8 @@
  * Each app can have a different default networks or different connectivity
  * status due to user-requested network policies, so we need to check
  * constraints on a per-UID basis.
+ *
+ * Test: atest com.android.server.job.controllers.ConnectivityControllerTest
  */
 public final class ConnectivityController extends StateController implements
         ConnectivityManager.OnNetworkActiveListener {
@@ -65,8 +67,9 @@
     private final ConnectivityManager mConnManager;
     private final NetworkPolicyManager mNetPolicyManager;
 
+    /** List of tracked jobs keyed by source UID. */
     @GuardedBy("mLock")
-    private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
+    private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
 
     public ConnectivityController(JobSchedulerService service) {
         super(service);
@@ -87,7 +90,12 @@
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         if (jobStatus.hasConnectivityConstraint()) {
             updateConstraintsSatisfied(jobStatus);
-            mTrackedJobs.add(jobStatus);
+            ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
+            if (jobs == null) {
+                jobs = new ArraySet<>();
+                mTrackedJobs.put(jobStatus.getSourceUid(), jobs);
+            }
+            jobs.add(jobStatus);
             jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
         }
     }
@@ -97,7 +105,10 @@
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
         if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
-            mTrackedJobs.remove(jobStatus);
+            ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
+            if (jobs != null) {
+                jobs.remove(jobStatus);
+            }
         }
     }
 
@@ -235,47 +246,26 @@
     /**
      * Update any jobs tracked by this controller that match given filters.
      *
-     * @param filterUid only update jobs belonging to this UID, or {@code -1} to
-     *            update all tracked jobs.
+     * @param filterUid     only update jobs belonging to this UID, or {@code -1} to
+     *                      update all tracked jobs.
      * @param filterNetwork only update jobs that would use this
-     *            {@link Network}, or {@code null} to update all tracked jobs.
+     *                      {@link Network}, or {@code null} to update all tracked jobs.
      */
     private void updateTrackedJobs(int filterUid, Network filterNetwork) {
         synchronized (mLock) {
             // Since this is a really hot codepath, temporarily cache any
             // answers that we get from ConnectivityManager.
-            final SparseArray<Network> uidToNetwork = new SparseArray<>();
             final SparseArray<NetworkCapabilities> networkToCapabilities = new SparseArray<>();
 
             boolean changed = false;
-            for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
-                final JobStatus js = mTrackedJobs.valueAt(i);
-                final int uid = js.getSourceUid();
-
-                final boolean uidMatch = (filterUid == -1 || filterUid == uid);
-                if (uidMatch) {
-                    Network network = uidToNetwork.get(uid);
-                    if (network == null) {
-                        network = mConnManager.getActiveNetworkForUid(uid);
-                        uidToNetwork.put(uid, network);
-                    }
-
-                    // Update either when we have a network match, or when the
-                    // job hasn't yet been evaluated against the currently
-                    // active network; typically when we just lost a network.
-                    final boolean networkMatch = (filterNetwork == null
-                            || Objects.equals(filterNetwork, network));
-                    final boolean forceUpdate = !Objects.equals(js.network, network);
-                    if (networkMatch || forceUpdate) {
-                        final int netId = network != null ? network.netId : -1;
-                        NetworkCapabilities capabilities = networkToCapabilities.get(netId);
-                        if (capabilities == null) {
-                            capabilities = mConnManager.getNetworkCapabilities(network);
-                            networkToCapabilities.put(netId, capabilities);
-                        }
-                        changed |= updateConstraintsSatisfied(js, network, capabilities);
-                    }
+            if (filterUid == -1) {
+                for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
+                    changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i),
+                            filterNetwork, networkToCapabilities);
                 }
+            } else {
+                changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid),
+                        filterNetwork, networkToCapabilities);
             }
             if (changed) {
                 mStateChangedListener.onControllerStateChanged();
@@ -283,6 +273,36 @@
         }
     }
 
+    private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, Network filterNetwork,
+            SparseArray<NetworkCapabilities> networkToCapabilities) {
+        if (jobs == null || jobs.size() == 0) {
+            return false;
+        }
+
+        final Network network = mConnManager.getActiveNetworkForUid(jobs.valueAt(0).getSourceUid());
+        final int netId = network != null ? network.netId : -1;
+        NetworkCapabilities capabilities = networkToCapabilities.get(netId);
+        if (capabilities == null) {
+            capabilities = mConnManager.getNetworkCapabilities(network);
+            networkToCapabilities.put(netId, capabilities);
+        }
+        final boolean networkMatch = (filterNetwork == null
+                || Objects.equals(filterNetwork, network));
+
+        boolean changed = false;
+        for (int i = jobs.size() - 1; i >= 0; i--) {
+            final JobStatus js = jobs.valueAt(i);
+
+            // Update either when we have a network match, or when the
+            // job hasn't yet been evaluated against the currently
+            // active network; typically when we just lost a network.
+            if (networkMatch || !Objects.equals(js.network, network)) {
+                changed |= updateConstraintsSatisfied(js, network, capabilities);
+            }
+        }
+        return changed;
+    }
+
     /**
      * We know the network has just come up. We want to run any jobs that are ready.
      */
@@ -290,12 +310,15 @@
     public void onNetworkActive() {
         synchronized (mLock) {
             for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
-                final JobStatus js = mTrackedJobs.valueAt(i);
-                if (js.isReady()) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Running " + js + " due to network activity.");
+                final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
+                for (int j = jobs.size() - 1; j >= 0; j--) {
+                    final JobStatus js = jobs.valueAt(j);
+                    if (js.isReady()) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Running " + js + " due to network activity.");
+                        }
+                        mStateChangedListener.onRunJobNow(js);
                     }
-                    mStateChangedListener.onRunJobNow(js);
                 }
             }
         }
@@ -334,8 +357,12 @@
     public void dumpControllerStateLocked(IndentingPrintWriter pw,
             Predicate<JobStatus> predicate) {
         for (int i = 0; i < mTrackedJobs.size(); i++) {
-            final JobStatus js = mTrackedJobs.valueAt(i);
-            if (predicate.test(js)) {
+            final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
+            for (int j = 0; j < jobs.size(); j++) {
+                final JobStatus js = jobs.valueAt(j);
+                if (!predicate.test(js)) {
+                    continue;
+                }
                 pw.print("#");
                 js.printUniqueId(pw);
                 pw.print(" from ");
@@ -355,20 +382,26 @@
         final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
 
         for (int i = 0; i < mTrackedJobs.size(); i++) {
-            final JobStatus js = mTrackedJobs.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
+            final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
+            for (int j = 0; j < jobs.size(); j++) {
+                final JobStatus js = jobs.valueAt(j);
+                if (!predicate.test(js)) {
+                    continue;
+                }
+                final long jsToken = proto.start(
+                        StateControllerProto.ConnectivityController.TRACKED_JOBS);
+                js.writeToShortProto(proto,
+                        StateControllerProto.ConnectivityController.TrackedJob.INFO);
+                proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
+                        js.getSourceUid());
+                NetworkRequest rn = js.getJob().getRequiredNetwork();
+                if (rn != null) {
+                    rn.writeToProto(proto,
+                            StateControllerProto.ConnectivityController.TrackedJob
+                                    .REQUIRED_NETWORK);
+                }
+                proto.end(jsToken);
             }
-            final long jsToken = proto.start(StateControllerProto.ConnectivityController.TRACKED_JOBS);
-            js.writeToShortProto(proto, StateControllerProto.ConnectivityController.TrackedJob.INFO);
-            proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
-                    js.getSourceUid());
-            NetworkRequest rn = js.getJob().getRequiredNetwork();
-            if (rn != null) {
-                rn.writeToProto(proto,
-                        StateControllerProto.ConnectivityController.TrackedJob.REQUIRED_NETWORK);
-            }
-            proto.end(jsToken);
         }
 
         proto.end(mToken);