Disable INTERNET/DUN when mobile data toggled off am: 061c84ba27

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1670525

Change-Id: I5a1482d06a92c74769f9b0f7c840cb2cc983edd9
This commit is contained in:
Benedict Wong
2021-04-20 21:00:55 +00:00
committed by Automerger Merge Worker
6 changed files with 326 additions and 20 deletions

View File

@@ -16,6 +16,8 @@
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;
@@ -26,14 +28,20 @@ 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.Uri;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -42,9 +50,11 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -61,6 +71,9 @@ import java.util.Set;
public class Vcn extends Handler {
private static final String TAG = Vcn.class.getSimpleName();
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;
@@ -110,6 +123,15 @@ public class Vcn extends Handler {
*/
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;
@@ -118,6 +140,8 @@ public class Vcn extends Handler {
@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;
/**
* Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
@@ -154,6 +178,8 @@ public class Vcn extends Handler {
// 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,
@@ -177,10 +203,19 @@ public class Vcn extends Handler {
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 to receive cached and future NetworkRequests
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
}
@@ -260,6 +295,9 @@ public class Vcn extends Handler {
case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
handleSafeModeStatusChanged();
break;
case MSG_EVENT_MOBILE_DATA_TOGGLED:
handleMobileDataToggled();
break;
case MSG_CMD_TEARDOWN:
handleTeardown();
break;
@@ -366,18 +404,37 @@ public class Vcn extends Handler {
if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
Slog.v(getLogTag(), "Bringing up new VcnGatewayConnection for request " + request);
if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
// Skip; this network does not provide any services if mobile data is disabled.
continue;
}
final VcnGatewayConnection vcnGatewayConnection =
mDeps.newVcnGatewayConnection(
mVcnContext,
mSubscriptionGroup,
mLastSnapshot,
gatewayConnectionConfig,
new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig));
new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig),
mIsMobileDataEnabled);
mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
}
}
}
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) {
Slog.v(getLogTag(), "VcnGatewayConnection quit: " + config);
mVcnGatewayConnections.remove(config);
@@ -396,12 +453,55 @@ public class Vcn extends Handler {
}
}
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) {
Slog.wtf(
getLogTag(),
"Found gatewayConnectionConfig without GatewayConnection");
} else {
// TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
gatewayConnection.teardownAsynchronously();
}
}
}
}
}
private boolean getMobileDataStatus() {
final TelephonyManager genericTelMan =
mVcnContext.getContext().getSystemService(TelephonyManager.class);
for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
if (genericTelMan.createForSubscriptionId(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 : config.getAllExposedCapabilities()) {
for (int cap : getExposedCapabilitiesForMobileDataState(config)) {
builder.addCapability(cap);
}
@@ -432,6 +532,16 @@ public class Vcn extends Handler {
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
static int getNetworkScore() {
@@ -485,6 +595,17 @@ public class Vcn extends Handler {
}
}
private class VcnMobileDataContentObserver extends ContentObserver {
private VcnMobileDataContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
}
}
/** External dependencies used by Vcn, for injection in tests */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies {
@@ -494,13 +615,36 @@ public class Vcn extends Handler {
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
VcnGatewayConnectionConfig connectionConfig,
VcnGatewayStatusCallback gatewayStatusCallback) {
VcnGatewayStatusCallback gatewayStatusCallback,
boolean isMobileDataEnabled) {
return new VcnGatewayConnection(
vcnContext,
subscriptionGroup,
snapshot,
connectionConfig,
gatewayStatusCallback);
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);
}
}
}

View File

@@ -16,6 +16,8 @@
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_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
@@ -517,6 +519,7 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
@NonNull private final Dependencies mDeps;
@NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
private final boolean mIsMobileDataEnabled;
@NonNull private final IpSecManager mIpSecManager;
@@ -626,13 +629,15 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull ParcelUuid subscriptionGroup,
@NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull VcnGatewayConnectionConfig connectionConfig,
@NonNull VcnGatewayStatusCallback gatewayStatusCallback) {
@NonNull VcnGatewayStatusCallback gatewayStatusCallback,
boolean isMobileDataEnabled) {
this(
vcnContext,
subscriptionGroup,
snapshot,
connectionConfig,
gatewayStatusCallback,
isMobileDataEnabled,
new Dependencies());
}
@@ -643,6 +648,7 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull VcnGatewayConnectionConfig connectionConfig,
@NonNull VcnGatewayStatusCallback gatewayStatusCallback,
boolean isMobileDataEnabled,
@NonNull Dependencies deps) {
super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
mVcnContext = vcnContext;
@@ -650,6 +656,7 @@ public class VcnGatewayConnection extends StateMachine {
mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
mGatewayStatusCallback =
Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback");
mIsMobileDataEnabled = isMobileDataEnabled;
mDeps = Objects.requireNonNull(deps, "Missing deps");
mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
@@ -1502,7 +1509,7 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull VcnNetworkAgent agent,
@NonNull VcnChildSessionConfiguration childConfig) {
final NetworkCapabilities caps =
buildNetworkCapabilities(mConnectionConfig, mUnderlying);
buildNetworkCapabilities(mConnectionConfig, mUnderlying, mIsMobileDataEnabled);
final LinkProperties lp =
buildConnectedLinkProperties(
mConnectionConfig, tunnelIface, childConfig, mUnderlying);
@@ -1515,7 +1522,7 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull IpSecTunnelInterface tunnelIface,
@NonNull VcnChildSessionConfiguration childConfig) {
final NetworkCapabilities caps =
buildNetworkCapabilities(mConnectionConfig, mUnderlying);
buildNetworkCapabilities(mConnectionConfig, mUnderlying, mIsMobileDataEnabled);
final LinkProperties lp =
buildConnectedLinkProperties(
mConnectionConfig, tunnelIface, childConfig, mUnderlying);
@@ -1843,7 +1850,8 @@ public class VcnGatewayConnection extends StateMachine {
@VisibleForTesting(visibility = Visibility.PRIVATE)
static NetworkCapabilities buildNetworkCapabilities(
@NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
@Nullable UnderlyingNetworkRecord underlying) {
@Nullable UnderlyingNetworkRecord underlying,
boolean isMobileDataEnabled) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
builder.addTransportType(TRANSPORT_CELLULAR);
@@ -1853,6 +1861,12 @@ public class VcnGatewayConnection extends StateMachine {
// Add exposed capabilities
for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) {
// Skip adding INTERNET or DUN if mobile data is disabled.
if (!isMobileDataEnabled
&& (cap == NET_CAPABILITY_INTERNET || cap == NET_CAPABILITY_DUN)) {
continue;
}
builder.addCapability(cap);
}

View File

@@ -65,6 +65,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
TEST_SUBSCRIPTION_SNAPSHOT,
mConfig,
mGatewayStatusCallback,
true /* isMobileDataEnabled */,
mDeps);
vgc.setIsQuitting(true);

View File

@@ -16,6 +16,8 @@
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_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
@@ -89,7 +91,8 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
doReturn(mWifiInfo).when(mWifiInfo).makeCopy(anyLong());
}
private void verifyBuildNetworkCapabilitiesCommon(int transportType) {
private void verifyBuildNetworkCapabilitiesCommon(
int transportType, boolean isMobileDataEnabled) {
final NetworkCapabilities.Builder capBuilder = new NetworkCapabilities.Builder();
capBuilder.addTransportType(transportType);
capBuilder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
@@ -109,10 +112,22 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
capBuilder.build(), new LinkProperties(), false);
final NetworkCapabilities vcnCaps =
VcnGatewayConnection.buildNetworkCapabilities(
VcnGatewayConnectionConfigTest.buildTestConfig(), record);
VcnGatewayConnectionConfigTest.buildTestConfig(),
record,
isMobileDataEnabled);
assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR));
assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
for (int cap : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
if (cap == NET_CAPABILITY_INTERNET || cap == NET_CAPABILITY_DUN) {
assertEquals(isMobileDataEnabled, vcnCaps.hasCapability(cap));
} else {
assertTrue(vcnCaps.hasCapability(cap));
}
}
assertArrayEquals(new int[] {TEST_UID}, vcnCaps.getAdministratorUids());
assertTrue(vcnCaps.getTransportInfo() instanceof VcnTransportInfo);
@@ -126,12 +141,17 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
@Test
public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception {
verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI);
verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI, true /* isMobileDataEnabled */);
}
@Test
public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception {
verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR);
verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR, true /* isMobileDataEnabled */);
}
@Test
public void testBuildNetworkCapabilitiesMobileDataDisabled() throws Exception {
verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR, false /* isMobileDataEnabled */);
}
@Test

