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