| /* |
| * Copyright (C) 2021 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_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; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| 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; |
| import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; |
| import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; |
| import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener; |
| import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; |
| |
| import org.junit.Before; |
| 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; |
| |
| public class VcnTest { |
| private static final String PKG_NAME = VcnTest.class.getPackage().getName(); |
| private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0)); |
| 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_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; |
| private Vcn.Dependencies mDeps; |
| |
| private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor; |
| |
| private TestLooper mTestLooper; |
| private VcnGatewayConnectionConfig mGatewayConnectionConfig; |
| private VcnConfig mConfig; |
| private Vcn mVcn; |
| |
| @Before |
| 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); |
| mDeps = mock(Vcn.Dependencies.class); |
| |
| mTestLooper = new TestLooper(); |
| |
| doReturn(PKG_NAME).when(mContext).getOpPackageName(); |
| 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(), anyBoolean()); |
| |
| doReturn(TEST_SUB_IDS_IN_GROUP).when(mSubscriptionSnapshot).getAllSubIdsInGroup(any()); |
| |
| mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class); |
| |
| final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext); |
| for (final int[] caps : TEST_CAPS) { |
| configBuilder.addGatewayConnectionConfig( |
| VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(caps)); |
| } |
| |
| mConfig = configBuilder.build(); |
| mVcn = |
| new Vcn( |
| mVcnContext, |
| TEST_SUB_GROUP, |
| mConfig, |
| mSubscriptionSnapshot, |
| mVcnCallback, |
| 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); |
| verify(mVcnNetworkProvider).registerListener(mNetworkRequestListenerCaptor.capture()); |
| |
| return mNetworkRequestListenerCaptor.getValue(); |
| } |
| |
| private void startVcnGatewayWithCapabilities( |
| NetworkRequestListener requestListener, int... netCapabilities) { |
| final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder(); |
| requestBuilder.addTransportType(TRANSPORT_CELLULAR); |
| for (final int netCapability : netCapabilities) { |
| requestBuilder.addCapability(netCapability); |
| } |
| |
| requestListener.onNetworkRequested(requestBuilder.build()); |
| mTestLooper.dispatchAll(); |
| } |
| |
| private void verifyUpdateSubscriptionSnapshotNotifiesGatewayConnections(int status) { |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); |
| |
| final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); |
| assertFalse(gatewayConnections.isEmpty()); |
| |
| final TelephonySubscriptionSnapshot updatedSnapshot = |
| mock(TelephonySubscriptionSnapshot.class); |
| |
| mVcn.setStatus(status); |
| |
| mVcn.updateSubscriptionSnapshot(updatedSnapshot); |
| mTestLooper.dispatchAll(); |
| |
| for (final VcnGatewayConnection gateway : gatewayConnections) { |
| verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot)); |
| } |
| } |
| |
| @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 testMobileDataStateListenersRegistered() { |
| // Validate state from setUp() |
| verify(mTelephonyManager, times(3)) |
| .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.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); |
| } |
| |
| @Test |
| public void testSubscriptionSnapshotUpdatesVcnGatewayConnectionsInSafeMode() { |
| verifyUpdateSubscriptionSnapshotNotifiesGatewayConnections(VCN_STATUS_CODE_SAFE_MODE); |
| } |
| |
| @Test |
| public void testSubscriptionSnapshotUpdatesMobileDataState() { |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); |
| |
| // Expect mobile data enabled from setUp() |
| assertTrue(mVcn.isMobileDataEnabled()); |
| |
| final TelephonySubscriptionSnapshot updatedSnapshot = |
| mock(TelephonySubscriptionSnapshot.class); |
| doReturn(TEST_SUB_IDS_IN_GROUP) |
| .when(updatedSnapshot) |
| .getAllSubIdsInGroup(eq(TEST_SUB_GROUP)); |
| doReturn(false).when(mTelephonyManager).isDataEnabled(); |
| |
| mVcn.updateSubscriptionSnapshot(updatedSnapshot); |
| mTestLooper.dispatchAll(); |
| |
| assertFalse(mVcn.isMobileDataEnabled()); |
| } |
| |
| @Test |
| public void testSubscriptionSnapshotUpdatesMobileDataStateListeners() { |
| final TelephonySubscriptionSnapshot updatedSnapshot = |
| mock(TelephonySubscriptionSnapshot.class); |
| |
| doReturn(new ArraySet<>(Arrays.asList(2, 4))) |
| .when(updatedSnapshot) |
| .getAllSubIdsInGroup(any()); |
| |
| mVcn.updateSubscriptionSnapshot(updatedSnapshot); |
| mTestLooper.dispatchAll(); |
| |
| verify(mTelephonyManager, times(4)) |
| .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); |
| verify(mTelephonyManager, times(2)) |
| .unregisterTelephonyCallback(any(VcnUserMobileDataStateListener.class)); |
| } |
| |
| private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { |
| for (final int[] caps : TEST_CAPS) { |
| startVcnGatewayWithCapabilities(requestListener, caps); |
| } |
| } |
| |
| public Set<VcnGatewayConnection> startGatewaysAndGetGatewayConnections( |
| NetworkRequestListener requestListener) { |
| triggerVcnRequestListeners(requestListener); |
| |
| final int numExpectedGateways = TEST_CAPS.length; |
| |
| final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); |
| assertEquals(numExpectedGateways, gatewayConnections.size()); |
| verify(mDeps, times(numExpectedGateways)) |
| .newVcnGatewayConnection( |
| eq(mVcnContext), |
| eq(TEST_SUB_GROUP), |
| eq(mSubscriptionSnapshot), |
| any(), |
| mGatewayStatusCallbackCaptor.capture(), |
| eq(MOBILE_DATA_ENABLED)); |
| |
| return gatewayConnections; |
| } |
| |
| private void verifySafeMode( |
| NetworkRequestListener requestListener, |
| Set<VcnGatewayConnection> activeGateways, |
| boolean expectInSafeMode) { |
| for (VcnGatewayConnection gatewayConnection : activeGateways) { |
| verify(gatewayConnection, never()).teardownAsynchronously(); |
| } |
| |
| assertEquals( |
| expectInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE, |
| mVcn.getStatus()); |
| verify(mVcnCallback).onSafeModeStatusChanged(expectInSafeMode); |
| } |
| |
| @Test |
| public void testGatewayEnteringAndExitingSafeModeNotifiesVcn() { |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| final Set<VcnGatewayConnection> gatewayConnections = |
| startGatewaysAndGetGatewayConnections(requestListener); |
| |
| // Doesn't matter which callback this gets, or which VCN is in safe mode - any Gateway |
| // entering Safemode should trigger safe mode |
| final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); |
| final VcnGatewayConnection gatewayConnection = gatewayConnections.iterator().next(); |
| |
| doReturn(true).when(gatewayConnection).isInSafeMode(); |
| statusCallback.onSafeModeStatusChanged(); |
| mTestLooper.dispatchAll(); |
| |
| verifySafeMode(requestListener, gatewayConnections, true /* expectInSafeMode */); |
| |
| // Verify that when all GatewayConnections exit safe mode, the VCN also exits safe mode |
| doReturn(false).when(gatewayConnection).isInSafeMode(); |
| statusCallback.onSafeModeStatusChanged(); |
| mTestLooper.dispatchAll(); |
| |
| verifySafeMode(requestListener, gatewayConnections, false /* expectInSafeMode */); |
| |
| // Re-trigger, verify safe mode callback does not get fired again for identical state |
| statusCallback.onSafeModeStatusChanged(); |
| mTestLooper.dispatchAll(); |
| |
| // Expect only once still; from above. |
| verify(mVcnCallback).onSafeModeStatusChanged(false); |
| } |
| |
| private void verifyGatewayQuit(int status) { |
| mVcn.setStatus(status); |
| |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| final Set<VcnGatewayConnection> gatewayConnections = |
| new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener)); |
| |
| final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); |
| statusCallback.onQuit(); |
| mTestLooper.dispatchAll(); |
| |
| // Verify that the VCN requests the networkRequests be resent |
| 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(gatewayConnections.size(), mVcn.getVcnGatewayConnections().size()); |
| verify(mDeps, times(gatewayConnections.size() + 1)) |
| .newVcnGatewayConnection( |
| eq(mVcnContext), |
| eq(TEST_SUB_GROUP), |
| eq(mSubscriptionSnapshot), |
| any(), |
| mGatewayStatusCallbackCaptor.capture(), |
| anyBoolean()); |
| } |
| |
| @Test |
| public void testGatewayQuitReevaluatesRequests() { |
| verifyGatewayQuit(VCN_STATUS_CODE_ACTIVE); |
| } |
| |
| @Test |
| public void testGatewayQuitReevaluatesRequestsInSafeMode() { |
| verifyGatewayQuit(VCN_STATUS_CODE_SAFE_MODE); |
| } |
| |
| @Test |
| public void testUpdateConfigReevaluatesGatewayConnections() { |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| startGatewaysAndGetGatewayConnections(requestListener); |
| 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 |
| // matches. |
| final List<VcnGatewayConnectionConfig> currentConfigs = |
| new ArrayList<>(mVcn.getVcnGatewayConnectionConfigMap().keySet()); |
| final VcnGatewayConnectionConfig activeConfig = currentConfigs.get(0); |
| final VcnGatewayConnectionConfig removedConfig = currentConfigs.get(1); |
| final VcnConfig updatedConfig = |
| new VcnConfig.Builder(mContext).addGatewayConnectionConfig(activeConfig).build(); |
| |
| mVcn.updateConfig(updatedConfig); |
| mTestLooper.dispatchAll(); |
| |
| final VcnGatewayConnection activeGatewayConnection = |
| mVcn.getVcnGatewayConnectionConfigMap().get(activeConfig); |
| final VcnGatewayConnection removedGatewayConnection = |
| mVcn.getVcnGatewayConnectionConfigMap().get(removedConfig); |
| verify(activeGatewayConnection, never()).teardownAsynchronously(); |
| verify(removedGatewayConnection).teardownAsynchronously(); |
| verify(mVcnNetworkProvider).resendAllRequests(requestListener); |
| } |
| |
| private void setupForMobileDataTest(boolean startingToggleState) { |
| // Start VcnGatewayConnections |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| mVcn.setMobileDataEnabled(startingToggleState); |
| triggerVcnRequestListeners(requestListener); |
| } |
| |
| private void verifyMobileDataToggledUpdatesGatewayConnections( |
| boolean startingToggleState, |
| boolean endingToggleState, |
| Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways) { |
| // 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(); |
| } |
| } |
| |
| final NetworkRequestListener requestListener = verifyAndGetRequestListener(); |
| if (startingToggleState != endingToggleState) { |
| verify(mVcnNetworkProvider).resendAllRequests(requestListener); |
| } |
| assertEquals(endingToggleState, mVcn.isMobileDataEnabled()); |
| } |
| |
| private void verifyGlobalMobileDataToggled( |
| boolean startingToggleState, boolean endingToggleState) { |
| setupForMobileDataTest(startingToggleState); |
| final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = |
| mVcn.getVcnGatewayConnectionConfigMap(); |
| |
| // Trigger data toggle change |
| final ArgumentCaptor<ContentObserver> captor = |
| ArgumentCaptor.forClass(ContentObserver.class); |
| verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); |
| final ContentObserver contentObserver = captor.getValue(); |
| |
| doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); |
| contentObserver.onChange(false /* selfChange, ignored */); |
| mTestLooper.dispatchAll(); |
| |
| // Verify resultant behavior |
| verifyMobileDataToggledUpdatesGatewayConnections( |
| startingToggleState, endingToggleState, gateways); |
| } |
| |
| @Test |
| public void testGlobalMobileDataEnabled() { |
| verifyGlobalMobileDataToggled( |
| false /* startingToggleState */, true /* endingToggleState */); |
| } |
| |
| @Test |
| public void testGlobalMobileDataDisabled() { |
| verifyGlobalMobileDataToggled( |
| true /* startingToggleState */, false /* endingToggleState */); |
| } |
| |
| @Test |
| public void testGlobalMobileDataObserverFiredWithoutChanges_dataEnabled() { |
| verifyGlobalMobileDataToggled( |
| false /* startingToggleState */, false /* endingToggleState */); |
| } |
| |
| @Test |
| public void testGlobalMobileDataObserverFiredWithoutChanges_dataDisabled() { |
| verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); |
| } |
| |
| private void verifySubscriptionMobileDataToggled( |
| boolean startingToggleState, boolean endingToggleState) { |
| setupForMobileDataTest(startingToggleState); |
| final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = |
| mVcn.getVcnGatewayConnectionConfigMap(); |
| |
| // Trigger data toggle change. |
| final ArgumentCaptor<VcnUserMobileDataStateListener> captor = |
| ArgumentCaptor.forClass(VcnUserMobileDataStateListener.class); |
| verify(mTelephonyManager, times(3)).registerTelephonyCallback(any(), captor.capture()); |
| final VcnUserMobileDataStateListener listener = captor.getValue(); |
| |
| doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); |
| listener.onUserMobileDataStateChanged(false /* enabled, ignored */); |
| mTestLooper.dispatchAll(); |
| |
| // Verify resultant behavior |
| verifyMobileDataToggledUpdatesGatewayConnections( |
| startingToggleState, endingToggleState, gateways); |
| } |
| |
| @Test |
| public void testSubscriptionMobileDataEnabled() { |
| verifyGlobalMobileDataToggled( |
| false /* startingToggleState */, true /* endingToggleState */); |
| } |
| |
| @Test |
| public void testSubscriptionMobileDataDisabled() { |
| verifyGlobalMobileDataToggled( |
| true /* startingToggleState */, false /* endingToggleState */); |
| } |
| |
| @Test |
| public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataEnabled() { |
| verifyGlobalMobileDataToggled( |
| false /* startingToggleState */, false /* endingToggleState */); |
| } |
| |
| @Test |
| public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataDisabled() { |
| verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); |
| } |
| } |