• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.job.controllers;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
21 
22 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
23 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.job.JobInfo;
28 import android.net.ConnectivityManager;
29 import android.net.ConnectivityManager.NetworkCallback;
30 import android.net.Network;
31 import android.net.NetworkCapabilities;
32 import android.net.NetworkPolicyManager;
33 import android.net.NetworkRequest;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.UserHandle;
38 import android.telephony.CellSignalStrength;
39 import android.telephony.SignalStrength;
40 import android.telephony.TelephonyCallback;
41 import android.telephony.TelephonyManager;
42 import android.text.format.DateUtils;
43 import android.util.ArrayMap;
44 import android.util.ArraySet;
45 import android.util.DataUnit;
46 import android.util.IndentingPrintWriter;
47 import android.util.Log;
48 import android.util.Pools;
49 import android.util.Slog;
50 import android.util.SparseArray;
51 import android.util.TimeUtils;
52 import android.util.proto.ProtoOutputStream;
53 
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.server.JobSchedulerBackgroundThread;
57 import com.android.server.LocalServices;
58 import com.android.server.job.JobSchedulerService;
59 import com.android.server.job.JobSchedulerService.Constants;
60 import com.android.server.job.StateControllerProto;
61 import com.android.server.net.NetworkPolicyManagerInternal;
62 
63 import java.util.ArrayList;
64 import java.util.Comparator;
65 import java.util.List;
66 import java.util.Objects;
67 import java.util.Set;
68 import java.util.function.Predicate;
69 
70 /**
71  * Handles changes in connectivity.
72  * <p>
73  * Each app can have a different default networks or different connectivity
74  * status due to user-requested network policies, so we need to check
75  * constraints on a per-UID basis.
76  *
77  * Test: atest com.android.server.job.controllers.ConnectivityControllerTest
78  */
79 public final class ConnectivityController extends RestrictingController implements
80         ConnectivityManager.OnNetworkActiveListener {
81     private static final String TAG = "JobScheduler.Connectivity";
82     private static final boolean DEBUG = JobSchedulerService.DEBUG
83             || Log.isLoggable(TAG, Log.DEBUG);
84 
85     // The networking stack has a hard limit so we can't make this configurable.
86     private static final int MAX_NETWORK_CALLBACKS = 125;
87     /**
88      * Minimum amount of time that should have elapsed before we'll update a {@link UidStats}
89      * instance.
90      */
91     private static final long MIN_STATS_UPDATE_INTERVAL_MS = 30_000L;
92     private static final long MIN_ADJUST_CALLBACK_INTERVAL_MS = 1_000L;
93 
94     private static final int UNBYPASSABLE_BG_BLOCKED_REASONS =
95             ~ConnectivityManager.BLOCKED_REASON_NONE;
96     private static final int UNBYPASSABLE_EJ_BLOCKED_REASONS =
97             ~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
98                     | ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
99                     | ConnectivityManager.BLOCKED_REASON_DOZE);
100     private static final int UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS =
101             ~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
102                     | ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
103                     | ConnectivityManager.BLOCKED_REASON_DOZE
104                     | ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
105                     | ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED);
106 
107     private final ConnectivityManager mConnManager;
108     private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
109 
110     /** List of tracked jobs keyed by source UID. */
111     @GuardedBy("mLock")
112     private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
113 
114     /**
115      * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
116      * grant an exception to in the app standby chain.
117      */
118     @GuardedBy("mLock")
119     private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();
120 
121     /**
122      * Set of currently available networks mapped to their latest network capabilities. Cache the
123      * latest capabilities to avoid unnecessary calls into ConnectivityManager.
124      */
125     @GuardedBy("mLock")
126     private final ArrayMap<Network, NetworkCapabilities> mAvailableNetworks = new ArrayMap<>();
127 
128     private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
129             new SparseArray<>();
130     private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
131         private int prioritizeExistenceOver(int threshold, int v1, int v2) {
132             // Check if they're both on the same side of the threshold.
133             if ((v1 > threshold && v2 > threshold) || (v1 <= threshold && v2 <= threshold)) {
134                 return 0;
135             }
136             // They're on opposite sides of the threshold.
137             if (v1 > threshold) {
138                 return -1;
139             }
140             return 1;
141         }
142 
143         @Override
144         public int compare(UidStats us1, UidStats us2) {
145             // Prioritize a UID ahead of another based on:
146             //   1. Already running connectivity jobs (so we don't drop the listener)
147             //   2. Waiting connectivity jobs would be ready with connectivity
148             //   3. An existing network satisfies a waiting connectivity job's requirements
149             //   4. TOP proc state
150             //   5. Existence of treat-as-EJ EJs (not just requested EJs)
151             //   6. FGS proc state
152             //   7. EJ enqueue time
153             //   8. Any other important job priorities/proc states
154             //   9. Enqueue time
155             // TODO: maybe consider number of jobs
156             // TODO: consider IMPORTANT_WHILE_FOREGROUND bit
157             final int runningPriority = prioritizeExistenceOver(0,
158                     us1.runningJobs.size(), us2.runningJobs.size());
159             if (runningPriority != 0) {
160                 return runningPriority;
161             }
162             // Prioritize any UIDs that have jobs that would be ready ahead of UIDs that don't.
163             final int readyWithConnPriority = prioritizeExistenceOver(0,
164                     us1.numReadyWithConnectivity, us2.numReadyWithConnectivity);
165             if (readyWithConnPriority != 0) {
166                 return readyWithConnPriority;
167             }
168             // They both have jobs that would be ready. Prioritize the UIDs whose requested
169             // network is available ahead of UIDs that don't have their requested network available.
170             final int reqAvailPriority = prioritizeExistenceOver(0,
171                     us1.numRequestedNetworkAvailable, us2.numRequestedNetworkAvailable);
172             if (reqAvailPriority != 0) {
173                 return reqAvailPriority;
174             }
175             // Prioritize the top app. If neither are top apps, then use a later prioritization
176             // check.
177             final int topPriority = prioritizeExistenceOver(JobInfo.BIAS_TOP_APP - 1,
178                     us1.baseBias, us2.baseBias);
179             if (topPriority != 0) {
180                 return topPriority;
181             }
182             // They're either both TOP or both not TOP. Prioritize the app that has runnable EJs
183             // pending.
184             final int ejPriority = prioritizeExistenceOver(0, us1.numEJs, us2.numEJs);
185             if (ejPriority != 0) {
186                 return ejPriority;
187             }
188             // They both have runnable EJs.
189             // Prioritize an FGS+ app. If neither are FGS+ apps, then use a later prioritization
190             // check.
191             final int fgsPriority = prioritizeExistenceOver(JobInfo.BIAS_FOREGROUND_SERVICE - 1,
192                     us1.baseBias, us2.baseBias);
193             if (fgsPriority != 0) {
194                 return fgsPriority;
195             }
196             // Order them by EJ enqueue time to help provide low EJ latency.
197             if (us1.earliestEJEnqueueTime < us2.earliestEJEnqueueTime) {
198                 return -1;
199             } else if (us1.earliestEJEnqueueTime > us2.earliestEJEnqueueTime) {
200                 return 1;
201             }
202             // Order by any latent important proc states.
203             if (us1.baseBias != us2.baseBias) {
204                 return us2.baseBias - us1.baseBias;
205             }
206             // Order by enqueue time.
207             if (us1.earliestEnqueueTime < us2.earliestEnqueueTime) {
208                 return -1;
209             }
210             return us1.earliestEnqueueTime > us2.earliestEnqueueTime ? 1 : 0;
211         }
212     };
213     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
214     private final Pools.Pool<UidDefaultNetworkCallback> mDefaultNetworkCallbackPool =
215             new Pools.SimplePool<>(MAX_NETWORK_CALLBACKS);
216     /**
217      * List of UidStats, sorted by priority as defined in {@link #mUidStatsComparator}. The sorting
218      * is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
219      */
220     private final List<UidStats> mSortedStats = new ArrayList<>();
221     @GuardedBy("mLock")
222     private long mLastCallbackAdjustmentTimeElapsed;
223     @GuardedBy("mLock")
224     private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();
225 
226     @GuardedBy("mLock")
227     private long mLastAllJobUpdateTimeElapsed;
228 
229     private static final int MSG_ADJUST_CALLBACKS = 0;
230     private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
231 
232     private final Handler mHandler;
233 
ConnectivityController(JobSchedulerService service)234     public ConnectivityController(JobSchedulerService service) {
235         super(service);
236         mHandler = new CcHandler(mContext.getMainLooper());
237 
238         mConnManager = mContext.getSystemService(ConnectivityManager.class);
239         mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
240 
241         // We're interested in all network changes; internally we match these
242         // network changes against the active network for each UID with jobs.
243         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
244         mConnManager.registerNetworkCallback(request, mNetworkCallback);
245     }
246 
247     @GuardedBy("mLock")
248     @Override
maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)249     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
250         if (jobStatus.hasConnectivityConstraint()) {
251             final UidStats uidStats =
252                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), false);
253             if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)) {
254                 uidStats.numReadyWithConnectivity++;
255             }
256             ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
257             if (jobs == null) {
258                 jobs = new ArraySet<>();
259                 mTrackedJobs.put(jobStatus.getSourceUid(), jobs);
260             }
261             jobs.add(jobStatus);
262             jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
263             updateConstraintsSatisfied(jobStatus);
264         }
265     }
266 
267     @GuardedBy("mLock")
268     @Override
prepareForExecutionLocked(JobStatus jobStatus)269     public void prepareForExecutionLocked(JobStatus jobStatus) {
270         if (jobStatus.hasConnectivityConstraint()) {
271             final UidStats uidStats =
272                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
273             uidStats.runningJobs.add(jobStatus);
274         }
275     }
276 
277     @GuardedBy("mLock")
278     @Override
unprepareFromExecutionLocked(JobStatus jobStatus)279     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
280         if (jobStatus.hasConnectivityConstraint()) {
281             final UidStats uidStats =
282                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
283             uidStats.runningJobs.remove(jobStatus);
284             postAdjustCallbacks();
285         }
286     }
287 
288     @GuardedBy("mLock")
289     @Override
maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate)290     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
291             boolean forUpdate) {
292         if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
293             ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
294             if (jobs != null) {
295                 jobs.remove(jobStatus);
296             }
297             final UidStats uidStats =
298                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
299             uidStats.numReadyWithConnectivity--;
300             uidStats.runningJobs.remove(jobStatus);
301             maybeRevokeStandbyExceptionLocked(jobStatus);
302             postAdjustCallbacks();
303         }
304     }
305 
306     @Override
startTrackingRestrictedJobLocked(JobStatus jobStatus)307     public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
308         // Don't need to start tracking the job. If the job needed network, it would already be
309         // tracked.
310         if (jobStatus.hasConnectivityConstraint()) {
311             updateConstraintsSatisfied(jobStatus);
312         }
313     }
314 
315     @Override
stopTrackingRestrictedJobLocked(JobStatus jobStatus)316     public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
317         // Shouldn't stop tracking the job here. If the job was tracked, it still needs network,
318         // even after being unrestricted.
319         if (jobStatus.hasConnectivityConstraint()) {
320             updateConstraintsSatisfied(jobStatus);
321         }
322     }
323 
324     @NonNull
getUidStats(int uid, String packageName, boolean shouldExist)325     private UidStats getUidStats(int uid, String packageName, boolean shouldExist) {
326         UidStats us = mUidStats.get(uid);
327         if (us == null) {
328             if (shouldExist) {
329                 // This shouldn't be happening. We create a UidStats object for the app when the
330                 // first job is scheduled in maybeStartTrackingJobLocked() and only ever drop the
331                 // object if the app is uninstalled or the user is removed. That means that if we
332                 // end up in this situation, onAppRemovedLocked() or onUserRemovedLocked() was
333                 // called before maybeStopTrackingJobLocked(), which is the reverse order of what
334                 // JobSchedulerService does (JSS calls maybeStopTrackingJobLocked() for all jobs
335                 // before calling onAppRemovedLocked() or onUserRemovedLocked()).
336                 Slog.wtfStack(TAG,
337                         "UidStats was null after job for " + packageName + " was registered");
338             }
339             us = new UidStats(uid);
340             mUidStats.append(uid, us);
341         }
342         return us;
343     }
344 
345     /**
346      * Returns true if the job's requested network is available. This DOES NOT necessarily mean
347      * that the UID has been granted access to the network.
348      */
isNetworkAvailable(JobStatus job)349     public boolean isNetworkAvailable(JobStatus job) {
350         synchronized (mLock) {
351             for (int i = 0; i < mAvailableNetworks.size(); ++i) {
352                 final Network network = mAvailableNetworks.keyAt(i);
353                 final NetworkCapabilities capabilities = mAvailableNetworks.valueAt(i);
354                 final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
355                 if (DEBUG) {
356                     Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
357                             + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
358                 }
359                 if (satisfied) {
360                     return true;
361                 }
362             }
363             return false;
364         }
365     }
366 
367     /**
368      * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
369      * chain.
370      */
371     @VisibleForTesting
372     @GuardedBy("mLock")
requestStandbyExceptionLocked(JobStatus job)373     void requestStandbyExceptionLocked(JobStatus job) {
374         final int uid = job.getSourceUid();
375         // Need to call this before adding the job.
376         final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
377         ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
378         if (jobs == null) {
379             jobs = new ArraySet<JobStatus>();
380             mRequestedWhitelistJobs.put(uid, jobs);
381         }
382         if (!jobs.add(job) || isExceptionRequested) {
383             if (DEBUG) {
384                 Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
385             }
386             return;
387         }
388         if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
389         mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
390     }
391 
392     /** Returns whether a standby exception has been requested for the UID. */
393     @VisibleForTesting
394     @GuardedBy("mLock")
isStandbyExceptionRequestedLocked(final int uid)395     boolean isStandbyExceptionRequestedLocked(final int uid) {
396         ArraySet jobs = mRequestedWhitelistJobs.get(uid);
397         return jobs != null && jobs.size() > 0;
398     }
399 
400     /**
401      * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
402      * thing stopping a job from running.
403      */
404     @GuardedBy("mLock")
405     @Override
evaluateStateLocked(JobStatus jobStatus)406     public void evaluateStateLocked(JobStatus jobStatus) {
407         if (!jobStatus.hasConnectivityConstraint()) {
408             return;
409         }
410 
411         final UidStats uidStats =
412                 getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
413 
414         if (jobStatus.shouldTreatAsExpeditedJob()) {
415             if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
416                 // Don't request a direct hole through any of the firewalls. Instead, mark the
417                 // constraint as satisfied if the network is available, and the job will get
418                 // through the firewalls once it starts running and the proc state is elevated.
419                 // This is the same behavior that FGS see.
420                 updateConstraintsSatisfied(jobStatus);
421             }
422             // Don't need to update constraint here if the network goes away. We'll do that as part
423             // of regular processing when we're notified about the drop.
424         } else if (jobStatus.isRequestedExpeditedJob()
425                 && jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
426             // Make sure we don't accidentally keep the constraint as satisfied if the job went
427             // from being expedited-ready to not-expeditable.
428             updateConstraintsSatisfied(jobStatus);
429         }
430 
431         // Always check the full job readiness stat in case the component has been disabled.
432         if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)
433                 && isNetworkAvailable(jobStatus)) {
434             if (DEBUG) {
435                 Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
436             }
437             uidStats.numReadyWithConnectivity++;
438             requestStandbyExceptionLocked(jobStatus);
439         } else {
440             if (DEBUG) {
441                 Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
442             }
443             // Don't decrement numReadyWithConnectivity here because we don't know if it was
444             // incremented for this job. The count will be set properly in
445             // maybeAdjustRegisteredCallbacksLocked().
446             maybeRevokeStandbyExceptionLocked(jobStatus);
447         }
448     }
449 
450     @GuardedBy("mLock")
451     @Override
reevaluateStateLocked(final int uid)452     public void reevaluateStateLocked(final int uid) {
453         // Check if we still need a connectivity exception in case the JobService was disabled.
454         ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
455         if (jobs == null) {
456             return;
457         }
458         for (int i = jobs.size() - 1; i >= 0; i--) {
459             evaluateStateLocked(jobs.valueAt(i));
460         }
461     }
462 
463     /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
464     @VisibleForTesting
465     @GuardedBy("mLock")
maybeRevokeStandbyExceptionLocked(final JobStatus job)466     void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
467         final int uid = job.getSourceUid();
468         if (!isStandbyExceptionRequestedLocked(uid)) {
469             return;
470         }
471         ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
472         if (jobs == null) {
473             Slog.wtf(TAG,
474                     "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
475                             + "standby exception has been requested.");
476             return;
477         }
478         if (!jobs.remove(job) || jobs.size() > 0) {
479             if (DEBUG) {
480                 Slog.i(TAG,
481                         "maybeRevokeStandbyExceptionLocked not revoking because there are still "
482                                 + jobs.size() + " jobs left.");
483             }
484             return;
485         }
486         // No more jobs that need an exception.
487         revokeStandbyExceptionLocked(uid);
488     }
489 
490     /**
491      * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
492      * for the uid.
493      */
494     @GuardedBy("mLock")
revokeStandbyExceptionLocked(final int uid)495     private void revokeStandbyExceptionLocked(final int uid) {
496         if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
497         mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
498         mRequestedWhitelistJobs.remove(uid);
499     }
500 
501     @GuardedBy("mLock")
502     @Override
onAppRemovedLocked(String pkgName, int uid)503     public void onAppRemovedLocked(String pkgName, int uid) {
504         if (mService.getPackagesForUidLocked(uid) == null) {
505             // All packages in the UID have been removed. It's safe to remove things based on
506             // UID alone.
507             mTrackedJobs.delete(uid);
508             UidStats uidStats = mUidStats.removeReturnOld(uid);
509             unregisterDefaultNetworkCallbackLocked(uid, sElapsedRealtimeClock.millis());
510             mSortedStats.remove(uidStats);
511             registerPendingUidCallbacksLocked();
512         }
513     }
514 
515     @GuardedBy("mLock")
516     @Override
onUserRemovedLocked(int userId)517     public void onUserRemovedLocked(int userId) {
518         final long nowElapsed = sElapsedRealtimeClock.millis();
519         for (int u = mUidStats.size() - 1; u >= 0; --u) {
520             UidStats uidStats = mUidStats.valueAt(u);
521             if (UserHandle.getUserId(uidStats.uid) == userId) {
522                 unregisterDefaultNetworkCallbackLocked(uidStats.uid, nowElapsed);
523                 mSortedStats.remove(uidStats);
524                 mUidStats.removeAt(u);
525             }
526         }
527         postAdjustCallbacks();
528     }
529 
530     @GuardedBy("mLock")
531     @Override
onUidBiasChangedLocked(int uid, int prevBias, int newBias)532     public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
533         UidStats uidStats = mUidStats.get(uid);
534         if (uidStats != null && uidStats.baseBias != newBias) {
535             uidStats.baseBias = newBias;
536             postAdjustCallbacks();
537         }
538     }
539 
540     @Override
541     @GuardedBy("mLock")
onBatteryStateChangedLocked()542     public void onBatteryStateChangedLocked() {
543         // Update job bookkeeping out of band to avoid blocking broadcast progress.
544         mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS);
545     }
546 
isUsable(NetworkCapabilities capabilities)547     private boolean isUsable(NetworkCapabilities capabilities) {
548         return capabilities != null
549                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
550     }
551 
552     /**
553      * Test to see if running the given job on the given network is insane.
554      * <p>
555      * For example, if a job is trying to send 10MB over a 128Kbps EDGE
556      * connection, it would take 10.4 minutes, and has no chance of succeeding
557      * before the job times out, so we'd be insane to try running it.
558      */
isInsane(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)559     private boolean isInsane(JobStatus jobStatus, Network network,
560             NetworkCapabilities capabilities, Constants constants) {
561         // Use the maximum possible time since it gives us an upper bound, even though the job
562         // could end up stopping earlier.
563         final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus);
564 
565         final long minimumChunkBytes = jobStatus.getMinimumNetworkChunkBytes();
566         if (minimumChunkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
567             final long bandwidthDown = capabilities.getLinkDownstreamBandwidthKbps();
568             // If we don't know the bandwidth, all we can do is hope the job finishes the minimum
569             // chunk in time.
570             if (bandwidthDown > 0) {
571                 // Divide by 8 to convert bits to bytes.
572                 final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
573                         / (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8));
574                 if (estimatedMillis > maxJobExecutionTimeMs) {
575                     // If we'd never finish the minimum chunk before the timeout, we'd be insane!
576                     Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over "
577                             + bandwidthDown + " kbps network would take "
578                             + estimatedMillis + "ms and job has "
579                             + maxJobExecutionTimeMs + "ms to run; that's insane!");
580                     return true;
581                 }
582             }
583             final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps();
584             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
585             if (bandwidthUp > 0) {
586                 // Divide by 8 to convert bits to bytes.
587                 final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
588                         / (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8));
589                 if (estimatedMillis > maxJobExecutionTimeMs) {
590                     // If we'd never finish the minimum chunk before the timeout, we'd be insane!
591                     Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp
592                             + " kbps network would take " + estimatedMillis + "ms and job has "
593                             + maxJobExecutionTimeMs + "ms to run; that's insane!");
594                     return true;
595                 }
596             }
597             return false;
598         }
599 
600         // Minimum chunk size isn't defined. Check using the estimated upload/download sizes.
601 
602         if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
603                 && mService.isBatteryCharging()) {
604             // We're charging and on an unmetered network. We don't have to be as conservative about
605             // making sure the job will run within its max execution time. Let's just hope the app
606             // supports interruptible work.
607             return false;
608         }
609 
610 
611         final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
612         if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
613             final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
614             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
615             if (bandwidth > 0) {
616                 // Divide by 8 to convert bits to bytes.
617                 final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
618                         / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
619                 if (estimatedMillis > maxJobExecutionTimeMs) {
620                     // If we'd never finish before the timeout, we'd be insane!
621                     Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
622                             + " kbps network would take " + estimatedMillis + "ms and job has "
623                             + maxJobExecutionTimeMs + "ms to run; that's insane!");
624                     return true;
625                 }
626             }
627         }
628 
629         final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
630         if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
631             final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
632             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
633             if (bandwidth > 0) {
634                 // Divide by 8 to convert bits to bytes.
635                 final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
636                         / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
637                 if (estimatedMillis > maxJobExecutionTimeMs) {
638                     // If we'd never finish before the timeout, we'd be insane!
639                     Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
640                             + " kbps network would take " + estimatedMillis + "ms and job has "
641                             + maxJobExecutionTimeMs + "ms to run; that's insane!");
642                     return true;
643                 }
644             }
645         }
646 
647         return false;
648     }
649 
isCongestionDelayed(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)650     private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
651             NetworkCapabilities capabilities, Constants constants) {
652         // If network is congested, and job is less than 50% through the
653         // developer-requested window, then we're okay delaying the job.
654         if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
655             return jobStatus.getFractionRunTime() < constants.CONN_CONGESTION_DELAY_FRAC;
656         } else {
657             return false;
658         }
659     }
660 
661     @GuardedBy("mLock")
isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities, Constants constants)662     private boolean isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities,
663             Constants constants) {
664         final int priority = jobStatus.getEffectivePriority();
665         if (priority >= JobInfo.PRIORITY_HIGH) {
666             return true;
667         }
668         if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) {
669             return true;
670         }
671         if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
672             return true;
673         }
674         if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
675             // Exclude VPNs because it's currently not possible to determine the VPN's underlying
676             // network, and thus the correct signal strength of the VPN's network.
677             // Transmitting data over a VPN is generally more battery-expensive than on the
678             // underlying network, so:
679             // TODO: find a good way to reduce job use of VPN when it'll be very expensive
680             // For now, we just pretend VPNs are always strong enough
681             return true;
682         }
683 
684         // VCNs running over WiFi will declare TRANSPORT_CELLULAR. When connected, a VCN will
685         // most likely be the default network. We ideally don't want this to restrict jobs when the
686         // VCN incorrectly declares the CELLULAR transport, but there's currently no way to
687         // determine if a network is a VCN. When there is:
688         // TODO(216127782): exclude VCN running over WiFi from this check
689 
690         int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
691         // Use the best strength found.
692         final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
693         for (int subId : subscriptionIds) {
694             CellSignalStrengthCallback callback = mSignalStrengths.get(subId);
695             if (callback != null) {
696                 signalStrength = Math.max(signalStrength, callback.signalStrength);
697             } else {
698                 Slog.wtf(TAG,
699                         "Subscription ID " + subId + " doesn't have a registered callback");
700             }
701         }
702         if (DEBUG) {
703             Slog.d(TAG, "Cell signal strength for job=" + signalStrength);
704         }
705         // Treat "NONE_OR_UNKNOWN" as "NONE".
706         if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
707             // If signal strength is poor, don't run MIN or LOW priority jobs, and only
708             // run DEFAULT priority jobs if the device is charging or the job has been waiting
709             // long enough.
710             if (priority > JobInfo.PRIORITY_DEFAULT) {
711                 return true;
712             }
713             if (priority < JobInfo.PRIORITY_DEFAULT) {
714                 return false;
715             }
716             // DEFAULT job.
717             return (mService.isBatteryCharging() && mService.isBatteryNotLow())
718                     || jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
719         }
720         if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_MODERATE) {
721             // If signal strength is moderate, only run MIN priority jobs when the device
722             // is charging, or the job is already running.
723             if (priority >= JobInfo.PRIORITY_LOW) {
724                 return true;
725             }
726             // MIN job.
727             if (mService.isBatteryCharging() && mService.isBatteryNotLow()) {
728                 return true;
729             }
730             final UidStats uidStats = getUidStats(
731                     jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
732             return uidStats.runningJobs.contains(jobStatus);
733         }
734         return true;
735     }
736 
copyCapabilities( @onNull final NetworkRequest request)737     private static NetworkCapabilities.Builder copyCapabilities(
738             @NonNull final NetworkRequest request) {
739         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
740         for (int transport : request.getTransportTypes()) builder.addTransportType(transport);
741         for (int capability : request.getCapabilities()) builder.addCapability(capability);
742         return builder;
743     }
744 
isStrictSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)745     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
746             NetworkCapabilities capabilities, Constants constants) {
747         // A restricted job that's out of quota MUST use an unmetered network.
748         if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
749                 && (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)
750                 || !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_TARE_WEALTH))) {
751             final NetworkCapabilities.Builder builder =
752                     copyCapabilities(jobStatus.getJob().getRequiredNetwork());
753             builder.addCapability(NET_CAPABILITY_NOT_METERED);
754             return builder.build().satisfiedByNetworkCapabilities(capabilities);
755         } else {
756             return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
757         }
758     }
759 
isRelaxedSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)760     private boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
761             NetworkCapabilities capabilities, Constants constants) {
762         // Only consider doing this for unrestricted prefetching jobs
763         if (!jobStatus.getJob().isPrefetch() || jobStatus.getStandbyBucket() == RESTRICTED_INDEX) {
764             return false;
765         }
766         final long estDownloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
767         if (estDownloadBytes <= 0) {
768             // Need to at least know the estimated download bytes for a prefetch job.
769             return false;
770         }
771 
772         // See if we match after relaxing any unmetered request
773         final NetworkCapabilities.Builder builder =
774                 copyCapabilities(jobStatus.getJob().getRequiredNetwork());
775         builder.removeCapability(NET_CAPABILITY_NOT_METERED);
776         if (builder.build().satisfiedByNetworkCapabilities(capabilities)
777                 && jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC) {
778             final long opportunisticQuotaBytes =
779                     mNetPolicyManagerInternal.getSubscriptionOpportunisticQuota(
780                             network, NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS);
781             final long estUploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
782             final long estimatedBytes = estDownloadBytes
783                     + (estUploadBytes == JobInfo.NETWORK_BYTES_UNKNOWN ? 0 : estUploadBytes);
784             return opportunisticQuotaBytes >= estimatedBytes;
785         }
786 
787         return false;
788     }
789 
790     @VisibleForTesting
isSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)791     boolean isSatisfied(JobStatus jobStatus, Network network,
792             NetworkCapabilities capabilities, Constants constants) {
793         // Zeroth, we gotta have a network to think about being satisfied
794         if (network == null || capabilities == null) return false;
795 
796         if (!isUsable(capabilities)) return false;
797 
798         // First, are we insane?
799         if (isInsane(jobStatus, network, capabilities, constants)) return false;
800 
801         // Second, is the network congested?
802         if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
803 
804         if (!isStrongEnough(jobStatus, capabilities, constants)) return false;
805 
806         // Is the network a strict match?
807         if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
808 
809         // Is the network a relaxed match?
810         if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
811 
812         return false;
813     }
814 
815     @GuardedBy("mLock")
maybeRegisterDefaultNetworkCallbackLocked(JobStatus jobStatus)816     private void maybeRegisterDefaultNetworkCallbackLocked(JobStatus jobStatus) {
817         final int sourceUid = jobStatus.getSourceUid();
818         if (mCurrentDefaultNetworkCallbacks.contains(sourceUid)) {
819             return;
820         }
821         final UidStats uidStats =
822                 getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
823         if (!mSortedStats.contains(uidStats)) {
824             mSortedStats.add(uidStats);
825         }
826         if (mCurrentDefaultNetworkCallbacks.size() >= MAX_NETWORK_CALLBACKS) {
827             postAdjustCallbacks();
828             return;
829         }
830         registerPendingUidCallbacksLocked();
831     }
832 
833     /**
834      * Register UID callbacks for UIDs that are next in line, based on the current order in {@link
835      * #mSortedStats}. This assumes that there are only registered callbacks for UIDs in the top
836      * {@value #MAX_NETWORK_CALLBACKS} UIDs and that the only UIDs missing callbacks are the lower
837      * priority ones.
838      */
839     @GuardedBy("mLock")
registerPendingUidCallbacksLocked()840     private void registerPendingUidCallbacksLocked() {
841         final int numCallbacks = mCurrentDefaultNetworkCallbacks.size();
842         final int numPending = mSortedStats.size();
843         if (numPending < numCallbacks) {
844             // This means there's a bug in the code >.<
845             Slog.wtf(TAG, "There are more registered callbacks than sorted UIDs: "
846                     + numCallbacks + " vs " + numPending);
847         }
848         for (int i = numCallbacks; i < numPending && i < MAX_NETWORK_CALLBACKS; ++i) {
849             UidStats uidStats = mSortedStats.get(i);
850             UidDefaultNetworkCallback callback = mDefaultNetworkCallbackPool.acquire();
851             if (callback == null) {
852                 callback = new UidDefaultNetworkCallback();
853             }
854             callback.setUid(uidStats.uid);
855             mCurrentDefaultNetworkCallbacks.append(uidStats.uid, callback);
856             mConnManager.registerDefaultNetworkCallbackForUid(uidStats.uid, callback, mHandler);
857         }
858     }
859 
postAdjustCallbacks()860     private void postAdjustCallbacks() {
861         postAdjustCallbacks(0);
862     }
863 
postAdjustCallbacks(long delayMs)864     private void postAdjustCallbacks(long delayMs) {
865         mHandler.sendEmptyMessageDelayed(MSG_ADJUST_CALLBACKS, delayMs);
866     }
867 
868     @GuardedBy("mLock")
maybeAdjustRegisteredCallbacksLocked()869     private void maybeAdjustRegisteredCallbacksLocked() {
870         mHandler.removeMessages(MSG_ADJUST_CALLBACKS);
871 
872         final int count = mUidStats.size();
873         if (count == mCurrentDefaultNetworkCallbacks.size()) {
874             // All of them are registered and there are no blocked UIDs.
875             // No point evaluating all UIDs.
876             return;
877         }
878 
879         final long nowElapsed = sElapsedRealtimeClock.millis();
880         if (nowElapsed - mLastCallbackAdjustmentTimeElapsed < MIN_ADJUST_CALLBACK_INTERVAL_MS) {
881             postAdjustCallbacks(MIN_ADJUST_CALLBACK_INTERVAL_MS);
882             return;
883         }
884 
885         mLastCallbackAdjustmentTimeElapsed = nowElapsed;
886         mSortedStats.clear();
887 
888         for (int u = 0; u < mUidStats.size(); ++u) {
889             UidStats us = mUidStats.valueAt(u);
890             ArraySet<JobStatus> jobs = mTrackedJobs.get(us.uid);
891             if (jobs == null || jobs.size() == 0) {
892                 unregisterDefaultNetworkCallbackLocked(us.uid, nowElapsed);
893                 continue;
894             }
895 
896             // We won't evaluate stats in the first 30 seconds after boot...That's probably okay.
897             if (us.lastUpdatedElapsed + MIN_STATS_UPDATE_INTERVAL_MS < nowElapsed) {
898                 us.earliestEnqueueTime = Long.MAX_VALUE;
899                 us.earliestEJEnqueueTime = Long.MAX_VALUE;
900                 us.numReadyWithConnectivity = 0;
901                 us.numRequestedNetworkAvailable = 0;
902                 us.numRegular = 0;
903                 us.numEJs = 0;
904 
905                 for (int j = 0; j < jobs.size(); ++j) {
906                     JobStatus job = jobs.valueAt(j);
907                     if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_CONNECTIVITY)) {
908                         us.numReadyWithConnectivity++;
909                         if (isNetworkAvailable(job)) {
910                             us.numRequestedNetworkAvailable++;
911                         }
912                         // Only use the enqueue time of jobs that would be ready to prevent apps
913                         // from gaming the system (eg. by scheduling a job that requires all
914                         // constraints and has a minimum latency of 6 months to always have the
915                         // earliest enqueue time).
916                         us.earliestEnqueueTime = Math.min(us.earliestEnqueueTime, job.enqueueTime);
917                         if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
918                             us.earliestEJEnqueueTime =
919                                     Math.min(us.earliestEJEnqueueTime, job.enqueueTime);
920                         }
921                     }
922                     if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
923                         us.numEJs++;
924                     } else {
925                         us.numRegular++;
926                     }
927                 }
928 
929                 us.lastUpdatedElapsed = nowElapsed;
930             }
931             mSortedStats.add(us);
932         }
933 
934         mSortedStats.sort(mUidStatsComparator);
935 
936         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
937         // Iterate in reverse order to remove existing callbacks before adding new ones.
938         for (int i = mSortedStats.size() - 1; i >= 0; --i) {
939             UidStats us = mSortedStats.get(i);
940             if (i >= MAX_NETWORK_CALLBACKS) {
941                 if (unregisterDefaultNetworkCallbackLocked(us.uid, nowElapsed)) {
942                     changedJobs.addAll(mTrackedJobs.get(us.uid));
943                 }
944             } else {
945                 UidDefaultNetworkCallback defaultNetworkCallback =
946                         mCurrentDefaultNetworkCallbacks.get(us.uid);
947                 if (defaultNetworkCallback == null) {
948                     // Not already registered.
949                     defaultNetworkCallback = mDefaultNetworkCallbackPool.acquire();
950                     if (defaultNetworkCallback == null) {
951                         defaultNetworkCallback = new UidDefaultNetworkCallback();
952                     }
953                     defaultNetworkCallback.setUid(us.uid);
954                     mCurrentDefaultNetworkCallbacks.append(us.uid, defaultNetworkCallback);
955                     mConnManager.registerDefaultNetworkCallbackForUid(
956                             us.uid, defaultNetworkCallback, mHandler);
957                 }
958             }
959         }
960         if (changedJobs.size() > 0) {
961             mStateChangedListener.onControllerStateChanged(changedJobs);
962         }
963     }
964 
965     @GuardedBy("mLock")
unregisterDefaultNetworkCallbackLocked(int uid, long nowElapsed)966     private boolean unregisterDefaultNetworkCallbackLocked(int uid, long nowElapsed) {
967         UidDefaultNetworkCallback defaultNetworkCallback = mCurrentDefaultNetworkCallbacks.get(uid);
968         if (defaultNetworkCallback == null) {
969             return false;
970         }
971         mCurrentDefaultNetworkCallbacks.remove(uid);
972         mConnManager.unregisterNetworkCallback(defaultNetworkCallback);
973         mDefaultNetworkCallbackPool.release(defaultNetworkCallback);
974         defaultNetworkCallback.clear();
975 
976         boolean changed = false;
977         final ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
978         if (jobs != null) {
979             // Since we're unregistering the callback, we can no longer monitor
980             // changes to the app's network and so we should just mark the
981             // connectivity constraint as not satisfied.
982             for (int j = jobs.size() - 1; j >= 0; --j) {
983                 changed |= updateConstraintsSatisfied(
984                         jobs.valueAt(j), nowElapsed, null, null);
985             }
986         }
987         return changed;
988     }
989 
990     @Nullable
getNetworkCapabilities(@ullable Network network)991     private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
992         if (network == null) {
993             return null;
994         }
995         synchronized (mLock) {
996             // There is technically a race here if the Network object is reused. This can happen
997             // only if that Network disconnects and the auto-incrementing network ID in
998             // ConnectivityService wraps. This shouldn't be a concern since we only make
999             // use of asynchronous calls.
1000             return mAvailableNetworks.get(network);
1001         }
1002     }
1003 
1004     @GuardedBy("mLock")
1005     @Nullable
getNetworkLocked(@onNull JobStatus jobStatus)1006     private Network getNetworkLocked(@NonNull JobStatus jobStatus) {
1007         final UidDefaultNetworkCallback defaultNetworkCallback =
1008                 mCurrentDefaultNetworkCallbacks.get(jobStatus.getSourceUid());
1009         if (defaultNetworkCallback == null) {
1010             return null;
1011         }
1012 
1013         UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
1014 
1015         final int unbypassableBlockedReasons;
1016         // TOP will probably have fewer reasons, so we may not have to worry about returning
1017         // BG_BLOCKED for a TOP app. However, better safe than sorry.
1018         if (uidStats.baseBias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
1019                 || (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1020             if (DEBUG) {
1021                 Slog.d(TAG, "Using FG bypass for " + jobStatus.getSourceUid());
1022             }
1023             unbypassableBlockedReasons = UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS;
1024         } else if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.startedAsExpeditedJob) {
1025             if (DEBUG) {
1026                 Slog.d(TAG, "Using EJ bypass for " + jobStatus.getSourceUid());
1027             }
1028             unbypassableBlockedReasons = UNBYPASSABLE_EJ_BLOCKED_REASONS;
1029         } else {
1030             if (DEBUG) {
1031                 Slog.d(TAG, "Using BG bypass for " + jobStatus.getSourceUid());
1032             }
1033             unbypassableBlockedReasons = UNBYPASSABLE_BG_BLOCKED_REASONS;
1034         }
1035 
1036         return (unbypassableBlockedReasons & defaultNetworkCallback.mBlockedReasons) == 0
1037                 ? defaultNetworkCallback.mDefaultNetwork : null;
1038     }
1039 
updateConstraintsSatisfied(JobStatus jobStatus)1040     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
1041         final long nowElapsed = sElapsedRealtimeClock.millis();
1042         final UidDefaultNetworkCallback defaultNetworkCallback =
1043                 mCurrentDefaultNetworkCallbacks.get(jobStatus.getSourceUid());
1044         if (defaultNetworkCallback == null) {
1045             maybeRegisterDefaultNetworkCallbackLocked(jobStatus);
1046             return updateConstraintsSatisfied(jobStatus, nowElapsed, null, null);
1047         }
1048         final Network network = getNetworkLocked(jobStatus);
1049         final NetworkCapabilities capabilities = getNetworkCapabilities(network);
1050         return updateConstraintsSatisfied(jobStatus, nowElapsed, network, capabilities);
1051     }
1052 
updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed, Network network, NetworkCapabilities capabilities)1053     private boolean updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed,
1054             Network network, NetworkCapabilities capabilities) {
1055         // TODO: consider matching against non-default networks
1056 
1057         final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
1058 
1059         final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);
1060 
1061         // Pass along the evaluated network for job to use; prevents race
1062         // conditions as default routes change over time, and opens the door to
1063         // using non-default routes.
1064         jobStatus.network = network;
1065 
1066         if (DEBUG) {
1067             Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
1068                     + " for " + jobStatus + ": usable=" + isUsable(capabilities)
1069                     + " satisfied=" + satisfied);
1070         }
1071         return changed;
1072     }
1073 
1074     @GuardedBy("mLock")
updateAllTrackedJobsLocked(boolean allowThrottle)1075     private void updateAllTrackedJobsLocked(boolean allowThrottle) {
1076         if (allowThrottle) {
1077             final long throttleTimeLeftMs =
1078                     (mLastAllJobUpdateTimeElapsed + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
1079                             - sElapsedRealtimeClock.millis();
1080             if (throttleTimeLeftMs > 0) {
1081                 Message msg = mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0);
1082                 mHandler.sendMessageDelayed(msg, throttleTimeLeftMs);
1083                 return;
1084             }
1085         }
1086 
1087         mHandler.removeMessages(MSG_UPDATE_ALL_TRACKED_JOBS);
1088         updateTrackedJobsLocked(-1, null);
1089         mLastAllJobUpdateTimeElapsed = sElapsedRealtimeClock.millis();
1090     }
1091 
1092     /**
1093      * Update any jobs tracked by this controller that match given filters.
1094      *
1095      * @param filterUid     only update jobs belonging to this UID, or {@code -1} to
1096      *                      update all tracked jobs.
1097      * @param filterNetwork only update jobs that would use this
1098      *                      {@link Network}, or {@code null} to update all tracked jobs.
1099      */
1100     @GuardedBy("mLock")
updateTrackedJobsLocked(int filterUid, @Nullable Network filterNetwork)1101     private void updateTrackedJobsLocked(int filterUid, @Nullable Network filterNetwork) {
1102         final ArraySet<JobStatus> changedJobs;
1103         if (filterUid == -1) {
1104             changedJobs = new ArraySet<>();
1105             for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
1106                 if (updateTrackedJobsLocked(mTrackedJobs.valueAt(i), filterNetwork)) {
1107                     changedJobs.addAll(mTrackedJobs.valueAt(i));
1108                 }
1109             }
1110         } else {
1111             if (updateTrackedJobsLocked(mTrackedJobs.get(filterUid), filterNetwork)) {
1112                 changedJobs = mTrackedJobs.get(filterUid);
1113             } else {
1114                 changedJobs = null;
1115             }
1116         }
1117         if (changedJobs != null && changedJobs.size() > 0) {
1118             mStateChangedListener.onControllerStateChanged(changedJobs);
1119         }
1120     }
1121 
1122     @GuardedBy("mLock")
updateTrackedJobsLocked(ArraySet<JobStatus> jobs, @Nullable Network filterNetwork)1123     private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs,
1124             @Nullable Network filterNetwork) {
1125         if (jobs == null || jobs.size() == 0) {
1126             return false;
1127         }
1128 
1129         UidDefaultNetworkCallback defaultNetworkCallback =
1130                 mCurrentDefaultNetworkCallbacks.get(jobs.valueAt(0).getSourceUid());
1131         if (defaultNetworkCallback == null) {
1132             // This method is only called via a network callback object. That means something
1133             // changed about a general network characteristic (since we wouldn't be in this
1134             // situation if called from a UID_specific callback). The general network callback
1135             // will handle adjusting the per-UID callbacks, so nothing left to do here.
1136             return false;
1137         }
1138 
1139         final long nowElapsed = sElapsedRealtimeClock.millis();
1140         boolean changed = false;
1141         for (int i = jobs.size() - 1; i >= 0; i--) {
1142             final JobStatus js = jobs.valueAt(i);
1143 
1144             final Network net = getNetworkLocked(js);
1145             final NetworkCapabilities netCap = getNetworkCapabilities(net);
1146             final boolean match = (filterNetwork == null
1147                     || Objects.equals(filterNetwork, net));
1148 
1149             // Update either when we have a network match, or when the
1150             // job hasn't yet been evaluated against the currently
1151             // active network; typically when we just lost a network.
1152             if (match || !Objects.equals(js.network, net)) {
1153                 changed |= updateConstraintsSatisfied(js, nowElapsed, net, netCap);
1154             }
1155         }
1156         return changed;
1157     }
1158 
1159     /**
1160      * We know the network has just come up. We want to run any jobs that are ready.
1161      */
1162     @Override
onNetworkActive()1163     public void onNetworkActive() {
1164         synchronized (mLock) {
1165             for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
1166                 final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
1167                 for (int j = jobs.size() - 1; j >= 0; j--) {
1168                     final JobStatus js = jobs.valueAt(j);
1169                     if (js.isReady()) {
1170                         if (DEBUG) {
1171                             Slog.d(TAG, "Running " + js + " due to network activity.");
1172                         }
1173                         mStateChangedListener.onRunJobNow(js);
1174                     }
1175                 }
1176             }
1177         }
1178     }
1179 
1180     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
1181         @Override
1182         public void onAvailable(Network network) {
1183             if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
1184             // Documentation says not to call getNetworkCapabilities here but wait for
1185             // onCapabilitiesChanged instead.  onCapabilitiesChanged should be called immediately
1186             // after this, so no need to update mAvailableNetworks here.
1187         }
1188 
1189         @Override
1190         public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
1191             if (DEBUG) {
1192                 Slog.v(TAG, "onCapabilitiesChanged: " + network);
1193             }
1194             synchronized (mLock) {
1195                 final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities);
1196                 if (oldCaps != null) {
1197                     maybeUnregisterSignalStrengthCallbackLocked(oldCaps);
1198                 }
1199                 maybeRegisterSignalStrengthCallbackLocked(capabilities);
1200                 updateTrackedJobsLocked(-1, network);
1201                 postAdjustCallbacks();
1202             }
1203         }
1204 
1205         @Override
1206         public void onLost(Network network) {
1207             if (DEBUG) {
1208                 Slog.v(TAG, "onLost: " + network);
1209             }
1210             synchronized (mLock) {
1211                 final NetworkCapabilities capabilities = mAvailableNetworks.remove(network);
1212                 if (capabilities != null) {
1213                     maybeUnregisterSignalStrengthCallbackLocked(capabilities);
1214                 }
1215                 for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
1216                     UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
1217                     if (Objects.equals(callback.mDefaultNetwork, network)) {
1218                         callback.mDefaultNetwork = null;
1219                     }
1220                 }
1221                 updateTrackedJobsLocked(-1, network);
1222                 postAdjustCallbacks();
1223             }
1224         }
1225 
1226         @GuardedBy("mLock")
1227         private void maybeRegisterSignalStrengthCallbackLocked(
1228                 @NonNull NetworkCapabilities capabilities) {
1229             if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
1230                 return;
1231             }
1232             TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
1233             final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
1234             for (int subId : subscriptionIds) {
1235                 if (mSignalStrengths.indexOfKey(subId) >= 0) {
1236                     continue;
1237                 }
1238                 TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
1239                 CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
1240                 idTm.registerTelephonyCallback(
1241                         JobSchedulerBackgroundThread.getExecutor(), callback);
1242                 mSignalStrengths.put(subId, callback);
1243 
1244                 final SignalStrength signalStrength = idTm.getSignalStrength();
1245                 if (signalStrength != null) {
1246                     callback.signalStrength = signalStrength.getLevel();
1247                 }
1248             }
1249         }
1250 
1251         @GuardedBy("mLock")
1252         private void maybeUnregisterSignalStrengthCallbackLocked(
1253                 @NonNull NetworkCapabilities capabilities) {
1254             if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
1255                 return;
1256             }
1257             ArraySet<Integer> activeIds = new ArraySet<>();
1258             for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) {
1259                 NetworkCapabilities nc = mAvailableNetworks.valueAt(i);
1260                 if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
1261                     activeIds.addAll(nc.getSubscriptionIds());
1262                 }
1263             }
1264             if (DEBUG) {
1265                 Slog.d(TAG, "Active subscription IDs: " + activeIds);
1266             }
1267             TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
1268             Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
1269             for (int subId : subscriptionIds) {
1270                 if (activeIds.contains(subId)) {
1271                     continue;
1272                 }
1273                 TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
1274                 CellSignalStrengthCallback callback = mSignalStrengths.removeReturnOld(subId);
1275                 if (callback != null) {
1276                     idTm.unregisterTelephonyCallback(callback);
1277                 } else {
1278                     Slog.wtf(TAG, "Callback for sub " + subId + " didn't exist?!?!");
1279                 }
1280             }
1281         }
1282     };
1283 
1284     private class CcHandler extends Handler {
CcHandler(Looper looper)1285         CcHandler(Looper looper) {
1286             super(looper);
1287         }
1288 
1289         @Override
handleMessage(Message msg)1290         public void handleMessage(Message msg) {
1291             synchronized (mLock) {
1292                 switch (msg.what) {
1293                     case MSG_ADJUST_CALLBACKS:
1294                         synchronized (mLock) {
1295                             maybeAdjustRegisteredCallbacksLocked();
1296                         }
1297                         break;
1298 
1299                     case MSG_UPDATE_ALL_TRACKED_JOBS:
1300                         synchronized (mLock) {
1301                             final boolean allowThrottle = msg.arg1 == 1;
1302                             updateAllTrackedJobsLocked(allowThrottle);
1303                         }
1304                         break;
1305                 }
1306             }
1307         }
1308     }
1309 
1310     private class UidDefaultNetworkCallback extends NetworkCallback {
1311         private int mUid;
1312         @Nullable
1313         private Network mDefaultNetwork;
1314         private int mBlockedReasons;
1315 
setUid(int uid)1316         private void setUid(int uid) {
1317             mUid = uid;
1318             mDefaultNetwork = null;
1319         }
1320 
clear()1321         private void clear() {
1322             mDefaultNetwork = null;
1323             mUid = UserHandle.USER_NULL;
1324         }
1325 
1326         @Override
onAvailable(Network network)1327         public void onAvailable(Network network) {
1328             if (DEBUG) Slog.v(TAG, "default-onAvailable(" + mUid + "): " + network);
1329         }
1330 
1331         @Override
onBlockedStatusChanged(Network network, int blockedReasons)1332         public void onBlockedStatusChanged(Network network, int blockedReasons) {
1333             if (DEBUG) {
1334                 Slog.v(TAG, "default-onBlockedStatusChanged(" + mUid + "): "
1335                         + network + " -> " + blockedReasons);
1336             }
1337             if (mUid == UserHandle.USER_NULL) {
1338                 return;
1339             }
1340             synchronized (mLock) {
1341                 mDefaultNetwork = network;
1342                 mBlockedReasons = blockedReasons;
1343                 updateTrackedJobsLocked(mUid, network);
1344             }
1345         }
1346 
1347         // Network transitions have some complicated behavior that JS doesn't handle very well.
1348         //
1349         // * If the default network changes from A to B without A disconnecting, then we'll only
1350         // get onAvailable(B) (and the subsequent onBlockedStatusChanged() call). Since we get
1351         // the onBlockedStatusChanged() call, we re-evaluate the job, but keep it running
1352         // (assuming the new network satisfies constraints). The app continues to use the old
1353         // network (if they use the network object provided through JobParameters.getNetwork())
1354         // because we don't notify them of the default network change. If the old network no
1355         // longer satisfies requested constraints, then we have a problem. Depending on the order
1356         // of calls, if the per-UID callback gets notified of the network change before the
1357         // general callback gets notified of the capabilities change, then the job's network
1358         // object will point to the new network and we won't stop the job, even though we told it
1359         // to use the old network that no longer satisfies its constraints. This is the behavior
1360         // we loosely had (ignoring race conditions between asynchronous and synchronous
1361         // connectivity calls) when we were calling the synchronous getActiveNetworkForUid() API.
1362         // However, we should fix it.
1363         // TODO: stop jobs when the existing capabilities change after default network change
1364         //
1365         // * If the default network changes from A to B because A disconnected, then we'll get
1366         // onLost(A) and then onAvailable(B). In this case, there will be a short period where JS
1367         // doesn't think there's an available network for the job, so we'll stop the job even
1368         // though onAvailable(B) will be called soon. One on hand, the app would have gotten a
1369         // network error as well because of A's disconnect, and this will allow JS to provide the
1370         // job with the new default network. On the other hand, we have to stop the job even
1371         // though it could have continued running with the new network and the job has to deal
1372         // with whatever backoff policy is set. For now, the current behavior is fine, but we may
1373         // want to see if there's a way to have a smoother transition.
1374 
1375         @Override
onLost(Network network)1376         public void onLost(Network network) {
1377             if (DEBUG) {
1378                 Slog.v(TAG, "default-onLost(" + mUid + "): " + network);
1379             }
1380             if (mUid == UserHandle.USER_NULL) {
1381                 return;
1382             }
1383             synchronized (mLock) {
1384                 if (Objects.equals(mDefaultNetwork, network)) {
1385                     mDefaultNetwork = null;
1386                     updateTrackedJobsLocked(mUid, network);
1387                     // Add a delay in case onAvailable()+onBlockedStatusChanged is called for a
1388                     // new network. If this onLost was called because the network is completely
1389                     // gone, the delay will hel make sure we don't have a short burst of adjusting
1390                     // callback calls.
1391                     postAdjustCallbacks(1000);
1392                 }
1393             }
1394         }
1395 
dumpLocked(IndentingPrintWriter pw)1396         private void dumpLocked(IndentingPrintWriter pw) {
1397             pw.print("UID: ");
1398             pw.print(mUid);
1399             pw.print("; ");
1400             if (mDefaultNetwork == null) {
1401                 pw.print("No network");
1402             } else {
1403                 pw.print("Network: ");
1404                 pw.print(mDefaultNetwork);
1405                 pw.print(" (blocked=");
1406                 pw.print(NetworkPolicyManager.blockedReasonsToString(mBlockedReasons));
1407                 pw.print(")");
1408             }
1409             pw.println();
1410         }
1411     }
1412 
1413     private static class UidStats {
1414         public final int uid;
1415         public int baseBias;
1416         public final ArraySet<JobStatus> runningJobs = new ArraySet<>();
1417         public int numReadyWithConnectivity;
1418         public int numRequestedNetworkAvailable;
1419         public int numEJs;
1420         public int numRegular;
1421         public long earliestEnqueueTime;
1422         public long earliestEJEnqueueTime;
1423         public long lastUpdatedElapsed;
1424 
UidStats(int uid)1425         private UidStats(int uid) {
1426             this.uid = uid;
1427         }
1428 
dumpLocked(IndentingPrintWriter pw, final long nowElapsed)1429         private void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
1430             pw.print("UidStats{");
1431             pw.print("uid", uid);
1432             pw.print("pri", baseBias);
1433             pw.print("#run", runningJobs.size());
1434             pw.print("#readyWithConn", numReadyWithConnectivity);
1435             pw.print("#netAvail", numRequestedNetworkAvailable);
1436             pw.print("#EJs", numEJs);
1437             pw.print("#reg", numRegular);
1438             pw.print("earliestEnqueue", earliestEnqueueTime);
1439             pw.print("earliestEJEnqueue", earliestEJEnqueueTime);
1440             pw.print("updated=");
1441             TimeUtils.formatDuration(lastUpdatedElapsed - nowElapsed, pw);
1442             pw.println("}");
1443         }
1444     }
1445 
1446     private class CellSignalStrengthCallback extends TelephonyCallback
1447             implements TelephonyCallback.SignalStrengthsListener {
1448         @GuardedBy("mLock")
1449         public int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_GREAT;
1450 
1451         @Override
onSignalStrengthsChanged(@onNull SignalStrength signalStrength)1452         public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
1453             synchronized (mLock) {
1454                 final int newSignalStrength = signalStrength.getLevel();
1455                 if (DEBUG) {
1456                     Slog.d(TAG, "Signal strength changing from "
1457                             + this.signalStrength + " to " + newSignalStrength);
1458                     for (CellSignalStrength css : signalStrength.getCellSignalStrengths()) {
1459                         Slog.d(TAG, "CSS: " + css.getLevel() + " " + css);
1460                     }
1461                 }
1462                 if (this.signalStrength == newSignalStrength) {
1463                     // This happens a lot.
1464                     return;
1465                 }
1466                 this.signalStrength = newSignalStrength;
1467                 // Update job bookkeeping out of band to avoid blocking callback progress.
1468                 mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0).sendToTarget();
1469             }
1470         }
1471     }
1472 
1473     @GuardedBy("mLock")
1474     @Override
dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)1475     public void dumpControllerStateLocked(IndentingPrintWriter pw,
1476             Predicate<JobStatus> predicate) {
1477         final long nowElapsed = sElapsedRealtimeClock.millis();
1478 
1479         if (mRequestedWhitelistJobs.size() > 0) {
1480             pw.print("Requested standby exceptions:");
1481             for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
1482                 pw.print(" ");
1483                 pw.print(mRequestedWhitelistJobs.keyAt(i));
1484                 pw.print(" (");
1485                 pw.print(mRequestedWhitelistJobs.valueAt(i).size());
1486                 pw.print(" jobs)");
1487             }
1488             pw.println();
1489         }
1490         if (mAvailableNetworks.size() > 0) {
1491             pw.println("Available networks:");
1492             pw.increaseIndent();
1493             for (int i = 0; i < mAvailableNetworks.size(); i++) {
1494                 pw.print(mAvailableNetworks.keyAt(i));
1495                 pw.print(": ");
1496                 pw.println(mAvailableNetworks.valueAt(i));
1497             }
1498             pw.decreaseIndent();
1499         } else {
1500             pw.println("No available networks");
1501         }
1502         pw.println();
1503 
1504         if (mSignalStrengths.size() > 0) {
1505             pw.println("Subscription ID signal strengths:");
1506             pw.increaseIndent();
1507             for (int i = 0; i < mSignalStrengths.size(); ++i) {
1508                 pw.print(mSignalStrengths.keyAt(i));
1509                 pw.print(": ");
1510                 pw.println(mSignalStrengths.valueAt(i).signalStrength);
1511             }
1512             pw.decreaseIndent();
1513         } else {
1514             pw.println("No cached signal strengths");
1515         }
1516         pw.println();
1517 
1518         pw.println("Current default network callbacks:");
1519         pw.increaseIndent();
1520         for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
1521             mCurrentDefaultNetworkCallbacks.valueAt(i).dumpLocked(pw);
1522         }
1523         pw.decreaseIndent();
1524         pw.println();
1525 
1526         pw.println("UID Pecking Order:");
1527         pw.increaseIndent();
1528         for (int i = 0; i < mSortedStats.size(); ++i) {
1529             pw.print(i);
1530             pw.print(": ");
1531             mSortedStats.get(i).dumpLocked(pw, nowElapsed);
1532         }
1533         pw.decreaseIndent();
1534         pw.println();
1535 
1536         for (int i = 0; i < mTrackedJobs.size(); i++) {
1537             final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
1538             for (int j = 0; j < jobs.size(); j++) {
1539                 final JobStatus js = jobs.valueAt(j);
1540                 if (!predicate.test(js)) {
1541                     continue;
1542                 }
1543                 pw.print("#");
1544                 js.printUniqueId(pw);
1545                 pw.print(" from ");
1546                 UserHandle.formatUid(pw, js.getSourceUid());
1547                 pw.print(": ");
1548                 pw.print(js.getJob().getRequiredNetwork());
1549                 pw.println();
1550             }
1551         }
1552     }
1553 
1554     @GuardedBy("mLock")
1555     @Override
dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)1556     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
1557             Predicate<JobStatus> predicate) {
1558         final long token = proto.start(fieldId);
1559         final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
1560 
1561         for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
1562             proto.write(
1563                     StateControllerProto.ConnectivityController.REQUESTED_STANDBY_EXCEPTION_UIDS,
1564                     mRequestedWhitelistJobs.keyAt(i));
1565         }
1566         for (int i = 0; i < mTrackedJobs.size(); i++) {
1567             final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
1568             for (int j = 0; j < jobs.size(); j++) {
1569                 final JobStatus js = jobs.valueAt(j);
1570                 if (!predicate.test(js)) {
1571                     continue;
1572                 }
1573                 final long jsToken = proto.start(
1574                         StateControllerProto.ConnectivityController.TRACKED_JOBS);
1575                 js.writeToShortProto(proto,
1576                         StateControllerProto.ConnectivityController.TrackedJob.INFO);
1577                 proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
1578                         js.getSourceUid());
1579                 proto.end(jsToken);
1580             }
1581         }
1582 
1583         proto.end(mToken);
1584         proto.end(token);
1585     }
1586 }
1587