Merge "Add CTS test for resolution stop failed"
diff --git a/Cronet/tests/cts/OWNERS b/Cronet/tests/OWNERS
similarity index 100%
rename from Cronet/tests/cts/OWNERS
rename to Cronet/tests/OWNERS
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
index 939a81c..5d2f6e5 100644
--- a/Cronet/tests/common/Android.bp
+++ b/Cronet/tests/common/Android.bp
@@ -28,7 +28,6 @@
     name: "NetHttpCoverageTests",
     defaults: ["CronetTestJavaDefaults"],
     enforce_default_target_sdk_version: true,
-    sdk_version: "test_current",
     min_sdk_version: "30",
     test_suites: ["general-tests", "mts-tethering"],
     static_libs: [
@@ -36,6 +35,9 @@
         "CtsNetHttpTestsLib",
         "NetHttpTestsLibPreJarJar",
     ],
-    jarjar_rules: ":framework-tethering-jarjar-rules",
+    jarjar_rules: ":net-http-test-jarjar-rules",
     compile_multilib: "both", // Include both the 32 and 64 bit versions
+    jni_libs: [
+       "cronet_aml_components_cronet_android_cronet_tests__testing"
+    ],
 }
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index 78ba71e..ed86854 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -26,6 +26,7 @@
 import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
@@ -49,6 +50,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Set;
+
 @RunWith(AndroidJUnit4.class)
 public class HttpEngineTest {
     private static final String HOST = "source.android.com";
@@ -182,6 +188,38 @@
         // server.
     }
 
+    private byte[] generateSha256() {
+        byte[] sha256 = new byte[32];
+        Arrays.fill(sha256, (byte) 58);
+        return sha256;
+    }
+
+    private Instant instantInFuture(int secondsIntoFuture) {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.SECOND, secondsIntoFuture);
+        return cal.getTime().toInstant();
+    }
+
+    @Test
+    public void testHttpEngine_AddPublicKeyPins() {
+        // CtsTestServer, when set in SslMode.NO_CLIENT_AUTH (required to trigger
+        // certificate verification, needed by this test), uses a certificate that
+        // doesn't match the hostname. For this reason, CtsTestServer cannot be used
+        // by this test.
+        Instant expirationInstant = instantInFuture(/* secondsIntoFuture */ 100);
+        boolean includeSubdomains = true;
+        Set<byte[]> pinsSha256 = Set.of(generateSha256());
+        mEngine = mEngineBuilder.addPublicKeyPins(
+                HOST, pinsSha256, includeSubdomains, expirationInstant).build();
+
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+        mCallback.expectCallback(ResponseStep.ON_FAILED);
+        assertNotNull("Expected an error", mCallback.mError);
+    }
+
     @Test
     public void testHttpEngine_EnableQuic() throws Exception {
         mEngine = mEngineBuilder.setEnableQuic(true).addQuicHint(HOST, 443, 443).build();
@@ -370,4 +408,20 @@
         // This way, if the request were to fail, the test would just be skipped instead of failing.
         assumeOKStatusCode(mCallback.mResponseInfo);
     }
+
+    @Test
+    public void testHttpEngine_enableBrotli_brotliAdvertised() {
+        mEngine = mEngineBuilder.setEnableBrotli(true).build();
+        mRequest =
+                mEngine.newUrlRequestBuilder(
+                        mTestServer.getEchoHeadersUrl(), mCallback.getExecutor(), mCallback)
+                        .build();
+        mRequest.start();
+
+        mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertThat(info.getHeaders().getAsMap().get("x-request-header-Accept-Encoding").toString())
+                .contains("br");
+        assertOKStatusCode(info);
+    }
 }
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index ac71653..adbc384 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -17,19 +17,39 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_genrule {
+    name: "net-http-test-jarjar-rules",
+    tool_files: [
+        ":NetHttpTestsLibPreJarJar{.jar}",
+        "jarjar_excludes.txt",
+    ],
+    tools: [
+        "jarjar-rules-generator",
+    ],
+    out: ["net_http_test_jarjar_rules.txt"],
+    cmd: "$(location jarjar-rules-generator) " +
+        "$(location :NetHttpTestsLibPreJarJar{.jar}) " +
+        "--prefix android.net.http.internal " +
+        "--excludes $(location jarjar_excludes.txt) " +
+        "--output $(out)",
+}
+
 android_library {
     name: "NetHttpTestsLibPreJarJar",
-    srcs: ["//external/cronet:cronet_aml_javatests_sources"],
-    sdk_version: "test_current",
+    srcs: [":cronet_aml_javatests_sources"],
+    sdk_version: "module_current",
     min_sdk_version: "30",
     static_libs: [
+        "cronet_testserver_utils",
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "junit",
     ],
     libs: [
         "android.test.base",
-        "framework-tethering-pre-jarjar",
+        // Needed for direct access to tethering's hidden apis and to avoid `symbol not found`
+        //  errors on some builds.
+        "framework-tethering.impl",
     ],
     lint: { test: true }
 }
@@ -40,9 +60,11 @@
         "CronetTestJavaDefaults",
         "mts-target-sdk-version-current",
      ],
-     sdk_version: "test_current",
      static_libs: ["NetHttpTestsLibPreJarJar"],
-     jarjar_rules: ":framework-tethering-jarjar-rules",
+     jarjar_rules: ":net-http-test-jarjar-rules",
+     jni_libs: [
+        "cronet_aml_components_cronet_android_cronet_tests__testing"
+     ],
      test_suites: [
          "general-tests",
          "mts-tethering",
diff --git a/Cronet/tests/mts/AndroidManifest.xml b/Cronet/tests/mts/AndroidManifest.xml
index 62c2060..f597134 100644
--- a/Cronet/tests/mts/AndroidManifest.xml
+++ b/Cronet/tests/mts/AndroidManifest.xml
@@ -21,7 +21,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.INTERNET"/>
 
-    <application>
+    <application android:networkSecurityConfig="@xml/network_security_config">
         <uses-library android:name="android.test.runner" />
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
new file mode 100644
index 0000000..01f4d6a
--- /dev/null
+++ b/Cronet/tests/mts/jarjar_excludes.txt
@@ -0,0 +1,10 @@
+# It's prohibited to jarjar androidx packages
+androidx\..+
+# Do not jarjar the api classes
+android\.net\..+
+# cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a
+# separate java base target to depend on.
+org\.chromium\.base\..+
+# Do not jarjar the tests and its utils as they also do JNI with cronet_tests.so
+org\.chromium\.net\..*Test.*(\$.+)?
+org\.chromium\.net\.NativeTestServer(\$.+)?
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/xml/network_security_config.xml b/Cronet/tests/mts/res/xml/network_security_config.xml
new file mode 100644
index 0000000..d44c36f
--- /dev/null
+++ b/Cronet/tests/mts/res/xml/network_security_config.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <!-- Used as the base URL by native test server (net::EmbeddedTestServer) -->
+        <domain includeSubdomains="true">127.0.0.1</domain>
+        <!-- Used by CronetHttpURLConnectionTest#testIOExceptionInterruptRethrown -->
+        <domain includeSubdomains="true">localhost</domain>
+        <!-- Used by CronetHttpURLConnectionTest#testBadIP -->
+        <domain includeSubdomains="true">0.0.0.0</domain>
+        <!-- Used by CronetHttpURLConnectionTest#testSetUseCachesFalse -->
+        <domain includeSubdomains="true">host-cache-test-host</domain>
+        <!-- Used by CronetHttpURLConnectionTest#testBadHostname -->
+        <domain includeSubdomains="true">this-weird-host-name-does-not-exist</domain>
+        <!-- Used by CronetUrlRequestContextTest#testHostResolverRules -->
+        <domain includeSubdomains="true">some-weird-hostname</domain>
+    </domain-config>
+</network-security-config>
\ No newline at end of file
diff --git a/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky
index 8353fd3..5372a4d 100644
--- a/Cronet/tools/import/copy.bara.sky
+++ b/Cronet/tools/import/copy.bara.sky
@@ -20,6 +20,7 @@
     # Exclude existing *OWNERS files
     "**/*OWNERS",
     "**/.git/**",
+    "**/.gitignore",
 ]
 
 cronet_origin_files = glob(
@@ -29,7 +30,6 @@
         "build/buildflag.h",
         "chrome/VERSION",
         "components/cronet/**",
-        "components/grpc_suport/**",
         "components/metrics/**",
         "components/nacl/**",
         "components/prefs/**",
@@ -97,7 +97,9 @@
         "third_party/protobuf/**",
         # Note: Only used for tests.
         "third_party/quic_trace/**",
-        "third_party/zlib/**",
+        # Note: Cronet currently uses Android's zlib
+        # "third_party/zlib/**",
+        "url/third_party/mozilla/**",
     ],
     exclude = common_excludes,
 )
diff --git a/Cronet/tools/import/import_cronet.sh b/Cronet/tools/import/import_cronet.sh
index d0c8deb..0f04af7 100755
--- a/Cronet/tools/import/import_cronet.sh
+++ b/Cronet/tools/import/import_cronet.sh
@@ -24,6 +24,8 @@
 #   -n rev: The new revision to import.
 #   -f: Force copybara to ignore a failure to find the last imported revision.
 
