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