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