+set -e -x
+
 OPTSTRING=fl:n:
 
 usage() {
@@ -36,7 +38,7 @@
 COPYBARA_FOLDER_ORIGIN="/tmp/copybara-origin"
 
 #######################################
-# Create upstream-import branch in external/cronet.
+# Create local upstream-import branch in external/cronet.
 # Globals:
 #   ANDROID_BUILD_TOP
 # Arguments:
@@ -44,10 +46,8 @@
 #######################################
 setup_upstream_import_branch() {
     local git_dir="${ANDROID_BUILD_TOP}/external/cronet"
-    local initial_empty_repo_sha="d1add53d6e90815f363c91d433735556ce79b0d2"
 
-    # Suppress error message if branch already exists.
-    (cd "${git_dir}" && git branch upstream-import "${initial_empty_repo_sha}") 2>/dev/null
+    (cd "${git_dir}" && git fetch aosp upstream-import:upstream-import)
 }
 
 #######################################
@@ -57,13 +57,18 @@
 # Arguments:
 #   new_rev, string
 #######################################
-setup_folder_origin() {
+setup_folder_origin() (
     local _new_rev=$1
     mkdir -p "${COPYBARA_FOLDER_ORIGIN}"
     cd "${COPYBARA_FOLDER_ORIGIN}"
 
-    # For this to work _new_rev must be a branch or a tag.
-    git clone --depth=1 --branch "${_new_rev}" https://chromium.googlesource.com/chromium/src.git
+    if [ -d src ]; then
+        (cd src && git fetch --tags && git checkout "${_new_rev}")
+    else
+        # For this to work _new_rev must be a branch or a tag.
+        git clone --depth=1 --branch "${_new_rev}" https://chromium.googlesource.com/chromium/src.git
+    fi
+
 
     cat <<EOF >.gclient
 solutions = [
@@ -80,9 +85,10 @@
     cd src
     # Set appropriate gclient flags to speed up syncing.
     gclient sync \
-        --no-history
-        --shallow
-}
+        --no-history \
+        --shallow \
+        --delete_unversioned_trees
+)
 
 #######################################
 # Runs the copybara import of Chromium
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index ff5acf5..3f35c6b 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -38,6 +38,13 @@
     name: "ConnectivityNextEnableDefaults",
     enabled: true,
 }
+java_defaults {
+    name: "NetworkStackApiShimSettingsForCurrentBranch",
+    // API shims to include in the networking modules built from the branch. Branches that disable
+    // the "next" targets must use stable shims (latest stable API level) instead of current shims
+    // (X_current API level).
+    static_libs: ["NetworkStackApiCurrentShims"],
+}
 apex_defaults {
     name: "ConnectivityApexDefaults",
     // Tethering app to include in the AOSP apex. Branches that disable the "next" targets may use
@@ -67,7 +74,7 @@
 apex_defaults {
     name: "CronetInTetheringApexDefaultsEnabled",
     jni_libs: [
-        "//external/cronet:cronet_aml_components_cronet_android_cronet",
+        "cronet_aml_components_cronet_android_cronet",
         "//external/cronet/third_party/boringssl:libcrypto",
         "//external/cronet/third_party/boringssl:libssl",
     ],
@@ -75,7 +82,7 @@
         riscv64: {
             // TODO: remove this when there is a riscv64 libcronet
             exclude_jni_libs: [
-                "//external/cronet:cronet_aml_components_cronet_android_cronet",
+                "cronet_aml_components_cronet_android_cronet",
                 "//external/cronet/third_party/boringssl:libcrypto",
                 "//external/cronet/third_party/boringssl:libssl",
             ],
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index a3756e0..74170cb 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -54,6 +54,7 @@
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/Cronet/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
@@ -75,12 +76,12 @@
 
 java_defaults {
     name: "CronetJavaDefaultsEnabled",
-    srcs: ["//external/cronet:cronet_aml_api_sources"],
+    srcs: [":cronet_aml_api_sources"],
     libs: [
         "androidx.annotation_annotation",
     ],
     impl_only_static_libs: [
-        "//external/cronet:cronet_aml_java",
+        "cronet_aml_java",
     ],
     api_dir: "cronet_enabled/api",
 }
@@ -101,8 +102,8 @@
 java_defaults {
   name: "CronetJavaPrejarjarDefaultsEnabled",
   static_libs: [
-    "//external/cronet:cronet_aml_api_java",
-    "//external/cronet:cronet_aml_java"
+    "cronet_aml_api_java",
+    "cronet_aml_java"
   ],
 }
 
diff --git a/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
index 6699c0d..a878fa5 100644
--- a/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
+++ b/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
@@ -67,17 +67,17 @@
     }
 }
 
-static void com_android_networkstack_tethering_util_setupNaSocket(JNIEnv *env, jobject clazz,
+static void com_android_networkstack_tethering_util_setupNaSocket(JNIEnv *env, jclass clazz,
         jobject javaFd) {
     com_android_networkstack_tethering_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_ADVERT);
 }
 
-static void com_android_networkstack_tethering_util_setupNsSocket(JNIEnv *env, jobject clazz,
+static void com_android_networkstack_tethering_util_setupNsSocket(JNIEnv *env, jclass clazz,
         jobject javaFd) {
     com_android_networkstack_tethering_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_SOLICIT);
 }
 
-static void com_android_networkstack_tethering_util_setupRaSocket(JNIEnv *env, jobject clazz,
+static void com_android_networkstack_tethering_util_setupRaSocket(JNIEnv *env, jclass clazz,
         jobject javaFd, jint ifIndex) {
     static const int kLinkLocalHopLimit = 255;
 
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 1a8d46b..f8d7e4c 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -46,6 +46,7 @@
     libs: [
         "unsupportedappusage",
         "app-compat-annotations",
+        "androidx.annotation_annotation",
     ],
     impl_only_libs: [
         // The build system will use framework-bluetooth module_current stubs, because
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index d90bd8d..b3c70cf 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -47,6 +47,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -58,7 +59,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -279,6 +282,102 @@
         return new NetworkTemplate(MATCH_PROXY, null, null);
     }
 
+    /**
+     * Template to match all metered carrier networks with the given IMSI.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "buildTemplateCarrierMetered is not supported on Android U devices or above");
+        }
+        return new NetworkTemplate.Builder(MATCH_CARRIER)
+                // Set.of will throw if subscriberId is null
+                .setSubscriberIds(Set.of(subscriberId))
+                .setMeteredness(METERED_YES)
+                .build();
+    }
+
+    /**
+     * Template to match cellular networks with the given IMSI, {@code ratType} and
+     * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
+     * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
+            int ratType, int metered) {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException("buildTemplateMobileWithRatType is not "
+                    + "supported on Android U devices or above");
+        }
+        return new NetworkTemplate.Builder(MATCH_MOBILE)
+                .setSubscriberIds(TextUtils.isEmpty(subscriberId)
+                        ? Collections.emptySet()
+                        : Set.of(subscriberId))
+                .setMeteredness(metered)
+                .setRatType(ratType)
+                .build();
+    }
+
+
+    /**
+     * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
+     * given key of the wifi network.
+     *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+     *                  to know details about the key.
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException("buildTemplateWifi is not "
+                    + "supported on Android U devices or above");
+        }
+        return new NetworkTemplate.Builder(MATCH_WIFI)
+                // Set.of will throw if wifiNetworkKey is null
+                .setWifiNetworkKeys(Set.of(wifiNetworkKey))
+                .build();
+    }
+
+    /**
+     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given
+     * key of the wifi network and IMSI.
+     *
+     * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
+     * of key of the wifi network.
+     *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+     *                  to know details about the key.
+     * @param subscriberId the IMSI associated to this wifi network.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey,
+            @Nullable String subscriberId) {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException("buildTemplateWifi is not "
+                    + "supported on Android U devices or above");
+        }
+        return new NetworkTemplate.Builder(MATCH_WIFI)
+                .setSubscriberIds(subscriberId == null
+                        ? Collections.emptySet()
+                        : Set.of(subscriberId))
+                .setWifiNetworkKeys(wifiNetworkKey == null
+                        ? Collections.emptySet()
+                        : Set.of(wifiNetworkKey))
+                .build();
+    }
+
     private final int mMatchRule;
 
     /**
@@ -830,8 +929,7 @@
      * subscribers.
      * <p>
      * For example, given an incoming template matching B, and the currently
-     * active merge set [A,B], we'd return a new template that primarily matches
-     * A, but also matches B.
+     * active merge set [A,B], we'd return a new template that matches both A and B.
      *
      * @hide
      */
@@ -840,6 +938,49 @@
                     + "Callers should have their own logic to merge template for"
                     + " different IMSIs and stop calling this function.")
     public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
+        return normalizeImpl(template, Collections.singletonList(merged));
+    }
+
+    /**
+     * Examine the given template and normalize it.
+     * We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     *
+     * There can be multiple merged subscriberIds for multi-SIM devices.
+     *
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that matches both A and B.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "normalize is not supported on Android U devices or above");
+        }
+        return normalizeImpl(template, mergedList);
+    }
+
+    /**
+     * Examine the given template and normalize it.
+     * We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     *
+     * There can be multiple merged subscriberIds for multi-SIM devices.
+     *
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that matches both A and B.
+     *
+     * @hide
+     */
+    private static NetworkTemplate normalizeImpl(NetworkTemplate template,
+            List<String[]> mergedList) {
         // Now there are several types of network which uses SubscriberId to store network
         // information. For instances:
         // The TYPE_WIFI with subscriberId means that it is a merged carrier wifi network.
@@ -847,18 +988,21 @@
 
         if (CollectionUtils.isEmpty(template.mMatchSubscriberIds)) return template;
 
-        if (CollectionUtils.contains(merged, template.mMatchSubscriberIds[0])) {
-            // Requested template subscriber is part of the merge group; return
-            // a template that matches all merged subscribers.
-            final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
-            // TODO: Use NetworkTemplate.Builder to build a template after NetworkTemplate
-            // could handle incompatible subscriberIds. See b/217805241.
-            return new NetworkTemplate(template.mMatchRule, merged,
-                    CollectionUtils.isEmpty(matchWifiNetworkKeys)
-                            ? new String[0] : new String[] { matchWifiNetworkKeys[0] },
-                    (template.mMatchRule == MATCH_MOBILE || template.mMatchRule == MATCH_CARRIER)
-                            ? METERED_YES : METERED_ALL,
-                    ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+        for (String[] merged : mergedList) {
+            if (CollectionUtils.contains(merged, template.mMatchSubscriberIds[0])) {
+                // Requested template subscriber is part of the merge group; return
+                // a template that matches all merged subscribers.
+                final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
+                // TODO: Use NetworkTemplate.Builder to build a template after NetworkTemplate
+                // could handle incompatible subscriberIds. See b/217805241.
+                return new NetworkTemplate(template.mMatchRule, merged,
+                        CollectionUtils.isEmpty(matchWifiNetworkKeys)
+                                ? new String[0] : new String[] { matchWifiNetworkKeys[0] },
+                        (template.mMatchRule == MATCH_MOBILE
+                                || template.mMatchRule == MATCH_CARRIER)
+                                ? METERED_YES : METERED_ALL,
+                        ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+            }
         }
 
         return template;
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index f6e0995..278f823 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -45,6 +45,7 @@
     srcs: [":framework-nearby-java-sources"],
     sdk_version: "module_current",
     libs: [
+        "androidx.annotation_annotation",
         "framework-annotations-lib",
         "framework-bluetooth",
     ],
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index b81032d..b6c1c9d 100644
--- a/nearby/tests/multidevices/host/Android.bp
+++ b/nearby/tests/multidevices/host/Android.bp
@@ -22,7 +22,10 @@
     name: "NearbyMultiDevicesTestSuite",
     main: "suite_main.py",
     srcs: ["*.py"],
-    libs: ["NearbyMultiDevicesHostHelper"],
+    libs: [
+        "NearbyMultiDevicesHostHelper",
+        "mobly",
+    ],
     test_suites: [
         "general-tests",
         "mts-tethering",
@@ -38,6 +41,11 @@
         // Package the JSON metadata with the Mobly test.
         "test_data/**/*",
     ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
 }
 
 python_library_host {
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index c1f6a70..fff0ed1 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -42,11 +42,6 @@
             <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
             <option name="run-command" value="wm dismiss-keyguard" />
         </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
-          <!-- Any python dependencies can be specified and will be installed with pip -->
-          <!-- TODO(b/225958696): Import python dependencies -->
-          <option name="dep-module" value="mobly" />
-        </target_preparer>
         <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
             <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
             <option name="screen-always-on" value="on" />
diff --git a/nearby/tests/multidevices/host/suite_main.py b/nearby/tests/multidevices/host/suite_main.py
index 4f5d48c..9a580fb 100644
--- a/nearby/tests/multidevices/host/suite_main.py
+++ b/nearby/tests/multidevices/host/suite_main.py
@@ -31,11 +31,9 @@
 ]
 
 
-def _valid_argument(arg: str) -> bool:
-    return arg.startswith(('--config', '-c', '--tests', '--test_case'))
-
-
 if __name__ == '__main__':
     logging.basicConfig(filename=_BOOTSTRAP_LOGGING_FILENAME, level=logging.INFO)
-    suite_runner.run_suite(argv=[arg for arg in sys.argv if _valid_argument(arg)],
-                           test_classes=_TEST_CLASSES_LIST)
+    if '--' in sys.argv:
+        index = sys.argv.index('--')
+        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+    suite_runner.run_suite(test_classes=_TEST_CLASSES_LIST)
diff --git a/service/Android.bp b/service/Android.bp
index 1523af9..e1376a1 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -138,6 +138,14 @@
     name: "service-connectivity-pre-jarjar",
     sdk_version: "system_server_current",
     min_sdk_version: "30",
+    // NetworkStackApiShimSettingsForCurrentBranch provides the latest available shims depending on
+    // the branch to "service-connectivity".
+    // There are Tethering.apk and TetheringNext.apk variants for the tethering APEX,
+    // which use NetworkStackApiStableShims and NetworkStackApiCurrentShims respectively.
+    // Note that there can be no service-connectivity-next because it would need to be configured in
+    // default_art_config.mk which doesn't support conditionals, hence this scheme of using a
+    // variable here.
+    defaults: ["NetworkStackApiShimSettingsForCurrentBranch"],
     srcs: [
         "src/**/*.java",
         ":framework-connectivity-shared-srcs",
@@ -183,7 +191,6 @@
         "PlatformProperties",
         "service-connectivity-protos",
         "service-connectivity-stats-protos",
-        "NetworkStackApiStableShims",
     ],
     apex_available: [
         "com.android.tethering",
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 062d272..ad4596d 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -52,7 +52,7 @@
 }
 
 jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env,
-                                                                          jobject clazz,
+                                                                          jclass clazz,
                                                                           jstring v4addr,
                                                                           jint prefixlen) {
     ScopedUtfChars address(env, v4addr);
@@ -84,7 +84,7 @@
 
 // Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
 jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address(
-        JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str,
+        JNIEnv* env, jclass clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str,
         jint mark) {
     ScopedUtfChars iface(env, ifaceStr);
     ScopedUtfChars addr4(env, v4Str);
@@ -125,7 +125,7 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env,
-                                                                               jobject clazz,
+                                                                               jclass clazz,
                                                                                jstring tuniface) {
     ScopedUtfChars v4interface(env, tuniface);
 
@@ -152,7 +152,7 @@
     return fd;
 }
 
-static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz,
+static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jclass clazz,
                                                                       jstring platSubnet,
                                                                       jint plat_suffix, jint mark) {
     ScopedUtfChars platSubnetStr(env, platSubnet);
@@ -174,7 +174,7 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env,
-                                                                              jobject clazz) {
+                                                                              jclass clazz) {
     // Will eventually be bound to htons(ETH_P_IPV6) protocol,
     // but only after appropriate bpf filter is attached.
     const int sock = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0);
@@ -199,7 +199,7 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env,
-                                                                           jobject clazz,
+                                                                           jclass clazz,
                                                                            jint mark) {
     int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW);
     if (sock < 0) {
@@ -218,7 +218,7 @@
 }
 
 static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt(
-        JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+        JNIEnv* env, jclass clazz, jobject javaFd, jstring addr6, jint ifindex) {
     int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
     if (sock < 0) {
         jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
@@ -244,7 +244,7 @@
 }
 
 static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket(
-        JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+        JNIEnv* env, jclass clazz, jobject javaFd, jstring addr6, jint ifindex) {
     ScopedUtfChars addrStr(env, addr6);
 
     int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
@@ -268,7 +268,7 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_startClatd(
-        JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
+        JNIEnv* env, jclass clazz, jobject tunJavaFd, jobject readSockJavaFd,
         jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
     ScopedUtfChars ifaceStr(env, iface);
     ScopedUtfChars pfx96Str(env, pfx96);
@@ -415,7 +415,7 @@
     }
 }
 
-static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jobject clazz,
+static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jclass clazz,
                                                                       jstring iface, jstring pfx96,
                                                                       jstring v4, jstring v6,
                                                                       jint pid) {
@@ -433,7 +433,7 @@
 }
 
 static jlong com_android_server_connectivity_ClatCoordinator_getSocketCookie(
-        JNIEnv* env, jobject clazz, jobject sockJavaFd) {
+        JNIEnv* env, jclass clazz, jobject sockJavaFd) {
     int sockFd = netjniutils::GetNativeFileDescriptor(env, sockJavaFd);
     if (sockFd < 0) {
         jniThrowExceptionFmt(env, "java/io/IOException", "Invalid socket file descriptor");
diff --git a/service/libconnectivity/src/connectivity_native.cpp b/service/libconnectivity/src/connectivity_native.cpp
index 9545ed1..a476498 100644
--- a/service/libconnectivity/src/connectivity_native.cpp
+++ b/service/libconnectivity/src/connectivity_native.cpp
@@ -23,8 +23,8 @@
 
 
 static std::shared_ptr<IConnectivityNative> getBinder() {
-    static ndk::SpAIBinder sBinder = ndk::SpAIBinder(reinterpret_cast<AIBinder*>(
-        AServiceManager_getService("connectivity_native")));
+    ndk::SpAIBinder sBinder = ndk::SpAIBinder(reinterpret_cast<AIBinder*>(
+        AServiceManager_checkService("connectivity_native")));
     return aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder);
 }
 
@@ -45,21 +45,33 @@
 
 int AConnectivityNative_blockPortForBind(in_port_t port) {
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     return getErrno(c->blockPortForBind(port));
 }
 
 int AConnectivityNative_unblockPortForBind(in_port_t port) {
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     return getErrno(c->unblockPortForBind(port));
 }
 
 int AConnectivityNative_unblockAllPortsForBind() {
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     return getErrno(c->unblockAllPortsForBind());
 }
 
 int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count) {
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     std::vector<int32_t> actualBlockedPorts;
     int err = getErrno(c->getPortsBlockedForBind(&actualBlockedPorts));
     if (err) {
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 5549fbe..843b7b3 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -310,9 +310,11 @@
                     NetworkStackConstants.IPV6_ADDR_ANY, 0), null, iface));
         }
 
+        // For testing purpose, fill legacy type for NetworkStatsService since it does not
+        // support transport types.
         final TestNetworkAgent agent = new TestNetworkAgent(context, looper, nc, lp,
-                new NetworkAgentConfig.Builder().build(), callingUid, binder,
-                mNetworkProvider);
+                new NetworkAgentConfig.Builder().setLegacyType(ConnectivityManager.TYPE_TEST)
+                        .build(), callingUid, binder, mNetworkProvider);
         agent.register();
         agent.markConnected();
         return agent;
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 6b7222a..7e288c6 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE;
 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
 import static android.net.SocketKeepalive.SUCCESS_PAUSED;
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 import static android.system.OsConstants.AF_INET;
@@ -88,8 +89,8 @@
 public class AutomaticOnOffKeepaliveTracker {
     private static final String TAG = "AutomaticOnOffKeepaliveTracker";
     private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
-    private static final long DEFAULT_TCP_POLLING_INTERVAL_MS = 120_000L;
     private static final long LOW_TCP_POLLING_INTERVAL_MS = 1_000L;
+    private static final int ADJUST_TCP_POLLING_DELAY_MS = 2000;
     private static final String AUTOMATIC_ON_OFF_KEEPALIVE_VERSION =
             "automatic_on_off_keepalive_version";
     /**
@@ -178,8 +179,7 @@
         private final Network mUnderpinnedNetwork;
 
         AutomaticOnOffKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki,
-                final boolean autoOnOff, @NonNull Context context,
-                @Nullable Network underpinnedNetwork)
+                final boolean autoOnOff, @Nullable Network underpinnedNetwork)
                 throws InvalidSocketException {
             this.mKi = Objects.requireNonNull(ki);
             mCallback = ki.mCallback;
@@ -280,12 +280,14 @@
         mAlarmManager = mDependencies.getAlarmManager(context);
     }
 
-    private void startTcpPollingAlarm(@NonNull final AlarmManager.OnAlarmListener listener) {
+    private void startTcpPollingAlarm(@NonNull AutomaticOnOffKeepalive ki) {
+        if (ki.mAlarmListener == null) return;
+
         final long triggerAtMillis =
-                SystemClock.elapsedRealtime() + getTcpPollingInterval();
+                mDependencies.getElapsedRealtime() + getTcpPollingIntervalMs(ki);
         // Setup a non-wake up alarm.
         mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, null /* tag */,
-                listener, mConnectivityServiceHandler);
+                ki.mAlarmListener, mConnectivityServiceHandler);
     }
 
     /**
@@ -322,7 +324,7 @@
             handleMaybeResumeKeepalive(ki);
         }
         // TODO: listen to socket status instead of periodically check.
-        startTcpPollingAlarm(ki.mAlarmListener);
+        startTcpPollingAlarm(ki);
     }
 
     /**
@@ -402,7 +404,7 @@
         }
         mAutomaticOnOffKeepalives.add(autoKi);
         if (STATE_ALWAYS_ON != autoKi.mAutomaticOnOffState) {
-            startTcpPollingAlarm(autoKi.mAlarmListener);
+            startTcpPollingAlarm(autoKi);
         }
     }
 
@@ -463,7 +465,7 @@
         if (null == ki) return;
         try {
             final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
-                    automaticOnOffKeepalives, mContext, underpinnedNetwork);
+                    automaticOnOffKeepalives, underpinnedNetwork);
             mConnectivityServiceHandler.obtainMessage(NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
                     // TODO : move ConnectivityService#encodeBool to a static lib.
                     automaticOnOffKeepalives ? 1 : 0, 0, autoKi).sendToTarget();
@@ -493,7 +495,7 @@
         if (null == ki) return;
         try {
             final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
-                    automaticOnOffKeepalives, mContext, underpinnedNetwork);
+                    automaticOnOffKeepalives, underpinnedNetwork);
             mConnectivityServiceHandler.obtainMessage(NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
                     // TODO : move ConnectivityService#encodeBool to a static lib.
                     automaticOnOffKeepalives ? 1 : 0, 0, autoKi).sendToTarget();
@@ -523,7 +525,7 @@
         try {
             final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
                     false /* autoOnOff, tcp keepalives are never auto on/off */,
