Merge "Add dependency on system API in connectivity"
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
index 6aca319..ea64343 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -100,7 +100,7 @@
                    AID_NETWORK_STACK)
 
 DEFINE_BPF_MAP_GRW(tether_downstream64_map, HASH, TetherDownstream64Key, TetherDownstream64Value,
-                   64, AID_NETWORK_STACK)
+                   1024, AID_NETWORK_STACK)
 
 DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, 64,
                    AID_NETWORK_STACK)
@@ -340,9 +340,9 @@
 
 // ----- IPv4 Support -----
 
-DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, Tether4Key, Tether4Value, 64, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
 
-DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 64, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
 
 static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
         const bool downstream, const bool updatetime) {
@@ -747,4 +747,4 @@
 }
 
 LICENSE("Apache 2.0");
-CRITICAL("netd");
+CRITICAL("tethering");
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 799637c..413b0cb 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -33,6 +33,8 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.DeviceConfigUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -94,6 +96,20 @@
             "tether_enable_select_all_prefix_ranges";
 
     /**
+     * Experiment flag to force choosing upstreams automatically.
+     *
+     * This setting is intended to help force-enable the feature on OEM devices that disabled it
+     * via resource overlays, and later noticed issues. To that end, it overrides
+     * config_tether_upstream_automatic when set to true.
+     *
+     * This flag is enabled if !=0 and less than the module APK version: see
+     * {@link DeviceConfigUtils#isFeatureEnabled}. It is also ignored after R, as later devices
+     * should just set config_tether_upstream_automatic to true instead.
+     */
+    public static final String TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION =
+            "tether_force_upstream_automatic_version";
+
+    /**
      * Default value that used to periodic polls tether offload stats from tethering offload HAL
      * to make the data warnings work.
      */
@@ -146,7 +162,9 @@
 
         isDunRequired = checkDunRequired(ctx);
 
-        chooseUpstreamAutomatically = getResourceBoolean(
+        final boolean forceAutomaticUpstream = !SdkLevel.isAtLeastS()
+                && isFeatureEnabled(ctx, TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION);
+        chooseUpstreamAutomatically = forceAutomaticUpstream || getResourceBoolean(
                 res, R.bool.config_tether_upstream_automatic, false /** defaultValue */);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
 
@@ -453,6 +471,11 @@
         return DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, name);
     }
 
+    @VisibleForTesting
+    protected boolean isFeatureEnabled(Context ctx, String featureVersionFlag) {
+        return DeviceConfigUtils.isFeatureEnabled(ctx, NAMESPACE_CONNECTIVITY, featureVersionFlag);
+    }
+
     private Resources getResources(Context ctx, int subId) {
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             return getResourcesForSubIdWrapper(ctx, subId);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 354e753..8cfa7d0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -53,6 +53,8 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
 import android.os.Bundle;
@@ -87,11 +89,13 @@
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
     private static final String PROVISIONING_APP_RESPONSE = "app_response";
+    private static final String TEST_PACKAGE_NAME = "com.android.tethering.test";
 
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private Context mContext;
     @Mock private Resources mResources;
     @Mock private SharedLog mLog;
+    @Mock private PackageManager mPm;
     @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener;
 
     // Like so many Android system APIs, these cannot be mocked because it is marked final.
@@ -182,7 +186,7 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mMockingSession = mockitoSession()
                 .initMocks(this)
@@ -196,6 +200,9 @@
                 eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
         doReturn(null).when(
                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY), anyString()));
+        doReturn(mPm).when(mContext).getPackageManager();
+        doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
+        doReturn(new PackageInfo()).when(mPm).getPackageInfo(anyString(), anyInt());
 
         when(mResources.getStringArray(R.array.config_tether_dhcp_range))
                 .thenReturn(new String[0]);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 237e2c2..1f4e371 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -30,12 +30,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
+import android.os.Build;
 import android.provider.DeviceConfig;
 import android.telephony.TelephonyManager;
 
@@ -43,9 +47,14 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.DeviceConfigUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -60,13 +69,18 @@
 public class TetheringConfigurationTest {
     private final SharedLog mLog = new SharedLog("TetheringConfigurationTest");
 
+    @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
     private static final String PROVISIONING_APP_RESPONSE = "app_response";
+    private static final String TEST_PACKAGE_NAME = "com.android.tethering.test";
+    private static final long TEST_PACKAGE_VERSION = 1234L;
     @Mock private Context mContext;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private Resources mResources;
     @Mock private Resources mResourcesForSubId;
+    @Mock private PackageManager mPackageManager;
     private Context mMockContext;
     private boolean mHasTelephonyManager;
     private boolean mEnableLegacyDhcpServer;
@@ -100,6 +114,16 @@
             }
             return super.getSystemService(name);
         }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        public String getPackageName() {
+            return TEST_PACKAGE_NAME;
+        }
     }
 
     @Before
