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