-                    mContext, null /* underpinnedNetwork, tcp keepalives do not refer to this */);
+                    null /* underpinnedNetwork, tcp keepalives do not refer to this */);
             mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, autoKi)
                     .sendToTarget();
         } catch (InvalidSocketException e) {
@@ -677,9 +679,15 @@
         }
     }
 
-    private long getTcpPollingInterval() {
+    private long getTcpPollingIntervalMs(@NonNull AutomaticOnOffKeepalive ki) {
         final boolean useLowTimer = mTestLowTcpPollingTimerUntilMs > System.currentTimeMillis();
-        return useLowTimer ? LOW_TCP_POLLING_INTERVAL_MS : DEFAULT_TCP_POLLING_INTERVAL_MS;
+        // Adjust the polling interval to be smaller than the keepalive delay to preserve
+        // some time for the system to restart the keepalive.
+        final int timer = ki.mKi.getKeepaliveIntervalSec() * 1000 - ADJUST_TCP_POLLING_DELAY_MS;
+        if (timer < MIN_INTERVAL_SEC) {
+            Log.wtf(TAG, "Unreasonably low keepalive delay: " + ki.mKi.getKeepaliveIntervalSec());
+        }
+        return useLowTimer ? LOW_TCP_POLLING_INTERVAL_MS : Math.max(timer, MIN_INTERVAL_SEC);
     }
 
     /**
@@ -786,5 +794,14 @@
             return DeviceConfigUtils.isFeatureEnabled(mContext, NAMESPACE_TETHERING, name,
                     defaultEnabled);
         }
+
+        /**
+         * Returns milliseconds since boot, including time spent in sleep.
+         *
+         * @return elapsed milliseconds since boot.
+         */
+        public long getElapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
     }
 }
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 06294db..60485b3 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -264,6 +264,10 @@
             return mSlot;
         }
 
