1 /* 2 * Copyright (C) 2020 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.vcn; 18 19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 21 import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; 22 23 import static com.android.server.VcnManagementService.LOCAL_LOG; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.net.ConnectivityManager; 28 import android.net.ConnectivityManager.NetworkCallback; 29 import android.net.LinkProperties; 30 import android.net.Network; 31 import android.net.NetworkCapabilities; 32 import android.net.NetworkRequest; 33 import android.net.TelephonyNetworkSpecifier; 34 import android.net.vcn.VcnManager; 35 import android.os.Handler; 36 import android.os.HandlerExecutor; 37 import android.os.ParcelUuid; 38 import android.os.PersistableBundle; 39 import android.telephony.CarrierConfigManager; 40 import android.telephony.SubscriptionManager; 41 import android.telephony.TelephonyCallback; 42 import android.telephony.TelephonyManager; 43 import android.util.ArrayMap; 44 import android.util.Slog; 45 import android.util.SparseArray; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.annotations.VisibleForTesting.Visibility; 49 import com.android.internal.util.IndentingPrintWriter; 50 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; 51 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.Comparator; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Objects; 58 import java.util.Set; 59 import java.util.TreeSet; 60 61 /** 62 * Tracks a set of Networks underpinning a VcnGatewayConnection. 63 * 64 * <p>A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST 65 * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to 66 * be reaped. 67 * 68 * @hide 69 */ 70 public class UnderlyingNetworkTracker { 71 @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName(); 72 73 /** 74 * Minimum signal strength for a WiFi network to be eligible for switching to 75 * 76 * <p>A network that satisfies this is eligible to become the selected underlying network with 77 * no additional conditions 78 */ 79 @VisibleForTesting(visibility = Visibility.PRIVATE) 80 static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70; 81 82 /** 83 * Minimum signal strength to continue using a WiFi network 84 * 85 * <p>A network that satisfies the conditions may ONLY continue to be used if it is already 86 * selected as the underlying network. A WiFi network satisfying this condition, but NOT the 87 * prospective-network RSSI threshold CANNOT be switched to. 88 */ 89 @VisibleForTesting(visibility = Visibility.PRIVATE) 90 static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; 91 92 /** Priority for any cellular network for which the subscription is listed as opportunistic */ 93 @VisibleForTesting(visibility = Visibility.PRIVATE) 94 static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0; 95 96 /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */ 97 @VisibleForTesting(visibility = Visibility.PRIVATE) 98 static final int PRIORITY_WIFI_IN_USE = 1; 99 100 /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */ 101 @VisibleForTesting(visibility = Visibility.PRIVATE) 102 static final int PRIORITY_WIFI_PROSPECTIVE = 2; 103 104 /** Priority for any standard macro cellular network */ 105 @VisibleForTesting(visibility = Visibility.PRIVATE) 106 static final int PRIORITY_MACRO_CELLULAR = 3; 107 108 /** Priority for any other networks (including unvalidated, etc) */ 109 @VisibleForTesting(visibility = Visibility.PRIVATE) 110 static final int PRIORITY_ANY = Integer.MAX_VALUE; 111 112 private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>(); 113 114 static { PRIORITY_TO_STRING_MAP.put( PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR")115 PRIORITY_TO_STRING_MAP.put( 116 PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR"); PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE")117 PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE"); PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE")118 PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE"); PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR")119 PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR"); PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY")120 PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY"); 121 } 122 123 @NonNull private final VcnContext mVcnContext; 124 @NonNull private final ParcelUuid mSubscriptionGroup; 125 @NonNull private final UnderlyingNetworkTrackerCallback mCb; 126 @NonNull private final Dependencies mDeps; 127 @NonNull private final Handler mHandler; 128 @NonNull private final ConnectivityManager mConnectivityManager; 129 @NonNull private final TelephonyCallback mActiveDataSubIdListener = 130 new VcnActiveDataSubscriptionIdListener(); 131 132 @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>(); 133 @Nullable private NetworkCallback mWifiBringupCallback; 134 @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback; 135 @Nullable private NetworkCallback mWifiExitRssiThresholdCallback; 136 @Nullable private UnderlyingNetworkListener mRouteSelectionCallback; 137 138 @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; 139 @Nullable private PersistableBundle mCarrierConfig; 140 private boolean mIsQuitting = false; 141 142 @Nullable private UnderlyingNetworkRecord mCurrentRecord; 143 @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; 144 UnderlyingNetworkTracker( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkTrackerCallback cb)145 public UnderlyingNetworkTracker( 146 @NonNull VcnContext vcnContext, 147 @NonNull ParcelUuid subscriptionGroup, 148 @NonNull TelephonySubscriptionSnapshot snapshot, 149 @NonNull UnderlyingNetworkTrackerCallback cb) { 150 this( 151 vcnContext, 152 subscriptionGroup, 153 snapshot, 154 cb, 155 new Dependencies()); 156 } 157 UnderlyingNetworkTracker( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull UnderlyingNetworkTrackerCallback cb, @NonNull Dependencies deps)158 private UnderlyingNetworkTracker( 159 @NonNull VcnContext vcnContext, 160 @NonNull ParcelUuid subscriptionGroup, 161 @NonNull TelephonySubscriptionSnapshot snapshot, 162 @NonNull UnderlyingNetworkTrackerCallback cb, 163 @NonNull Dependencies deps) { 164 mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); 165 mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); 166 mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); 167 mCb = Objects.requireNonNull(cb, "Missing cb"); 168 mDeps = Objects.requireNonNull(deps, "Missing deps"); 169 170 mHandler = new Handler(mVcnContext.getLooper()); 171 172 mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class); 173 mVcnContext 174 .getContext() 175 .getSystemService(TelephonyManager.class) 176 .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener); 177 178 // TODO: Listen for changes in carrier config that affect this. 179 for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { 180 PersistableBundle config = 181 mVcnContext 182 .getContext() 183 .getSystemService(CarrierConfigManager.class) 184 .getConfigForSubId(subId); 185 186 if (config != null) { 187 mCarrierConfig = config; 188 189 // Attempt to use (any) non-opportunistic subscription. If this subscription is 190 // opportunistic, continue and try to find a non-opportunistic subscription, using 191 // the opportunistic ones as a last resort. 192 if (!isOpportunistic(mLastSnapshot, Collections.singleton(subId))) { 193 break; 194 } 195 } 196 } 197 198 registerOrUpdateNetworkRequests(); 199 } 200 registerOrUpdateNetworkRequests()201 private void registerOrUpdateNetworkRequests() { 202 NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback; 203 NetworkCallback oldWifiCallback = mWifiBringupCallback; 204 NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback; 205 NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback; 206 List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks); 207 mCellBringupCallbacks.clear(); 208 209 // Register new callbacks. Make-before-break; always register new callbacks before removal 210 // of old callbacks 211 if (!mIsQuitting) { 212 mRouteSelectionCallback = new UnderlyingNetworkListener(); 213 mConnectivityManager.registerNetworkCallback( 214 getRouteSelectionRequest(), mRouteSelectionCallback, mHandler); 215 216 mWifiEntryRssiThresholdCallback = new NetworkBringupCallback(); 217 mConnectivityManager.registerNetworkCallback( 218 getWifiEntryRssiThresholdNetworkRequest(), 219 mWifiEntryRssiThresholdCallback, 220 mHandler); 221 222 mWifiExitRssiThresholdCallback = new NetworkBringupCallback(); 223 mConnectivityManager.registerNetworkCallback( 224 getWifiExitRssiThresholdNetworkRequest(), 225 mWifiExitRssiThresholdCallback, 226 mHandler); 227 228 mWifiBringupCallback = new NetworkBringupCallback(); 229 mConnectivityManager.requestBackgroundNetwork( 230 getWifiNetworkRequest(), mWifiBringupCallback, mHandler); 231 232 for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { 233 final NetworkBringupCallback cb = new NetworkBringupCallback(); 234 mCellBringupCallbacks.add(cb); 235 236 mConnectivityManager.requestBackgroundNetwork( 237 getCellNetworkRequestForSubId(subId), cb, mHandler); 238 } 239 } else { 240 mRouteSelectionCallback = null; 241 mWifiBringupCallback = null; 242 mWifiEntryRssiThresholdCallback = null; 243 mWifiExitRssiThresholdCallback = null; 244 // mCellBringupCallbacks already cleared above. 245 } 246 247 // Unregister old callbacks (as necessary) 248 if (oldRouteSelectionCallback != null) { 249 mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback); 250 } 251 if (oldWifiCallback != null) { 252 mConnectivityManager.unregisterNetworkCallback(oldWifiCallback); 253 } 254 if (oldWifiEntryRssiThresholdCallback != null) { 255 mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback); 256 } 257 if (oldWifiExitRssiThresholdCallback != null) { 258 mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback); 259 } 260 for (NetworkCallback cellBringupCallback : oldCellCallbacks) { 261 mConnectivityManager.unregisterNetworkCallback(cellBringupCallback); 262 } 263 } 264 265 /** 266 * Builds the Route selection request 267 * 268 * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue 269 * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only 270 * carrier owned networks may be selected, as the request specifies only subIds in the VCN's 271 * subscription group, while the VCN networks are excluded by virtue of not having subIds set on 272 * the VCN-exposed networks. 273 * 274 * <p>If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return 275 * a NetworkRequest that only matches Test Networks. 276 */ getRouteSelectionRequest()277 private NetworkRequest getRouteSelectionRequest() { 278 if (mVcnContext.isInTestMode()) { 279 return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)); 280 } 281 282 return getBaseNetworkRequestBuilder() 283 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) 284 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) 285 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) 286 .build(); 287 } 288 289 /** 290 * Builds the WiFi bringup request 291 * 292 * <p>This request is built specifically to match only carrier-owned WiFi networks, but is also 293 * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of 294 * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this 295 * request. As such, it will bind to a Carrier WiFi Network that has already been brought up, 296 * but will NEVER bring up a Carrier WiFi network itself. 297 */ getWifiNetworkRequest()298 private NetworkRequest getWifiNetworkRequest() { 299 return getBaseNetworkRequestBuilder() 300 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 301 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) 302 .build(); 303 } 304 305 /** 306 * Builds the WiFi entry threshold signal strength request 307 * 308 * <p>This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold. 309 * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a 310 * pace to effectively select a short-lived WiFi offload network. 311 */ getWifiEntryRssiThresholdNetworkRequest()312 private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() { 313 return getBaseNetworkRequestBuilder() 314 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 315 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) 316 // Ensure wifi updates signal strengths when crossing this threshold. 317 .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig)) 318 .build(); 319 } 320 321 /** 322 * Builds the WiFi exit threshold signal strength request 323 * 324 * <p>This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold. 325 * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a 326 * pace to effectively select away from a failing WiFi network. 327 */ getWifiExitRssiThresholdNetworkRequest()328 private NetworkRequest getWifiExitRssiThresholdNetworkRequest() { 329 return getBaseNetworkRequestBuilder() 330 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 331 .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) 332 // Ensure wifi updates signal strengths when crossing this threshold. 333 .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig)) 334 .build(); 335 } 336 337 /** 338 * Builds a Cellular bringup request for a given subId 339 * 340 * <p>This request is filed in order to ensure that the Telephony stack always has a 341 * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to 342 * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony 343 * will bring up additional underlying Cellular networks. 344 * 345 * <p>Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified 346 * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier. 347 */ getCellNetworkRequestForSubId(int subId)348 private NetworkRequest getCellNetworkRequestForSubId(int subId) { 349 return getBaseNetworkRequestBuilder() 350 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 351 .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) 352 .build(); 353 } 354 355 /** 356 * Builds and returns a NetworkRequest builder common to all Underlying Network requests 357 */ getBaseNetworkRequestBuilder()358 private NetworkRequest.Builder getBaseNetworkRequestBuilder() { 359 return new NetworkRequest.Builder() 360 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 361 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) 362 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) 363 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); 364 } 365 366 /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */ getTestNetworkRequest(@onNull Set<Integer> subIds)367 private NetworkRequest getTestNetworkRequest(@NonNull Set<Integer> subIds) { 368 return new NetworkRequest.Builder() 369 .clearCapabilities() 370 .addTransportType(NetworkCapabilities.TRANSPORT_TEST) 371 .setSubscriptionIds(subIds) 372 .build(); 373 } 374 375 /** 376 * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. 377 * 378 * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to 379 * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered 380 * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change. 381 */ updateSubscriptionSnapshot(@onNull TelephonySubscriptionSnapshot newSnapshot)382 public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) { 383 Objects.requireNonNull(newSnapshot, "Missing newSnapshot"); 384 385 final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot; 386 mLastSnapshot = newSnapshot; 387 388 // Only trigger re-registration if subIds in this group have changed 389 if (oldSnapshot 390 .getAllSubIdsInGroup(mSubscriptionGroup) 391 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) { 392 return; 393 } 394 registerOrUpdateNetworkRequests(); 395 } 396 397 /** Tears down this Tracker, and releases all underlying network requests. */ teardown()398 public void teardown() { 399 mVcnContext.ensureRunningOnLooperThread(); 400 mIsQuitting = true; 401 402 // Will unregister all existing callbacks, but not register new ones due to quitting flag. 403 registerOrUpdateNetworkRequests(); 404 405 mVcnContext 406 .getContext() 407 .getSystemService(TelephonyManager.class) 408 .unregisterTelephonyCallback(mActiveDataSubIdListener); 409 } 410 reevaluateNetworks()411 private void reevaluateNetworks() { 412 if (mIsQuitting || mRouteSelectionCallback == null) { 413 return; // UnderlyingNetworkTracker has quit. 414 } 415 416 TreeSet<UnderlyingNetworkRecord> sorted = 417 mRouteSelectionCallback.getSortedUnderlyingNetworks(); 418 UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first(); 419 if (Objects.equals(mCurrentRecord, candidate)) { 420 return; 421 } 422 423 mCurrentRecord = candidate; 424 mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord); 425 } 426 isOpportunistic( @onNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds)427 private static boolean isOpportunistic( 428 @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) { 429 if (snapshot == null) { 430 logWtf("Got null snapshot"); 431 return false; 432 } 433 434 for (int subId : subIds) { 435 if (snapshot.isOpportunistic(subId)) { 436 return true; 437 } 438 } 439 440 return false; 441 } 442 443 /** 444 * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped. 445 * 446 * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being 447 * reaped, and no action is taken on any events firing. 448 */ 449 @VisibleForTesting 450 class NetworkBringupCallback extends NetworkCallback {} 451 452 /** 453 * RouteSelectionCallback is used to select the "best" underlying Network. 454 * 455 * <p>The "best" network is determined by ConnectivityService, which is treated as a source of 456 * truth. 457 */ 458 @VisibleForTesting 459 class UnderlyingNetworkListener extends NetworkCallback { 460 private final Map<Network, UnderlyingNetworkRecord.Builder> 461 mUnderlyingNetworkRecordBuilders = new ArrayMap<>(); 462 UnderlyingNetworkListener()463 UnderlyingNetworkListener() { 464 super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO); 465 } 466 getSortedUnderlyingNetworks()467 private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() { 468 TreeSet<UnderlyingNetworkRecord> sorted = 469 new TreeSet<>( 470 UnderlyingNetworkRecord.getComparator( 471 mSubscriptionGroup, 472 mLastSnapshot, 473 mCurrentRecord, 474 mCarrierConfig)); 475 476 for (UnderlyingNetworkRecord.Builder builder : 477 mUnderlyingNetworkRecordBuilders.values()) { 478 if (builder.isValid()) { 479 sorted.add(builder.build()); 480 } 481 } 482 483 return sorted; 484 } 485 486 @Override onAvailable(@onNull Network network)487 public void onAvailable(@NonNull Network network) { 488 mUnderlyingNetworkRecordBuilders.put( 489 network, new UnderlyingNetworkRecord.Builder(network)); 490 } 491 492 @Override onLost(@onNull Network network)493 public void onLost(@NonNull Network network) { 494 mUnderlyingNetworkRecordBuilders.remove(network); 495 496 reevaluateNetworks(); 497 } 498 499 @Override onCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities networkCapabilities)500 public void onCapabilitiesChanged( 501 @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { 502 final UnderlyingNetworkRecord.Builder builder = 503 mUnderlyingNetworkRecordBuilders.get(network); 504 if (builder == null) { 505 logWtf("Got capabilities change for unknown key: " + network); 506 return; 507 } 508 509 builder.setNetworkCapabilities(networkCapabilities); 510 if (builder.isValid()) { 511 reevaluateNetworks(); 512 } 513 } 514 515 @Override onLinkPropertiesChanged( @onNull Network network, @NonNull LinkProperties linkProperties)516 public void onLinkPropertiesChanged( 517 @NonNull Network network, @NonNull LinkProperties linkProperties) { 518 final UnderlyingNetworkRecord.Builder builder = 519 mUnderlyingNetworkRecordBuilders.get(network); 520 if (builder == null) { 521 logWtf("Got link properties change for unknown key: " + network); 522 return; 523 } 524 525 builder.setLinkProperties(linkProperties); 526 if (builder.isValid()) { 527 reevaluateNetworks(); 528 } 529 } 530 531 @Override onBlockedStatusChanged(@onNull Network network, boolean isBlocked)532 public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) { 533 final UnderlyingNetworkRecord.Builder builder = 534 mUnderlyingNetworkRecordBuilders.get(network); 535 if (builder == null) { 536 logWtf("Got blocked status change for unknown key: " + network); 537 return; 538 } 539 540 builder.setIsBlocked(isBlocked); 541 if (builder.isValid()) { 542 reevaluateNetworks(); 543 } 544 } 545 } 546 getWifiEntryRssiThreshold(@ullable PersistableBundle carrierConfig)547 private static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { 548 if (carrierConfig != null) { 549 return carrierConfig.getInt( 550 VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, 551 WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT); 552 } 553 554 return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; 555 } 556 getWifiExitRssiThreshold(@ullable PersistableBundle carrierConfig)557 private static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { 558 if (carrierConfig != null) { 559 return carrierConfig.getInt( 560 VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, 561 WIFI_EXIT_RSSI_THRESHOLD_DEFAULT); 562 } 563 564 return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; 565 } 566 567 /** A record of a single underlying network, caching relevant fields. */ 568 public static class UnderlyingNetworkRecord { 569 @NonNull public final Network network; 570 @NonNull public final NetworkCapabilities networkCapabilities; 571 @NonNull public final LinkProperties linkProperties; 572 public final boolean isBlocked; 573 574 @VisibleForTesting(visibility = Visibility.PRIVATE) UnderlyingNetworkRecord( @onNull Network network, @NonNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties, boolean isBlocked)575 UnderlyingNetworkRecord( 576 @NonNull Network network, 577 @NonNull NetworkCapabilities networkCapabilities, 578 @NonNull LinkProperties linkProperties, 579 boolean isBlocked) { 580 this.network = network; 581 this.networkCapabilities = networkCapabilities; 582 this.linkProperties = linkProperties; 583 this.isBlocked = isBlocked; 584 } 585 586 @Override equals(Object o)587 public boolean equals(Object o) { 588 if (this == o) return true; 589 if (!(o instanceof UnderlyingNetworkRecord)) return false; 590 final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o; 591 592 return network.equals(that.network) 593 && networkCapabilities.equals(that.networkCapabilities) 594 && linkProperties.equals(that.linkProperties) 595 && isBlocked == that.isBlocked; 596 } 597 598 @Override hashCode()599 public int hashCode() { 600 return Objects.hash(network, networkCapabilities, linkProperties, isBlocked); 601 } 602 603 /** 604 * Gives networks a priority class, based on the following priorities: 605 * 606 * <ol> 607 * <li>Opportunistic cellular 608 * <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT 609 * <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT 610 * <li>Macro cellular 611 * <li>Any others 612 * </ol> 613 */ calculatePriorityClass( ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig)614 private int calculatePriorityClass( 615 ParcelUuid subscriptionGroup, 616 TelephonySubscriptionSnapshot snapshot, 617 UnderlyingNetworkRecord currentlySelected, 618 PersistableBundle carrierConfig) { 619 final NetworkCapabilities caps = networkCapabilities; 620 621 // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED 622 623 if (isBlocked) { 624 logWtf("Network blocked for System Server: " + network); 625 return PRIORITY_ANY; 626 } 627 628 if (caps.hasTransport(TRANSPORT_CELLULAR) 629 && isOpportunistic(snapshot, caps.getSubscriptionIds())) { 630 // If this carrier is the active data provider, ensure that opportunistic is only 631 // ever prioritized if it is also the active data subscription. This ensures that 632 // if an opportunistic subscription is still in the process of being switched to, 633 // or switched away from, the VCN does not attempt to continue using it against the 634 // decision made at the telephony layer. Failure to do so may result in the modem 635 // switching back and forth. 636 // 637 // Allow the following two cases: 638 // 1. Active subId is NOT in the group that this VCN is supporting 639 // 2. This opportunistic subscription is for the active subId 640 if (!snapshot.getAllSubIdsInGroup(subscriptionGroup) 641 .contains(SubscriptionManager.getActiveDataSubscriptionId()) 642 || caps.getSubscriptionIds() 643 .contains(SubscriptionManager.getActiveDataSubscriptionId())) { 644 return PRIORITY_OPPORTUNISTIC_CELLULAR; 645 } 646 } 647 648 if (caps.hasTransport(TRANSPORT_WIFI)) { 649 if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig) 650 && currentlySelected != null 651 && network.equals(currentlySelected.network)) { 652 return PRIORITY_WIFI_IN_USE; 653 } 654 655 if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { 656 return PRIORITY_WIFI_PROSPECTIVE; 657 } 658 } 659 660 // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might 661 // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be 662 // the case if the Default Data SubId does not support certain services (eg voice 663 // calling) 664 if (caps.hasTransport(TRANSPORT_CELLULAR) 665 && !isOpportunistic(snapshot, caps.getSubscriptionIds())) { 666 return PRIORITY_MACRO_CELLULAR; 667 } 668 669 return PRIORITY_ANY; 670 } 671 getComparator( ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig)672 private static Comparator<UnderlyingNetworkRecord> getComparator( 673 ParcelUuid subscriptionGroup, 674 TelephonySubscriptionSnapshot snapshot, 675 UnderlyingNetworkRecord currentlySelected, 676 PersistableBundle carrierConfig) { 677 return (left, right) -> { 678 return Integer.compare( 679 left.calculatePriorityClass( 680 subscriptionGroup, snapshot, currentlySelected, carrierConfig), 681 right.calculatePriorityClass( 682 subscriptionGroup, snapshot, currentlySelected, carrierConfig)); 683 }; 684 } 685 686 /** Dumps the state of this record for logging and debugging purposes. */ dump( IndentingPrintWriter pw, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig)687 private void dump( 688 IndentingPrintWriter pw, 689 ParcelUuid subscriptionGroup, 690 TelephonySubscriptionSnapshot snapshot, 691 UnderlyingNetworkRecord currentlySelected, 692 PersistableBundle carrierConfig) { 693 pw.println("UnderlyingNetworkRecord:"); 694 pw.increaseIndent(); 695 696 final int priorityClass = 697 calculatePriorityClass( 698 subscriptionGroup, snapshot, currentlySelected, carrierConfig); 699 pw.println( 700 "Priority class: " + PRIORITY_TO_STRING_MAP.get(priorityClass) + " (" 701 + priorityClass + ")"); 702 pw.println("mNetwork: " + network); 703 pw.println("mNetworkCapabilities: " + networkCapabilities); 704 pw.println("mLinkProperties: " + linkProperties); 705 706 pw.decreaseIndent(); 707 } 708 709 /** Builder to incrementally construct an UnderlyingNetworkRecord. */ 710 private static class Builder { 711 @NonNull private final Network mNetwork; 712 713 @Nullable private NetworkCapabilities mNetworkCapabilities; 714 @Nullable private LinkProperties mLinkProperties; 715 boolean mIsBlocked; 716 boolean mWasIsBlockedSet; 717 718 @Nullable private UnderlyingNetworkRecord mCached; 719 Builder(@onNull Network network)720 private Builder(@NonNull Network network) { 721 mNetwork = network; 722 } 723 724 @NonNull getNetwork()725 private Network getNetwork() { 726 return mNetwork; 727 } 728 setNetworkCapabilities(@onNull NetworkCapabilities networkCapabilities)729 private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { 730 mNetworkCapabilities = networkCapabilities; 731 mCached = null; 732 } 733 734 @Nullable getNetworkCapabilities()735 private NetworkCapabilities getNetworkCapabilities() { 736 return mNetworkCapabilities; 737 } 738 setLinkProperties(@onNull LinkProperties linkProperties)739 private void setLinkProperties(@NonNull LinkProperties linkProperties) { 740 mLinkProperties = linkProperties; 741 mCached = null; 742 } 743 setIsBlocked(boolean isBlocked)744 private void setIsBlocked(boolean isBlocked) { 745 mIsBlocked = isBlocked; 746 mWasIsBlockedSet = true; 747 mCached = null; 748 } 749 isValid()750 private boolean isValid() { 751 return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet; 752 } 753 build()754 private UnderlyingNetworkRecord build() { 755 if (!isValid()) { 756 throw new IllegalArgumentException( 757 "Called build before UnderlyingNetworkRecord was valid"); 758 } 759 760 if (mCached == null) { 761 mCached = 762 new UnderlyingNetworkRecord( 763 mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked); 764 } 765 766 return mCached; 767 } 768 } 769 } 770 logWtf(String msg)771 private static void logWtf(String msg) { 772 Slog.wtf(TAG, msg); 773 LOCAL_LOG.log(TAG + " WTF: " + msg); 774 } 775 logWtf(String msg, Throwable tr)776 private static void logWtf(String msg, Throwable tr) { 777 Slog.wtf(TAG, msg, tr); 778 LOCAL_LOG.log(TAG + " WTF: " + msg + tr); 779 } 780 781 /** Dumps the state of this record for logging and debugging purposes. */ dump(IndentingPrintWriter pw)782 public void dump(IndentingPrintWriter pw) { 783 pw.println("UnderlyingNetworkTracker:"); 784 pw.increaseIndent(); 785 786 pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig)); 787 pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig)); 788 pw.println( 789 "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network)); 790 791 pw.println("Underlying networks:"); 792 pw.increaseIndent(); 793 if (mRouteSelectionCallback != null) { 794 for (UnderlyingNetworkRecord record : 795 mRouteSelectionCallback.getSortedUnderlyingNetworks()) { 796 record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig); 797 } 798 } 799 pw.decreaseIndent(); 800 pw.println(); 801 802 pw.decreaseIndent(); 803 } 804 805 private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback 806 implements ActiveDataSubscriptionIdListener { 807 @Override onActiveDataSubscriptionIdChanged(int subId)808 public void onActiveDataSubscriptionIdChanged(int subId) { 809 reevaluateNetworks(); 810 } 811 } 812 813 /** Callbacks for being notified of the changes in, or to the selected underlying network. */ 814 public interface UnderlyingNetworkTrackerCallback { 815 /** 816 * Fired when a new underlying network is selected, or properties have changed. 817 * 818 * <p>This callback does NOT signal a mobility event. 819 * 820 * @param underlyingNetworkRecord The details of the new underlying network 821 */ onSelectedUnderlyingNetworkChanged( @ullable UnderlyingNetworkRecord underlyingNetworkRecord)822 void onSelectedUnderlyingNetworkChanged( 823 @Nullable UnderlyingNetworkRecord underlyingNetworkRecord); 824 } 825 826 private static class Dependencies {} 827 } 828