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.NET_CAPABILITY_DUN; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; 22 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 23 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; 24 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE; 25 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; 26 27 import static com.android.server.VcnManagementService.LOCAL_LOG; 28 import static com.android.server.VcnManagementService.VDBG; 29 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.content.ContentResolver; 33 import android.database.ContentObserver; 34 import android.net.NetworkCapabilities; 35 import android.net.NetworkRequest; 36 import android.net.NetworkScore; 37 import android.net.Uri; 38 import android.net.vcn.VcnConfig; 39 import android.net.vcn.VcnGatewayConnectionConfig; 40 import android.net.vcn.VcnManager.VcnErrorCode; 41 import android.os.Handler; 42 import android.os.HandlerExecutor; 43 import android.os.Message; 44 import android.os.ParcelUuid; 45 import android.provider.Settings; 46 import android.telephony.TelephonyCallback; 47 import android.telephony.TelephonyManager; 48 import android.util.ArrayMap; 49 import android.util.ArraySet; 50 import android.util.Slog; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.annotations.VisibleForTesting.Visibility; 54 import com.android.internal.util.IndentingPrintWriter; 55 import com.android.server.VcnManagementService.VcnCallback; 56 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; 57 import com.android.server.vcn.util.LogUtils; 58 59 import java.util.Arrays; 60 import java.util.Collections; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.Iterator; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Map.Entry; 67 import java.util.Objects; 68 import java.util.Set; 69 70 /** 71 * Represents an single instance of a VCN. 72 * 73 * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group, 74 * including per-capability networks, network selection, and multi-homing. 75 * 76 * @hide 77 */ 78 public class Vcn extends Handler { 79 private static final String TAG = Vcn.class.getSimpleName(); 80 81 private static final int VCN_LEGACY_SCORE_INT = 52; 82 83 private static final List<Integer> CAPS_REQUIRING_MOBILE_DATA = 84 Arrays.asList(NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN); 85 86 private static final int MSG_EVENT_BASE = 0; 87 private static final int MSG_CMD_BASE = 100; 88 89 /** 90 * A carrier app updated the configuration. 91 * 92 * <p>Triggers update of config, re-evaluating all active and underlying networks. 93 * 94 * @param obj VcnConfig 95 */ 96 private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE; 97 98 /** 99 * A NetworkRequest was added or updated. 100 * 101 * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary. 102 * 103 * @param obj NetworkRequest 104 */ 105 private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1; 106 107 /** 108 * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed. 109 * 110 * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections. 111 * 112 * @param obj TelephonySubscriptionSnapshot 113 */ 114 private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2; 115 116 /** 117 * A GatewayConnection owned by this VCN quit. 118 * 119 * @param obj VcnGatewayConnectionConfig 120 */ 121 private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3; 122 123 /** 124 * Triggers reevaluation of safe mode conditions. 125 * 126 * <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically, 127 * leaving the underlying networks marked as NOT_VCN_MANAGED. 128 * 129 * <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put 130 * into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to 131 * determine if any are in safe mode. 132 */ 133 private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4; 134 135 /** 136 * Triggers reevaluation of mobile data enabled conditions. 137 * 138 * <p>Upon this notification, the VCN will check if any of the underlying subIds have mobile 139 * data enabled. If not, the VCN will restart any GatewayConnections providing INTERNET or DUN 140 * with the current mobile data toggle status. 141 */ 142 private static final int MSG_EVENT_MOBILE_DATA_TOGGLED = MSG_EVENT_BASE + 5; 143 144 /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ 145 private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; 146 147 @NonNull private final VcnContext mVcnContext; 148 @NonNull private final ParcelUuid mSubscriptionGroup; 149 @NonNull private final Dependencies mDeps; 150 @NonNull private final VcnNetworkRequestListener mRequestListener; 151 @NonNull private final VcnCallback mVcnCallback; 152 @NonNull private final VcnContentResolver mContentResolver; 153 @NonNull private final ContentObserver mMobileDataSettingsObserver; 154 155 @NonNull 156 private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners = 157 new ArrayMap<>(); 158 159 /** 160 * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. 161 * 162 * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added 163 * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives 164 * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig. 165 * 166 * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise 167 * there is potential for a orphaned VcnGatewayConnection instance that does not get properly 168 * shut down. 169 * 170 * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this 171 * map once they have finished tearing down, which is reported to this VCN via {@link 172 * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from 173 * the NetworkProvider so that another VcnGatewayConnectionConfig can match the 174 * previously-matched request. 175 */ 176 // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles 177 @NonNull 178 private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = 179 new HashMap<>(); 180 181 @NonNull private VcnConfig mConfig; 182 @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; 183 184 /** 185 * The current status of this Vcn instance 186 * 187 * <p>The value will be {@link VCN_STATUS_CODE_ACTIVE} while all VcnGatewayConnections are in 188 * good standing, {@link VCN_STATUS_CODE_SAFE_MODE} if any VcnGatewayConnections are in safe 189 * mode, and {@link VCN_STATUS_CODE_INACTIVE} once a teardown has been commanded. 190 */ 191 // Accessed from different threads, but always under lock in VcnManagementService 192 private volatile int mCurrentStatus = VCN_STATUS_CODE_ACTIVE; 193 194 private boolean mIsMobileDataEnabled = false; 195 Vcn( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnCallback vcnCallback)196 public Vcn( 197 @NonNull VcnContext vcnContext, 198 @NonNull ParcelUuid subscriptionGroup, 199 @NonNull VcnConfig config, 200 @NonNull TelephonySubscriptionSnapshot snapshot, 201 @NonNull VcnCallback vcnCallback) { 202 this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies()); 203 } 204 205 @VisibleForTesting(visibility = Visibility.PRIVATE) Vcn( @onNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnCallback vcnCallback, @NonNull Dependencies deps)206 public Vcn( 207 @NonNull VcnContext vcnContext, 208 @NonNull ParcelUuid subscriptionGroup, 209 @NonNull VcnConfig config, 210 @NonNull TelephonySubscriptionSnapshot snapshot, 211 @NonNull VcnCallback vcnCallback, 212 @NonNull Dependencies deps) { 213 super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); 214 mVcnContext = vcnContext; 215 mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); 216 mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback"); 217 mDeps = Objects.requireNonNull(deps, "Missing deps"); 218 mRequestListener = new VcnNetworkRequestListener(); 219 mContentResolver = mDeps.newVcnContentResolver(mVcnContext); 220 mMobileDataSettingsObserver = new VcnMobileDataContentObserver(this /* handler */); 221 222 final Uri uri = Settings.Global.getUriFor(Settings.Global.MOBILE_DATA); 223 mContentResolver.registerContentObserver( 224 uri, true /* notifyForDescendants */, mMobileDataSettingsObserver); 225 226 mConfig = Objects.requireNonNull(config, "Missing config"); 227 mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); 228 229 // Update mIsMobileDataEnabled before starting handling of NetworkRequests. 230 mIsMobileDataEnabled = getMobileDataStatus(); 231 232 // Register mobile data state listeners. 233 updateMobileDataStateListeners(); 234 235 // Register to receive cached and future NetworkRequests 236 mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); 237 } 238 239 /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */ updateConfig(@onNull VcnConfig config)240 public void updateConfig(@NonNull VcnConfig config) { 241 Objects.requireNonNull(config, "Missing config"); 242 243 sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config)); 244 } 245 246 /** Asynchronously updates the Subscription snapshot for this VCN. */ updateSubscriptionSnapshot(@onNull TelephonySubscriptionSnapshot snapshot)247 public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { 248 Objects.requireNonNull(snapshot, "Missing snapshot"); 249 250 sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot)); 251 } 252 253 /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */ teardownAsynchronously()254 public void teardownAsynchronously() { 255 sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); 256 } 257 258 /** Synchronously retrieves the current status code. */ getStatus()259 public int getStatus() { 260 return mCurrentStatus; 261 } 262 263 /** Sets the status of this VCN */ 264 @VisibleForTesting(visibility = Visibility.PRIVATE) setStatus(int status)265 public void setStatus(int status) { 266 mCurrentStatus = status; 267 } 268 269 /** Get current Gateways for testing purposes */ 270 @VisibleForTesting(visibility = Visibility.PRIVATE) getVcnGatewayConnections()271 public Set<VcnGatewayConnection> getVcnGatewayConnections() { 272 return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values())); 273 } 274 275 /** Get current Configs and Gateways for testing purposes */ 276 @VisibleForTesting(visibility = Visibility.PRIVATE) 277 public Map<VcnGatewayConnectionConfig, VcnGatewayConnection> getVcnGatewayConnectionConfigMap()278 getVcnGatewayConnectionConfigMap() { 279 return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections)); 280 } 281 282 private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { 283 @Override onNetworkRequested(@onNull NetworkRequest request)284 public void onNetworkRequested(@NonNull NetworkRequest request) { 285 Objects.requireNonNull(request, "Missing request"); 286 287 sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, request)); 288 } 289 } 290 291 @Override handleMessage(@onNull Message msg)292 public void handleMessage(@NonNull Message msg) { 293 if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE 294 && mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) { 295 return; 296 } 297 298 switch (msg.what) { 299 case MSG_EVENT_CONFIG_UPDATED: 300 handleConfigUpdated((VcnConfig) msg.obj); 301 break; 302 case MSG_EVENT_NETWORK_REQUESTED: 303 handleNetworkRequested((NetworkRequest) msg.obj); 304 break; 305 case MSG_EVENT_SUBSCRIPTIONS_CHANGED: 306 handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj); 307 break; 308 case MSG_EVENT_GATEWAY_CONNECTION_QUIT: 309 handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj); 310 break; 311 case MSG_EVENT_SAFE_MODE_STATE_CHANGED: 312 handleSafeModeStatusChanged(); 313 break; 314 case MSG_EVENT_MOBILE_DATA_TOGGLED: 315 handleMobileDataToggled(); 316 break; 317 case MSG_CMD_TEARDOWN: 318 handleTeardown(); 319 break; 320 default: 321 logWtf("Unknown msg.what: " + msg.what); 322 } 323 } 324 handleConfigUpdated(@onNull VcnConfig config)325 private void handleConfigUpdated(@NonNull VcnConfig config) { 326 // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode() 327 logDbg("Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode()); 328 329 mConfig = config; 330 331 // Teardown any GatewayConnections whose configs have been removed and get all current 332 // requests 333 for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : 334 mVcnGatewayConnections.entrySet()) { 335 final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey(); 336 final VcnGatewayConnection gatewayConnection = entry.getValue(); 337 338 // GatewayConnectionConfigs must match exactly (otherwise authentication or 339 // connection details may have changed). 340 if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) { 341 if (gatewayConnection == null) { 342 logWtf("Found gatewayConnectionConfig without GatewayConnection"); 343 } else { 344 gatewayConnection.teardownAsynchronously(); 345 } 346 } 347 } 348 349 // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be 350 // satisfied start a new GatewayConnection) 351 mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); 352 } 353 handleTeardown()354 private void handleTeardown() { 355 logDbg("Tearing down"); 356 mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener); 357 358 for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { 359 gatewayConnection.teardownAsynchronously(); 360 } 361 362 // Unregister MobileDataStateListeners 363 for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) { 364 getTelephonyManager().unregisterTelephonyCallback(listener); 365 } 366 mMobileDataStateListeners.clear(); 367 368 mCurrentStatus = VCN_STATUS_CODE_INACTIVE; 369 } 370 handleSafeModeStatusChanged()371 private void handleSafeModeStatusChanged() { 372 logVdbg("VcnGatewayConnection safe mode status changed"); 373 boolean hasSafeModeGatewayConnection = false; 374 375 // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode 376 for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { 377 if (gatewayConnection.isInSafeMode()) { 378 hasSafeModeGatewayConnection = true; 379 break; 380 } 381 } 382 383 final int oldStatus = mCurrentStatus; 384 mCurrentStatus = 385 hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE; 386 if (oldStatus != mCurrentStatus) { 387 mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection); 388 logInfo( 389 "Safe mode " 390 + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited")); 391 } 392 } 393 handleNetworkRequested(@onNull NetworkRequest request)394 private void handleNetworkRequested(@NonNull NetworkRequest request) { 395 logVdbg("Received request " + request); 396 397 // If preexisting VcnGatewayConnection(s) satisfy request, return 398 for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { 399 if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { 400 logDbg("Request already satisfied by existing VcnGatewayConnection: " + request); 401 return; 402 } 403 } 404 405 // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it 406 // up 407 for (VcnGatewayConnectionConfig gatewayConnectionConfig : 408 mConfig.getGatewayConnectionConfigs()) { 409 if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { 410 logDbg("Bringing up new VcnGatewayConnection for request " + request); 411 412 if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) { 413 // Skip; this network does not provide any services if mobile data is disabled. 414 continue; 415 } 416 417 // This should never happen, by virtue of checking for the above check for 418 // pre-existing VcnGatewayConnections that satisfy a given request, but if state 419 // that affects the satsifying of requests changes, this is theoretically possible. 420 if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) { 421 logWtf( 422 "Attempted to bring up VcnGatewayConnection for config " 423 + "with existing VcnGatewayConnection"); 424 return; 425 } 426 427 final VcnGatewayConnection vcnGatewayConnection = 428 mDeps.newVcnGatewayConnection( 429 mVcnContext, 430 mSubscriptionGroup, 431 mLastSnapshot, 432 gatewayConnectionConfig, 433 new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig), 434 mIsMobileDataEnabled); 435 mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); 436 437 return; 438 } 439 } 440 441 logVdbg("Request could not be fulfilled by VCN: " + request); 442 } 443 getExposedCapabilitiesForMobileDataState( VcnGatewayConnectionConfig gatewayConnectionConfig)444 private Set<Integer> getExposedCapabilitiesForMobileDataState( 445 VcnGatewayConnectionConfig gatewayConnectionConfig) { 446 if (mIsMobileDataEnabled) { 447 return gatewayConnectionConfig.getAllExposedCapabilities(); 448 } 449 450 final Set<Integer> exposedCapsWithoutMobileData = 451 new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities()); 452 exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA); 453 454 return exposedCapsWithoutMobileData; 455 } 456 handleGatewayConnectionQuit(VcnGatewayConnectionConfig config)457 private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) { 458 logDbg("VcnGatewayConnection quit: " + config); 459 mVcnGatewayConnections.remove(config); 460 461 // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied 462 // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check 463 // in handleMessage() 464 mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); 465 } 466 handleSubscriptionsChanged(@onNull TelephonySubscriptionSnapshot snapshot)467 private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { 468 mLastSnapshot = snapshot; 469 470 for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { 471 gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); 472 } 473 474 updateMobileDataStateListeners(); 475 476 // Update the mobile data state after updating the subscription snapshot as a change in 477 // subIds for a subGroup may affect the mobile data state. 478 handleMobileDataToggled(); 479 } 480 updateMobileDataStateListeners()481 private void updateMobileDataStateListeners() { 482 final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); 483 final HandlerExecutor executor = new HandlerExecutor(this); 484 485 // Register new callbacks 486 for (int subId : subIdsInGroup) { 487 if (!mMobileDataStateListeners.containsKey(subId)) { 488 final VcnUserMobileDataStateListener listener = 489 new VcnUserMobileDataStateListener(); 490 491 getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener); 492 mMobileDataStateListeners.put(subId, listener); 493 } 494 } 495 496 // Unregister old callbacks 497 Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator = 498 mMobileDataStateListeners.entrySet().iterator(); 499 while (iterator.hasNext()) { 500 final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next(); 501 if (!subIdsInGroup.contains(entry.getKey())) { 502 getTelephonyManager().unregisterTelephonyCallback(entry.getValue()); 503 iterator.remove(); 504 } 505 } 506 } 507 handleMobileDataToggled()508 private void handleMobileDataToggled() { 509 final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled; 510 mIsMobileDataEnabled = getMobileDataStatus(); 511 512 if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) { 513 // Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other 514 // services, the VcnGatewayConnections will be restarted without advertising INTERNET or 515 // DUN. 516 for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : 517 mVcnGatewayConnections.entrySet()) { 518 final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey(); 519 final VcnGatewayConnection gatewayConnection = entry.getValue(); 520 521 final Set<Integer> exposedCaps = 522 gatewayConnectionConfig.getAllExposedCapabilities(); 523 if (exposedCaps.contains(NET_CAPABILITY_INTERNET) 524 || exposedCaps.contains(NET_CAPABILITY_DUN)) { 525 if (gatewayConnection == null) { 526 logWtf("Found gatewayConnectionConfig without" + " GatewayConnection"); 527 } else { 528 // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown. 529 gatewayConnection.teardownAsynchronously(); 530 } 531 } 532 } 533 534 // Trigger re-evaluation of all requests; mobile data state impacts supported caps. 535 mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); 536 537 logDbg("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled")); 538 } 539 } 540 getMobileDataStatus()541 private boolean getMobileDataStatus() { 542 for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { 543 if (getTelephonyManagerForSubid(subId).isDataEnabled()) { 544 return true; 545 } 546 } 547 548 return false; 549 } 550 isRequestSatisfiedByGatewayConnectionConfig( @onNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config)551 private boolean isRequestSatisfiedByGatewayConnectionConfig( 552 @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { 553 final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); 554 builder.addTransportType(TRANSPORT_CELLULAR); 555 builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); 556 for (int cap : getExposedCapabilitiesForMobileDataState(config)) { 557 builder.addCapability(cap); 558 } 559 560 return request.canBeSatisfiedBy(builder.build()); 561 } 562 getTelephonyManager()563 private TelephonyManager getTelephonyManager() { 564 return mVcnContext.getContext().getSystemService(TelephonyManager.class); 565 } 566 getTelephonyManagerForSubid(int subid)567 private TelephonyManager getTelephonyManagerForSubid(int subid) { 568 return getTelephonyManager().createForSubscriptionId(subid); 569 } 570 getLogPrefix()571 private String getLogPrefix() { 572 return "[" 573 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) 574 + "-" 575 + System.identityHashCode(this) 576 + "] "; 577 } 578 logVdbg(String msg)579 private void logVdbg(String msg) { 580 if (VDBG) { 581 Slog.v(TAG, getLogPrefix() + msg); 582 } 583 } 584 logDbg(String msg)585 private void logDbg(String msg) { 586 Slog.d(TAG, getLogPrefix() + msg); 587 } 588 logDbg(String msg, Throwable tr)589 private void logDbg(String msg, Throwable tr) { 590 Slog.d(TAG, getLogPrefix() + msg, tr); 591 } 592 logInfo(String msg)593 private void logInfo(String msg) { 594 Slog.i(TAG, getLogPrefix() + msg); 595 LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg); 596 } 597 logInfo(String msg, Throwable tr)598 private void logInfo(String msg, Throwable tr) { 599 Slog.i(TAG, getLogPrefix() + msg, tr); 600 LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr); 601 } 602 logErr(String msg)603 private void logErr(String msg) { 604 Slog.e(TAG, getLogPrefix() + msg); 605 LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg); 606 } 607 logErr(String msg, Throwable tr)608 private void logErr(String msg, Throwable tr) { 609 Slog.e(TAG, getLogPrefix() + msg, tr); 610 LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr); 611 } 612 logWtf(String msg)613 private void logWtf(String msg) { 614 Slog.wtf(TAG, getLogPrefix() + msg); 615 LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg); 616 } 617 logWtf(String msg, Throwable tr)618 private void logWtf(String msg, Throwable tr) { 619 Slog.wtf(TAG, getLogPrefix() + msg, tr); 620 LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr); 621 } 622 623 /** 624 * Dumps the state of this Vcn for logging and debugging purposes. 625 * 626 * <p>PII and credentials MUST NEVER be dumped here. 627 */ dump(IndentingPrintWriter pw)628 public void dump(IndentingPrintWriter pw) { 629 pw.println("Vcn (" + mSubscriptionGroup + "):"); 630 pw.increaseIndent(); 631 632 pw.println("mCurrentStatus: " + mCurrentStatus); 633 pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled); 634 pw.println(); 635 636 pw.println("mVcnGatewayConnections:"); 637 pw.increaseIndent(); 638 for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) { 639 gw.dump(pw); 640 } 641 pw.decreaseIndent(); 642 pw.println(); 643 644 pw.decreaseIndent(); 645 } 646 647 @VisibleForTesting(visibility = Visibility.PRIVATE) isMobileDataEnabled()648 public boolean isMobileDataEnabled() { 649 return mIsMobileDataEnabled; 650 } 651 652 @VisibleForTesting(visibility = Visibility.PRIVATE) setMobileDataEnabled(boolean isMobileDataEnabled)653 public void setMobileDataEnabled(boolean isMobileDataEnabled) { 654 mIsMobileDataEnabled = isMobileDataEnabled; 655 } 656 657 /** Retrieves the network score for a VCN Network */ 658 // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider getNetworkScore()659 static NetworkScore getNetworkScore() { 660 // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in 661 // NetworkOffer/NetworkAgent. 662 return new NetworkScore.Builder() 663 .setLegacyInt(VCN_LEGACY_SCORE_INT) 664 .setTransportPrimary(true) 665 .build(); 666 } 667 668 /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */ 669 @VisibleForTesting(visibility = Visibility.PACKAGE) 670 public interface VcnGatewayStatusCallback { 671 /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */ onSafeModeStatusChanged()672 void onSafeModeStatusChanged(); 673 674 /** Callback by a VcnGatewayConnection to indicate that an error occurred. */ onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)675 void onGatewayConnectionError( 676 @NonNull String gatewayConnectionName, 677 @VcnErrorCode int errorCode, 678 @Nullable String exceptionClass, 679 @Nullable String exceptionMessage); 680 681 /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */ onQuit()682 void onQuit(); 683 } 684 685 private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { 686 public final VcnGatewayConnectionConfig mGatewayConnectionConfig; 687 VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig)688 VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) { 689 mGatewayConnectionConfig = gatewayConnectionConfig; 690 } 691 692 @Override onQuit()693 public void onQuit() { 694 sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig)); 695 } 696 697 @Override onSafeModeStatusChanged()698 public void onSafeModeStatusChanged() { 699 sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED)); 700 } 701 702 @Override onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)703 public void onGatewayConnectionError( 704 @NonNull String gatewayConnectionName, 705 @VcnErrorCode int errorCode, 706 @Nullable String exceptionClass, 707 @Nullable String exceptionMessage) { 708 mVcnCallback.onGatewayConnectionError( 709 gatewayConnectionName, errorCode, exceptionClass, exceptionMessage); 710 } 711 } 712 713 private class VcnMobileDataContentObserver extends ContentObserver { VcnMobileDataContentObserver(Handler handler)714 private VcnMobileDataContentObserver(Handler handler) { 715 super(handler); 716 } 717 718 @Override onChange(boolean selfChange)719 public void onChange(boolean selfChange) { 720 sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); 721 } 722 } 723 724 @VisibleForTesting(visibility = Visibility.PRIVATE) 725 class VcnUserMobileDataStateListener extends TelephonyCallback 726 implements TelephonyCallback.UserMobileDataStateListener { 727 728 @Override onUserMobileDataStateChanged(boolean enabled)729 public void onUserMobileDataStateChanged(boolean enabled) { 730 sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); 731 } 732 } 733 734 /** External dependencies used by Vcn, for injection in tests */ 735 @VisibleForTesting(visibility = Visibility.PRIVATE) 736 public static class Dependencies { 737 /** Builds a new VcnGatewayConnection */ newVcnGatewayConnection( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, VcnGatewayConnectionConfig connectionConfig, VcnGatewayStatusCallback gatewayStatusCallback, boolean isMobileDataEnabled)738 public VcnGatewayConnection newVcnGatewayConnection( 739 VcnContext vcnContext, 740 ParcelUuid subscriptionGroup, 741 TelephonySubscriptionSnapshot snapshot, 742 VcnGatewayConnectionConfig connectionConfig, 743 VcnGatewayStatusCallback gatewayStatusCallback, 744 boolean isMobileDataEnabled) { 745 return new VcnGatewayConnection( 746 vcnContext, 747 subscriptionGroup, 748 snapshot, 749 connectionConfig, 750 gatewayStatusCallback, 751 isMobileDataEnabled); 752 } 753 754 /** Builds a new VcnContentResolver instance */ newVcnContentResolver(VcnContext vcnContext)755 public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) { 756 return new VcnContentResolver(vcnContext); 757 } 758 } 759 760 /** Proxy Implementation of NetworkAgent, used for testing. */ 761 @VisibleForTesting(visibility = Visibility.PRIVATE) 762 public static class VcnContentResolver { 763 private final ContentResolver mImpl; 764 VcnContentResolver(VcnContext vcnContext)765 public VcnContentResolver(VcnContext vcnContext) { 766 mImpl = vcnContext.getContext().getContentResolver(); 767 } 768 769 /** Registers the content observer */ registerContentObserver( @onNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer)770 public void registerContentObserver( 771 @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) { 772 mImpl.registerContentObserver(uri, notifyForDescendants, observer); 773 } 774 } 775 } 776