+        int getKeepaliveIntervalSec() {
+            return mInterval;
+        }
+
         private int checkNetworkConnected() {
             if (!mNai.networkInfo.isConnectedOrConnecting()) {
                 return ERROR_INVALID_NETWORK;
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
index 2e79182..37dc7a0 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -99,7 +99,8 @@
             }
             case TYPE_COMPONENT_EXPEDITED_JOB: {
                 final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
-                if ((capabilities & ActivityManager.PROCESS_CAPABILITY_NETWORK) == 0) {
+                if ((capabilities
+                        & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) == 0) {
                     observer.onNetworkStateChecked(
                             INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES,
                             "Unexpected capabilities: " + capabilities);
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 5fc3068..da79158 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -76,6 +76,7 @@
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 
+// TODO: b/268552823 Improve the readability of IpSecManagerTunnelTest
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
 public class IpSecManagerTunnelTest extends IpSecBaseTest {
@@ -83,11 +84,6 @@
 
     private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
 
-    // Redefine this flag here so that IPsec code shipped in a mainline module can build on old
-    // platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released.
-    private static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
-            "android.software.ipsec_tunnel_migration";
-
     private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
     private static final InetAddress REMOTE_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.2");
     private static final InetAddress LOCAL_OUTER_6 =
@@ -263,14 +259,23 @@
          *
          * @param ipsecNetwork The IPsec Interface based Network for binding sockets on
          * @param tunnelIface The IPsec tunnel interface that will be tested
-         * @param underlyingTunUtils The utility of the IPsec tunnel interface's underlying TUN
-         *     network
-         * @return the integer port of the inner socket if outbound, or 0 if inbound
-         *     IpSecTunnelTestRunnable
+         * @param tunUtils The utility of the IPsec tunnel interface's underlying TUN network
+         * @param inTunnelTransform The inbound tunnel mode transform
+         * @param outTunnelTransform The outbound tunnel mode transform
+         * @param localOuter The local address of the outer IP packet
+         * @param remoteOuter The remote address of the outer IP packet
+         * @param seqNum The expected sequence number of the inbound packet
          * @throws Exception if any part of the test failed.
          */
         public abstract int run(
-                Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils underlyingTunUtils)
+                Network ipsecNetwork,
+                IpSecTunnelInterface tunnelIface,
+                TunUtils tunUtils,
+                IpSecTransform inTunnelTransform,
+                IpSecTransform outTunnelTransform,
+                InetAddress localOuter,
+                InetAddress remoteOuter,
+                int seqNum)
                 throws Exception;
     }
 
@@ -306,18 +311,28 @@
     }
 
     private interface IpSecTunnelTestRunnableFactory {
+        /**
+         * Build a IpSecTunnelTestRunnable.
+         *
+         * @param transportInTunnelMode indicate if there needs to be a transport mode transform
+         *     inside the tunnel mode transform
+         * @param spi The IPsec SPI
+         * @param localInner The local address of the inner IP packet
+         * @param remoteInner The remote address of the inner IP packet
+         * @param inTransportTransform The inbound transport mode transform
+         * @param outTransportTransform The outbound transport mode transform
+         * @param encapPort The port of the UDP encapsulation socket
+         * @param innerSocketPort The inner socket port
+         */
         IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
                 boolean transportInTunnelMode,
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
                 int encapPort,
-                int innerSocketPort,
-                int expectedPacketSize)
+                int innerSocketPort)
                 throws Exception;
     }
 
