Merge "Listen for individual subscription mobile data toggles"

This commit is contained in:
Benedict Wong
2022-01-13 07:07:45 +00:00
committed by Gerrit Code Review
2 changed files with 175 additions and 26 deletions

View File

@@ -39,10 +39,13 @@ import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode; import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Message; import android.os.Message;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.provider.Settings; import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Slog; import android.util.Slog;
@@ -57,6 +60,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -148,6 +152,10 @@ public class Vcn extends Handler {
@NonNull private final VcnContentResolver mContentResolver; @NonNull private final VcnContentResolver mContentResolver;
@NonNull private final ContentObserver mMobileDataSettingsObserver; @NonNull private final ContentObserver mMobileDataSettingsObserver;
@NonNull
private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
new ArrayMap<>();
/** /**
* Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
* *
@@ -221,6 +229,9 @@ public class Vcn extends Handler {
// Update mIsMobileDataEnabled before starting handling of NetworkRequests. // Update mIsMobileDataEnabled before starting handling of NetworkRequests.
mIsMobileDataEnabled = getMobileDataStatus(); mIsMobileDataEnabled = getMobileDataStatus();
// Register mobile data state listeners.
updateMobileDataStateListeners();
// Register to receive cached and future NetworkRequests // Register to receive cached and future NetworkRequests
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
} }
@@ -348,6 +359,12 @@ public class Vcn extends Handler {
gatewayConnection.teardownAsynchronously(); gatewayConnection.teardownAsynchronously();
} }
// Unregister MobileDataStateListeners
for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
getTelephonyManager().unregisterTelephonyCallback(listener);
}
mMobileDataStateListeners.clear();
mCurrentStatus = VCN_STATUS_CODE_INACTIVE; mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
} }
@@ -454,11 +471,40 @@ public class Vcn extends Handler {
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
} }
updateMobileDataStateListeners();
// Update the mobile data state after updating the subscription snapshot as a change in // Update the mobile data state after updating the subscription snapshot as a change in
// subIds for a subGroup may affect the mobile data state. // subIds for a subGroup may affect the mobile data state.
handleMobileDataToggled(); 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() { private void handleMobileDataToggled() {
final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled; final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
mIsMobileDataEnabled = getMobileDataStatus(); mIsMobileDataEnabled = getMobileDataStatus();
@@ -493,11 +539,8 @@ public class Vcn extends Handler {
} }
private boolean getMobileDataStatus() { private boolean getMobileDataStatus() {
final TelephonyManager genericTelMan =
mVcnContext.getContext().getSystemService(TelephonyManager.class);
for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) { for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) { if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
return true; return true;
} }
} }
@@ -517,6 +560,14 @@ public class Vcn extends Handler {
return request.canBeSatisfiedBy(builder.build()); 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() { private String getLogPrefix() {
return "[" return "["
+ LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup) + 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 */ /** External dependencies used by Vcn, for injection in tests */
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies { public static class Dependencies {

View File

@@ -58,6 +58,7 @@ import android.util.ArraySet;
import com.android.server.VcnManagementService.VcnCallback; import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener;
import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
import org.junit.Before; import org.junit.Before;
@@ -207,6 +208,13 @@ public class VcnTest {
.registerContentObserver(eq(uri), eq(true), any(ContentObserver.class)); .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 @Test
public void testMobileDataStateCheckedOnInitialization_enabled() { public void testMobileDataStateCheckedOnInitialization_enabled() {
// Validate state from setUp() // Validate state from setUp()
@@ -263,6 +271,24 @@ public class VcnTest {
assertFalse(mVcn.isMobileDataEnabled()); 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) { private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
for (final int[] caps : TEST_CAPS) { for (final int[] caps : TEST_CAPS) {
startVcnGatewayWithCapabilities(requestListener, caps); startVcnGatewayWithCapabilities(requestListener, caps);
@@ -402,24 +428,17 @@ public class VcnTest {
verify(mVcnNetworkProvider).resendAllRequests(requestListener); verify(mVcnNetworkProvider).resendAllRequests(requestListener);
} }
private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) { private void setupForMobileDataTest(boolean startingToggleState) {
final ArgumentCaptor<ContentObserver> captor =
ArgumentCaptor.forClass(ContentObserver.class);
verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture());
final ContentObserver contentObserver = captor.getValue();
// Start VcnGatewayConnections // Start VcnGatewayConnections
final NetworkRequestListener requestListener = verifyAndGetRequestListener(); final NetworkRequestListener requestListener = verifyAndGetRequestListener();
mVcn.setMobileDataEnabled(startingToggleState); mVcn.setMobileDataEnabled(startingToggleState);
triggerVcnRequestListeners(requestListener); 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 // Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the
// toggle state changed. // toggle state changed.
for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) { for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) {
@@ -433,29 +452,98 @@ public class VcnTest {
} }
} }
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
if (startingToggleState != endingToggleState) { if (startingToggleState != endingToggleState) {
verify(mVcnNetworkProvider).resendAllRequests(requestListener); verify(mVcnNetworkProvider).resendAllRequests(requestListener);
} }
assertEquals(endingToggleState, mVcn.isMobileDataEnabled()); assertEquals(endingToggleState, mVcn.isMobileDataEnabled());
} }
@Test private void verifyGlobalMobileDataToggled(
public void testMobileDataEnabled() { boolean startingToggleState, boolean endingToggleState) {
verifyMobileDataToggled(false /* startingToggleState */, true /* 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 @Test
public void testMobileDataDisabled() { public void testGlobalMobileDataEnabled() {
verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */); verifyGlobalMobileDataToggled(
false /* startingToggleState */, true /* endingToggleState */);
} }
@Test @Test
public void testMobileDataObserverFiredWithoutChanges_dataEnabled() { public void testGlobalMobileDataDisabled() {
verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */); verifyGlobalMobileDataToggled(
true /* startingToggleState */, false /* endingToggleState */);
} }
@Test @Test
public void testMobileDataObserverFiredWithoutChanges_dataDisabled() { public void testGlobalMobileDataObserverFiredWithoutChanges_dataEnabled() {
verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); 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 */);
} }
} }