Listen for individual subscription mobile data toggles
This change ensures that the VCN listens for changes on a
per-subscription basis. In the DSDS case, the
Settings.Global.MOBILE_DATA toggles is insufficient, as the toggles for
individual subscriptions are changed instead of the global toggle.
Without this patch, the VCN may ignore changes in the user toggles, and
continue providing data service even after user data is disabled.
Similarly, it may fail to bring up a VCN when user data is re-enabled.
Bug: 214247774
Test: atest FrameworksVcnTests
Change-Id: I1e84c25f48eab90dca163eda809fc11d5d21bc31
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index e0cc8e1..f29c40f 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -39,10 +39,13 @@
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;
@@ -57,6 +60,7 @@
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;
@@ -148,6 +152,10 @@
@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.
*
@@ -221,6 +229,9 @@
// 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);
}
@@ -348,6 +359,12 @@
gatewayConnection.teardownAsynchronously();
}
+ // Unregister MobileDataStateListeners
+ for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
+ getTelephonyManager().unregisterTelephonyCallback(listener);
+ }
+ mMobileDataStateListeners.clear();
+
mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
}
@@ -454,11 +471,40 @@
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();
@@ -493,11 +539,8 @@
}
private boolean getMobileDataStatus() {
- final TelephonyManager genericTelMan =
- mVcnContext.getContext().getSystemService(TelephonyManager.class);
-
for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
- if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) {
+ if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
return true;
}
}
@@ -517,6 +560,14 @@
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)
@@ -670,6 +721,16 @@
}
}
+ @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 {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 5d2f9d7..6d26968 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -58,6 +58,7 @@
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;
@@ -208,6 +209,13 @@
}
@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());
@@ -263,6 +271,24 @@
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);
@@ -402,24 +428,17 @@
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();
-
+ private void setupForMobileDataTest(boolean startingToggleState) {
// Start VcnGatewayConnections
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
mVcn.setMobileDataEnabled(startingToggleState);
triggerVcnRequestListeners(requestListener);
- final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways =
- mVcn.getVcnGatewayConnectionConfigMap();
+ }
- // Trigger data toggle change.
- doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled();
- contentObserver.onChange(false /* selfChange, ignored */);
- mTestLooper.dispatchAll();
-
+ 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()) {
@@ -433,29 +452,98 @@
}
}
+ final NetworkRequestListener requestListener = verifyAndGetRequestListener();
if (startingToggleState != endingToggleState) {
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
}
assertEquals(endingToggleState, mVcn.isMobileDataEnabled());
}
- @Test
- public void testMobileDataEnabled() {
- verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */);
+ 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 testMobileDataDisabled() {
- verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */);
+ public void testGlobalMobileDataEnabled() {
+ verifyGlobalMobileDataToggled(
+ false /* startingToggleState */, true /* endingToggleState */);
}
@Test
- public void testMobileDataObserverFiredWithoutChanges_dataEnabled() {
- verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */);
+ public void testGlobalMobileDataDisabled() {
+ verifyGlobalMobileDataToggled(
+ true /* startingToggleState */, false /* endingToggleState */);
}
@Test
- public void testMobileDataObserverFiredWithoutChanges_dataDisabled() {
- verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */);
+ 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 */);
}
}