@@ -327,17 +342,21 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
                 int encapPort,
-                int unusedInnerSocketPort,
-                int expectedPacketSize) {
+                int unusedInnerSocketPort) {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     // Build a socket and send traffic
                     JavaUdpSocket socket = new JavaUdpSocket(localInner);
@@ -357,9 +376,14 @@
                     // Verify that an encrypted packet is sent. As of right now, checking encrypted
                     // body is not possible, due to the test not knowing some of the fields of the
                     // inner IP header (flow label, flags, etc)
+                    int innerFamily = localInner instanceof Inet4Address ? AF_INET : AF_INET6;
+                    int outerFamily = localOuter instanceof Inet4Address ? AF_INET : AF_INET6;
+                    boolean useEncap = encapPort != 0;
+                    int expectedPacketSize =
+                            getPacketSize(
+                                    innerFamily, outerFamily, useEncap, transportInTunnelMode);
                     tunUtils.awaitEspPacketNoPlaintext(
-                            spi, TEST_DATA, encapPort != 0, expectedPacketSize);
-
+                            spi, TEST_DATA, useEncap, expectedPacketSize);
                     socket.close();
 
                     return innerSocketPort;
@@ -375,18 +399,22 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
                 int encapPort,
-                int innerSocketPort,
-                int expectedPacketSize)
+                int innerSocketPort)
                 throws Exception {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     // Build a socket and receive traffic
                     JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort);
@@ -420,18 +448,22 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
                 int encapPort,
-                int innerSocketPort,
-                int expectedPacketSize)
+                int innerSocketPort)
                 throws Exception {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     // Build a socket and receive traffic
                     JavaUdpSocket socket = new JavaUdpSocket(localInner);
@@ -456,7 +488,8 @@
                                         remoteOuter,
                                         localOuter,
                                         socket.getPort(),
-                                        encapPort);
+                                        encapPort,
+                                        seqNum);
                     } else {
                         pkt =
                                 getTunnelModePacket(
@@ -466,7 +499,8 @@
                                         remoteOuter,
                                         localOuter,
                                         socket.getPort(),
-                                        encapPort);
+                                        encapPort,
+                                        seqNum);
                     }
                     tunUtils.injectPacket(pkt);
 
@@ -498,17 +532,21 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
                 int encapPort,
-                int unusedInnerSocketPort,
-                int expectedPacketSize) {
+                int unusedInnerSocketPort) {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     mTestRunnableFactory
                             .getIpSecTunnelTestRunnable(
@@ -516,15 +554,19 @@
                                     spi,
                                     localInner,
                                     remoteInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
                                     encapPort,
-                                    unusedInnerSocketPort,
-                                    expectedPacketSize)
-                            .run(ipsecNetwork, tunnelIface, sTunWrapper.utils);
-
+                                    unusedInnerSocketPort)
+                            .run(
+                                    ipsecNetwork,
+                                    tunnelIface,
+                                    tunUtils,
+                                    inTunnelTransform,
+                                    outTunnelTransform,
+                                    localOuter,
+                                    remoteOuter,
+                                    seqNum);
                     tunnelIface.setUnderlyingNetwork(sTunWrapperNew.network);
 
                     // Verify migrating to IPv4 and IPv6 addresses. It ensures that not only
@@ -623,19 +665,143 @@
                                     spi,
                                     localInner,
                                     remoteInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
                                     useEncap ? encapSocket.getPort() : 0,
-                                    0,
-                                    expectedPacketSize)
-                            .run(ipsecNetwork, tunnelIface, tunUtils);
+                                    0)
+                            .run(
+                                    ipsecNetwork,
+                                    tunnelIface,
+                                    tunUtils,
+                                    inTransform,
+                                    outTransform,
+                                    localOuter,
+                                    remoteOuter,
+                                    1 /* seqNum */);
                 }
             }
         }
     }
 
+    private class MigrateTunnelModeIpSecTransformTestRunnableFactory
+            implements IpSecTunnelTestRunnableFactory {
+        private final IpSecTunnelTestRunnableFactory mTestRunnableFactory;
+
+        MigrateTunnelModeIpSecTransformTestRunnableFactory(boolean isOutputTest) {
+            if (isOutputTest) {
+                mTestRunnableFactory = new OutputIpSecTunnelTestRunnableFactory();
+            } else {
+                mTestRunnableFactory = new InputPacketGeneratorIpSecTunnelTestRunnableFactory();
+            }
+        }
+
+        @Override
+        public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+                boolean transportInTunnelMode,
+                int spi,
+                InetAddress localInner,
+                InetAddress remoteInner,
+                IpSecTransform inTransportTransform,
+                IpSecTransform outTransportTransform,
+                int encapPort,
+                int unusedInnerSocketPort) {
+            return new IpSecTunnelTestRunnable() {
+                @Override
+                public int run(
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
+                        throws Exception {
+                    final IpSecTunnelTestRunnable testRunnable =
+                            mTestRunnableFactory.getIpSecTunnelTestRunnable(
+                                    transportInTunnelMode,
+                                    spi,
+                                    localInner,
+                                    remoteInner,
+                                    inTransportTransform,
+                                    outTransportTransform,
+                                    encapPort,
+                                    unusedInnerSocketPort);
+                    testRunnable.run(
+                            ipsecNetwork,
+                            tunnelIface,
+                            tunUtils,
+                            inTunnelTransform,
+                            outTunnelTransform,
+                            localOuter,
+                            remoteOuter,
+                            seqNum++);
+
+                    tunnelIface.setUnderlyingNetwork(sTunWrapperNew.network);
+                    checkMigrateTunnelModeTransform(
+                            testRunnable,
+                            inTunnelTransform,
+                            outTunnelTransform,
+                            tunnelIface,
+                            ipsecNetwork,
+                            sTunWrapperNew.utils,
+                            LOCAL_OUTER_4_NEW,
+                            REMOTE_OUTER_4_NEW,
+                            seqNum++);
+
+                    // Only test migration to IPv6 in non-UDP Encapsulation case
+                    if (encapPort == 0) {
+                        checkMigrateTunnelModeTransform(
+                                testRunnable,
+                                inTunnelTransform,
+                                outTunnelTransform,
+                                tunnelIface,
+                                ipsecNetwork,
+                                sTunWrapperNew.utils,
+                                LOCAL_OUTER_6_NEW,
+                                REMOTE_OUTER_6_NEW,
+                                seqNum++);
+                    }
+
+                    // Unused return value for MigrateTunnelModeIpSecTransformTest
+                    return 0;
+                }
+            };
+        }
+
+        private void checkMigrateTunnelModeTransform(
+                IpSecTunnelTestRunnable testRunnable,
+                IpSecTransform inTunnelTransform,
+                IpSecTransform outTunnelTransform,
+                IpSecTunnelInterface tunnelIface,
+                Network ipsecNetwork,
+                TunUtils tunUtils,
+                InetAddress newLocalOuter,
+                InetAddress newRemoteOuter,
+                int seqNum)
+                throws Exception {
+            mISM.startTunnelModeTransformMigration(
+                    inTunnelTransform, newRemoteOuter, newLocalOuter);
+            mISM.startTunnelModeTransformMigration(
+                    outTunnelTransform, newLocalOuter, newRemoteOuter);
+
+            mISM.applyTunnelModeTransform(
+                    tunnelIface, IpSecManager.DIRECTION_IN, inTunnelTransform);
+            mISM.applyTunnelModeTransform(
+                    tunnelIface, IpSecManager.DIRECTION_OUT, outTunnelTransform);
+
+            testRunnable.run(
+                    ipsecNetwork,
+                    tunnelIface,
+                    tunUtils,
+                    inTunnelTransform,
+                    outTunnelTransform,
+                    newLocalOuter,
+                    newRemoteOuter,
+                    seqNum);
+        }
+    }
+
     private void checkTunnelOutput(
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
@@ -680,6 +846,28 @@
                 new MigrateIpSecTunnelTestRunnableFactory(false));
     }
 
