diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 4fd9373ad1df9..46f4b0b41a45a 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED; @@ -35,7 +37,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; @@ -161,6 +165,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final VcnContext mVcnContext; @NonNull private final BroadcastReceiver mPkgChangeReceiver; + @NonNull + private final TrackingNetworkCallback mTrackingNetworkCallback = new TrackingNetworkCallback(); + /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */ @Nullable private LocationPermissionChecker mLocationPermissionChecker; @@ -357,6 +364,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { public void systemReady() { mContext.getSystemService(ConnectivityManager.class) .registerNetworkProvider(mNetworkProvider); + mContext.getSystemService(ConnectivityManager.class) + .registerNetworkCallback( + new NetworkRequest.Builder().clearCapabilities().build(), + mTrackingNetworkCallback); mTelephonySubscriptionTracker.register(); mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext()); } @@ -792,8 +803,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); } + final NetworkCapabilities result = ncBuilder.build(); return new VcnUnderlyingNetworkPolicy( - false /* isTearDownRequested */, ncBuilder.build()); + mTrackingNetworkCallback.requiresRestartForCarrierWifi(result), result); }); } @@ -940,6 +952,49 @@ public class VcnManagementService extends IVcnManagementService.Stub { @Nullable String exceptionMessage); } + /** + * TrackingNetworkCallback tracks all active networks + * + *

This is used to ensure that no underlying networks have immutable capabilities changed + * without requiring a Network restart. + */ + private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback { + private final Map mCaps = new ArrayMap<>(); + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) { + synchronized (mCaps) { + mCaps.put(network, caps); + } + } + + @Override + public void onLost(Network network) { + synchronized (mCaps) { + mCaps.remove(network); + } + } + + private boolean requiresRestartForCarrierWifi(NetworkCapabilities caps) { + if (!caps.hasTransport(TRANSPORT_WIFI) || caps.getSubIds() == null) { + return false; + } + + synchronized (mCaps) { + for (NetworkCapabilities existing : mCaps.values()) { + if (existing.hasTransport(TRANSPORT_WIFI) + && caps.getSubIds().equals(existing.getSubIds())) { + // Restart if any immutable capabilities have changed + return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED); + } + } + } + + return false; + } + } + /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */ private class VcnCallbackImpl implements VcnCallback { @NonNull private final ParcelUuid mSubGroup; diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 4ad7136aabb29..4ffbf3147ee66 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.net.ConnectivityManager.NetworkCallback; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; @@ -55,8 +57,10 @@ import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkCapabilities.Transport; +import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; @@ -258,6 +262,10 @@ public class VcnManagementServiceTest { verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); verify(mSubscriptionTracker).register(); + verify(mConnMgr) + .registerNetworkCallback( + eq(new NetworkRequest.Builder().clearCapabilities().build()), + any(NetworkCallback.class)); } @Test @@ -706,10 +714,8 @@ public class VcnManagementServiceTest { .checkLocationPermission(eq(TEST_PACKAGE_NAME), any(), eq(TEST_UID), any()); } - private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport( - int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) { - setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive); - + private NetworkCapabilities.Builder getNetworkCapabilitiesBuilderForTransport( + int subId, int transport) { final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder() .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) @@ -718,7 +724,16 @@ public class VcnManagementServiceTest { ncBuilder.setSubIds(Collections.singleton(subId)); } - return mVcnMgmtSvc.getUnderlyingNetworkPolicy(ncBuilder.build(), new LinkProperties()); + return ncBuilder; + } + + private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport( + int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) { + setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive); + + return mVcnMgmtSvc.getUnderlyingNetworkPolicy( + getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), + new LinkProperties()); } @Test @@ -780,6 +795,53 @@ public class VcnManagementServiceTest { true /* isRestricted */); } + private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) { + mVcnMgmtSvc.systemReady(); + + final ArgumentCaptor captor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mConnMgr) + .registerNetworkCallback( + eq(new NetworkRequest.Builder().clearCapabilities().build()), + captor.capture()); + captor.getValue().onCapabilitiesChanged(new Network(0), caps); + } + + @Test + public void testGetUnderlyingNetworkPolicyVcnWifi_unrestrictingExistingNetworkRequiresRestart() + throws Exception { + final NetworkCapabilities existingNetworkCaps = + getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) + .build(); + setupTrackedCarrierWifiNetwork(existingNetworkCaps); + + // Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED + // immutable capability + final VcnUnderlyingNetworkPolicy policy = + mVcnMgmtSvc.getUnderlyingNetworkPolicy( + getNetworkCapabilitiesBuilderForTransport( + TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) + .build(), + new LinkProperties()); + assertTrue(policy.isTeardownRequested()); + } + + @Test + public void testGetUnderlyingNetworkPolicyVcnWifi_restrictingExistingNetworkRequiresRestart() + throws Exception { + final NetworkCapabilities existingNetworkCaps = + getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) + .build(); + setupTrackedCarrierWifiNetwork(existingNetworkCaps); + + final VcnUnderlyingNetworkPolicy policy = + startVcnAndGetPolicyForTransport( + TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI); + + assertTrue(policy.isTeardownRequested()); + } + @Test public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception { setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */);