From 5f57cc8350056b6f1e8d6fdc11df396605d81d7d Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Thu, 13 Jan 2022 07:49:51 +0000 Subject: 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 Original-Change: https://android-review.googlesource.com/1947700 Change-Id: I1e84c25f48eab90dca163eda809fc11d5d21bc31 Merged-In: I1e84c25f48eab90dca163eda809fc11d5d21bc31 --- services/core/java/com/android/server/vcn/Vcn.java | 69 ++++++++++- tests/vcn/java/com/android/server/vcn/VcnTest.java | 130 +++++++++++++++++---- 2 files changed, 174 insertions(+), 25 deletions(-) diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index e0cc8e182079..f29c40f74353 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.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; @@ -57,6 +60,7 @@ 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; @@ -148,6 +152,10 @@ public class Vcn extends Handler { @NonNull private final VcnContentResolver mContentResolver; @NonNull private final ContentObserver mMobileDataSettingsObserver; + @NonNull + private final Map mMobileDataStateListeners = + new ArrayMap<>(); + /** * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. * @@ -221,6 +229,9 @@ public class Vcn extends Handler { // 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 @@ public class Vcn extends Handler { gatewayConnection.teardownAsynchronously(); } + // Unregister MobileDataStateListeners + for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) { + getTelephonyManager().unregisterTelephonyCallback(listener); + } + mMobileDataStateListeners.clear(); + mCurrentStatus = VCN_STATUS_CODE_INACTIVE; } @@ -454,11 +471,40 @@ public class Vcn extends Handler { 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 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> iterator = + mMobileDataStateListeners.entrySet().iterator(); + while (iterator.hasNext()) { + final Entry 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 @@ public class Vcn extends Handler { } 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 @@ public class Vcn extends Handler { 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 @@ public class Vcn extends Handler { } } + @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 5d2f9d748581..6d269686e42f 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 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; @@ -207,6 +208,13 @@ public class VcnTest { .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() @@ -263,6 +271,24 @@ public class VcnTest { 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 @@ public class VcnTest { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } - private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) { - final ArgumentCaptor 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 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 gateways) { // Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the // toggle state changed. for (Entry entry : gateways.entrySet()) { @@ -433,29 +452,98 @@ public class VcnTest { } } + 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 gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change + final ArgumentCaptor 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 gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change. + final ArgumentCaptor 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 testMobileDataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); } @Test - public void testMobileDataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); } } -- cgit v1.2.3-59-g8ed1b