+    private void checkMigrateTunnelModeTransformOutput(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        checkTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                new MigrateTunnelModeIpSecTransformTestRunnableFactory(true /* isOutputTest */));
+    }
+
+    private void checkMigrateTunnelModeTransformInput(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        checkTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                new MigrateTunnelModeIpSecTransformTestRunnableFactory(false /* isOutputTest */));
+    }
+
     /**
      * Validates that the kernel can talk to itself.
      *
@@ -719,22 +907,19 @@
                                     spi,
                                     localInner,
                                     remoteInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
                                     useEncap ? encapSocket.getPort() : 0,
-                                    0,
-                                    expectedPacketSize);
+                                    0);
             int innerSocketPort =
                     buildTunnelNetworkAndRunTests(
-                    localInner,
-                    remoteInner,
-                    localOuter,
-                    remoteOuter,
-                    spi,
-                    useEncap ? encapSocket : null,
-                    outputIpSecTunnelTestRunnable);
+                            localInner,
+                            remoteInner,
+                            localOuter,
+                            remoteOuter,
+                            spi,
+                            useEncap ? encapSocket : null,
+                            outputIpSecTunnelTestRunnable);
 
             // Input direction tests, with matching inner socket ports.
             IpSecTunnelTestRunnable inputIpSecTunnelTestRunnable =
@@ -744,13 +929,10 @@
                                     spi,
                                     remoteInner,
                                     localInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
                                     useEncap ? encapSocket.getPort() : 0,
-                                    innerSocketPort,
-                                    expectedPacketSize);
+                                    innerSocketPort);
             buildTunnelNetworkAndRunTests(
                     remoteInner,
                     localInner,
@@ -805,13 +987,10 @@
                             spi,
                             localInner,
                             remoteInner,
-                            localOuter,
-                            remoteOuter,
                             inTransportTransform,
                             outTransportTransform,
                             useEncap ? encapSocket.getPort() : 0,
-                            0,
-                            expectedPacketSize));
+                            0));
         }
     }
 
@@ -870,7 +1049,16 @@
                 mISM.applyTunnelModeTransform(
                         tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
 
-                innerSocketPort = test.run(testNetwork, tunnelIface, sTunWrapper.utils);
+                innerSocketPort =
+                        test.run(
+                                testNetwork,
+                                tunnelIface,
+                                sTunWrapper.utils,
+                                inTransform,
+                                outTransform,
+                                localOuter,
+                                remoteOuter,
+                                1 /* seqNum */);
             }
 
             // Teardown the test network
@@ -909,13 +1097,14 @@
     }
 
     private EspHeader buildTransportModeEspPacket(
-            int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
+            int spi, int seqNum, InetAddress src, InetAddress dst, Payload payload)
+            throws Exception {
         IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
 
         return new EspHeader(
                 payload.getProtocolId(),
                 spi,
-                1, // sequence number
+                seqNum,
                 CRYPT_KEY, // Same key for auth and crypt
                 payload.getPacketBytes(preEspIpHeader));
     }
@@ -928,13 +1117,14 @@
             InetAddress dstOuter,
             int port,
             int encapPort,
+            int seqNum,
             Payload payload)
             throws Exception {
         IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload);
         return new EspHeader(
                 innerIp.getProtocolId(),
                 spi,
-                1, // sequence number
+                seqNum, // sequence number
                 CRYPT_KEY, // Same key for auth and crypt
                 innerIp.getPacketBytes());
     }
@@ -958,13 +1148,14 @@
             InetAddress srcOuter,
             InetAddress dstOuter,
             int port,
-            int encapPort)
+            int encapPort,
+            int seqNum)
             throws Exception {
         UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
 
         EspHeader espPayload =
                 buildTunnelModeEspPacket(
-                        spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp);
+                        spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, seqNum, udp);
         return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
     }
 
@@ -976,11 +1167,13 @@
             InetAddress srcOuter,
             InetAddress dstOuter,
             int port,
-            int encapPort)
+            int encapPort,
+            int seqNum)
             throws Exception {
         UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
 
-        EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp);
+        EspHeader espPayload =
+                buildTransportModeEspPacket(spiInner, seqNum, srcInner, dstInner, udp);
         espPayload =
                 buildTunnelModeEspPacket(
                         spiOuter,
@@ -990,6 +1183,7 @@
                         dstOuter,
                         port,
                         encapPort,
+                        seqNum,
                         espPayload);
         return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
     }
@@ -998,13 +1192,19 @@
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
-        checkTunnelOutput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
-        checkTunnelInput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+        checkMigrateTunnelOutput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+        checkMigrateTunnelInput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
     }
 
-    /** Checks if FEATURE_IPSEC_TUNNEL_MIGRATION is enabled on the device */
-    private static boolean hasIpsecTunnelMigrateFeature() {
-        return sContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION);
+    private void doTestMigrateTunnelModeTransform(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelMigrateFeature());
+        checkMigrateTunnelModeTransformOutput(
+                innerFamily, outerFamily, useEncap, transportInTunnelMode);
+        checkMigrateTunnelModeTransformInput(
+                innerFamily, outerFamily, useEncap, transportInTunnelMode);
     }
 
     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
@@ -1012,28 +1212,7 @@
     public void testHasIpSecTunnelMigrateFeature() throws Exception {
         // FEATURE_IPSEC_TUNNEL_MIGRATION is required when VSR API is U/U+
         if (getVsrApiLevel() > Build.VERSION_CODES.TIRAMISU) {
-            assertTrue(hasIpsecTunnelMigrateFeature());
-        }
-    }
-
-    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-    @Test
-    public void testMigrateTunnelModeTransform() throws Exception {
-        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
-        assumeTrue(hasIpsecTunnelMigrateFeature());
-
-        IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
-        transformBuilder.setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
-        transformBuilder.setAuthentication(
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
-        int spi = getRandomSpi(LOCAL_OUTER_4, REMOTE_OUTER_4);
-
-        try (IpSecManager.SecurityParameterIndex outSpi =
-                        mISM.allocateSecurityParameterIndex(REMOTE_OUTER_4, spi);
-                IpSecTransform outTunnelTransform =
-                        transformBuilder.buildTunnelModeTransform(LOCAL_INNER_4, outSpi)) {
-            mISM.startTunnelModeTransformMigration(
-                    outTunnelTransform, LOCAL_OUTER_4_NEW, REMOTE_OUTER_4_NEW);
+            assertTrue(mCtsNetUtils.hasIpsecTunnelMigrateFeature());
         }
     }
 
@@ -1266,4 +1445,76 @@
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET6, AF_INET6, false, false);
     }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, true, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, true, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET6, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, true, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, true, false);
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index 0377160..268d8d2 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -22,7 +22,6 @@
 import static android.net.cts.PacketUtils.UDP_HDRLEN;
 import static android.system.OsConstants.IPPROTO_UDP;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.os.ParcelFileDescriptor;
@@ -32,6 +31,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -140,10 +140,8 @@
     public byte[] awaitEspPacketNoPlaintext(
             int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
         final byte[] espPkt = awaitPacket(
-                (pkt) -> isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
-
-        // Validate packet size
-        assertEquals(expectedPacketSize, espPkt.length);
+            (pkt) -> expectedPacketSize == pkt.length
+                    && isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
 
         return espPkt; // We've found the packet we're looking for.
     }
@@ -153,11 +151,11 @@
     }
 
     private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
-        // Check SPI byte by byte.
-        return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
-                && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff)
-                && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff)
-                && pkt[espOffset + 3] == (byte) (spi & 0xff);
+        ByteBuffer buffer = ByteBuffer.wrap(pkt);
+        buffer.get(new byte[espOffset]); // Skip IP, UDP header
+        int actualSpi = buffer.getInt();
+
+        return actualSpi == spi;
     }
 
     /**
@@ -180,8 +178,13 @@
 
     private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
         if (isIpv6(pkt)) {
-            // IPv6 UDP encap not supported by kernels; assume non-encap.
-            return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+            if (encap) {
+                return pkt[IP6_PROTO_OFFSET] == IPPROTO_UDP
+                        && isSpiEqual(pkt, IP6_HDRLEN + UDP_HDRLEN, spi);
+            } else {
+                return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+            }
+
         } else {
             // Use default IPv4 header length (assuming no options)
             if (encap) {
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index df3a4aa..d817630 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -75,6 +75,13 @@
 
 public final class CtsNetUtils {
     private static final String TAG = CtsNetUtils.class.getSimpleName();
+
+    // Redefine this flag here so that IPsec code shipped in a mainline module can build on old
+    // platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released.
+    // TODO: b/275378783 Remove this flag and use the platform API when it is available.
+    private static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+            "android.software.ipsec_tunnel_migration";
+
     private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int PRIVATE_DNS_PROBE_MS = 1_000;
 
@@ -115,6 +122,11 @@
                 || getFirstApiLevel() >= Build.VERSION_CODES.Q;
     }
 
+    /** Checks if FEATURE_IPSEC_TUNNEL_MIGRATION is enabled on the device */
+    public boolean hasIpsecTunnelMigrateFeature() {
+        return mContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION);
+    }
+
     /**
      * Sets the given appop using shell commands
      *
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index e3d80a0..12919ae 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -21,7 +21,10 @@
 
 android_test {
     name: "FrameworksNetIntegrationTests",
-    defaults: ["framework-connectivity-internal-test-defaults"],
+    defaults: [
+        "framework-connectivity-internal-test-defaults",
+        "NetworkStackApiShimSettingsForCurrentBranch",
+    ],
     platform_apis: true,
     certificate: "platform",
     srcs: [
@@ -33,6 +36,13 @@
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        // It does not matter if NetworkStackApiStableLib or NetworkStackApiCurrentLib is used here,
+        // since the shims for the branch are already included via
+        // NetworkStackApiShimSettingsForCurrentBranch, and will be used in priority as they are
+        // first in the classpath.
+        // If the wrong shims are used for some reason, tests that use newer APIs fail.
+        // TODO: have NetworkStackApiStableLib link dynamically against the shims to remove this
+        // order-dependent setup.
         "NetworkStackApiStableLib",
         "androidx.test.ext.junit",
         "frameworks-net-integration-testutils",
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 6c89c38..1cc0c89 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -3817,13 +3817,13 @@
 
     @Test
     public void testExplicitlySelected() throws Exception {
-        NetworkRequest request = new NetworkRequest.Builder()
+        final NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
                 .build();
-        TestNetworkCallback callback = new TestNetworkCallback();
+        final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, callback);
 
-        // Bring up validated cell.
+        // Bring up validated cell
         mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellAgent);
@@ -3881,6 +3881,12 @@
         assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         expectUnvalidationCheckWillNotNotify(mWiFiAgent);
 
+        // Now request cell so it doesn't disconnect during the test
+        final NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .clearCapabilities().addTransportType(TRANSPORT_CELLULAR).build();
+        final TestNetworkCallback cellCallback = new TestNetworkCallback();
+        mCm.requestNetwork(cellRequest, cellCallback);
+
         mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
         mEthernetAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mEthernetAgent);
@@ -3922,6 +3928,7 @@
 
         callback.expect(LOST, mWiFiAgent);
         callback.expect(LOST, mCellAgent);
+        mCm.unregisterNetworkCallback(cellCallback);
     }
 
     private void doTestFirstEvaluation(
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 4f0b9c4..696eff4 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -28,8 +28,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.longThat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -57,6 +57,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
@@ -94,6 +95,7 @@
     private static final int NETID_MASK = 0xffff;
     private static final int TIMEOUT_MS = 30_000;
     private static final int MOCK_RESOURCE_ID = 5;
+    private static final int TEST_KEEPALIVE_INTERVAL_SEC = 10;
     private AutomaticOnOffKeepaliveTracker mAOOKeepaliveTracker;
     private HandlerThread mHandlerThread;
 
@@ -334,9 +336,13 @@
         final KeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
                 dstAddress, dstPort, new byte[] {1});
         final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(cb, nai, kpd,
-                10 /* interval */, KeepaliveInfo.TYPE_NATT, fd);
+                TEST_KEEPALIVE_INTERVAL_SEC, KeepaliveInfo.TYPE_NATT, fd);
         mKeepaliveTracker.setReturnedKeepaliveInfo(ki);
 
