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 logInfo( 345 "Config updated, restarting gateway " 346 + gatewayConnection.getLogPrefix()); 347 gatewayConnection.teardownAsynchronously(); 348 } 349 } 350 } 351 352 // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be 353 // satisfied start a new GatewayConnection) 354 mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); 355 } 356 handleTeardown()357 private void handleTeardown() { 358 logDbg("Tearing down"); 359 mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener); 360 361 for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { 362 gatewayConnection.teardownAsynchronously(); 363 } 364 365 // Unregister MobileDataStateListeners 366 for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) { 367 getTelephonyManager().unregisterTelephonyCallback(listener); 368 } 369 mMobileDataStateListeners.clear(); 370 371 mCurrentStatus = VCN_STATUS_CODE_INACTIVE; 372 } 373 handleSafeModeStatusChanged()374 private void handleSafeModeStatusChanged() { 375 logVdbg("VcnGatewayConnection safe mode status changed"); 376 boolean hasSafeModeGatewayConnection = false; 377 378 // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode 379 for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { 380 if (gatewayConnection.isInSafeMode()) { 381 hasSafeModeGatewayConnection = true; 382 break; 383 } 384 } 385 386 final int oldStatus = mCurrentStatus; 387 mCurrentStatus = 388 hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE; 389 if (oldStatus != mCurrentStatus) { 390 mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection); 391 logInfo( 392 "Safe mode " 393 + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited")); 394 } 395 } 396 handleNetworkRequested(@onNull NetworkRequest request)397 private void handleNetworkRequested(@NonNull NetworkRequest request) { 398 logVdbg("Received request " + request); 399 400 // If preexisting VcnGatewayConnection(s) satisfy request, return 401 for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) { 402 if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { 403 logVdbg("Request already satisfied by existing VcnGatewayConnection: " + request); 404 return; 405 } 406 } 407 408 // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it 409 // up 410 for (VcnGatewayConnectionConfig gatewayConnectionConfig : 411 mConfig.getGatewayConnectionConfigs()) { 412 if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) { 413 if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) { 414 // Skip; this network does not provide any services if mobile data is disabled. 415 continue; 416 } 417 418 // This should never happen, by virtue of checking for the above check for 419 // pre-existing VcnGatewayConnections that satisfy a given request, but if state 420 // that affects the satsifying of requests changes, this is theoretically possible. 421 if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) { 422 logWtf( 423 "Attempted to bring up VcnGatewayConnection for config " 424 + "with existing VcnGatewayConnection"); 425 return; 426 } 427 428 logInfo("Bringing up new VcnGatewayConnection for request " + request); 429 final VcnGatewayConnection vcnGatewayConnection = 430 mDeps.newVcnGatewayConnection( 431 mVcnContext, 432 mSubscriptionGroup, 433 mLastSnapshot, 434 gatewayConnectionConfig, 435 new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig), 436 mIsMobileDataEnabled); 437 mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); 438 439 return; 440 } 441 } 442 443 logVdbg("Request could not be fulfilled by VCN: " + request); 444 } 445 getExposedCapabilitiesForMobileDataState( VcnGatewayConnectionConfig gatewayConnectionConfig)446 private Set<Integer> getExposedCapabilitiesForMobileDataState( 447 VcnGatewayConnectionConfig gatewayConnectionConfig) { 448 if (mIsMobileDataEnabled) { 449 return gatewayConnectionConfig.getAllExposedCapabilities(); 450 } 451 452 final Set<Integer> exposedCapsWithoutMobileData = 453 new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities()); 454 exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA); 455 456 return exposedCapsWithoutMobileData; 457 } 458 handleGatewayConnectionQuit(VcnGatewayConnectionConfig config)459 private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) { 460 logInfo("VcnGatewayConnection quit: " + config); 461 mVcnGatewayConnections.remove(config); 462 463 // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied 464 // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check 465 // in handleMessage() 466 mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); 467 } 468 handleSubscriptionsChanged(@onNull TelephonySubscriptionSnapshot snapshot)469 private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { 470 mLastSnapshot = snapshot; 471 472 for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { 473 gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); 474 } 475 476 updateMobileDataStateListeners(); 477 478 // Update the mobile data state after updating the subscription snapshot as a change in 479 // subIds for a subGroup may affect the mobile data state. 480 handleMobileDataToggled(); 481 } 482 updateMobileDataStateListeners()483 private void updateMobileDataStateListeners() { 484 final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); 485 final HandlerExecutor executor = new HandlerExecutor(this); 486 487 // Register new callbacks 488 for (int subId : subIdsInGroup) { 489 if (!mMobileDataStateListeners.containsKey(subId)) { 490 final VcnUserMobileDataStateListener listener = 491 new VcnUserMobileDataStateListener(); 492 493 getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener); 494 mMobileDataStateListeners.put(subId, listener); 495 } 496 } 497 498 // Unregister old callbacks 499 Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator = 500 mMobileDataStateListeners.entrySet().iterator(); 501 while (iterator.hasNext()) { 502 final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next(); 503 if (!subIdsInGroup.contains(entry.getKey())) { 504 getTelephonyManager().unregisterTelephonyCallback(entry.getValue()); 505 iterator.remove(); 506 } 507 } 508 } 509 handleMobileDataToggled()510 private void handleMobileDataToggled() { 511 final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled; 512 mIsMobileDataEnabled = getMobileDataStatus(); 513 514 if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) { 515 // Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other 516 // services, the VcnGatewayConnections will be restarted without advertising INTERNET or 517 // DUN. 518 for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : 519 mVcnGatewayConnections.entrySet()) { 520 final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey(); 521 final VcnGatewayConnection gatewayConnection = entry.getValue(); 522 523 final Set<Integer> exposedCaps = 524 gatewayConnectionConfig.getAllExposedCapabilities(); 525 if (exposedCaps.contains(NET_CAPABILITY_INTERNET) 526 || exposedCaps.contains(NET_CAPABILITY_DUN)) { 527 if (gatewayConnection == null) { 528 logWtf("Found gatewayConnectionConfig without" + " GatewayConnection"); 529 } else { 530 // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown. 531 gatewayConnection.teardownAsynchronously(); 532 } 533 } 534 } 535 536 // Trigger re-evaluation of all requests; mobile data state impacts supported caps. 537 mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); 538 539 logInfo("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled")); 540 } 541 } 542 getMobileDataStatus()543 private boolean getMobileDataStatus() { 544 for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { 545 if (getTelephonyManagerForSubid(subId).isDataEnabled()) { 546 return true; 547 } 548 } 549 550 return false; 551 } 552 isRequestSatisfiedByGatewayConnectionConfig( @onNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config)553 private boolean isRequestSatisfiedByGatewayConnectionConfig( 554 @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { 555 final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); 556 builder.addTransportType(TRANSPORT_CELLULAR); 557 builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); 558 for (int cap : getExposedCapabilitiesForMobileDataState(config)) { 559 builder.addCapability(cap); 560 } 561 562 return request.canBeSatisfiedBy(builder.build()); 563 } 564 getTelephonyManager()565 private TelephonyManager getTelephonyManager() { 566 return mVcnContext.getContext().getSystemService(TelephonyManager.class); 567 } 568 getTelephonyManagerForSubid(int subid)569 private TelephonyManager getTelephonyManagerForSubid(int subid) { 570 return getTelephonyManager().createForSubscriptionId(subid); 571 } 572 getLogPrefix()573 private String getLogPrefix() { 574 return "(" 575 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) 576 + "-" 577 + System.identityHashCode(this) 578 + ") "; 579 } 580 logVdbg(String msg)581 private void logVdbg(String msg) { 582 if (VDBG) { 583 Slog.v(TAG, getLogPrefix() + msg); 584 } 585 } 586 logDbg(String msg)587 private void logDbg(String msg) { 588 Slog.d(TAG, getLogPrefix() + msg); 589 } 590 logDbg(String msg, Throwable tr)591 private void logDbg(String msg, Throwable tr) { 592 Slog.d(TAG, getLogPrefix() + msg, tr); 593 } 594 logInfo(String msg)595 private void logInfo(String msg) { 596 Slog.i(TAG, getLogPrefix() + msg); 597 LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg); 598 } 599 logInfo(String msg, Throwable tr)600 private void logInfo(String msg, Throwable tr) { 601 Slog.i(TAG, getLogPrefix() + msg, tr); 602 LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr); 603 } 604 logErr(String msg)605 private void logErr(String msg) { 606 Slog.e(TAG, getLogPrefix() + msg); 607 LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg); 608 } 609 logErr(String msg, Throwable tr)610 private void logErr(String msg, Throwable tr) { 611 Slog.e(TAG, getLogPrefix() + msg, tr); 612 LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr); 613 } 614 logWtf(String msg)615 private void logWtf(String msg) { 616 Slog.wtf(TAG, getLogPrefix() + msg); 617 LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg); 618 } 619 logWtf(String msg, Throwable tr)620 private void logWtf(String msg, Throwable tr) { 621 Slog.wtf(TAG, getLogPrefix() + msg, tr); 622 LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr); 623 } 624 625 /** 626 * Dumps the state of this Vcn for logging and debugging purposes. 627 * 628 * <p>PII and credentials MUST NEVER be dumped here. 629 * 630 * <p>This method is not thread safe and MUST run on the VCN thread. 631 */ dump(IndentingPrintWriter pw)632 public void dump(IndentingPrintWriter pw) { 633 mVcnContext.ensureRunningOnLooperThread(); 634 635 pw.println("Vcn (" + mSubscriptionGroup + "):"); 636 pw.increaseIndent(); 637 638 pw.println("mCurrentStatus: " + mCurrentStatus); 639 pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled); 640 pw.println(); 641 642 pw.println("mVcnGatewayConnections:"); 643 pw.increaseIndent(); 644 for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) { 645 gw.dump(pw); 646 } 647 pw.decreaseIndent(); 648 pw.println(); 649 650 pw.decreaseIndent(); 651 } 652 653 @VisibleForTesting(visibility = Visibility.PRIVATE) isMobileDataEnabled()654 public boolean isMobileDataEnabled() { 655 return mIsMobileDataEnabled; 656 } 657 658 @VisibleForTesting(visibility = Visibility.PRIVATE) setMobileDataEnabled(boolean isMobileDataEnabled)659 public void setMobileDataEnabled(boolean isMobileDataEnabled) { 660 mIsMobileDataEnabled = isMobileDataEnabled; 661 } 662 663 /** Retrieves the network score for a VCN Network */ 664 // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider getNetworkScore()665 static NetworkScore getNetworkScore() { 666 // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in 667 // NetworkOffer/NetworkAgent. 668 return new NetworkScore.Builder() 669 .setLegacyInt(VCN_LEGACY_SCORE_INT) 670 .setTransportPrimary(true) 671 .build(); 672 } 673 674 /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */ 675 @VisibleForTesting(visibility = Visibility.PACKAGE) 676 public interface VcnGatewayStatusCallback { 677 /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */ onSafeModeStatusChanged()678 void onSafeModeStatusChanged(); 679 680 /** Callback by a VcnGatewayConnection to indicate that an error occurred. */ onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)681 void onGatewayConnectionError( 682 @NonNull String gatewayConnectionName, 683 @VcnErrorCode int errorCode, 684 @Nullable String exceptionClass, 685 @Nullable String exceptionMessage); 686 687 /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */ onQuit()688 void onQuit(); 689 } 690 691 private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { 692 public final VcnGatewayConnectionConfig mGatewayConnectionConfig; 693 VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig)694 VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) { 695 mGatewayConnectionConfig = gatewayConnectionConfig; 696 } 697 698 @Override onQuit()699 public void onQuit() { 700 sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig)); 701 } 702 703 @Override onSafeModeStatusChanged()704 public void onSafeModeStatusChanged() { 705 sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED)); 706 } 707 708 @Override onGatewayConnectionError( @onNull String gatewayConnectionName, @VcnErrorCode int errorCode, @Nullable String exceptionClass, @Nullable String exceptionMessage)709 public void onGatewayConnectionError( 710 @NonNull String gatewayConnectionName, 711 @VcnErrorCode int errorCode, 712 @Nullable String exceptionClass, 713 @Nullable String exceptionMessage) { 714 mVcnCallback.onGatewayConnectionError( 715 gatewayConnectionName, errorCode, exceptionClass, exceptionMessage); 716 } 717 } 718 719 private class VcnMobileDataContentObserver extends ContentObserver { VcnMobileDataContentObserver(Handler handler)720 private VcnMobileDataContentObserver(Handler handler) { 721 super(handler); 722 } 723 724 @Override onChange(boolean selfChange)725 public void onChange(boolean selfChange) { 726 sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); 727 } 728 } 729 730 @VisibleForTesting(visibility = Visibility.PRIVATE) 731 class VcnUserMobileDataStateListener extends TelephonyCallback 732 implements TelephonyCallback.UserMobileDataStateListener { 733 734 @Override onUserMobileDataStateChanged(boolean enabled)735 public void onUserMobileDataStateChanged(boolean enabled) { 736 sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED)); 737 } 738 } 739 740 /** External dependencies used by Vcn, for injection in tests */ 741 @VisibleForTesting(visibility = Visibility.PRIVATE) 742 public static class Dependencies { 743 /** Builds a new VcnGatewayConnection */ newVcnGatewayConnection( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, VcnGatewayConnectionConfig connectionConfig, VcnGatewayStatusCallback gatewayStatusCallback, boolean isMobileDataEnabled)744 public VcnGatewayConnection newVcnGatewayConnection( 745 VcnContext vcnContext, 746 ParcelUuid subscriptionGroup, 747 TelephonySubscriptionSnapshot snapshot, 748 VcnGatewayConnectionConfig connectionConfig, 749 VcnGatewayStatusCallback gatewayStatusCallback, 750 boolean isMobileDataEnabled) { 751 return new VcnGatewayConnection( 752 vcnContext, 753 subscriptionGroup, 754 snapshot, 755 connectionConfig, 756 gatewayStatusCallback, 757 isMobileDataEnabled); 758 } 759 760 /** Builds a new VcnContentResolver instance */ newVcnContentResolver(VcnContext vcnContext)761 public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) { 762 return new VcnContentResolver(vcnContext); 763 } 764 } 765 766 /** Proxy Implementation of NetworkAgent, used for testing. */ 767 @VisibleForTesting(visibility = Visibility.PRIVATE) 768 public static class VcnContentResolver { 769 private final ContentResolver mImpl; 770 VcnContentResolver(VcnContext vcnContext)771 public VcnContentResolver(VcnContext vcnContext) { 772 mImpl = vcnContext.getContext().getContentResolver(); 773 } 774 775 /** Registers the content observer */ registerContentObserver( @onNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer)776 public void registerContentObserver( 777 @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) { 778 mImpl.registerContentObserver(uri, notifyForDescendants, observer); 779 } 780 } 781 } 782