@@ -110,9 +134,15 @@
                 .mockStatic(DeviceConfig.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
+        DeviceConfigUtils.resetPackageVersionCacheForTest();
         doReturn(null).when(
                 () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
                 eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        setTetherForceUpstreamAutomaticFlagVersion(null);
+
+        final PackageInfo pi = new PackageInfo();
+        pi.setLongVersionCode(TEST_PACKAGE_VERSION);
+        doReturn(pi).when(mPackageManager).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt());
 
         when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
                 new String[0]);
@@ -141,6 +171,7 @@
     @After
     public void tearDown() throws Exception {
         mMockingSession.finishMocking();
+        DeviceConfigUtils.resetPackageVersionCacheForTest();
     }
 
     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
@@ -455,4 +486,52 @@
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
         assertTrue(testEnable.isSelectAllPrefixRangeEnabled());
     }
+
+    @Test
+    public void testChooseUpstreamAutomatically() throws Exception {
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
+                .thenReturn(true);
+        assertChooseUpstreamAutomaticallyIs(true);
+
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
+                .thenReturn(false);
+        assertChooseUpstreamAutomaticallyIs(false);
+    }
+
+    // The flag override only works on R-
+    @Test @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testChooseUpstreamAutomatically_FlagOverride() throws Exception {
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
+                .thenReturn(false);
+        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mMockContext, NAMESPACE_CONNECTIVITY,
+                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION));
+
+        assertChooseUpstreamAutomaticallyIs(true);
+
+        setTetherForceUpstreamAutomaticFlagVersion(0L);
+        assertChooseUpstreamAutomaticallyIs(false);
+
+        setTetherForceUpstreamAutomaticFlagVersion(Long.MAX_VALUE);
+        assertChooseUpstreamAutomaticallyIs(false);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testChooseUpstreamAutomatically_FlagOverrideAfterR() throws Exception {
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
+                .thenReturn(false);
+        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
+        assertChooseUpstreamAutomaticallyIs(false);
+    }
+
+    private void setTetherForceUpstreamAutomaticFlagVersion(Long version) {
+        doReturn(version == null ? null : Long.toString(version)).when(
+                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+                        eq(TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION)));
+    }
+
+    private void assertChooseUpstreamAutomaticallyIs(boolean value) {
+        assertEquals(value, new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)
+                .chooseUpstreamAutomatically);
+    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 60fddb5..c5a43b7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -358,6 +358,11 @@
         }
 
         @Override
+        protected boolean isFeatureEnabled(Context ctx, String featureVersionFlag) {
+            return false;
+        }
+
+        @Override
         protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
             return mResources;
         }
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 813e6c7..f351b47 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -30,6 +30,7 @@
         "ctstestrunner-axt",
         "ub-uiautomator",
         "CtsHostsideNetworkTestsAidl",
+        "modules-utils-build",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index a663cd6..9b437e6 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -75,6 +75,7 @@
 import android.util.Log;
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
@@ -740,13 +741,14 @@
                 getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
         receiver.register();
 
-
         // Expect the system default network not to change.
         final NeverChangeNetworkCallback neverChangeCallback = new NeverChangeNetworkCallback();
         final Network defaultNetwork = mCM.getActiveNetwork();
-        runWithShellPermissionIdentity(() ->
-                mCM.registerSystemDefaultNetworkCallback(neverChangeCallback,
-                        new Handler(Looper.getMainLooper())), NETWORK_SETTINGS);
+        if (SdkLevel.isAtLeastS()) {
+            runWithShellPermissionIdentity(() ->
+                    mCM.registerSystemDefaultNetworkCallback(neverChangeCallback,
+                            new Handler(Looper.getMainLooper())), NETWORK_SETTINGS);
+        }
 
         FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
 
@@ -765,19 +767,21 @@
 
         checkTrafficOnVpn();
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
 
-        // Check that system default network callback has not seen any network changes, even though
-        // the app's default network changed. This needs to be done before testing private
-        // DNS because checkStrictModePrivateDns will set the private DNS server to a nonexistent
-        // name, which will cause validation to fail and cause the default network to switch (e.g.,
-        // from wifi to cellular).
-        assertEquals(defaultNetwork, neverChangeCallback.getFirstNetwork());
         assertNotEqual(defaultNetwork, mCM.getActiveNetwork());