+        // Mock elapsed real time to verify the alarm timer.
+        final long time = SystemClock.elapsedRealtime();
+        doReturn(time).when(mDependencies).getElapsedRealtime();
+
         mAOOKeepaliveTracker.startNattKeepalive(nai, fd, 10 /* intervalSeconds */, cb,
                 srcAddress.toString(), srcPort, dstAddress.toString(), dstPort,
                 true /* automaticOnOffKeepalives */, underpinnedNetwork);
@@ -344,8 +350,11 @@
 
         final ArgumentCaptor<AlarmManager.OnAlarmListener> listenerCaptor =
                 ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
-        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME), anyLong(),
-                any(), listenerCaptor.capture(), eq(mTestHandler));
+        // The alarm timer should be smaller than the keepalive delay. Verify the alarm trigger time
+        // is higher than base time but smaller than the keepalive delay.
+        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME),
+                longThat(t -> t > time + 1000L && t < time + TEST_KEEPALIVE_INTERVAL_SEC * 1000L),
+                any() /* tag */, listenerCaptor.capture(), eq(mTestHandler));
         final AlarmManager.OnAlarmListener listener = listenerCaptor.getValue();
 
         // For realism, the listener should be posted on the handler
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 79987e6..dd9177ee 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -48,6 +48,7 @@
 
 import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
 import static com.android.server.connectivity.Vpn.AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+import static com.android.server.connectivity.Vpn.DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC;
 import static com.android.server.connectivity.Vpn.DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
 import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_AUTO;
 import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP;
@@ -1860,6 +1861,13 @@
 
     private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
             IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
+        return verifySetupPlatformVpn(vpnProfile, ikeConfig, mtuSupportsIpv6,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
+            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6,
+            boolean areLongLivedTcpConnectionsExpensive) throws Exception {
         if (!mtuSupportsIpv6) {
             doReturn(IPV6_MIN_MTU - 1).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(),
                     anyBoolean());
@@ -1942,8 +1950,10 @@
 
         // Check if allowBypass is set or not.
         assertTrue(nacCaptor.getValue().isBypassableVpn());
-        assertTrue(((VpnTransportInfo) ncCaptor.getValue().getTransportInfo()).isBypassable());
-
+        final VpnTransportInfo info = (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
+        assertTrue(info.isBypassable());
+        assertEquals(areLongLivedTcpConnectionsExpensive,
+                info.areLongLivedTcpConnectionsExpensive());
         return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
     }
 
@@ -2069,7 +2079,8 @@
         final PlatformVpnSnapshot vpnSnapShot =
                 verifySetupPlatformVpn(profile,
                         createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        false /* mtuSupportsIpv6 */);
+                        false /* mtuSupportsIpv6 */,
+                        expectedKeepalive < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC);
         // Simulate a new network coming up
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
         verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
@@ -2116,7 +2127,9 @@
                 PREFERRED_IKE_PROTOCOL_IPV4_UDP,
                 AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */);
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
     }
 
     @Test
@@ -2126,7 +2139,9 @@
                 PREFERRED_IKE_PROTOCOL_IPV4_UDP,
                 AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */);
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
     }
 
     @Test
@@ -2136,7 +2151,9 @@
                 PREFERRED_IKE_PROTOCOL_AUTO,
                 TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */);
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
     }
 
     @Test
@@ -2150,7 +2167,9 @@
                 PREFERRED_IKE_PROTOCOL_IPV4_UDP,
                 AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_AUTO /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */);
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
     }
 
     @Test
@@ -2160,7 +2179,9 @@
                 PREFERRED_IKE_PROTOCOL_IPV4_UDP,
                 TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_IPV4 /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_UDP /* expectedEncapType */);
+                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
     }
 
     @Test
@@ -2170,7 +2191,9 @@
                 PREFERRED_IKE_PROTOCOL_IPV6_ESP,
                 TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_NONE /* expectedEncapType */);
+                ESP_ENCAP_TYPE_NONE /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
     }
 
     @Test
@@ -2180,7 +2203,9 @@
                 PREFERRED_IKE_PROTOCOL_IPV6_UDP,
                 TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
                 ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
-                ESP_ENCAP_TYPE_UDP /* expectedEncapType */);
+                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
     }
 
     private NetworkCapabilities createTestCellNc() {
@@ -2193,7 +2218,9 @@
     }
 
     private void doTestReadCarrierConfig(NetworkCapabilities nc, int simState, int preferredIpProto,
-            int expectedKeepaliveTimer, int expectedIpVersion, int expectedEncapType)
+            int expectedKeepaliveTimer, int expectedIpVersion, int expectedEncapType,
+            boolean expectedReadFromCarrierConfig,
+            boolean areLongLivedTcpConnectionsExpensive)
             throws Exception {
         final Ikev2VpnProfile ikeProfile =
                 new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
@@ -2206,7 +2233,8 @@
         final PlatformVpnSnapshot vpnSnapShot =
                 verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
                         createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
-                        false /* mtuSupportsIpv6 */);
+                        false /* mtuSupportsIpv6 */,
+                        true /* areLongLivedTcpConnectionsExpensive */);
 
         final CarrierConfigManager.CarrierConfigChangeListener listener =
                 getCarrierConfigListener();
@@ -2221,15 +2249,31 @@
         vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, nc);
         verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
                 expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
+        if (expectedReadFromCarrierConfig) {
+            final ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                    ArgumentCaptor.forClass(NetworkCapabilities.class);
+            verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
+
+            final VpnTransportInfo info =
+                    (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
+            assertEquals(areLongLivedTcpConnectionsExpensive,
+                    info.areLongLivedTcpConnectionsExpensive());
+        } else {
+            verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
+        }
 
         reset(mExecutor);
         reset(mIkeSessionWrapper);
+        reset(mMockNetworkAgent);
 
         // Trigger carrier config change
         listener.onCarrierConfigChanged(1 /* logicalSlotIndex */, TEST_SUB_ID,
                 -1 /* carrierId */, -1 /* specificCarrierId */);
         verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
                 expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
+        // Expect no NetworkCapabilities change.
+        // Call to doSendNetworkCapabilities() will not be triggered.
+        verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
     }
 
     @Test
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
index eb686ce..5129128 100755
--- a/tools/gen_jarjar.py
+++ b/tools/gen_jarjar.py
@@ -120,9 +120,11 @@
                         _get_toplevel_class(clazz) not in excluded_classes and
                         not any(r.fullmatch(clazz) for r in exclude_regexes)):
                     outfile.write(f'rule {clazz} {args.prefix}.@0\n')
-                    # Also include jarjar rules for unit tests of the class, so the package matches
-                    outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
-                    outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
+                    # Also include jarjar rules for unit tests of the class if it's not explicitly
+                    # excluded, so the package matches
+                    if not any(r.fullmatch(clazz + 'Test') for r in exclude_regexes):
+                        outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
+                        outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
 
 
 def _main():
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index 163f8b6..21482d9 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -14,8 +14,6 @@
 //
 // This file is automatically generated by gen_android_bp. Do not edit.
 
-soong_namespace {}
-
 build = ["Android.extras.bp"]
 
 // The actual license can be found in Android.extras.bp
@@ -23,6 +21,9 @@
     default_applicable_licenses: [
         "external_cronet_license",
     ],
+    default_visibility: [
+        ":__subpackages__",
+    ],
 }
 
 // GN: //components/cronet/android:cronet_api_java
@@ -54,6 +55,9 @@
         "components/cronet/android/api/src/android/net/http/UrlRequest.java",
         "components/cronet/android/api/src/android/net/http/UrlResponseInfo.java",
     ],
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
 }
 
 // GN: //base/allocator:buildflags
