blob: 5a5f7ef7b225ec669e1cf571da3584e89492ea61 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vcn;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
import static com.android.server.VcnManagementService.LOCAL_LOG;
import static com.android.server.VcnManagementService.VDBG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.NetworkScore;
import android.net.Uri;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.util.LogUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
/**
* Represents an single instance of a VCN.
*
* <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
* including per-capability networks, network selection, and multi-homing.
*
* @hide
*/
public class Vcn extends Handler {
private static final String TAG = Vcn.class.getSimpleName();
private static final int VCN_LEGACY_SCORE_INT = 52;
private static final List<Integer> CAPS_REQUIRING_MOBILE_DATA =
Arrays.asList(NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN);
private static final int MSG_EVENT_BASE = 0;
private static final int MSG_CMD_BASE = 100;
/**
* A carrier app updated the configuration.
*
* <p>Triggers update of config, re-evaluating all active and underlying networks.
*
* @param obj VcnConfig
*/
private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;
/**
* A NetworkRequest was added or updated.
*
* <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
*
* @param obj NetworkRequest
*/
private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
/**
* The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed.
*
* <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections.
*
* @param obj TelephonySubscriptionSnapshot
*/
private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
/**
* A GatewayConnection owned by this VCN quit.
*
* @param obj VcnGatewayConnectionConfig
*/
private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
/**
* Triggers reevaluation of safe mode conditions.
*
* <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically,
* leaving the underlying networks marked as NOT_VCN_MANAGED.
*
* <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put
* into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to
* determine if any are in safe mode.
*/
private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4;
/**
* Triggers reevaluation of mobile data enabled conditions.
*
* <p>Upon this notification, the VCN will check if any of the underlying subIds have mobile
* data enabled. If not, the VCN will restart any GatewayConnections providing INTERNET or DUN
* with the current mobile data toggle status.
*/
private static final int MSG_EVENT_MOBILE_DATA_TOGGLED = MSG_EVENT_BASE + 5;
/** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
@NonNull private final VcnContext mVcnContext;
@NonNull private final ParcelUuid mSubscriptionGroup;
@NonNull private final Dependencies mDeps;
@NonNull private final VcnNetworkRequestListener mRequestListener;
@NonNull private final VcnCallback mVcnCallback;
@NonNull private final VcnContentResolver mContentResolver;
@NonNull private final ContentObserver mMobileDataSettingsObserver;
@NonNull
private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
new ArrayMap<>();
/**
* Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
*
* <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
* to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
* a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
*
* <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
* there is potential for a orphaned VcnGatewayConnection instance that does not get properly
* shut down.
*
* <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
* map once they have finished tearing down, which is reported to this VCN via {@link
* VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
* the NetworkProvider so that another VcnGatewayConnectionConfig can match the
* previously-matched request.
*/
// TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
@NonNull
private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
new HashMap<>();
@NonNull private VcnConfig mConfig;
@NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
/**
* The current status of this Vcn instance
*
* <p>The value will be {@link VCN_STATUS_CODE_ACTIVE} while all VcnGatewayConnections are in
* good standing, {@link VCN_STATUS_CODE_SAFE_MODE} if any VcnGatewayConnections are in safe
* mode, and {@link VCN_STATUS_CODE_INACTIVE} once a teardown has been commanded.
*/
// Accessed from different threads, but always under lock in VcnManagementService
private volatile int mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
private boolean mIsMobileDataEnabled = false;
public Vcn(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
@NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull VcnCallback vcnCallback) {
this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Vcn(
@NonNull VcnContext vcnContext,
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
@NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull VcnCallback vcnCallback,
@NonNull Dependencies deps) {
super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
mVcnContext = vcnContext;
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
mDeps = Objects.requireNonNull(deps, "Missing deps");
mRequestListener = new VcnNetworkRequestListener();
mContentResolver = mDeps.newVcnContentResolver(mVcnContext);
mMobileDataSettingsObserver = new VcnMobileDataContentObserver(this /* handler */);
final Uri uri = Settings.Global.getUriFor(Settings.Global.MOBILE_DATA);
mContentResolver.registerContentObserver(
uri, true /* notifyForDescendants */, mMobileDataSettingsObserver);
mConfig = Objects.requireNonNull(config, "Missing config");
mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
// Update mIsMobileDataEnabled before starting handling of NetworkRequests.
mIsMobileDataEnabled = getMobileDataStatus();
// Register mobile data state listeners.
updateMobileDataStateListeners();
// Register to receive cached and future NetworkRequests
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
}
/** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
public void updateConfig(@NonNull VcnConfig config) {
Objects.requireNonNull(config, "Missing config");
sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
}
/** Asynchronously updates the Subscription snapshot for this VCN. */
public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
Objects.requireNonNull(snapshot, "Missing snapshot");
sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot));
}
/** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
public void teardownAsynchronously() {
sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
}
/** Synchronously retrieves the current status code. */
public int getStatus() {
return mCurrentStatus;
}
/** Sets the status of this VCN */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public void setStatus(int status) {
mCurrentStatus = status;
}
/** Get current Gateways for testing purposes */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Set<VcnGatewayConnection> getVcnGatewayConnections() {
return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
}
/** Get current Configs and Gateways for testing purposes */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
getVcnGatewayConnectionConfigMap() {
return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
}
private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
@Override
public void onNetworkRequested(@NonNull NetworkRequest request) {
Objects.requireNonNull(request, "Missing request");
sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, request));
}
}
@Override
public void handleMessage(@NonNull Message msg) {
if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
&& mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
return;
}
switch (msg.what) {
case MSG_EVENT_CONFIG_UPDATED:
handleConfigUpdated((VcnConfig) msg.obj);
break;
case MSG_EVENT_NETWORK_REQUESTED:
handleNetworkRequested((NetworkRequest) msg.obj);
break;
case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
break;
case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
break;
case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
handleSafeModeStatusChanged();
break;
case MSG_EVENT_MOBILE_DATA_TOGGLED:
handleMobileDataToggled();
break;
case MSG_CMD_TEARDOWN:
handleTeardown();
break;
default:
logWtf("Unknown msg.what: " + msg.what);
}
}
private void handleConfigUpdated(@NonNull VcnConfig config) {
// TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
logDbg("Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode());
mConfig = config;
// Teardown any GatewayConnections whose configs have been removed and get all current
// requests
for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
mVcnGatewayConnections.entrySet()) {
final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
final VcnGatewayConnection gatewayConnection = entry.getValue();
// GatewayConnectionConfigs must match exactly (otherwise authentication or
// connection details may have changed).
if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
if (gatewayConnection == null) {
logWtf("Found gatewayConnectionConfig without GatewayConnection");
} else {
logInfo(
"Config updated, restarting gateway "
+ gatewayConnection.getLogPrefix());
gatewayConnection.teardownAsynchronously();
}
}
}
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
// satisfied start a new GatewayConnection)
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
}
private void handleTeardown() {
logDbg("Tearing down");
mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
gatewayConnection.teardownAsynchronously();
}
// Unregister MobileDataStateListeners
for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
getTelephonyManager().unregisterTelephonyCallback(listener);
}
mMobileDataStateListeners.clear();
mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
}
private void handleSafeModeStatusChanged() {
logVdbg("VcnGatewayConnection safe mode status changed");
boolean hasSafeModeGatewayConnection = false;
// If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
if (gatewayConnection.isInSafeMode()) {
hasSafeModeGatewayConnection = true;
break;
}
}
final int oldStatus = mCurrentStatus;
mCurrentStatus =
hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
if (oldStatus != mCurrentStatus) {
mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
logInfo(
"Safe mode "
+ (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
}
}
private void handleNetworkRequested(@NonNull NetworkRequest request) {
logVdbg("Received request " + request);
// If preexisting VcnGatewayConnection(s) satisfy request, return
for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
logVdbg("Request already satisfied by existing VcnGatewayConnection: " + request);
return;
}
}
// If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
// up
for (VcnGatewayConnectionConfig gatewayConnectionConfig :
mConfig.getGatewayConnectionConfigs()) {
if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
// Skip; this network does not provide any services if mobile data is disabled.
continue;
}
// This should never happen, by virtue of checking for the above check for
// pre-existing VcnGatewayConnections that satisfy a given request, but if state
// that affects the satsifying of requests changes, this is theoretically possible.
if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) {
logWtf(
"Attempted to bring up VcnGatewayConnection for config "
+ "with existing VcnGatewayConnection");
return;
}
logInfo("Bringing up new VcnGatewayConnection for request " + request);
final VcnGatewayConnection vcnGatewayConnection =
mDeps.newVcnGatewayConnection(
mVcnContext,
mSubscriptionGroup,
mLastSnapshot,
gatewayConnectionConfig,
new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig),
mIsMobileDataEnabled);
mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
return;
}
}
logVdbg("Request could not be fulfilled by VCN: " + request);
}
private Set<Integer> getExposedCapabilitiesForMobileDataState(
VcnGatewayConnectionConfig gatewayConnectionConfig) {
if (mIsMobileDataEnabled) {
return gatewayConnectionConfig.getAllExposedCapabilities();
}
final Set<Integer> exposedCapsWithoutMobileData =
new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities());
exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA);
return exposedCapsWithoutMobileData;
}
private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
logInfo("VcnGatewayConnection quit: " + config);
mVcnGatewayConnections.remove(config);
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
// start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
// in handleMessage()
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
}
private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
mLastSnapshot = snapshot;
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
}
updateMobileDataStateListeners();
// Update the mobile data state after updating the subscription snapshot as a change in
// subIds for a subGroup may affect the mobile data state.
handleMobileDataToggled();
}
private void updateMobileDataStateListeners() {
final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
final HandlerExecutor executor = new HandlerExecutor(this);
// Register new callbacks
for (int subId : subIdsInGroup) {
if (!mMobileDataStateListeners.containsKey(subId)) {
final VcnUserMobileDataStateListener listener =
new VcnUserMobileDataStateListener();
getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
mMobileDataStateListeners.put(subId, listener);
}
}
// Unregister old callbacks
Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
mMobileDataStateListeners.entrySet().iterator();
while (iterator.hasNext()) {
final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
if (!subIdsInGroup.contains(entry.getKey())) {
getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
iterator.remove();
}
}
}
private void handleMobileDataToggled() {
final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
mIsMobileDataEnabled = getMobileDataStatus();
if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) {
// Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other
// services, the VcnGatewayConnections will be restarted without advertising INTERNET or
// DUN.
for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
mVcnGatewayConnections.entrySet()) {
final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
final VcnGatewayConnection gatewayConnection = entry.getValue();
final Set<Integer> exposedCaps =
gatewayConnectionConfig.getAllExposedCapabilities();
if (exposedCaps.contains(NET_CAPABILITY_INTERNET)
|| exposedCaps.contains(NET_CAPABILITY_DUN)) {
if (gatewayConnection == null) {
logWtf("Found gatewayConnectionConfig without" + " GatewayConnection");
} else {
// TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
gatewayConnection.teardownAsynchronously();
}
}
}
// Trigger re-evaluation of all requests; mobile data state impacts supported caps.
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
logInfo("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
}
}
private boolean getMobileDataStatus() {
for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
return true;
}
}
return false;
}
private boolean isRequestSatisfiedByGatewayConnectionConfig(
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
builder.addTransportType(TRANSPORT_CELLULAR);
builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
for (int cap : getExposedCapabilitiesForMobileDataState(config)) {
builder.addCapability(cap);
}
return request.canBeSatisfiedBy(builder.build());
}
private TelephonyManager getTelephonyManager() {
return mVcnContext.getContext().getSystemService(TelephonyManager.class);
}
private TelephonyManager getTelephonyManagerForSubid(int subid) {
return getTelephonyManager().createForSubscriptionId(subid);
}
private String getLogPrefix() {
return "("
+ LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
+ "-"
+ System.identityHashCode(this)
+ ") ";
}
private void logVdbg(String msg) {
if (VDBG) {
Slog.v(TAG, getLogPrefix() + msg);
}
}
private void logDbg(String msg) {
Slog.d(TAG, getLogPrefix() + msg);
}
private void logDbg(String msg, Throwable tr) {
Slog.d(TAG, getLogPrefix() + msg, tr);
}
private void logInfo(String msg) {
Slog.i(TAG, getLogPrefix() + msg);
LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
}
private void logInfo(String msg, Throwable tr) {
Slog.i(TAG, getLogPrefix() + msg, tr);
LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
}
private void logErr(String msg) {
Slog.e(TAG, getLogPrefix() + msg);
LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
}
private void logErr(String msg, Throwable tr) {
Slog.e(TAG, getLogPrefix() + msg, tr);
LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr);
}
private void logWtf(String msg) {
Slog.wtf(TAG, getLogPrefix() + msg);
LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg);
}
private void logWtf(String msg, Throwable tr) {
Slog.wtf(TAG, getLogPrefix() + msg, tr);
LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr);
}
/**
* Dumps the state of this Vcn for logging and debugging purposes.
*
* <p>PII and credentials MUST NEVER be dumped here.
*
* <p>This method is not thread safe and MUST run on the VCN thread.
*/
public void dump(IndentingPrintWriter pw) {
mVcnContext.ensureRunningOnLooperThread();
pw.println("Vcn (" + mSubscriptionGroup + "):");
pw.increaseIndent();
pw.println("mCurrentStatus: " + mCurrentStatus);
pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled);
pw.println();
pw.println("mVcnGatewayConnections:");
pw.increaseIndent();
for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) {
gw.dump(pw);
}
pw.decreaseIndent();
pw.println();
pw.decreaseIndent();
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
public boolean isMobileDataEnabled() {
return mIsMobileDataEnabled;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
public void setMobileDataEnabled(boolean isMobileDataEnabled) {
mIsMobileDataEnabled = isMobileDataEnabled;
}
/** Retrieves the network score for a VCN Network */
// Package visibility for use in VcnGatewayConnection and VcnNetworkProvider
static NetworkScore getNetworkScore() {
// TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in
// NetworkOffer/NetworkAgent.
return new NetworkScore.Builder()
.setLegacyInt(VCN_LEGACY_SCORE_INT)
.setTransportPrimary(true)
.build();
}
/** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
@VisibleForTesting(visibility = Visibility.PACKAGE)
public interface VcnGatewayStatusCallback {
/** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
void onSafeModeStatusChanged();
/** Callback by a VcnGatewayConnection to indicate that an error occurred. */
void onGatewayConnectionError(
@NonNull String gatewayConnectionName,
@VcnErrorCode int errorCode,
@Nullable String exceptionClass,
@Nullable String exceptionMessage);
/** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
void onQuit();
}
private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
mGatewayConnectionConfig = gatewayConnectionConfig;
}
@Override
public void onQuit() {
sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
}
@Override
public void onSafeModeStatusChanged() {
sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
}
@Override
public void onGatewayConnectionError(
@NonNull String gatewayConnectionName,
@VcnErrorCode int errorCode,
@Nullable String exceptionClass,
@Nullable String exceptionMessage) {
mVcnCallback.onGatewayConnectionError(
gatewayConnectionName, errorCode, exceptionClass, exceptionMessage);
}
}
private class VcnMobileDataContentObserver extends ContentObserver {
private VcnMobileDataContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
}
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
class VcnUserMobileDataStateListener extends TelephonyCallback
implements TelephonyCallback.UserMobileDataStateListener {
@Override
public void onUserMobileDataStateChanged(boolean enabled) {
sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
}
}
/** External dependencies used by Vcn, for injection in tests */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies {
/** Builds a new VcnGatewayConnection */
public VcnGatewayConnection newVcnGatewayConnection(
VcnContext vcnContext,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
VcnGatewayConnectionConfig connectionConfig,
VcnGatewayStatusCallback gatewayStatusCallback,
boolean isMobileDataEnabled) {
return new VcnGatewayConnection(
vcnContext,
subscriptionGroup,
snapshot,
connectionConfig,
gatewayStatusCallback,
isMobileDataEnabled);
}
/** Builds a new VcnContentResolver instance */
public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) {
return new VcnContentResolver(vcnContext);
}
}
/** Proxy Implementation of NetworkAgent, used for testing. */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class VcnContentResolver {
private final ContentResolver mImpl;
public VcnContentResolver(VcnContext vcnContext) {
mImpl = vcnContext.getContext().getContentResolver();
}
/** Registers the content observer */
public void registerContentObserver(
@NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) {
mImpl.registerContentObserver(uri, notifyForDescendants, observer);
}
}
}