-        neverChangeCallback.assertNeverChanged();
-        runWithShellPermissionIdentity(
-                () ->  mCM.unregisterNetworkCallback(neverChangeCallback),
-                NETWORK_SETTINGS);
+        if (SdkLevel.isAtLeastS()) {
+            // Check that system default network callback has not seen any network changes, even
+            // though the app's default network changed. This needs to be done before testing
+            // private DNS because checkStrictModePrivateDns will set the private DNS server to
+            // a nonexistent name, which will cause validation to fail and cause the default
+            // network to switch (e.g., from wifi to cellular).
+            assertEquals(defaultNetwork, neverChangeCallback.getFirstNetwork());
+            neverChangeCallback.assertNeverChanged();
+            runWithShellPermissionIdentity(
+                    () -> mCM.unregisterNetworkCallback(neverChangeCallback),
+                    NETWORK_SETTINGS);
+        }
 
         checkStrictModePrivateDns();
 
@@ -799,7 +803,7 @@
 
         checkTrafficOnVpn();
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
 
         checkStrictModePrivateDns();
     }
@@ -988,7 +992,7 @@
         assertTrue(isNetworkMetered(mNetwork));
         assertTrue(mCM.isActiveNetworkMetered());
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
     }
 
     public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
@@ -1016,7 +1020,7 @@
         // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
         assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
     }
 
     public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
@@ -1045,7 +1049,7 @@
         // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
         assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
     }
 
     public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
@@ -1071,7 +1075,7 @@
         assertTrue(isNetworkMetered(mNetwork));
         assertTrue(mCM.isActiveNetworkMetered());
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
     }
 
     public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
@@ -1098,7 +1102,7 @@
         assertTrue(isNetworkMetered(mNetwork));
         assertTrue(mCM.isActiveNetworkMetered());
 
-        expectVpnTransportInfo(mCM.getActiveNetwork());
+        maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
     }
 
     public void testB141603906() throws Exception {
@@ -1148,7 +1152,8 @@
         }
     }
 
-    private void expectVpnTransportInfo(Network network) {
+    private void maybeExpectVpnTransportInfo(Network network) {
+        if (!SdkLevel.isAtLeastS()) return;
         final NetworkCapabilities vpnNc = mCM.getNetworkCapabilities(network);
         assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
         final TransportInfo ti = vpnNc.getTransportInfo();
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index aea33ca..1046b50 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -35,6 +35,7 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
@@ -67,6 +68,7 @@
 import androidx.test.InstrumentationRegistry
 import com.android.connectivity.aidl.INetworkAgent
 import com.android.connectivity.aidl.INetworkAgentRegistry
+import com.android.modules.utils.build.SdkLevel
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
@@ -321,6 +323,9 @@
             addCapability(NET_CAPABILITY_NOT_SUSPENDED)
             addCapability(NET_CAPABILITY_NOT_ROAMING)
             addCapability(NET_CAPABILITY_NOT_VPN)
+            if (SdkLevel.isAtLeastS()) {
+                addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+            }
             if (null != name) {
                 setNetworkSpecifier(StringNetworkSpecifier(name))
             }
@@ -558,6 +563,9 @@
             addTransportType(TRANSPORT_VPN)
             removeCapability(NET_CAPABILITY_NOT_VPN)
             setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE))
+            if (SdkLevel.isAtLeastS()) {
+                addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+            }
         }
         val defaultNetwork = mCM.activeNetwork
         assertNotNull(defaultNetwork)
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index d118c8a..30c4e72 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -16,8 +16,12 @@
 
 package android.net.cts;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -29,6 +33,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.NonNull;
 import android.net.MacAddress;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkCapabilities;
@@ -43,6 +48,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.networkstack.apishim.ConstantsShim;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
@@ -152,29 +159,44 @@
                 .getRequestorPackageName());
     }
 