@@ -991,7 +995,6 @@
         "base/allocator/dispatcher/dispatcher.cc",
         "base/allocator/dispatcher/internal/dispatch_data.cc",
         "base/allocator/dispatcher/reentry_guard.cc",
-        "base/allocator/partition_allocator/shim/allocator_shim.cc",
         "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc",
         "base/android/android_hardware_buffer_compat.cc",
         "base/android/android_image_reader_compat.cc",
@@ -1563,6 +1566,11 @@
         "-Wl,-wrap,vasprintf",
     ],
     target: {
+        android: {
+            srcs: [
+                "base/allocator/partition_allocator/shim/allocator_shim.cc",
+            ],
+        },
         android_arm: {
             srcs: [
                 "base/android/reached_code_profiler.cc",
@@ -1602,6 +1610,11 @@
                 "-msse3",
             ],
         },
+        glibc: {
+            srcs: [
+                "base/allocator/partition_allocator/shim/allocator_shim.cc",
+            ],
+        },
     },
 }
 
@@ -1662,7 +1675,6 @@
         "base/allocator/dispatcher/dispatcher.cc",
         "base/allocator/dispatcher/internal/dispatch_data.cc",
         "base/allocator/dispatcher/reentry_guard.cc",
-        "base/allocator/partition_allocator/shim/allocator_shim.cc",
         "base/at_exit.cc",
         "base/barrier_closure.cc",
         "base/base64.cc",
@@ -2131,6 +2143,9 @@
     ],
     target: {
         android: {
+            srcs: [
+                "base/allocator/partition_allocator/shim/allocator_shim.cc",
+            ],
             shared_libs: [
                 "libandroid",
                 "liblog",
@@ -2611,9 +2626,14 @@
                 "-Wl,-wrap,vasprintf",
             ],
         },
+        glibc: {
+            srcs: [
+                "base/allocator/partition_allocator/shim/allocator_shim.cc",
+                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc",
+            ],
+        },
         host: {
             srcs: [
-                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc",
                 "base/base_paths_posix.cc",
                 "base/debug/stack_trace_posix.cc",
                 "base/files/file_util_linux.cc",
@@ -6556,6 +6576,9 @@
         "-Wl,-wrap,vasprintf",
     ],
     stem: "libcronet.108.0.5359.128",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
     target: {
         android_arm: {
             cflags: [
@@ -8274,6 +8297,9 @@
         "-Wl,-wrap,vasprintf",
     ],
     stem: "libcronet_tests",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
     target: {
         android_arm: {
             cflags: [
@@ -8549,6 +8575,9 @@
         "-Wl,-wrap,vasprintf",
     ],
     stem: "libcronet_unittests_android__library",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
     target: {
         android_arm: {
             cflags: [
@@ -11183,6 +11212,9 @@
         "-Aorg.chromium.chrome.skipGenJni",
         "-Apackage_prefix=android.net.http.internal",
     ],
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
 }
 
 // GN: //gn:java
@@ -11460,6 +11492,9 @@
     javacflags: [
         "-Aorg.chromium.chrome.skipGenJni",
     ],
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
 }
 
 // GN: //base/android/jni_generator:jni_processor
@@ -16147,6 +16182,9 @@
         "-Wl,-wrap,vasprintf",
     ],
     stem: "libnet_unittests__library",
+    visibility: [
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
     target: {
         android_arm: {
             cflags: [
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index 075ed58..0f12cf3 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -294,6 +294,12 @@
 # Name of cronet api target
 java_api_target_name = "//components/cronet/android:cronet_api_java"
 
+# Visibility set for package default
+package_default_visibility = ":__subpackages__"
+
+# Visibility set for modules used from Connectivity
+connectivity_visibility = "//packages/modules/Connectivity:__subpackages__"
+
 # ----------------------------------------------------------------------------
 # End of configuration.
 # ----------------------------------------------------------------------------
@@ -445,6 +451,7 @@
     self.target['android_arm'] = Target('android_arm')
     self.target['android_arm64'] = Target('android_arm64')
     self.target['host'] = Target('host')
+    self.target['glibc'] = Target('glibc')
     self.stl = None
     self.cpp_std = None
     self.dist = dict()
@@ -478,6 +485,8 @@
     self.javacflags = set()
     self.c_std = None
     self.default_applicable_licenses = set()
+    self.default_visibility = []
+    self.visibility = []
 
   def to_string(self, output):
     if self.comment:
@@ -536,6 +545,8 @@
     self._output_field(output, 'javacflags')
     self._output_field(output, 'c_std')
     self._output_field(output, 'default_applicable_licenses')
+    self._output_field(output, 'default_visibility')
+    self._output_field(output, 'visibility')
     if self.rtti:
       self._output_field(output, 'rtti')
 
@@ -1725,6 +1736,7 @@
       module.srcs.add(':' + create_action_module(blueprint, target, 'java_genrule', is_test_target).name)
   preprocessor_module = create_java_jni_preprocessor(blueprint)
   module.plugins.add(preprocessor_module.name)
+  module.visibility.append(connectivity_visibility)
   blueprint.add_module(module)
   return module
 
@@ -1740,6 +1752,7 @@
     ':' + create_action_module(blueprint, gn.get_target(dep), 'java_genrule', False).name
     for dep in get_api_java_actions(gn)])
   blueprint.add_module(source_module)
+  source_module.visibility.append(connectivity_visibility)
   return source_module
 
 def update_jni_registration_module(module, gn):
@@ -1748,6 +1761,28 @@
                       for source in get_non_api_java_sources(gn)
                       if source.endswith('.java')])
 
+
+def turn_off_allocator_shim_for_musl(module):
+  allocation_shim = "base/allocator/partition_allocator/shim/allocator_shim.cc"
+  allocator_shim_files = {
+    allocation_shim,
+    "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc",
+  }
+  module.srcs -= allocator_shim_files
+  for arch in module.target.values():
+    arch.srcs -= allocator_shim_files
+  module.target['android'].srcs.add(allocation_shim)
+  if gn_utils.TESTING_SUFFIX in module.name:
+    # allocator_shim_default_dispatch_to_glibc is only added to the __testing version of base
+    # since base_base__testing is compiled for host. When compiling for host. Soong compiles
+    # using glibc or musl(experimental). We currently only support compiling for glibc.
+    module.target['glibc'].srcs.update(allocator_shim_files)
+  else:
+    # allocator_shim_default_dispatch_to_glibc does not exist in the prod version of base
+    # `base_base` since this only compiles for android and bionic is used. Bionic is the equivalent
+    # of glibc but for android.
+    module.target['glibc'].srcs.add(allocation_shim)
+
 def create_blueprint_for_targets(gn, targets, test_targets):
   """Generate a blueprint for a list of GN targets."""
   blueprint = Blueprint()
@@ -1794,10 +1829,14 @@
   blueprint.add_module(defaults)
 
   for target in targets:
-    create_modules_from_target(blueprint, gn, target, is_test_target=False)
+    module = create_modules_from_target(blueprint, gn, target, is_test_target=False)
+    if module:
+      module.visibility.append(connectivity_visibility)
 
   for test_target in test_targets:
-    create_modules_from_target(blueprint, gn, test_target + gn_utils.TESTING_SUFFIX, is_test_target=True)
+    module = create_modules_from_target(blueprint, gn, test_target + gn_utils.TESTING_SUFFIX, is_test_target=True)
+    if module:
+      module.visibility.append(connectivity_visibility)
 
   create_java_api_module(blueprint, gn)
   java_module = create_java_module(blueprint, gn, is_test_target=False)
@@ -1807,6 +1846,8 @@
   for module in blueprint.modules.values():
     if 'cronet_jni_registration' in module.name:
       update_jni_registration_module(module, gn)
+    if module.name in ['cronet_aml_base_base', 'cronet_aml_base_base' + gn_utils.TESTING_SUFFIX]:
+      turn_off_allocator_shim_for_musl(module)
 
   # Merge in additional hardcoded arguments.
   for module in blueprint.modules.values():
@@ -1828,11 +1869,12 @@
 
   return blueprint
 
-def create_default_license_module(blueprint):
-  default_license = Module("package", "", "PACKAGE")
-  default_license.comment = "The actual license can be found in Android.extras.bp"
-  default_license.default_applicable_licenses.add(CRONET_LICENSE_NAME)
-  blueprint.add_module(default_license)
+def create_package_module(blueprint):
+  package = Module("package", "", "PACKAGE")
+  package.comment = "The actual license can be found in Android.extras.bp"
+  package.default_applicable_licenses.add(CRONET_LICENSE_NAME)
+  package.default_visibility.append(package_default_visibility)
+  blueprint.add_module(package)
 
 def main():
   parser = argparse.ArgumentParser(
@@ -1886,7 +1928,7 @@
   # Add any proto groups to the blueprint.
   for l_name, t_names in proto_groups.items():
     create_proto_group_modules(blueprint, gn, l_name, t_names)
-  create_default_license_module(blueprint)
+  create_package_module(blueprint)
   output = [
       """// Copyright (C) 2022 The Android Open Source Project
 //
@@ -1904,8 +1946,6 @@
 //
 // This file is automatically generated by %s. Do not edit.
 
-soong_namespace {}
-
 build = ["Android.extras.bp"]
 """ % (tool_name)
   ]