View File

@@ -202,6 +202,7 @@ public class VcnGatewayConnectionTestBase {
TEST_SUBSCRIPTION_SNAPSHOT,
mConfig,
mGatewayStatusCallback,
true /* isMobileDataEnabled */,
mDeps);
}

View File

@@ -16,16 +16,24 @@
package com.android.server.vcn;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
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_SAFE_MODE;
import static com.android.server.vcn.Vcn.VcnContentResolver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -35,12 +43,16 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.database.ContentObserver;
import android.net.NetworkRequest;
import android.net.Uri;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import com.android.server.VcnManagementService.VcnCallback;
@@ -53,7 +65,10 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
@@ -62,14 +77,21 @@ public class VcnTest {
private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0));
private static final int NETWORK_SCORE = 0;
private static final int PROVIDER_ID = 5;
private static final boolean MOBILE_DATA_ENABLED = true;
private static final Set<Integer> TEST_SUB_IDS_IN_GROUP =
new ArraySet<>(Arrays.asList(1, 2, 3));
private static final int[][] TEST_CAPS =
new int[][] {
new int[] {NET_CAPABILITY_MMS, NET_CAPABILITY_INTERNET},
new int[] {NET_CAPABILITY_DUN}
new int[] {NET_CAPABILITY_IMS, NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN},
new int[] {NET_CAPABILITY_CBS, NET_CAPABILITY_INTERNET},
new int[] {NET_CAPABILITY_FOTA, NET_CAPABILITY_DUN},
new int[] {NET_CAPABILITY_MMS}
};
private Context mContext;
private VcnContext mVcnContext;
private TelephonyManager mTelephonyManager;
private VcnContentResolver mContentResolver;
private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
private VcnNetworkProvider mVcnNetworkProvider;
private VcnCallback mVcnCallback;
@@ -86,6 +108,9 @@ public class VcnTest {
public void setUp() {
mContext = mock(Context.class);
mVcnContext = mock(VcnContext.class);
mTelephonyManager =
setupAndGetTelephonyManager(MOBILE_DATA_ENABLED /* isMobileDataEnabled */);
mContentResolver = mock(VcnContentResolver.class);
mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
mVcnNetworkProvider = mock(VcnNetworkProvider.class);
mVcnCallback = mock(VcnCallback.class);
@@ -97,12 +122,15 @@ public class VcnTest {
doReturn(mContext).when(mVcnContext).getContext();
doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
doReturn(mContentResolver).when(mDeps).newVcnContentResolver(eq(mVcnContext));
// Setup VcnGatewayConnection instance generation
doAnswer((invocation) -> {
// Mock-within a doAnswer is safe, because it doesn't actually run nested.
return mock(VcnGatewayConnection.class);
}).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any(), any());
}).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any(), any(), anyBoolean());
doReturn(TEST_SUB_IDS_IN_GROUP).when(mSubscriptionSnapshot).getAllSubIdsInGroup(any());
mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class);
@@ -123,6 +151,16 @@ public class VcnTest {
mDeps);
}
private TelephonyManager setupAndGetTelephonyManager(boolean isMobileDataEnabled) {
final TelephonyManager telephonyManager = mock(TelephonyManager.class);
VcnTestUtils.setupSystemService(
mContext, telephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
doReturn(telephonyManager).when(telephonyManager).createForSubscriptionId(anyInt());
doReturn(isMobileDataEnabled).when(telephonyManager).isDataEnabled();
return telephonyManager;
}
private NetworkRequestListener verifyAndGetRequestListener() {
ArgumentCaptor<NetworkRequestListener> mNetworkRequestListenerCaptor =
ArgumentCaptor.forClass(NetworkRequestListener.class);
@@ -163,6 +201,39 @@ public class VcnTest {
}
}
@Test
public void testContentObserverRegistered() {
// Validate state from setUp()
final Uri uri = Settings.Global.getUriFor(Settings.Global.MOBILE_DATA);
verify(mContentResolver)
.registerContentObserver(eq(uri), eq(true), any(ContentObserver.class));
}
@Test
public void testMobileDataStateCheckedOnInitialization_enabled() {
// Validate state from setUp()
assertTrue(mVcn.isMobileDataEnabled());
verify(mTelephonyManager).isDataEnabled();
}
@Test
public void testMobileDataStateCheckedOnInitialization_disabled() {
// Build and setup new telephonyManager to ensure method call count is reset.
final TelephonyManager telephonyManager =
setupAndGetTelephonyManager(false /* isMobileDataEnabled */);
final Vcn vcn =
new Vcn(
mVcnContext,
TEST_SUB_GROUP,
mConfig,
mSubscriptionSnapshot,
mVcnCallback,
mDeps);
assertFalse(vcn.isMobileDataEnabled());
verify(mTelephonyManager).isDataEnabled();
}
@Test
public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
verifyUpdateSubscriptionSnapshotNotifiesGatewayConnections(VCN_STATUS_CODE_ACTIVE);
@@ -193,7 +264,8 @@ public class VcnTest {
eq(TEST_SUB_GROUP),
eq(mSubscriptionSnapshot),
any(),
mGatewayStatusCallbackCaptor.capture());
mGatewayStatusCallbackCaptor.capture(),
eq(MOBILE_DATA_ENABLED));
return gatewayConnections;
}
@@ -256,20 +328,21 @@ public class VcnTest {
mTestLooper.dispatchAll();
// Verify that the VCN requests the networkRequests be resent
assertEquals(1, mVcn.getVcnGatewayConnections().size());
assertEquals(gatewayConnections.size() - 1, mVcn.getVcnGatewayConnections().size());
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
// Verify that the VcnGatewayConnection is restarted if a request exists for it
triggerVcnRequestListeners(requestListener);
mTestLooper.dispatchAll();
assertEquals(2, mVcn.getVcnGatewayConnections().size());
assertEquals(gatewayConnections.size(), mVcn.getVcnGatewayConnections().size());
verify(mDeps, times(gatewayConnections.size() + 1))
.newVcnGatewayConnection(
eq(mVcnContext),
eq(TEST_SUB_GROUP),
eq(mSubscriptionSnapshot),
any(),
mGatewayStatusCallbackCaptor.capture());
mGatewayStatusCallbackCaptor.capture(),
anyBoolean());
}
@Test
@@ -286,7 +359,7 @@ public class VcnTest {
public void testUpdateConfigReevaluatesGatewayConnections() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
startGatewaysAndGetGatewayConnections(requestListener);
assertEquals(2, mVcn.getVcnGatewayConnectionConfigMap().size());
assertEquals(TEST_CAPS.length, mVcn.getVcnGatewayConnectionConfigMap().size());
// Create VcnConfig with only one VcnGatewayConnectionConfig so a gateway connection is torn
// down. Reuse existing VcnGatewayConnectionConfig so that the gateway connection name
@@ -309,4 +382,57 @@ public class VcnTest {
verify(removedGatewayConnection).teardownAsynchronously();
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
}
private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) {
final ArgumentCaptor<ContentObserver> captor =
ArgumentCaptor.forClass(ContentObserver.class);
verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture());
final ContentObserver contentObserver = captor.getValue();
// Start VcnGatewayConnections
mVcn.setMobileDataEnabled(startingToggleState);
triggerVcnRequestListeners(verifyAndGetRequestListener());
final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways =
mVcn.getVcnGatewayConnectionConfigMap();
// Trigger data toggle change.
doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled();
contentObserver.onChange(false /* selfChange, ignored */);
mTestLooper.dispatchAll();
// Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the
// toggle state changed.
for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) {
final Set<Integer> exposedCaps = entry.getKey().getAllExposedCapabilities();
if (startingToggleState != endingToggleState
&& (exposedCaps.contains(NET_CAPABILITY_INTERNET)
|| exposedCaps.contains(NET_CAPABILITY_DUN))) {
verify(entry.getValue()).teardownAsynchronously();
} else {
verify(entry.getValue(), never()).teardownAsynchronously();
}
}
assertEquals(endingToggleState, mVcn.isMobileDataEnabled());
}
@Test
public void testMobileDataEnabled() {
verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */);
}
@Test
public void testMobileDataDisabled() {
verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */);
}
@Test
public void testMobileDataObserverFiredWithoutChanges_dataEnabled() {
verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */);
}
@Test
public void testMobileDataObserverFiredWithoutChanges_dataDisabled() {
verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */);
}
}