• 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.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.net.ConnectivityManager;
33 import android.net.ConnectivityManager.NetworkCallback;
34 import android.net.Network;
35 import android.net.NetworkCapabilities;
36 import android.net.NetworkPolicyManager;
37 import android.net.NetworkRequest;
38 import android.os.BatteryManager;
39 import android.os.BatteryManagerInternal;
40 import android.os.Handler;
41 import android.os.Looper;
42 import android.os.Message;
43 import android.os.UserHandle;
44 import android.text.format.DateUtils;
45 import android.util.ArrayMap;
46 import android.util.ArraySet;
47 import android.util.DataUnit;
48 import android.util.IndentingPrintWriter;
49 import android.util.Log;
50 import android.util.Pools;
51 import android.util.Slog;
52 import android.util.SparseArray;
53 import android.util.TimeUtils;
54 import android.util.proto.ProtoOutputStream;
55 
56 import com.android.internal.annotations.GuardedBy;
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.server.LocalServices;
59 import com.android.server.job.JobSchedulerService;
60 import com.android.server.job.JobSchedulerService.Constants;
61 import com.android.server.job.StateControllerProto;
62 import com.android.server.net.NetworkPolicyManagerInternal;
63 
64 import java.util.ArrayList;
65 import java.util.Comparator;
66 import java.util.List;
67 import java.util.Objects;
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     private final ChargingTracker mChargingTracker;
111 
112     /** List of tracked jobs keyed by source UID. */
113     @GuardedBy("mLock")
114     private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
115 
116     /**
117      * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
118      * grant an exception to in the app standby chain.
119      */
120     @GuardedBy("mLock")
121     private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();
122 
123     /**
124      * Set of currently available networks mapped to their latest network capabilities. Cache the
125      * latest capabilities to avoid unnecessary calls into ConnectivityManager.
126      */
127     @GuardedBy("mLock")
128     private final ArrayMap<Network, NetworkCapabilities> mAvailableNetworks = new ArrayMap<>();
129 
130     private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
131             new SparseArray<>();
132     private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
133         private int prioritizeExistenceOver(int threshold, int v1, int v2) {
134             // Check if they're both on the same side of the threshold.
135             if ((v1 > threshold && v2 > threshold) || (v1 <= threshold && v2 <= threshold)) {
136                 return 0;
137             }
138             // They're on opposite sides of the threshold.
139             if (v1 > threshold) {
140                 return -1;
141             }
142             return 1;
143         }
144 
145         @Override
146         public int compare(UidStats us1, UidStats us2) {
147             // Prioritize a UID ahead of another based on:
148             //   1. Already running connectivity jobs (so we don't drop the listener)
149             //   2. Waiting connectivity jobs would be ready with connectivity
150             //   3. An existing network satisfies a waiting connectivity job's requirements
151             //   4. TOP proc state
152             //   5. Existence of treat-as-EJ EJs (not just requested EJs)
153             //   6. FGS proc state
154             //   7. EJ enqueue time
155             //   8. Any other important job priorities/proc states
156             //   9. Enqueue time
157             // TODO: maybe consider number of jobs
158             // TODO: consider IMPORTANT_WHILE_FOREGROUND bit
159             final int runningPriority = prioritizeExistenceOver(0,
160                     us1.runningJobs.size(), us2.runningJobs.size());
161             if (runningPriority != 0) {
162                 return runningPriority;
163             }
164             // Prioritize any UIDs that have jobs that would be ready ahead of UIDs that don't.
165             final int readyWithConnPriority = prioritizeExistenceOver(0,
166                     us1.numReadyWithConnectivity, us2.numReadyWithConnectivity);
167             if (readyWithConnPriority != 0) {
168                 return readyWithConnPriority;
169             }
170             // They both have jobs that would be ready. Prioritize the UIDs whose requested
171             // network is available ahead of UIDs that don't have their requested network available.
172             final int reqAvailPriority = prioritizeExistenceOver(0,
173                     us1.numRequestedNetworkAvailable, us2.numRequestedNetworkAvailable);
174             if (reqAvailPriority != 0) {
175                 return reqAvailPriority;
176             }
177             // Prioritize the top app. If neither are top apps, then use a later prioritization
178             // check.
179             final int topPriority = prioritizeExistenceOver(JobInfo.PRIORITY_TOP_APP - 1,
180                     us1.basePriority, us2.basePriority);
181             if (topPriority != 0) {
182                 return topPriority;
183             }
184             // They're either both TOP or both not TOP. Prioritize the app that has runnable EJs
185             // pending.
186             final int ejPriority = prioritizeExistenceOver(0, us1.numEJs, us2.numEJs);
187             if (ejPriority != 0) {
188                 return ejPriority;
189             }
190             // They both have runnable EJs.
191             // Prioritize an FGS+ app. If neither are FGS+ apps, then use a later prioritization
192             // check.
193             final int fgsPriority = prioritizeExistenceOver(JobInfo.PRIORITY_FOREGROUND_SERVICE - 1,
194                     us1.basePriority, us2.basePriority);
195             if (fgsPriority != 0) {
196                 return fgsPriority;
197             }
198             // Order them by EJ enqueue time to help provide low EJ latency.
199             if (us1.earliestEJEnqueueTime < us2.earliestEJEnqueueTime) {
200                 return -1;
201             } else if (us1.earliestEJEnqueueTime > us2.earliestEJEnqueueTime) {
202                 return 1;
203             }
204             // Order by any latent important proc states.
205             if (us1.basePriority != us2.basePriority) {
206                 return us2.basePriority - us1.basePriority;
207             }
208             // Order by enqueue time.
209             if (us1.earliestEnqueueTime < us2.earliestEnqueueTime) {
210                 return -1;
211             }
212             return us1.earliestEnqueueTime > us2.earliestEnqueueTime ? 1 : 0;
213         }
214     };
215     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
216     private final Pools.Pool<UidDefaultNetworkCallback> mDefaultNetworkCallbackPool =
217             new Pools.SimplePool<>(MAX_NETWORK_CALLBACKS);
218     /**
219      * List of UidStats, sorted by priority as defined in {@link #mUidStatsComparator}. The sorting
220      * is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
221      */
222     private final List<UidStats> mSortedStats = new ArrayList<>();
223     private long mLastCallbackAdjustmentTimeElapsed;
224 
225     private static final int MSG_ADJUST_CALLBACKS = 0;
226 
227     private final Handler mHandler;
228 
ConnectivityController(JobSchedulerService service)229     public ConnectivityController(JobSchedulerService service) {
230         super(service);
231         mHandler = new CcHandler(mContext.getMainLooper());
232 
233         mConnManager = mContext.getSystemService(ConnectivityManager.class);
234         mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
235 
236         // We're interested in all network changes; internally we match these
237         // network changes against the active network for each UID with jobs.
238         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
239         mConnManager.registerNetworkCallback(request, mNetworkCallback);
240 
241         mChargingTracker = new ChargingTracker();
242         mChargingTracker.startTracking();
243     }
244 
245     @GuardedBy("mLock")
246     @Override
maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)247     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
248         if (jobStatus.hasConnectivityConstraint()) {
249             final UidStats uidStats =
250                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), false);
251             if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)) {
252                 uidStats.numReadyWithConnectivity++;
253             }
254             ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
255             if (jobs == null) {
256                 jobs = new ArraySet<>();
257                 mTrackedJobs.put(jobStatus.getSourceUid(), jobs);
258             }
259             jobs.add(jobStatus);
260             jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
261             updateConstraintsSatisfied(jobStatus);
262         }
263     }
264 
265     @GuardedBy("mLock")
266     @Override
prepareForExecutionLocked(JobStatus jobStatus)267     public void prepareForExecutionLocked(JobStatus jobStatus) {
268         if (jobStatus.hasConnectivityConstraint()) {
269             final UidStats uidStats =
270                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
271             uidStats.runningJobs.add(jobStatus);
272         }
273     }
274 
275     @GuardedBy("mLock")
276     @Override
unprepareFromExecutionLocked(JobStatus jobStatus)277     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
278         if (jobStatus.hasConnectivityConstraint()) {
279             final UidStats uidStats =
280                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
281             uidStats.runningJobs.remove(jobStatus);
282             postAdjustCallbacks();
283         }
284     }
285 
286     @GuardedBy("mLock")
287     @Override
maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate)288     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
289             boolean forUpdate) {
290         if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
291             ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
292             if (jobs != null) {
293                 jobs.remove(jobStatus);
294             }
295             final UidStats uidStats =
296                     getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
297             uidStats.numReadyWithConnectivity--;
298             uidStats.runningJobs.remove(jobStatus);
299             maybeRevokeStandbyExceptionLocked(jobStatus);
300             postAdjustCallbacks();
301         }
302     }
303 
304     @Override
startTrackingRestrictedJobLocked(JobStatus jobStatus)305     public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
306         // Don't need to start tracking the job. If the job needed network, it would already be
307         // tracked.
308         if (jobStatus.hasConnectivityConstraint()) {
309             updateConstraintsSatisfied(jobStatus);
310         }
311     }
312 
313     @Override
stopTrackingRestrictedJobLocked(JobStatus jobStatus)314     public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
315         // Shouldn't stop tracking the job here. If the job was tracked, it still needs network,
316         // even after being unrestricted.
317         if (jobStatus.hasConnectivityConstraint()) {
318             updateConstraintsSatisfied(jobStatus);
319         }
320     }
321 
322     @NonNull
getUidStats(int uid, String packageName, boolean shouldExist)323     private UidStats getUidStats(int uid, String packageName, boolean shouldExist) {
324         UidStats us = mUidStats.get(uid);
325         if (us == null) {
326             if (shouldExist) {
327                 // This shouldn't be happening. We create a UidStats object for the app when the
328                 // first job is scheduled in maybeStartTrackingJobLocked() and only ever drop the
329                 // object if the app is uninstalled or the user is removed. That means that if we
330                 // end up in this situation, onAppRemovedLocked() or onUserRemovedLocked() was
331                 // called before maybeStopTrackingJobLocked(), which is the reverse order of what
332                 // JobSchedulerService does (JSS calls maybeStopTrackingJobLocked() for all jobs
333                 // before calling onAppRemovedLocked() or onUserRemovedLocked()).
334                 Slog.wtfStack(TAG,
335                         "UidStats was null after job for " + packageName + " was registered");
336             }
337             us = new UidStats(uid);
338             mUidStats.append(uid, us);
339         }
340         return us;
341     }
342 
343     /**
344      * Returns true if the job's requested network is available. This DOES NOT necessarily mean
345      * that the UID has been granted access to the network.
346      */
isNetworkAvailable(JobStatus job)347     public boolean isNetworkAvailable(JobStatus job) {
348         synchronized (mLock) {
349             for (int i = 0; i < mAvailableNetworks.size(); ++i) {
350                 final Network network = mAvailableNetworks.keyAt(i);
351                 final NetworkCapabilities capabilities = mAvailableNetworks.valueAt(i);
352                 final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
353                 if (DEBUG) {
354                     Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
355                             + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
356                 }
357                 if (satisfied) {
358                     return true;
359                 }
360             }
361             return false;
362         }
363     }
364 
365     /**
366      * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
367      * chain.
368      */
369     @VisibleForTesting
370     @GuardedBy("mLock")
requestStandbyExceptionLocked(JobStatus job)371     void requestStandbyExceptionLocked(JobStatus job) {
372         final int uid = job.getSourceUid();
373         // Need to call this before adding the job.
374         final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
375         ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
376         if (jobs == null) {
377             jobs = new ArraySet<JobStatus>();
378             mRequestedWhitelistJobs.put(uid, jobs);
379         }
380         if (!jobs.add(job) || isExceptionRequested) {
381             if (DEBUG) {
382                 Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
383             }
384             return;
385         }
386         if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
387         mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
388     }
389 
390     /** Returns whether a standby exception has been requested for the UID. */
391     @VisibleForTesting
392     @GuardedBy("mLock")
isStandbyExceptionRequestedLocked(final int uid)393     boolean isStandbyExceptionRequestedLocked(final int uid) {
394         ArraySet jobs = mRequestedWhitelistJobs.get(uid);
395         return jobs != null && jobs.size() > 0;
396     }
397 
398     /**
399      * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
400      * thing stopping a job from running.
401      */
402     @GuardedBy("mLock")
403     @Override
evaluateStateLocked(JobStatus jobStatus)404     public void evaluateStateLocked(JobStatus jobStatus) {
405         if (!jobStatus.hasConnectivityConstraint()) {
406             return;
407         }
408 
409         final UidStats uidStats =
410                 getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
411 
412         if (jobStatus.shouldTreatAsExpeditedJob()) {
413             if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
414                 // Don't request a direct hole through any of the firewalls. Instead, mark the
415                 // constraint as satisfied if the network is available, and the job will get
416                 // through the firewalls once it starts running and the proc state is elevated.
417                 // This is the same behavior that FGS see.
418                 updateConstraintsSatisfied(jobStatus);
419             }
420             // Don't need to update constraint here if the network goes away. We'll do that as part
421             // of regular processing when we're notified about the drop.
422         } else if (jobStatus.isRequestedExpeditedJob()
423                 && jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
424             // Make sure we don't accidentally keep the constraint as satisfied if the job went
425             // from being expedited-ready to not-expeditable.
426             updateConstraintsSatisfied(jobStatus);
427         }
428 
429         // Always check the full job readiness stat in case the component has been disabled.
430         if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)
431                 && isNetworkAvailable(jobStatus)) {
432             if (DEBUG) {
433                 Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
434             }
435             uidStats.numReadyWithConnectivity++;
436             requestStandbyExceptionLocked(jobStatus);
437         } else {
438             if (DEBUG) {
439                 Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
440             }
441             // Don't decrement numReadyWithConnectivity here because we don't know if it was
442             // incremented for this job. The count will be set properly in
443             // maybeAdjustRegisteredCallbacksLocked().
444             maybeRevokeStandbyExceptionLocked(jobStatus);
445         }
446     }
447 
448     @GuardedBy("mLock")
449     @Override
reevaluateStateLocked(final int uid)450     public void reevaluateStateLocked(final int uid) {
451         // Check if we still need a connectivity exception in case the JobService was disabled.
452         ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
453         if (jobs == null) {
454             return;
455         }
456         for (int i = jobs.size() - 1; i >= 0; i--) {
457             evaluateStateLocked(jobs.valueAt(i));
458         }
459     }
460 
461     /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
462     @VisibleForTesting
463     @GuardedBy("mLock")
maybeRevokeStandbyExceptionLocked(final JobStatus job)464     void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
465         final int uid = job.getSourceUid();
466         if (!isStandbyExceptionRequestedLocked(uid)) {
467             return;
468         }
469         ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
470         if (jobs == null) {
471             Slog.wtf(TAG,
472                     "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
473                             + "standby exception has been requested.");
474             return;
475         }
476         if (!jobs.remove(job) || jobs.size() > 0) {
477             if (DEBUG) {
478                 Slog.i(TAG,
479                         "maybeRevokeStandbyExceptionLocked not revoking because there are still "
480                                 + jobs.size() + " jobs left.");
481             }
482             return;
483         }
484         // No more jobs that need an exception.
485         revokeStandbyExceptionLocked(uid);
486     }
487 
488     /**
489      * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
490      * for the uid.
491      */
492     @GuardedBy("mLock")
revokeStandbyExceptionLocked(final int uid)493     private void revokeStandbyExceptionLocked(final int uid) {
494         if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
495         mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
496         mRequestedWhitelistJobs.remove(uid);
497     }
498 
499     @GuardedBy("mLock")
500     @Override
onAppRemovedLocked(String pkgName, int uid)501     public void onAppRemovedLocked(String pkgName, int uid) {
502         if (mService.getPackagesForUidLocked(uid) == null) {
503             // All packages in the UID have been removed. It's safe to remove things based on
504             // UID alone.
505             mTrackedJobs.delete(uid);
506             UidStats uidStats = mUidStats.removeReturnOld(uid);
507             unregisterDefaultNetworkCallbackLocked(uid, sElapsedRealtimeClock.millis());
508             mSortedStats.remove(uidStats);
509             registerPendingUidCallbacksLocked();
510         }
511     }
512 
513     @GuardedBy("mLock")
514     @Override
onUserRemovedLocked(int userId)515     public void onUserRemovedLocked(int userId) {
516         final long nowElapsed = sElapsedRealtimeClock.millis();
517         for (int u = mUidStats.size() - 1; u >= 0; --u) {
518             UidStats uidStats = mUidStats.valueAt(u);
519             if (UserHandle.getUserId(uidStats.uid) == userId) {
520                 unregisterDefaultNetworkCallbackLocked(uidStats.uid, nowElapsed);
521                 mSortedStats.remove(uidStats);
522                 mUidStats.removeAt(u);
523             }
524         }
525         postAdjustCallbacks();
526     }
527 
528     @GuardedBy("mLock")
529     @Override
onUidPriorityChangedLocked(int uid, int newPriority)530     public void onUidPriorityChangedLocked(int uid, int newPriority) {
531         UidStats uidStats = mUidStats.get(uid);
532         if (uidStats != null && uidStats.basePriority != newPriority) {
533             uidStats.basePriority = newPriority;
534             postAdjustCallbacks();
535         }
536     }
537 
isUsable(NetworkCapabilities capabilities)538     private boolean isUsable(NetworkCapabilities capabilities) {
539         return capabilities != null
540                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
541     }
542 
543     /**
544      * Test to see if running the given job on the given network is insane.
545      * <p>
546      * For example, if a job is trying to send 10MB over a 128Kbps EDGE
547      * connection, it would take 10.4 minutes, and has no chance of succeeding
548      * before the job times out, so we'd be insane to try running it.
549      */
isInsane(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)550     private boolean isInsane(JobStatus jobStatus, Network network,
551             NetworkCapabilities capabilities, Constants constants) {
552         if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
553                 && mChargingTracker.isCharging()) {
554             // We're charging and on an unmetered network. We don't have to be as conservative about
555             // making sure the job will run within its max execution time. Let's just hope the app
556             // supports interruptible work.
557             return false;
558         }
559 
560         // Use the maximum possible time since it gives us an upper bound, even though the job
561         // could end up stopping earlier.
562         final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus);
563 
564         final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
565         if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
566             final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
567             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
568             if (bandwidth > 0) {
569                 // Divide by 8 to convert bits to bytes.
570                 final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
571                         / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
572                 if (estimatedMillis > maxJobExecutionTimeMs) {
573                     // If we'd never finish before the timeout, we'd be insane!
574                     Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
575                             + " kbps network would take " + estimatedMillis + "ms and job has "
576                             + maxJobExecutionTimeMs + "ms to run; that's insane!");
577                     return true;
578                 }
579             }
580         }
581 
582         final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
583         if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
584             final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
585             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
586             if (bandwidth > 0) {
587                 // Divide by 8 to convert bits to bytes.
588                 final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
589                         / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
590                 if (estimatedMillis > maxJobExecutionTimeMs) {
591                     // If we'd never finish before the timeout, we'd be insane!
592                     Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
593                             + " kbps network would take " + estimatedMillis + "ms and job has "
594                             + maxJobExecutionTimeMs + "ms to run; that's insane!");
595                     return true;
596                 }
597             }
598         }
599 
600         return false;
601     }
602 
isCongestionDelayed(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)603     private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
604             NetworkCapabilities capabilities, Constants constants) {
605         // If network is congested, and job is less than 50% through the
606         // developer-requested window, then we're okay delaying the job.
607         if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
608             return jobStatus.getFractionRunTime() < constants.CONN_CONGESTION_DELAY_FRAC;
609         } else {
610             return false;
611         }
612     }
613 
copyCapabilities( @onNull final NetworkRequest request)614     private static NetworkCapabilities.Builder copyCapabilities(
615             @NonNull final NetworkRequest request) {
616         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
617         for (int transport : request.getTransportTypes()) builder.addTransportType(transport);
618         for (int capability : request.getCapabilities()) builder.addCapability(capability);
619         return builder;
620     }
621 
isStrictSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)622     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
623             NetworkCapabilities capabilities, Constants constants) {
624         // A restricted job that's out of quota MUST use an unmetered network.
625         if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
626                 && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
627             final NetworkCapabilities.Builder builder =
628                     copyCapabilities(jobStatus.getJob().getRequiredNetwork());
629             builder.addCapability(NET_CAPABILITY_NOT_METERED);
630             return builder.build().satisfiedByNetworkCapabilities(capabilities);
631         } else {
632             return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
633         }
634     }
635 
isRelaxedSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)636     private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
637             NetworkCapabilities capabilities, Constants constants) {
638         // Only consider doing this for unrestricted prefetching jobs
639         if (!jobStatus.getJob().isPrefetch() || jobStatus.getStandbyBucket() == RESTRICTED_INDEX) {
640             return false;
641         }
642 
643         // See if we match after relaxing any unmetered request
644         final NetworkCapabilities.Builder builder =
645                 copyCapabilities(jobStatus.getJob().getRequiredNetwork());
646         builder.removeCapability(NET_CAPABILITY_NOT_METERED);
647         if (builder.build().satisfiedByNetworkCapabilities(capabilities)) {
648             // TODO: treat this as "maybe" response; need to check quotas
649             return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
650         } else {
651             return false;
652         }
653     }
654 
655     @VisibleForTesting
isSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants)656     boolean isSatisfied(JobStatus jobStatus, Network network,
657             NetworkCapabilities capabilities, Constants constants) {
658         // Zeroth, we gotta have a network to think about being satisfied
659         if (network == null || capabilities == null) return false;
660 
661         if (!isUsable(capabilities)) return false;
662 
663         // First, are we insane?
664         if (isInsane(jobStatus, network, capabilities, constants)) return false;
665 
666         // Second, is the network congested?
667         if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
668 
669         // Third, is the network a strict match?
670         if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
671 
672         // Third, is the network a relaxed match?
673         if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
674 
675         return false;
676     }
677 
678     @GuardedBy("mLock")
maybeRegisterDefaultNetworkCallbackLocked(JobStatus jobStatus)679     private void maybeRegisterDefaultNetworkCallbackLocked(JobStatus jobStatus) {
680         final int sourceUid = jobStatus.getSourceUid();
681         if (mCurrentDefaultNetworkCallbacks.contains(sourceUid)) {
682             return;
683         }
684         final UidStats uidStats =
685                 getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
686         if (!mSortedStats.contains(uidStats)) {
687             mSortedStats.add(uidStats);
688         }
689         if (mCurrentDefaultNetworkCallbacks.size() >= MAX_NETWORK_CALLBACKS) {
690             postAdjustCallbacks();
691             return;
692         }
693         registerPendingUidCallbacksLocked();
694     }
695 
696     /**
697      * Register UID callbacks for UIDs that are next in line, based on the current order in {@link
698      * #mSortedStats}. This assumes that there are only registered callbacks for UIDs in the top
699      * {@value #MAX_NETWORK_CALLBACKS} UIDs and that the only UIDs missing callbacks are the lower
700      * priority ones.
701      */
702     @GuardedBy("mLock")
registerPendingUidCallbacksLocked()703     private void registerPendingUidCallbacksLocked() {
704         final int numCallbacks = mCurrentDefaultNetworkCallbacks.size();
705         final int numPending = mSortedStats.size();
706         if (numPending < numCallbacks) {
707             // This means there's a bug in the code >.<
708             Slog.wtf(TAG, "There are more registered callbacks than sorted UIDs: "
709                     + numCallbacks + " vs " + numPending);
710         }
711         for (int i = numCallbacks; i < numPending && i < MAX_NETWORK_CALLBACKS; ++i) {
712             UidStats uidStats = mSortedStats.get(i);
713             UidDefaultNetworkCallback callback = mDefaultNetworkCallbackPool.acquire();
714             if (callback == null) {
715                 callback = new UidDefaultNetworkCallback();
716             }
717             callback.setUid(uidStats.uid);
718             mCurrentDefaultNetworkCallbacks.append(uidStats.uid, callback);
719             mConnManager.registerDefaultNetworkCallbackForUid(uidStats.uid, callback, mHandler);
720         }
721     }
722 
postAdjustCallbacks()723     private void postAdjustCallbacks() {
724         postAdjustCallbacks(0);
725     }
726 
postAdjustCallbacks(long delayMs)727     private void postAdjustCallbacks(long delayMs) {
728         mHandler.sendEmptyMessageDelayed(MSG_ADJUST_CALLBACKS, delayMs);
729     }
730 
731     @GuardedBy("mLock")
maybeAdjustRegisteredCallbacksLocked()732     private void maybeAdjustRegisteredCallbacksLocked() {
733         mHandler.removeMessages(MSG_ADJUST_CALLBACKS);
734 
735         final int count = mUidStats.size();
736         if (count == mCurrentDefaultNetworkCallbacks.size()) {
737             // All of them are registered and there are no blocked UIDs.
738             // No point evaluating all UIDs.
739             return;
740         }
741 
742         final long nowElapsed = sElapsedRealtimeClock.millis();
743         if (nowElapsed - mLastCallbackAdjustmentTimeElapsed < MIN_ADJUST_CALLBACK_INTERVAL_MS) {
744             postAdjustCallbacks(MIN_ADJUST_CALLBACK_INTERVAL_MS);
745             return;
746         }
747 
748         mLastCallbackAdjustmentTimeElapsed = nowElapsed;
749         mSortedStats.clear();
750 
751         for (int u = 0; u < mUidStats.size(); ++u) {
752             UidStats us = mUidStats.valueAt(u);
753             ArraySet<JobStatus> jobs = mTrackedJobs.get(us.uid);
754             if (jobs == null || jobs.size() == 0) {
755                 unregisterDefaultNetworkCallbackLocked(us.uid, nowElapsed);
756                 continue;
757             }
758 
759             // We won't evaluate stats in the first 30 seconds after boot...That's probably okay.
760             if (us.lastUpdatedElapsed + MIN_STATS_UPDATE_INTERVAL_MS < nowElapsed) {
761                 us.earliestEnqueueTime = Long.MAX_VALUE;
762                 us.earliestEJEnqueueTime = Long.MAX_VALUE;
763                 us.numReadyWithConnectivity = 0;
764                 us.numRequestedNetworkAvailable = 0;
765                 us.numRegular = 0;
766                 us.numEJs = 0;
767 
768                 for (int j = 0; j < jobs.size(); ++j) {
769                     JobStatus job = jobs.valueAt(j);
770                     if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_CONNECTIVITY)) {
771                         us.numReadyWithConnectivity++;
772                         if (isNetworkAvailable(job)) {
773                             us.numRequestedNetworkAvailable++;
774                         }
775                         // Only use the enqueue time of jobs that would be ready to prevent apps
776                         // from gaming the system (eg. by scheduling a job that requires all
777                         // constraints and has a minimum latency of 6 months to always have the
778                         // earliest enqueue time).
779                         us.earliestEnqueueTime = Math.min(us.earliestEnqueueTime, job.enqueueTime);
780                         if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
781                             us.earliestEJEnqueueTime =
782                                     Math.min(us.earliestEJEnqueueTime, job.enqueueTime);
783                         }
784                     }
785                     if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
786                         us.numEJs++;
787                     } else {
788                         us.numRegular++;
789                     }
790                 }
791 
792                 us.lastUpdatedElapsed = nowElapsed;
793             }
794             mSortedStats.add(us);
795         }
796 
797         mSortedStats.sort(mUidStatsComparator);
798 
799         boolean changed = false;
800         // Iterate in reverse order to remove existing callbacks before adding new ones.
801         for (int i = mSortedStats.size() - 1; i >= 0; --i) {
802             UidStats us = mSortedStats.get(i);
803             if (i >= MAX_NETWORK_CALLBACKS) {
804                 changed |= unregisterDefaultNetworkCallbackLocked(us.uid, nowElapsed);
805             } else {
806                 UidDefaultNetworkCallback defaultNetworkCallback =
807                         mCurrentDefaultNetworkCallbacks.get(us.uid);
808                 if (defaultNetworkCallback == null) {
809                     // Not already registered.
810                     defaultNetworkCallback = mDefaultNetworkCallbackPool.acquire();
811                     if (defaultNetworkCallback == null) {
812                         defaultNetworkCallback = new UidDefaultNetworkCallback();
813                     }
814                     defaultNetworkCallback.setUid(us.uid);
815                     mCurrentDefaultNetworkCallbacks.append(us.uid, defaultNetworkCallback);
816                     mConnManager.registerDefaultNetworkCallbackForUid(
817                             us.uid, defaultNetworkCallback, mHandler);
818                 }
819             }
820         }
821         if (changed) {
822             mStateChangedListener.onControllerStateChanged();
823         }
824     }
825 
826     @GuardedBy("mLock")
unregisterDefaultNetworkCallbackLocked(int uid, long nowElapsed)827     private boolean unregisterDefaultNetworkCallbackLocked(int uid, long nowElapsed) {
828         UidDefaultNetworkCallback defaultNetworkCallback = mCurrentDefaultNetworkCallbacks.get(uid);
829         if (defaultNetworkCallback == null) {
830             return false;
831         }
832         mCurrentDefaultNetworkCallbacks.remove(uid);
833         mConnManager.unregisterNetworkCallback(defaultNetworkCallback);
834         mDefaultNetworkCallbackPool.release(defaultNetworkCallback);
835         defaultNetworkCallback.clear();
836 
837         boolean changed = false;
838         final ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
839         if (jobs != null) {
840             // Since we're unregistering the callback, we can no longer monitor
841             // changes to the app's network and so we should just mark the
842             // connectivity constraint as not satisfied.
843             for (int j = jobs.size() - 1; j >= 0; --j) {
844                 changed |= updateConstraintsSatisfied(
845                         jobs.valueAt(j), nowElapsed, null, null);
846             }
847         }
848         return changed;
849     }
850 
851     @Nullable
getNetworkCapabilities(@ullable Network network)852     private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
853         if (network == null) {
854             return null;
855         }
856         synchronized (mLock) {
857             // There is technically a race here if the Network object is reused. This can happen
858             // only if that Network disconnects and the auto-incrementing network ID in
859             // ConnectivityService wraps. This shouldn't be a concern since we only make
860             // use of asynchronous calls.
861             return mAvailableNetworks.get(network);
862         }
863     }
864 
865     @GuardedBy("mLock")
866     @Nullable
getNetworkLocked(@onNull JobStatus jobStatus)867     private Network getNetworkLocked(@NonNull JobStatus jobStatus) {
868         final UidDefaultNetworkCallback defaultNetworkCallback =
869                 mCurrentDefaultNetworkCallbacks.get(jobStatus.getSourceUid());
870         if (defaultNetworkCallback == null) {
871             return null;
872         }
873 
874         UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
875 
876         final int unbypassableBlockedReasons;
877         // TOP will probably have fewer reasons, so we may not have to worry about returning
878         // BG_BLOCKED for a TOP app. However, better safe than sorry.
879         if (uidStats.basePriority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE
880                 || (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
881             if (DEBUG) {
882                 Slog.d(TAG, "Using FG bypass for " + jobStatus.getSourceUid());
883             }
884             unbypassableBlockedReasons = UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS;
885         } else if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.startedAsExpeditedJob) {
886             if (DEBUG) {
887                 Slog.d(TAG, "Using EJ bypass for " + jobStatus.getSourceUid());
888             }
889             unbypassableBlockedReasons = UNBYPASSABLE_EJ_BLOCKED_REASONS;
890         } else {
891             if (DEBUG) {
892                 Slog.d(TAG, "Using BG bypass for " + jobStatus.getSourceUid());
893             }
894             unbypassableBlockedReasons = UNBYPASSABLE_BG_BLOCKED_REASONS;
895         }
896 
897         return (unbypassableBlockedReasons & defaultNetworkCallback.mBlockedReasons) == 0
898                 ? defaultNetworkCallback.mDefaultNetwork : null;
899     }
900 
updateConstraintsSatisfied(JobStatus jobStatus)901     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
902         final long nowElapsed = sElapsedRealtimeClock.millis();
903         final UidDefaultNetworkCallback defaultNetworkCallback =
904                 mCurrentDefaultNetworkCallbacks.get(jobStatus.getSourceUid());
905         if (defaultNetworkCallback == null) {
906             maybeRegisterDefaultNetworkCallbackLocked(jobStatus);
907             return updateConstraintsSatisfied(jobStatus, nowElapsed, null, null);
908         }
909         final Network network = getNetworkLocked(jobStatus);
910         final NetworkCapabilities capabilities = getNetworkCapabilities(network);
911         return updateConstraintsSatisfied(jobStatus, nowElapsed, network, capabilities);
912     }
913 
updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed, Network network, NetworkCapabilities capabilities)914     private boolean updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed,
915             Network network, NetworkCapabilities capabilities) {
916         // TODO: consider matching against non-default networks
917 
918         final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
919 
920         final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);
921 
922         // Pass along the evaluated network for job to use; prevents race
923         // conditions as default routes change over time, and opens the door to
924         // using non-default routes.
925         jobStatus.network = network;
926 
927         if (DEBUG) {
928             Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
929                     + " for " + jobStatus + ": usable=" + isUsable(capabilities)
930                     + " satisfied=" + satisfied);
931         }
932         return changed;
933     }
934 
935     /**
936      * Update any jobs tracked by this controller that match given filters.
937      *
938      * @param filterUid     only update jobs belonging to this UID, or {@code -1} to
939      *                      update all tracked jobs.
940      * @param filterNetwork only update jobs that would use this
941      *                      {@link Network}, or {@code null} to update all tracked jobs.
942      */
943     @GuardedBy("mLock")
updateTrackedJobsLocked(int filterUid, @Nullable Network filterNetwork)944     private void updateTrackedJobsLocked(int filterUid, @Nullable Network filterNetwork) {
945         boolean changed = false;
946         if (filterUid == -1) {
947             for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
948                 changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i), filterNetwork);
949             }
950         } else {
951             changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid), filterNetwork);
952         }
953         if (changed) {
954             mStateChangedListener.onControllerStateChanged();
955         }
956     }
957 
958     @GuardedBy("mLock")
updateTrackedJobsLocked(ArraySet<JobStatus> jobs, @Nullable Network filterNetwork)959     private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs,
960             @Nullable Network filterNetwork) {
961         if (jobs == null || jobs.size() == 0) {
962             return false;
963         }
964 
965         UidDefaultNetworkCallback defaultNetworkCallback =
966                 mCurrentDefaultNetworkCallbacks.get(jobs.valueAt(0).getSourceUid());
967         if (defaultNetworkCallback == null) {
968             // This method is only called via a network callback object. That means something
969             // changed about a general network characteristic (since we wouldn't be in this
970             // situation if called from a UID_specific callback). The general network callback
971             // will handle adjusting the per-UID callbacks, so nothing left to do here.
972             return false;
973         }
974 
975         final long nowElapsed = sElapsedRealtimeClock.millis();
976         boolean changed = false;
977         for (int i = jobs.size() - 1; i >= 0; i--) {
978             final JobStatus js = jobs.valueAt(i);
979 
980             final Network net = getNetworkLocked(js);
981             final NetworkCapabilities netCap = getNetworkCapabilities(net);
982             final boolean match = (filterNetwork == null
983                     || Objects.equals(filterNetwork, net));
984 
985             // Update either when we have a network match, or when the
986             // job hasn't yet been evaluated against the currently
987             // active network; typically when we just lost a network.
988             if (match || !Objects.equals(js.network, net)) {
989                 changed |= updateConstraintsSatisfied(js, nowElapsed, net, netCap);
990             }
991         }
992         return changed;
993     }
994 
995     /**
996      * We know the network has just come up. We want to run any jobs that are ready.
997      */
998     @Override
onNetworkActive()999     public void onNetworkActive() {
1000         synchronized (mLock) {
1001             for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
1002                 final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
1003                 for (int j = jobs.size() - 1; j >= 0; j--) {
1004                     final JobStatus js = jobs.valueAt(j);
1005                     if (js.isReady()) {
1006                         if (DEBUG) {
1007                             Slog.d(TAG, "Running " + js + " due to network activity.");
1008                         }
1009                         mStateChangedListener.onRunJobNow(js);
1010                     }
1011                 }
1012             }
1013         }
1014     }
1015 
1016     private final class ChargingTracker extends BroadcastReceiver {
1017         /**
1018          * Track whether we're "charging", where charging means that we're ready to commit to
1019          * doing work.
1020          */
1021         private boolean mCharging;
1022 
ChargingTracker()1023         ChargingTracker() {}
1024 
startTracking()1025         public void startTracking() {
1026             IntentFilter filter = new IntentFilter();
1027             filter.addAction(BatteryManager.ACTION_CHARGING);
1028             filter.addAction(BatteryManager.ACTION_DISCHARGING);
1029             mContext.registerReceiver(this, filter);
1030 
1031             // Initialise tracker state.
1032             final BatteryManagerInternal batteryManagerInternal =
1033                     LocalServices.getService(BatteryManagerInternal.class);
1034             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
1035         }
1036 
isCharging()1037         public boolean isCharging() {
1038             return mCharging;
1039         }
1040 
1041         @Override
onReceive(Context context, Intent intent)1042         public void onReceive(Context context, Intent intent) {
1043             synchronized (mLock) {
1044                 final String action = intent.getAction();
1045                 if (BatteryManager.ACTION_CHARGING.equals(action)) {
1046                     if (mCharging) {
1047                         return;
1048                     }
1049                     mCharging = true;
1050                 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
1051                     if (!mCharging) {
1052                         return;
1053                     }
1054                     mCharging = false;
1055                 }
1056                 updateTrackedJobsLocked(-1, null);
1057             }
1058         }
1059     }
1060 
1061     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
1062         @Override
1063         public void onAvailable(Network network) {
1064             if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
1065             // Documentation says not to call getNetworkCapabilities here but wait for
1066             // onCapabilitiesChanged instead.  onCapabilitiesChanged should be called immediately
1067             // after this, so no need to update mAvailableNetworks here.
1068         }
1069 
1070         @Override
1071         public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
1072             if (DEBUG) {
1073                 Slog.v(TAG, "onCapabilitiesChanged: " + network);
1074             }
1075             synchronized (mLock) {
1076                 mAvailableNetworks.put(network, capabilities);
1077                 updateTrackedJobsLocked(-1, network);
1078                 postAdjustCallbacks();
1079             }
1080         }
1081 
1082         @Override
1083         public void onLost(Network network) {
1084             if (DEBUG) {
1085                 Slog.v(TAG, "onLost: " + network);
1086             }
1087             synchronized (mLock) {
1088                 mAvailableNetworks.remove(network);
1089                 for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
1090                     UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
1091                     if (Objects.equals(callback.mDefaultNetwork, network)) {
1092                         callback.mDefaultNetwork = null;
1093                     }
1094                 }
1095                 updateTrackedJobsLocked(-1, network);
1096                 postAdjustCallbacks();
1097             }
1098         }
1099     };
1100 
1101     private class CcHandler extends Handler {
CcHandler(Looper looper)1102         CcHandler(Looper looper) {
1103             super(looper);
1104         }
1105 
1106         @Override
handleMessage(Message msg)1107         public void handleMessage(Message msg) {
1108             synchronized (mLock) {
1109                 switch (msg.what) {
1110                     case MSG_ADJUST_CALLBACKS:
1111                         synchronized (mLock) {
1112                             maybeAdjustRegisteredCallbacksLocked();
1113                         }
1114                         break;
1115                 }
1116             }
1117         }
1118     }
1119 
1120     private class UidDefaultNetworkCallback extends NetworkCallback {
1121         private int mUid;
1122         @Nullable
1123         private Network mDefaultNetwork;
1124         private int mBlockedReasons;
1125 
setUid(int uid)1126         private void setUid(int uid) {
1127             mUid = uid;
1128             mDefaultNetwork = null;
1129         }
1130 
clear()1131         private void clear() {
1132             mDefaultNetwork = null;
1133             mUid = UserHandle.USER_NULL;
1134         }
1135 
1136         @Override
onAvailable(Network network)1137         public void onAvailable(Network network) {
1138             if (DEBUG) Slog.v(TAG, "default-onAvailable(" + mUid + "): " + network);
1139         }
1140 
1141         @Override
onBlockedStatusChanged(Network network, int blockedReasons)1142         public void onBlockedStatusChanged(Network network, int blockedReasons) {
1143             if (DEBUG) {
1144                 Slog.v(TAG, "default-onBlockedStatusChanged(" + mUid + "): "
1145                         + network + " -> " + blockedReasons);
1146             }
1147             if (mUid == UserHandle.USER_NULL) {
1148                 return;
1149             }
1150             synchronized (mLock) {
1151                 mDefaultNetwork = network;
1152                 mBlockedReasons = blockedReasons;
1153                 updateTrackedJobsLocked(mUid, network);
1154             }
1155         }
1156 
1157         // Network transitions have some complicated behavior that JS doesn't handle very well.
1158         //
1159         // * If the default network changes from A to B without A disconnecting, then we'll only
1160         // get onAvailable(B) (and the subsequent onBlockedStatusChanged() call). Since we get
1161         // the onBlockedStatusChanged() call, we re-evaluate the job, but keep it running
1162         // (assuming the new network satisfies constraints). The app continues to use the old
1163         // network (if they use the network object provided through JobParameters.getNetwork())
1164         // because we don't notify them of the default network change. If the old network no
1165         // longer satisfies requested constraints, then we have a problem. Depending on the order
1166         // of calls, if the per-UID callback gets notified of the network change before the
1167         // general callback gets notified of the capabilities change, then the job's network
1168         // object will point to the new network and we won't stop the job, even though we told it
1169         // to use the old network that no longer satisfies its constraints. This is the behavior
1170         // we loosely had (ignoring race conditions between asynchronous and synchronous
1171         // connectivity calls) when we were calling the synchronous getActiveNetworkForUid() API.
1172         // However, we should fix it.
1173         // TODO: stop jobs when the existing capabilities change after default network change
1174         //
1175         // * If the default network changes from A to B because A disconnected, then we'll get
1176         // onLost(A) and then onAvailable(B). In this case, there will be a short period where JS
1177         // doesn't think there's an available network for the job, so we'll stop the job even
1178         // though onAvailable(B) will be called soon. One on hand, the app would have gotten a
1179         // network error as well because of A's disconnect, and this will allow JS to provide the
1180         // job with the new default network. On the other hand, we have to stop the job even
1181         // though it could have continued running with the new network and the job has to deal
1182         // with whatever backoff policy is set. For now, the current behavior is fine, but we may
1183         // want to see if there's a way to have a smoother transition.
1184 
1185         @Override
onLost(Network network)1186         public void onLost(Network network) {
1187             if (DEBUG) {
1188                 Slog.v(TAG, "default-onLost(" + mUid + "): " + network);
1189             }
1190             if (mUid == UserHandle.USER_NULL) {
1191                 return;
1192             }
1193             synchronized (mLock) {
1194                 if (Objects.equals(mDefaultNetwork, network)) {
1195                     mDefaultNetwork = null;
1196                     updateTrackedJobsLocked(mUid, network);
1197                     // Add a delay in case onAvailable()+onBlockedStatusChanged is called for a
1198                     // new network. If this onLost was called because the network is completely
1199                     // gone, the delay will hel make sure we don't have a short burst of adjusting
1200                     // callback calls.
1201                     postAdjustCallbacks(1000);
1202                 }
1203             }
1204         }
1205 
dumpLocked(IndentingPrintWriter pw)1206         private void dumpLocked(IndentingPrintWriter pw) {
1207             pw.print("UID: ");
1208             pw.print(mUid);
1209             pw.print("; ");
1210             if (mDefaultNetwork == null) {
1211                 pw.print("No network");
1212             } else {
1213                 pw.print("Network: ");
1214                 pw.print(mDefaultNetwork);
1215                 pw.print(" (blocked=");
1216                 pw.print(NetworkPolicyManager.blockedReasonsToString(mBlockedReasons));
1217                 pw.print(")");
1218             }
1219             pw.println();
1220         }
1221     }
1222 
1223     private static class UidStats {
1224         public final int uid;
1225         public int basePriority;
1226         public final ArraySet<JobStatus> runningJobs = new ArraySet<>();
1227         public int numReadyWithConnectivity;
1228         public int numRequestedNetworkAvailable;
1229         public int numEJs;
1230         public int numRegular;
1231         public long earliestEnqueueTime;
1232         public long earliestEJEnqueueTime;
1233         public long lastUpdatedElapsed;
1234 
UidStats(int uid)1235         private UidStats(int uid) {
1236             this.uid = uid;
1237         }
1238 
dumpLocked(IndentingPrintWriter pw, final long nowElapsed)1239         private void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
1240             pw.print("UidStats{");
1241             pw.print("uid", uid);
1242             pw.print("pri", basePriority);
1243             pw.print("#run", runningJobs.size());
1244             pw.print("#readyWithConn", numReadyWithConnectivity);
1245             pw.print("#netAvail", numRequestedNetworkAvailable);
1246             pw.print("#EJs", numEJs);
1247             pw.print("#reg", numRegular);
1248             pw.print("earliestEnqueue", earliestEnqueueTime);
1249             pw.print("earliestEJEnqueue", earliestEJEnqueueTime);
1250             pw.print("updated=");
1251             TimeUtils.formatDuration(lastUpdatedElapsed - nowElapsed, pw);
1252             pw.println("}");
1253         }
1254     }
1255 
1256     @GuardedBy("mLock")
1257     @Override
dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)1258     public void dumpControllerStateLocked(IndentingPrintWriter pw,
1259             Predicate<JobStatus> predicate) {
1260         final long nowElapsed = sElapsedRealtimeClock.millis();
1261 
1262         if (mRequestedWhitelistJobs.size() > 0) {
1263             pw.print("Requested standby exceptions:");
1264             for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
1265                 pw.print(" ");
1266                 pw.print(mRequestedWhitelistJobs.keyAt(i));
1267                 pw.print(" (");
1268                 pw.print(mRequestedWhitelistJobs.valueAt(i).size());
1269                 pw.print(" jobs)");
1270             }
1271             pw.println();
1272         }
1273         if (mAvailableNetworks.size() > 0) {
1274             pw.println("Available networks:");
1275             pw.increaseIndent();
1276             for (int i = 0; i < mAvailableNetworks.size(); i++) {
1277                 pw.print(mAvailableNetworks.keyAt(i));
1278                 pw.print(": ");
1279                 pw.println(mAvailableNetworks.valueAt(i));
1280             }
1281             pw.decreaseIndent();
1282         } else {
1283             pw.println("No available networks");
1284         }
1285         pw.println();
1286 
1287         pw.println("Current default network callbacks:");
1288         pw.increaseIndent();
1289         for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
1290             mCurrentDefaultNetworkCallbacks.valueAt(i).dumpLocked(pw);
1291         }
1292         pw.decreaseIndent();
1293         pw.println();
1294 
1295         pw.println("UID Pecking Order:");
1296         pw.increaseIndent();
1297         for (int i = 0; i < mSortedStats.size(); ++i) {
1298             pw.print(i);
1299             pw.print(": ");
1300             mSortedStats.get(i).dumpLocked(pw, nowElapsed);
1301         }
1302         pw.decreaseIndent();
1303         pw.println();
1304 
1305         for (int i = 0; i < mTrackedJobs.size(); i++) {
1306             final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
1307             for (int j = 0; j < jobs.size(); j++) {
1308                 final JobStatus js = jobs.valueAt(j);
1309                 if (!predicate.test(js)) {
1310                     continue;
1311                 }
1312                 pw.print("#");
1313                 js.printUniqueId(pw);
1314                 pw.print(" from ");
1315                 UserHandle.formatUid(pw, js.getSourceUid());
1316                 pw.print(": ");
1317                 pw.print(js.getJob().getRequiredNetwork());
1318                 pw.println();
1319             }
1320         }
1321     }
1322 
1323     @GuardedBy("mLock")
1324     @Override
dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)1325     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
1326             Predicate<JobStatus> predicate) {
1327         final long token = proto.start(fieldId);
1328         final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
1329 
1330         for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
1331             proto.write(
1332                     StateControllerProto.ConnectivityController.REQUESTED_STANDBY_EXCEPTION_UIDS,
1333                     mRequestedWhitelistJobs.keyAt(i));
1334         }
1335         for (int i = 0; i < mTrackedJobs.size(); i++) {
1336             final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
1337             for (int j = 0; j < jobs.size(); j++) {
1338                 final JobStatus js = jobs.valueAt(j);
1339                 if (!predicate.test(js)) {
1340                     continue;
1341                 }
1342                 final long jsToken = proto.start(
1343                         StateControllerProto.ConnectivityController.TRACKED_JOBS);
1344                 js.writeToShortProto(proto,
1345                         StateControllerProto.ConnectivityController.TrackedJob.INFO);
1346                 proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
1347                         js.getSourceUid());
1348                 proto.end(jsToken);
1349             }
1350         }
1351 
1352         proto.end(mToken);
1353         proto.end(token);
1354     }
1355 }
1356