+    private void addNotVcnManagedCapability(@NonNull NetworkCapabilities nc) {
+        if (SdkLevel.isAtLeastS()) {
+            nc.addCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+    }
+
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testCanBeSatisfiedBy() {
         final LocalNetworkSpecifier specifier1 = new LocalNetworkSpecifier(1234 /* id */);
         final LocalNetworkSpecifier specifier2 = new LocalNetworkSpecifier(5678 /* id */);
 
+        // Some requests are adding NOT_VCN_MANAGED capability automatically. Add it to the
+        // capabilities below for bypassing the check.
         final NetworkCapabilities capCellularMmsInternet = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_MMS)
                 .addCapability(NET_CAPABILITY_INTERNET);
+        addNotVcnManagedCapability(capCellularMmsInternet);
         final NetworkCapabilities capCellularVpnMmsInternet =
                 new NetworkCapabilities(capCellularMmsInternet).addTransportType(TRANSPORT_VPN);
+        addNotVcnManagedCapability(capCellularVpnMmsInternet);
         final NetworkCapabilities capCellularMmsInternetSpecifier1 =
                 new NetworkCapabilities(capCellularMmsInternet).setNetworkSpecifier(specifier1);
+        addNotVcnManagedCapability(capCellularMmsInternetSpecifier1);
         final NetworkCapabilities capVpnInternetSpecifier1 = new NetworkCapabilities()
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addTransportType(TRANSPORT_VPN)
                 .setNetworkSpecifier(specifier1);
+        addNotVcnManagedCapability(capVpnInternetSpecifier1);
         final NetworkCapabilities capCellularMmsInternetMatchallspecifier =
                 new NetworkCapabilities(capCellularMmsInternet)
-                    .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+                        .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        addNotVcnManagedCapability(capCellularMmsInternetMatchallspecifier);
         final NetworkCapabilities capCellularMmsInternetSpecifier2 =
-                new NetworkCapabilities(capCellularMmsInternet).setNetworkSpecifier(specifier2);
+                new NetworkCapabilities(capCellularMmsInternet)
+                        .setNetworkSpecifier(specifier2);
+        addNotVcnManagedCapability(capCellularMmsInternetSpecifier2);
 
         final NetworkRequest requestCellularInternetSpecifier1 = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
@@ -239,7 +261,8 @@
 
         final NetworkCapabilities capCellInternetBWSpecifier1Signal =
                 new NetworkCapabilities.Builder(capCellInternetBWSpecifier1)
-                    .setSignalStrength(-123).build();
+                        .setSignalStrength(-123).build();
+        addNotVcnManagedCapability(capCellInternetBWSpecifier1Signal);
         assertCorrectlySatisfies(true, requestCombination,
                 capCellInternetBWSpecifier1Signal);
 
@@ -273,4 +296,80 @@
         assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
                 .clearCapabilities().build().getRequestorUid());
     }
+
+    // TODO: 1. Refactor test cases with helper method.
+    //       2. Test capability that does not yet exist.
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testBypassingVcnForNonInternetRequest() {
+        // Make an empty request. Verify the NOT_VCN_MANAGED is added.
+        final NetworkRequest emptyRequest = new NetworkRequest.Builder().build();
+        assertTrue(emptyRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a request explicitly add NOT_VCN_MANAGED. Verify the NOT_VCN_MANAGED is preserved.
+        final NetworkRequest mmsAddNotVcnRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_MMS)
+                .addCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .build();
+        assertTrue(mmsAddNotVcnRequest.hasCapability(
+                ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Similar to above, but the opposite order.
+        final NetworkRequest mmsAddNotVcnRequest2 = new NetworkRequest.Builder()
+                .addCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addCapability(NET_CAPABILITY_MMS)
+                .build();
+        assertTrue(mmsAddNotVcnRequest2.hasCapability(
+                ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a request explicitly remove NOT_VCN_MANAGED. Verify the NOT_VCN_MANAGED is removed.
+        final NetworkRequest removeNotVcnRequest = new NetworkRequest.Builder()
+                .removeCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED).build();
+        assertFalse(removeNotVcnRequest.hasCapability(
+                ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a request add some capability inside VCN supported capabilities.
+        // Verify the NOT_VCN_MANAGED is added.
+        final NetworkRequest notRoamRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_NOT_ROAMING).build();
+        assertTrue(notRoamRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a internet request. Verify the NOT_VCN_MANAGED is added.
+        final NetworkRequest internetRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET).build();
+        assertTrue(internetRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a internet request which explicitly removed NOT_VCN_MANAGED.
+        // Verify the NOT_VCN_MANAGED is removed.
+        final NetworkRequest internetRemoveNotVcnRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .removeCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED).build();
+        assertFalse(internetRemoveNotVcnRequest.hasCapability(
+                ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a normal MMS request. Verify the request could bypass VCN.
+        final NetworkRequest mmsRequest =
+                new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build();
+        assertFalse(mmsRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a SUPL request along with internet. Verify NOT_VCN_MANAGED is not added since
+        // SUPL is not in the supported list.
+        final NetworkRequest suplWithInternetRequest = new NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_SUPL)
+                        .addCapability(NET_CAPABILITY_INTERNET).build();
+        assertFalse(suplWithInternetRequest.hasCapability(
+                ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a FOTA request with explicitly add NOT_VCN_MANAGED capability. Verify
+        // NOT_VCN_MANAGED is preserved.
+        final NetworkRequest fotaRequest = new NetworkRequest.Builder()
+                        .addCapability(NET_CAPABILITY_FOTA)
+                        .addCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED).build();
+        assertTrue(fotaRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+        // Make a DUN request, which is in {@code VCN_SUPPORTED_CAPABILITIES}.
+        // Verify NOT_VCN_MANAGED is preserved.
+        final NetworkRequest dunRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_DUN).build();
+        assertTrue(dunRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+    }
 }