Snap for 7195589 from 99b0cbb34d15235f25dca9a288d4e5bbf1cff34b to mainline-media-swcodec-release

Change-Id: Ie7b5ff3b0ec30502938409bc4b080017a7e92ff1
diff --git a/Android.bp b/Android.bp
index 802ca42..2daa1d4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,12 +22,12 @@
 //                                            /    \
 //           +NetworkStackApiStableShims --> /      \ <-- +NetworkStackApiCurrentShims
 //           +NetworkStackReleaseApiLevel   /        \    +NetworkStackDevApiLevel
-//           +jarjar apistub.api[latest].* /          \   +module src/
-//            to apistub.*                /            \
+//           +jarjar apishim.api[latest].* /          \
+//            to apishim.*                /            \
 //                                       /              \
-//         NetworkStackApiStableDependencies             \
+//                                      /                \
 //                                     /                  \               android libs w/ all code
-//                   +module src/ --> /                    \              (also used in unit tests)
+//                                    / <- +module src/ -> \              (also used in unit tests)
 //                                   /                      \                        |
 //               NetworkStackApiStableLib               NetworkStackApiCurrentLib <--*
 //                          |                                     |
@@ -41,6 +41,14 @@
 //                                                         TestNetworkStack
 
 // Common defaults to define SDK level
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Whether to enable the targets in this file that target current SDKs.
+// Set to false in branches like mainline-prod where API classes are too old to build current code.
+enable_current_sdk_targets = false
+
 java_defaults {
     name: "NetworkStackDevApiLevel",
     min_sdk_version: "29",
@@ -54,27 +62,116 @@
     target_sdk_version: "30",
 }
 
-// Filegroups for the API shims
-filegroup {
-    name: "NetworkStackApiCurrentShims",
+// Libraries for the API shims
+java_defaults {
+    name: "NetworkStackShimsDefaults",
+    libs: [
+        "androidx.annotation_annotation",
+        "networkstack-client",
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",  // For InProcessNetworkStack and InProcessTethering
+    ],
+    min_sdk_version: "29",
+}
+
+// Common shim code. This includes the shim interface definitions themselves, and things like
+// ShimUtils and UnsupportedApiLevelException. Compiles against system_current because ShimUtils
+// needs access to all Build.VERSION_CODES.*, which by definition are only in the newest SDK.
+// TODO: consider moving ShimUtils into a library (or removing it in favour of SdkLevel) and compile
+// this target against the lowest-supported SDK (currently 29).
+java_library {
+    name: "NetworkStackShimsCommon",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: ["apishim/common/**/*.java"],
+    sdk_version: "system_current",
+    visibility: ["//visibility:private"],
+}
+
+// Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding
+// system_X SDK. this ensures that each shim can only use SDK classes that exist in its SDK level.
+java_library {
+    name: "NetworkStackApi29Shims",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: ["apishim/29/**/*.java"],
+    libs: [
+        "NetworkStackShimsCommon",
+    ],
+    sdk_version: "system_29",
+    visibility: ["//visibility:private"],
+}
+
+java_library {
+    name: "NetworkStackApi30Shims",
+    defaults: ["NetworkStackShimsDefaults"],
     srcs: [
-        "apishim/common/**/*.java",
-        "apishim/29/**/*.java",
         "apishim/30/**/*.java",
+    ],
+    libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+    ],
+    sdk_version: "system_30",
+    visibility: ["//visibility:private"],
+}
+
+// Shims for APIs being added to the current development version of Android. These APIs are not
+// stable and have no defined version number. These could be called 10000, but they use the next
+// integer so if the next SDK release happens to use that integer, we don't need to rename them.
+java_library {
+    name: "NetworkStackApi31Shims",
+    enabled: enable_current_sdk_targets,
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: [
         "apishim/31/**/*.java",
-        ":networkstack-module-utils-srcs",
+    ],
+    libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "framework-connectivity",
+    ],
+    sdk_version: "module_current",
+    visibility: ["//visibility:private"],
+}
+
+// API current uses the API current shims directly.
+// The current (in-progress) shims are in the com.android.networkstack.apishim package and are
+// called directly by the networkstack code.
+java_library {
+    name: "NetworkStackApiCurrentShims",
+    enabled: enable_current_sdk_targets,
+    defaults: ["NetworkStackShimsDefaults"],
+    static_libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "NetworkStackApi31Shims",
+    ],
+    sdk_version: "module_current",
+    visibility: [
+        "//packages/modules/Connectivity/Tethering",
+        "//packages/modules/Connectivity/tests/cts/net",
     ],
 }
 
-// API stable shims only include the compat package, but it is jarjared to replace the non-compat
-// package
-filegroup {
+// API stable uses jarjar to rename the latest stable apishim package from
+// com.android.networkstack.apishim.apiXX to com.android.networkstack.apishim, which is called by
+// the networkstack code.
+java_library {
     name: "NetworkStackApiStableShims",
-    srcs: [
-        "apishim/common/**/*.java",
-        "apishim/29/**/*.java",
-        "apishim/30/**/*.java",
-        ":networkstack-module-utils-srcs",
+    defaults: ["NetworkStackShimsDefaults"],
+    static_libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+    ],
+    jarjar_rules: "apishim/jarjar-rules-compat.txt",
+    sdk_version: "system_30",
+    visibility: [
+        "//packages/modules/Connectivity/Tethering",
+        "//packages/modules/Connectivity/tests/cts/net",
     ],
 }
 
@@ -84,11 +181,12 @@
     name: "NetworkStackAndroidLibraryDefaults",
     srcs: [
         ":framework-networkstack-shared-srcs",
+        ":networkstack-module-utils-srcs",
     ],
     libs: ["unsupportedappusage"],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-java",
+        "netd_aidl_interface-lateststable-java",
         "netlink-client",
         "networkstack-client",
         "net-utils-framework-common",
@@ -101,43 +199,42 @@
     plugins: ["java_api_finder"],
 }
 
-// The versions of the android library containing network stack code compiled for each SDK variant
-// API current uses the sources of the API current shims directly.
-// This allows API current code to be treated identically to code in src/ (it will be moved
-// there eventually), and to use the compat shim as fallback on older devices.
+// The versions of the android library containing network stack code compiled for each SDK variant.
 android_library {
     name: "NetworkStackApiCurrentLib",
     defaults: ["NetworkStackDevApiLevel", "NetworkStackAndroidLibraryDefaults"],
     srcs: [
-        ":NetworkStackApiCurrentShims",
         "src/**/*.java",
         ":statslog-networkstack-java-gen-current"
     ],
+    static_libs: ["NetworkStackApiCurrentShims"],
     manifest: "AndroidManifestBase.xml",
-    enabled: false, // Disabled in mainline-prod
-}
-
-// For API stable, first build the dependencies using jarjar compat rules, then build the sources
-// linking with the dependencies.
-java_library {
-    name: "NetworkStackApiStableDependencies",
-    defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"],
-    srcs: [":NetworkStackApiStableShims"],
-    jarjar_rules: "apishim/jarjar-rules-compat.txt",
+    enabled: enable_current_sdk_targets,
+    visibility: [
+        "//frameworks/base/tests/net/integration",
+        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests/cts/net",
+        "//packages/modules/NetworkStack/tests/unit",
+        "//packages/modules/NetworkStack/tests/integration",
+    ],
 }
 
 android_library {
     name: "NetworkStackApiStableLib",
-    defaults: ["NetworkStackReleaseApiLevel"],
+    defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"],
     srcs: [
         "src/**/*.java",
         ":statslog-networkstack-java-gen-stable",
     ],
-    // API stable uses a jarjared version of the shims
-    static_libs: [
-        "NetworkStackApiStableDependencies",
-    ],
+    static_libs: ["NetworkStackApiStableShims"],
     manifest: "AndroidManifestBase.xml",
+    visibility: [
+        "//frameworks/base/tests/net/integration",
+        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests/cts/net",
+        "//packages/modules/NetworkStack/tests/unit",
+        "//packages/modules/NetworkStack/tests/integration",
+    ],
 }
 
 filegroup {
@@ -146,7 +243,6 @@
     visibility: [
         "//packages/modules/NetworkStack/tests/unit",
         "//packages/modules/NetworkStack/tests/integration",
-        "//frameworks/base/packages/Tethering/tests/integration",
         "//packages/modules/Connectivity/Tethering/tests/integration",
     ]
 }
@@ -181,7 +277,7 @@
     // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
     // the default CaptivePortalLogin.
     required: ["PlatformNetworkPermissionConfig", "PlatformCaptivePortalLogin"],
-    enabled: false, // Disabled in mainline-prod
+    enabled: enable_current_sdk_targets,
 }
 
 // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
@@ -190,7 +286,7 @@
     defaults: ["NetworkStackAppDefaults", "NetworkStackDevApiLevel"],
     static_libs: ["NetworkStackApiCurrentLib"],
     manifest: "AndroidManifest.xml",
-    enabled: false, // Disabled in mainline-prod
+    enabled: enable_current_sdk_targets,
 }
 
 // NetworkStack build targeting the current API release, for testing on in-development SDK
@@ -202,7 +298,7 @@
     manifest: "AndroidManifest_Next.xml",
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
-    enabled: false, // Disabled in mainline-prod
+    enabled: enable_current_sdk_targets,
 }
 
 // Updatable network stack for finalized API
@@ -217,19 +313,6 @@
     updatable: true,
 }
 
-// Android library to derive test APKs for integration tests
-android_library {
-    name: "TestNetworkStackLib",
-    defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
-    static_libs: ["NetworkStackApiStableLib"],
-    manifest: "AndroidManifestBase.xml",
-    visibility: [
-        "//frameworks/base/tests/net/integration",
-        "//cts/tests/tests/net",
-        "//packages/modules/Connectivity/tests/cts/net",
-    ],
-}
-
 cc_library_shared {
     name: "libnetworkstackutilsjni",
     srcs: [
diff --git a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
index 42216a9..9e27666 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.networkstack.apishim.api29;
 
-import android.net.CaptivePortalData;
+import android.net.Uri;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -28,7 +28,7 @@
 import org.json.JSONObject;
 
 /**
- * Compatibility implementation of {@link CaptivePortalDataShim}.
+ * Compatibility implementation of {@link CaptivePortalData}.
  *
  * <p>Use {@link com.android.networkstack.apishim.CaptivePortalDataShimImpl} instead of this
  * fallback implementation.
@@ -37,7 +37,7 @@
     protected CaptivePortalDataShimImpl() {}
 
     /**
-     * Parse a {@link android.net.CaptivePortalData} from JSON.
+     * Parse a {@link android.net.CaptivePortalDataShim} from JSON.
      *
      * <p>Use
      * {@link com.android.networkstack.apishim.CaptivePortalDataShimImpl#fromJson(JSONObject)}
@@ -56,19 +56,46 @@
         return null;
     }
 
+    @Override
+    public int getUserPortalUrlSource() {
+        // Not supported in API level 29
+        return ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
+    }
+
     @VisibleForTesting
     public static boolean isSupported() {
         return false;
     }
 
     /**
-     * Generate a {@link CaptivePortalData} object with a friendly name set
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name set
      *
      * @param friendlyName The friendly name to set
      * @return a {@link CaptivePortalData} object with a friendly name set
      */
-    public CaptivePortalData withVenueFriendlyName(String friendlyName) {
+    @Override
+    public CaptivePortalDataShim withVenueFriendlyName(String friendlyName)
+            throws UnsupportedApiLevelException {
         // Not supported in API level 29
-        return null;
+        throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29");
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external
+     * URLs set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    @Override
+    public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
+            throws UnsupportedApiLevelException {
+        // Not supported in API level 29
+        throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29");
     }
 }
diff --git a/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..e504495
--- /dev/null
+++ b/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.networkstack.apishim.api29;
+
+import android.content.Context;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+/**
+ * Implementation of {@link ConnectivityManagerShim} for API 29.
+ */
+public class ConnectivityManagerShimImpl implements ConnectivityManagerShim {
+    protected ConnectivityManagerShimImpl(Context context) {}
+
+    /**
+     * Get a new instance of {@link ConnectivityManagerShim}.
+     */
+    public static ConnectivityManagerShim newInstance(Context context) {
+        return new ConnectivityManagerShimImpl(context);
+    }
+    /**
+     * See android.net.ConnectivityManager#requestBackgroundNetwork
+     * @throws UnsupportedApiLevelException if API is not available in this API level.
+     */
+    @Override
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @Nullable Handler handler, @NonNull NetworkCallback networkCallback)
+            throws UnsupportedApiLevelException {
+        // Not supported for API 29.
+        throw new UnsupportedApiLevelException("Not supported in API 29.");
+    }
+
+    /**
+     * See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback
+     * @throws UnsupportedApiLevelException if API is not available in this API level.
+     */
+    @Override
+    public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+            @NonNull Handler handler) throws UnsupportedApiLevelException {
+        // Not supported for API 29.
+        throw new UnsupportedApiLevelException("Not supported in API 29.");
+    }
+}
diff --git a/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java b/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java
index b655858..0b000a9 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/ConstantsShim.java
@@ -34,4 +34,12 @@
     // Constants defined in android.net.ConnectivityDiagnosticsManager.
     public static final int DETECTION_METHOD_DNS_EVENTS = 1;
     public static final int DETECTION_METHOD_TCP_METRICS = 2;
+
+    // Constants defined in android.net.CaptivePortalData.
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0;
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1;
+
+    // Constants defined in android.net.NetworkCapabilities.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
 }
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
index 8dc7b5c..e68020b 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.networkstack.apishim.api29;
 
-import android.net.CaptivePortalData;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -121,10 +120,7 @@
      * @param captivePortalData Captive portal data to be used
      */
     public void setCaptivePortalData(@NonNull LinkProperties lp,
-            @Nullable CaptivePortalData captivePortalData) {
-        if (lp == null) {
-            return;
-        }
-        lp.setCaptivePortalData(captivePortalData);
+            @Nullable CaptivePortalDataShim captivePortalData) {
+        // Not supported on this API level: no-op
     }
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
index 19a41db..5825021 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -39,10 +39,14 @@
     @NonNull
     protected final CaptivePortalData mData;
 
-    protected CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
+    public CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
         mData = data;
     }
 
+    public CaptivePortalData getData() {
+        return mData;
+    }
+
     /**
      * Parse a {@link CaptivePortalDataShim} from a JSON object.
      * @throws JSONException The JSON is not a representation of correct captive portal data.
@@ -116,4 +120,36 @@
     public void notifyChanged(INetworkMonitorCallbacks cb) throws RemoteException {
         cb.notifyCaptivePortalDataChanged(mData);
     }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name set
+     *
+     * @param friendlyName The friendly name to set
+     * @return a {@link CaptivePortalDataShim} object with a friendly name set
+     */
+    @Override
+    public CaptivePortalDataShim withVenueFriendlyName(String friendlyName)
+            throws UnsupportedApiLevelException {
+        // Not supported in API level 29
+        throw new UnsupportedApiLevelException("FriendlyName not supported on API 30");
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external
+     * URLs set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    @Override
+    public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
+            throws UnsupportedApiLevelException {
+        // Not supported in API level 29
+        throw new UnsupportedApiLevelException("PasspointInfo not supported on API 30");
+    }
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..ac62522
--- /dev/null
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.networkstack.apishim.api30;
+
+import android.content.Context;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+/**
+ * Implementation of {@link ConnectivityManagerShim} for API 30.
+ */
+public class ConnectivityManagerShimImpl
+        extends com.android.networkstack.apishim.api29.ConnectivityManagerShimImpl {
+    protected ConnectivityManagerShimImpl(Context context) {
+        super(context);
+    }
+
+    /**
+     * Get a new instance of {@link ConnectivityManagerShim}.
+     */
+    public static ConnectivityManagerShim newInstance(Context context) {
+        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
+            return com.android.networkstack.apishim.api29.ConnectivityManagerShimImpl
+                    .newInstance(context);
+        }
+        return new ConnectivityManagerShimImpl(context);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#requestBackgroundNetwork
+     * @throws UnsupportedApiLevelException if API is not available in this API level.
+     */
+    @Override
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @Nullable Handler handler, @NonNull NetworkCallback networkCallback)
+            throws UnsupportedApiLevelException {
+        // Not supported for API 30.
+        throw new UnsupportedApiLevelException("Not supported in API 30.");
+    }
+
+    /**
+     * See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback
+     * @throws UnsupportedApiLevelException if API is not available in this API level.
+     */
+    @Override
+    public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+            @NonNull Handler handler) throws UnsupportedApiLevelException {
+        // Not supported for API 30.
+        throw new UnsupportedApiLevelException("Not supported in API 30.");
+    }
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
index 27fd745..d5012f8 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
@@ -38,8 +38,8 @@
     public static final int DETECTION_METHOD_TCP_METRICS =
             DataStallReport.DETECTION_METHOD_TCP_METRICS;
 
-    /**
-     * @see android.net.NetworkCapabilities
-     */
+    // Constants defined in android.net.NetworkCapabilities.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
     public static final int TRANSPORT_TEST = 7;
+
 }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
index 5d9b013..477dd42a 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/NetworkInformationShimImpl.java
@@ -21,6 +21,7 @@
 import android.net.NetworkCapabilities;
 import android.net.Uri;
 import android.os.Build;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -37,6 +38,8 @@
  */
 public class NetworkInformationShimImpl extends
         com.android.networkstack.apishim.api29.NetworkInformationShimImpl {
+    private static final String TAG = "api30.NetworkInformationShimImpl";
+
     protected NetworkInformationShimImpl() {}
 
     /**
@@ -105,4 +108,20 @@
             @NonNull Inet4Address serverAddress) {
         lp.setDhcpServerAddress(serverAddress);
     }
+
+    @Override
+    public void setCaptivePortalData(@NonNull LinkProperties lp,
+            @Nullable CaptivePortalDataShim captivePortalData) {
+        if (lp == null) {
+            return;
+        }
+        if (!(captivePortalData instanceof CaptivePortalDataShimImpl)) {
+            // The caller passed in a subclass that is not a CaptivePortalDataShimImpl.
+            // This is a programming error, but don't crash with ClassCastException.
+            Log.wtf(TAG, "Expected CaptivePortalDataShimImpl, but got "
+                    + captivePortalData.getClass().getName());
+            return;
+        }
+        lp.setCaptivePortalData(((CaptivePortalDataShimImpl) captivePortalData).getData());
+    }
 }
diff --git a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
index 955167d..6e3eb19 100644
--- a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
@@ -17,6 +17,7 @@
 package com.android.networkstack.apishim;
 
 import android.net.CaptivePortalData;
+import android.net.Uri;
 
 import androidx.annotation.NonNull;
 
@@ -27,7 +28,7 @@
  */
 public class CaptivePortalDataShimImpl
         extends com.android.networkstack.apishim.api30.CaptivePortalDataShimImpl {
-    protected CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
+    public CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
         super(data);
     }
 
@@ -37,14 +38,46 @@
     }
 
     /**
-     * Generate a {@link CaptivePortalData} object with a friendly name set
+     * Get the information source of the User portal
+     * @return The source that the User portal was obtained from
+     */
+    @Override
+    public int getUserPortalUrlSource() {
+        return mData.getUserPortalUrlSource();
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name set
      *
      * @param friendlyName The friendly name to set
-     * @return a {@link CaptivePortalData} object with a friendly name set
+     * @return a {@link CaptivePortalDataShim} object with a friendly name set
      */
-    public CaptivePortalData withVenueFriendlyName(String friendlyName) {
-        return new CaptivePortalData.Builder(mData)
+    @Override
+    public CaptivePortalDataShim withVenueFriendlyName(String friendlyName) {
+        return new CaptivePortalDataShimImpl(new CaptivePortalData.Builder(mData)
                 .setVenueFriendlyName(friendlyName)
-                .build();
+                .build());
+    }
+
+    /**
+     * Generate a {@link CaptivePortalDataShim} object with a friendly name and Passpoint external
+     * URLs set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @return a {@link CaptivePortalDataShim} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    @Override
+    public CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl) {
+        return new CaptivePortalDataShimImpl(new CaptivePortalData.Builder(mData)
+                .setVenueFriendlyName(friendlyName)
+                .setVenueInfoUrl(venueInfoUrl, ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(termsAndConditionsUrl,
+                        ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .build());
     }
 }
diff --git a/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java b/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..0c3fa3e
--- /dev/null
+++ b/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+
+/**
+ * Implementation of {@link ConnectivityManagerShim} for API 31.
+ */
+public class ConnectivityManagerShimImpl
+        extends com.android.networkstack.apishim.api30.ConnectivityManagerShimImpl  {
+    private final ConnectivityManager mCm;
+
+    protected ConnectivityManagerShimImpl(Context context) {
+        super(context);
+        mCm = context.getSystemService(ConnectivityManager.class);
+    }
+
+    /**
+     * Get a new instance of {@link ConnectivityManagerShim}.
+     */
+    public static ConnectivityManagerShim newInstance(Context context) {
+        if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) {
+            return com.android.networkstack.apishim.api30.ConnectivityManagerShimImpl
+                    .newInstance(context);
+        }
+        return new ConnectivityManagerShimImpl(context);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#requestBackgroundNetwork
+     */
+    @Override
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @Nullable Handler handler, @NonNull NetworkCallback networkCallback) {
+        mCm.requestBackgroundNetwork(request, handler, networkCallback);
+    }
+
+    /**
+     * See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback
+     */
+    @Override
+    public void registerSystemDefaultNetworkCallback(
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
+        mCm.registerSystemDefaultNetworkCallback(networkCallback, handler);
+    }
+}
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
index 4bd5532..26ac9d4 100644
--- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -16,12 +16,12 @@
 
 package com.android.networkstack.apishim.common;
 
-import android.annotation.NonNull;
-import android.net.CaptivePortalData;
 import android.net.INetworkMonitorCallbacks;
 import android.net.Uri;
 import android.os.RemoteException;
 
+import androidx.annotation.NonNull;
+
 /**
  * Compatibility interface for {@link android.net.CaptivePortalData}.
  */
@@ -57,6 +57,11 @@
     String getVenueFriendlyName();
 
     /**
+     * @see CaptivePortalData#getUserPortalUrlSource()
+     */
+    int getUserPortalUrlSource();
+
+    /**
      * @see INetworkMonitorCallbacks#notifyCaptivePortalDataChanged(android.net.CaptivePortalData)
      */
     void notifyChanged(INetworkMonitorCallbacks cb) throws RemoteException;
@@ -65,7 +70,25 @@
      * Generate a {@link CaptivePortalData} object with a friendly name set
      *
      * @param friendlyName The friendly name to set
+     * @throws UnsupportedApiLevelException when used with API level lower than 31
      * @return a {@link CaptivePortalData} object with a friendly name set
      */
-    CaptivePortalData withVenueFriendlyName(@NonNull String friendlyName);
+    CaptivePortalDataShim withVenueFriendlyName(@NonNull String friendlyName)
+            throws UnsupportedApiLevelException;
+
+    /**
+     * Generate a {@link CaptivePortalData} object with a friendly name and Passpoint external URLs
+     * set
+     *
+     * @param friendlyName The friendly name to set
+     * @param venueInfoUrl Venue information URL
+     * @param termsAndConditionsUrl Terms and conditions URL
+     *
+     * @throws UnsupportedApiLevelException when used with API level lower than 31
+     * @return a {@link CaptivePortalData} object with friendly name, venue info URL and terms
+     * and conditions URL set
+     */
+    CaptivePortalDataShim withPasspointInfo(@NonNull String friendlyName,
+            @NonNull Uri venueInfoUrl, @NonNull Uri termsAndConditionsUrl)
+            throws UnsupportedApiLevelException;
 }
diff --git a/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java b/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java
new file mode 100644
index 0000000..2d60d97
--- /dev/null
+++ b/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.networkstack.apishim.common;
+
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+
+/**
+ * Interface used to access API methods in {@link android.net.ConnectivityManager}, with
+ * appropriate fallbacks if the methods are not yet part of the released API.
+ *
+ * <p>This interface makes it easier for callers to use ConnectivityManagerShimImpl, as it's more
+ * obvious what methods must be implemented on each API level, and it abstracts from callers the
+ * need to reference classes that have different implementations (which also does not work well
+ * with IDEs).
+ */
+public interface ConnectivityManagerShim {
+    /** See android.net.ConnectivityManager#requestBackgroundNetwork */
+    void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @Nullable Handler handler, @NonNull NetworkCallback networkCallback)
+            throws UnsupportedApiLevelException;
+
+    /** See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback */
+    void registerSystemDefaultNetworkCallback(
+            @NonNull NetworkCallback networkCallback, @NonNull Handler handler)
+            throws UnsupportedApiLevelException;
+}
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
index 6cdcf8c..b2ed53e 100644
--- a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
@@ -16,7 +16,6 @@
 
 package com.android.networkstack.apishim.common;
 
-import android.net.CaptivePortalData;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -83,5 +82,5 @@
      * @param captivePortalData Captive portal data to be used
      */
     void setCaptivePortalData(@NonNull LinkProperties lp,
-            @Nullable CaptivePortalData captivePortalData);
+            @Nullable CaptivePortalDataShim captivePortalData);
 }
diff --git a/common/captiveportal/Android.bp b/common/captiveportal/Android.bp
index 0b49eb2..876e733 100644
--- a/common/captiveportal/Android.bp
+++ b/common/captiveportal/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library {
     name: "captiveportal-lib",
     srcs: ["src/**/*.java"],
@@ -23,4 +27,4 @@
     sdk_version: "system_current",
     // this is part of updatable modules(NetworkStack) which targets 29(Q)
     min_sdk_version: "29",
-}
\ No newline at end of file
+}
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 644b0a4..5d1c9e3 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -17,6 +17,10 @@
 // Shared utility sources to be used by multiple network modules
 // TODO: remove all frameworks/base dependencies on packages/modules/NetworkStack and
 // frameworks/base/packages/Tethering by moving these files to frameworks/libs/net.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 filegroup {
     name: "net-module-utils-srcs",
     srcs: [
diff --git a/common/netlinkclient/Android.bp b/common/netlinkclient/Android.bp
index 2b4a2d6..9a60e57 100644
--- a/common/netlinkclient/Android.bp
+++ b/common/netlinkclient/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library {
     name: "netlink-client",
     srcs: [
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index dc91881..96ff315 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -15,6 +15,10 @@
 //
 
 // AIDL interfaces between the core system and the networking mainline module.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 aidl_interface {
     name: "ipmemorystore-aidl-interfaces",
     local_include_dir: "src",
@@ -68,7 +72,7 @@
         // For framework parcelables.
         "frameworks/base/core/java",
         // For API parcelables in connectivity
-        "frameworks/base/packages/Connectivity/framework/src",
+        "frameworks/base/packages/Connectivity/framework/aidl-export",
         "frameworks/native/aidl/binder", // For PersistableBundle.aidl
     ],
     srcs: [
@@ -152,8 +156,8 @@
         "src/android/net/shared/**/*.java",
     ],
     static_libs: [
-        "ipmemorystore-aidl-interfaces-java",
-        "networkstack-aidl-interfaces-java",
+        "ipmemorystore-aidl-interfaces-V10-java",
+        "networkstack-aidl-interfaces-V10-java",
     ],
     visibility: [
         "//frameworks/base/packages/Tethering",
@@ -162,4 +166,8 @@
         "//frameworks/opt/net/wifi/service",
         "//packages/modules/NetworkStack",
     ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
 }
diff --git a/proguard.flags b/proguard.flags
index af4262a..13425ce 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -8,6 +8,10 @@
     static final int EVENT_*;
 }
 
+-keepclassmembers public class * extends com.android.networkstack.util.Struct {
+    public <init>(...);
+}
+
 # The lite proto runtime uses reflection to access fields based on the names in
 # the schema, keep all the fields.
 # This replicates the base proguard rule used by the build by default
diff --git a/src/com/android/networkstack/packets/NeighborAdvertisement.java b/src/com/android/networkstack/packets/NeighborAdvertisement.java
new file mode 100644
index 0000000..e6cdfc8
--- /dev/null
+++ b/src/com/android/networkstack/packets/NeighborAdvertisement.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.networkstack.packets;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NaHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse Neighbor Advertisement packet.
+ *
+ * @hide
+ */
+public class NeighborAdvertisement {
+    @NonNull
+    public final EthernetHeader ethHdr;
+    @NonNull
+    public final Ipv6Header ipv6Hdr;
+    @NonNull
+    public final Icmpv6Header icmpv6Hdr;
+    @NonNull
+    public final NaHeader naHdr;
+    @NonNull
+    public final LlaOption tlla;
+
+    public NeighborAdvertisement(@NonNull final EthernetHeader ethHdr,
+            @NonNull final Ipv6Header ipv6Hdr, @NonNull final Icmpv6Header icmpv6Hdr,
+            @NonNull final NaHeader naHdr, @NonNull final LlaOption tlla) {
+        this.ethHdr = ethHdr;
+        this.ipv6Hdr = ipv6Hdr;
+        this.icmpv6Hdr = icmpv6Hdr;
+        this.naHdr = naHdr;
+        this.tlla = tlla;
+    }
+
+    /**
+     * Convert a Neighbor Advertisement instance to ByteBuffer.
+     */
+    public ByteBuffer toByteBuffer() {
+        final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+        final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
+        final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
+        final int naHeaderLen = Struct.getSize(NaHeader.class);
+        final int tllaOptionLen = Struct.getSize(LlaOption.class);
+        final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
+                + icmpv6HeaderLen + naHeaderLen + tllaOptionLen);
+
+        ethHdr.writeToByteBuffer(packet);
+        ipv6Hdr.writeToByteBuffer(packet);
+        icmpv6Hdr.writeToByteBuffer(packet);
+        naHdr.writeToByteBuffer(packet);
+        tlla.writeToByteBuffer(packet);
+        packet.flip();
+
+        return packet;
+    }
+
+    /**
+     * Build a Neighbor Advertisement packet from the required specified parameters.
+     */
+    public static ByteBuffer build(@NonNull final MacAddress srcMac,
+            @NonNull final MacAddress dstMac, @NonNull final Inet6Address srcIp,
+            @NonNull final Inet6Address dstIp, int flags, @NonNull final Inet6Address target) {
+        final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
+        return Ipv6Utils.buildNaPacket(srcMac, dstMac, srcIp, dstIp, flags, target, tlla);
+    }
+
+    /**
+     * Parse a Neighbor Advertisement packet from ByteBuffer.
+     */
+    public static NeighborAdvertisement parse(@NonNull final byte[] recvbuf, final int length)
+            throws ParseException {
+        if (length < ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN
+                || recvbuf.length < length) {
+            throw new ParseException("Invalid packet length: " + length);
+        }
+        final ByteBuffer packet = ByteBuffer.wrap(recvbuf, 0, length);
+
+        // Parse each header and option in Neighbor Advertisement packet in order.
+        final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, packet);
+        final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, packet);
+        final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, packet);
+        final NaHeader naHdr = Struct.parse(NaHeader.class, packet);
+        final LlaOption tlla = Struct.parse(LlaOption.class, packet);
+
+        return new NeighborAdvertisement(ethHdr, ipv6Hdr, icmpv6Hdr, naHdr, tlla);
+    }
+
+    /**
+     * Thrown when parsing Neighbor Advertisement packet failed.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index f8a9bab..b4a3ae9 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -167,7 +167,9 @@
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
+import com.android.networkstack.apishim.api29.ConstantsShim;
 import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.networkstack.apishim.common.ShimUtils;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.metrics.DataStallDetectionStats;
@@ -515,6 +517,8 @@
     private final boolean mPrivateIpNoInternetEnabled;
 
     private final boolean mMetricsEnabled;
+    @NonNull
+    private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance();
 
     // The validation metrics are accessed by individual probe threads, and by the StateMachine
     // thread. All accesses must be synchronized to make sure the StateMachine thread can see
@@ -1227,6 +1231,22 @@
             if (!mEvaluationTimer.isStarted()) {
                 mEvaluationTimer.start();
             }
+
+            // Check if the network is captive with Terms & Conditions page. The first network
+            // evaluation for captive networks with T&Cs returns early but NetworkMonitor will then
+            // keep checking for connectivity to determine when the T&Cs are cleared.
+            if (isTermsAndConditionsCaptive(mInfoShim.getCaptivePortalData(mLinkProperties))
+                    && mValidations == 0) {
+                mLastPortalProbeResult = new CaptivePortalProbeResult(
+                        CaptivePortalProbeResult.PORTAL_CODE,
+                        mLinkProperties.getCaptivePortalData().getUserPortalUrl()
+                                .toString(), null,
+                        CaptivePortalProbeResult.PROBE_UNKNOWN);
+                mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                        mLastPortalProbeResult.redirectUrl);
+                transitionTo(mCaptivePortalState);
+                return;
+            }
             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
             if (mUidResponsibleForReeval != INVALID_UID) {
                 TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
@@ -1562,6 +1582,16 @@
                         // Transit EvaluatingPrivateDnsState to get to Validated
                         // state (even if no Private DNS validation required).
                         transitionTo(mEvaluatingPrivateDnsState);
+                    } else if (isTermsAndConditionsCaptive(
+                            mInfoShim.getCaptivePortalData(mLinkProperties))) {
+                        mLastPortalProbeResult = new CaptivePortalProbeResult(
+                                CaptivePortalProbeResult.PORTAL_CODE,
+                                mLinkProperties.getCaptivePortalData().getUserPortalUrl()
+                                        .toString(), null,
+                                CaptivePortalProbeResult.PROBE_UNKNOWN);
+                        mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                                mLastPortalProbeResult.redirectUrl);
+                        transitionTo(mCaptivePortalState);
                     } else if (probeResult.isPortal()) {
                         mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
                                 probeResult.redirectUrl);
@@ -3566,4 +3596,17 @@
     private static Uri getCaptivePortalApiUrl(LinkProperties lp) {
         return NetworkInformationShimImpl.newInstance().getCaptivePortalApiUrl(lp);
     }
+
+    /**
+     * Check if the network is captive with terms and conditions page
+     * @return true if network is captive with T&C page, false otherwise
+     */
+    private boolean isTermsAndConditionsCaptive(CaptivePortalDataShim captivePortalDataShim) {
+        return captivePortalDataShim != null
+                && captivePortalDataShim.getUserPortalUrl() != null
+                && !TextUtils.isEmpty(captivePortalDataShim.getUserPortalUrl().toString())
+                && captivePortalDataShim.isCaptive()
+                && captivePortalDataShim.getUserPortalUrlSource()
+                == ConstantsShim.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT;
+    }
 }
diff --git a/tests/hostdriven/Android.bp b/tests/hostdriven/Android.bp
index 3509f89..6c3de45 100644
--- a/tests/hostdriven/Android.bp
+++ b/tests/hostdriven/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_test_host {
     name: "NetworkStackHostTests",
     srcs: ["host/src/**/*.kt"],
diff --git a/tests/hostlib/Android.bp b/tests/hostlib/Android.bp
index 9a88634..189a88c 100644
--- a/tests/hostlib/Android.bp
+++ b/tests/hostlib/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library_host {
     name: "net-host-tests-utils",
     srcs: [
@@ -26,4 +30,4 @@
         "kotlin-test",
         "cts-install-lib-host",
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 2c30b3c..2cffc3d 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_defaults {
     name: "NetworkStackIntegrationTestsJniDefaults",
     defaults: ["libnetworkstackutilsjni_deps"],
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 1b5660c..451437e 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -31,7 +31,6 @@
 import static android.system.OsConstants.ETH_P_IPV6;
 import static android.system.OsConstants.IFA_F_TEMPORARY;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
 
 import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
 import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
@@ -39,18 +38,11 @@
 import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_CHECKSUM_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
 
 import static junit.framework.Assert.fail;
@@ -141,7 +133,9 @@
 
 import com.android.internal.util.StateMachine;
 import com.android.net.module.util.ArrayTrackRecord;
-import com.android.net.module.util.IpUtils;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RdnssOption;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.ShimUtils;
@@ -180,6 +174,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.nio.ByteBuffer;
@@ -1468,111 +1463,25 @@
     // TODO: move this and the following method to a common location and use them in ApfTest.
     private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
             throws Exception {
-        final int optLen = 4;
-        IpPrefix prefix = new IpPrefix(prefixString);
-        ByteBuffer option = ByteBuffer.allocate(optLen * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR);
-        option.put((byte) ICMPV6_ND_OPTION_PIO);      // Type
-        option.put((byte) optLen);                    // Length in 8-byte units
-        option.put((byte) prefix.getPrefixLength());  // Prefix length
-        option.put((byte) 0b11000000);                // L = 1, A = 1
-        option.putInt(valid);
-        option.putInt(preferred);
-        option.putInt(0);                             // Reserved
-        option.put(prefix.getRawAddress());
-        option.flip();
-        return option;
+        return PrefixInformationOption.build(new IpPrefix(prefixString),
+                (byte) 0b11000000 /* L = 1, A = 1 */, valid, preferred);
     }
 
     private static ByteBuffer buildRdnssOption(int lifetime, String... servers) throws Exception {
-        final int optLen = 1 + 2 * servers.length;
-        ByteBuffer option = ByteBuffer.allocate(optLen * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR);
-        option.put((byte) ICMPV6_ND_OPTION_RDNSS);  // Type
-        option.put((byte) optLen);                  // Length in 8-byte units
-        option.putShort((short) 0);                 // Reserved
-        option.putInt(lifetime);                    // Lifetime
-        for (String server : servers) {
-            option.put(InetAddress.getByName(server).getAddress());
-        }
-        option.flip();
-        return option;
-    }
-
-    // HACK: these functions are here because IpUtils#transportChecksum is private. Even if we made
-    // that public, it won't be available on Q devices, and this test needs to run on Q devices.
-    // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
-    private static int checksumFold(int sum) {
-        while (sum > 0xffff) {
-            sum = (sum >> 16) + (sum & 0xffff);
-        }
-        return sum;
-    }
-
-    private static short checksumAdjust(short checksum, short oldWord, short newWord) {
-        checksum = (short) ~checksum;
-        int tempSum = checksumFold(uint16(checksum) + uint16(newWord) + 0xffff - uint16(oldWord));
-        return (short) ~tempSum;
-    }
-
-    public static int uint16(short s) {
-        return s & 0xffff;
-    }
-
-    private static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset,
-            int transportLen) {
-        // The ICMPv6 checksum is the same as the TCP checksum, except the pseudo-header uses
-        // 58 (ICMPv6) instead of 6 (TCP). Calculate the TCP checksum, and then do an incremental
-        // checksum adjustment  for the change in the next header byte.
-        short checksum = IpUtils.tcpChecksum(buf, ipOffset, transportOffset, transportLen);
-        return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
+        return RdnssOption.build(lifetime, servers);
     }
 
     private static ByteBuffer buildRaPacket(short lifetime, ByteBuffer... options)
             throws Exception {
-        final MacAddress srcMac = MacAddress.fromString("33:33:00:00:00:01");
-        final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
-        final byte[] routerLinkLocal = InetAddresses.parseNumericAddress("fe80::1").getAddress();
-        final byte[] allNodes = InetAddresses.parseNumericAddress("ff02::1").getAddress();
+        final MacAddress dstMac = MacAddress.fromString("33:33:00:00:00:01");
+        final MacAddress srcMac = MacAddress.fromString("01:02:03:04:05:06");
+        final Inet6Address routerLinkLocal =
+                (Inet6Address) InetAddresses.parseNumericAddress("fe80::1");
 
-        final ByteBuffer packet = ByteBuffer.allocate(TEST_DEFAULT_MTU);
-        int icmpLen = ICMPV6_RA_HEADER_LEN;
-
-        // Ethernet header.
-        packet.put(srcMac.toByteArray());
-        packet.put(dstMac.toByteArray());
-        packet.putShort((short) ETHER_TYPE_IPV6);
-
-        // IPv6 header.
-        packet.putInt(0x600abcde);                       // Version, traffic class, flowlabel
-        packet.putShort((short) 0);                      // Length, TBD
-        packet.put((byte) IPPROTO_ICMPV6);               // Next header
-        packet.put((byte) 0xff);                         // Hop limit
-        packet.put(routerLinkLocal);                     // Source address
-        packet.put(allNodes);                            // Destination address
-
-        // Router advertisement.
-        packet.put((byte) ICMPV6_ROUTER_ADVERTISEMENT);  // ICMP type
-        packet.put((byte) 0);                            // ICMP code
-        packet.putShort((short) 0);                      // Checksum, TBD
-        packet.put((byte) 0);                            // Hop limit, unspecified
-        packet.put((byte) 0);                            // M=0, O=0
-        packet.putShort(lifetime);                       // Router lifetime
-        packet.putInt(0);                                // Reachable time, unspecified
-        packet.putInt(100);                              // Retrans time 100ms.
-
-        for (ByteBuffer option : options) {
-            packet.put(option);
-            option.clear();  // So we can reuse it in a future packet.
-            icmpLen += option.capacity();
-        }
-
-        // Populate length and checksum fields.
-        final int transportOffset = ETHER_HEADER_LEN + IPV6_HEADER_LEN;
-        final short checksum = icmpv6Checksum(packet, ETHER_HEADER_LEN, transportOffset, icmpLen);
-        packet.putShort(ETHER_HEADER_LEN + IPV6_LEN_OFFSET, (short) icmpLen);
-        packet.putShort(transportOffset + ICMPV6_CHECKSUM_OFFSET, checksum);
-
-        packet.flip();
-        return packet;
+        return Ipv6Utils.buildRaPacket(srcMac, dstMac, routerLinkLocal,
+                IPV6_ADDR_ALL_NODES_MULTICAST, (byte) 0 /* M=0, O=0 */, lifetime,
+                0 /* Reachable time, unspecified */, 100 /* Retrans time 100ms */,
+                options);
     }
 
     private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index eb52f92..e308b96 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_defaults {
     name: "NetworkStackTestsDefaults",
     platform_apis: true,
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index fa1f420..0c9087f 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_shared {
     name: "libnetworkstacktestsjni",
     srcs: [
diff --git a/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java b/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java
new file mode 100644
index 0000000..3317b2b
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/packets/NeighborAdvertisementTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.networkstack.packets;
+
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NeighborAdvertisementTest {
+    private static final Inet6Address TEST_SRC_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::dfd9:50a0:cc7b:7d6d");
+    private static final Inet6Address TEST_TARGET_ADDR =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:db8:1:0:c928:250d:b90c:3178");
+    private static final byte[] TEST_SOURCE_MAC_ADDR = new byte[] {
+            (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25
+    };
+    private static final byte[] TEST_DST_MAC_ADDR = new byte[] {
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+        // ICMP type, code, checksum
+        (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+        // flags
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+        (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+        (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+        // TLLA option
+        (byte) 0x02, (byte) 0x01,
+        // Link-Layer address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA_LESS_LENGTH = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+    };
+    private static final byte[] TEST_GRATUITOUS_NA_TRUNCATED = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25, (byte) 0xc1, (byte) 0x25,
+        // ether type
+        (byte) 0x86, (byte) 0xdd,
+        // version, priority and flow label
+        (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // length
+        (byte) 0x00, (byte) 0x20,
+        // next header
+        (byte) 0x3a,
+        // hop limit
+        (byte) 0xff,
+        // source address
+        (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0xdf, (byte) 0xd9, (byte) 0x50, (byte) 0xa0,
+        (byte) 0xcc, (byte) 0x7b, (byte) 0x7d, (byte) 0x6d,
+        // destination address
+        (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+        // ICMP type, code, checksum
+        (byte) 0x88, (byte) 0x00, (byte) 0x3a, (byte) 0x3c,
+        // flags
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target address
+        (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+        (byte) 0xc9, (byte) 0x28, (byte) 0x25, (byte) 0x0d,
+        (byte) 0xb9, (byte) 0x0c, (byte) 0x31, (byte) 0x78,
+        // TLLA option
+        (byte) 0x02, (byte) 0x01,
+        // Link-Layer address
+        (byte) 0xea, (byte) 0xbe, (byte) 0x11, (byte) 0x25,
+    };
+
+    @Test
+    public void testGratuitousNa_build() throws Exception {
+        final ByteBuffer na = NeighborAdvertisement.build(
+                MacAddress.fromBytes(TEST_SOURCE_MAC_ADDR),
+                MacAddress.fromBytes(TEST_DST_MAC_ADDR),
+                TEST_SRC_ADDR, IPV6_ADDR_ALL_ROUTERS_MULTICAST, 0 /* flags */, TEST_TARGET_ADDR);
+        assertArrayEquals(na.array(), TEST_GRATUITOUS_NA);
+    }
+
+    @Test
+    public void testGratuitousNa_parse() throws Exception {
+        final NeighborAdvertisement na = NeighborAdvertisement.parse(TEST_GRATUITOUS_NA,
+                TEST_GRATUITOUS_NA.length);
+
+        assertArrayEquals(TEST_SOURCE_MAC_ADDR, na.ethHdr.srcMac.toByteArray());
+        assertArrayEquals(TEST_DST_MAC_ADDR, na.ethHdr.dstMac.toByteArray());
+        assertEquals(ETH_P_IPV6, na.ethHdr.etherType);
+        assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader);
+        assertEquals(0xff, na.ipv6Hdr.hopLimit);
+        assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp);
+        assertEquals(TEST_SRC_ADDR, na.ipv6Hdr.srcIp);
+        assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type);
+        assertEquals(0, na.icmpv6Hdr.code);
+        assertEquals(0, na.naHdr.flags);
+        assertEquals(TEST_TARGET_ADDR, na.naHdr.target);
+        assertEquals(2, na.tlla.type);
+        assertEquals(1, na.tlla.length);
+        assertArrayEquals(TEST_SOURCE_MAC_ADDR, na.tlla.linkLayerAddress.toByteArray());
+
+        assertArrayEquals(TEST_GRATUITOUS_NA, na.toByteBuffer().array());
+    }
+
+    @Test
+    public void testGratuitousNa_invalidByteBufferParameters() throws Exception {
+        assertThrows(NeighborAdvertisement.ParseException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA, 0));
+    }
+
+    @Test
+    public void testGratuitousNa_lessPacketLength() throws Exception {
+        assertThrows(NeighborAdvertisement.ParseException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_LESS_LENGTH,
+                                                  TEST_GRATUITOUS_NA_LESS_LENGTH.length));
+    }
+
+    @Test
+    public void testGratuitousNa_truncatedPacket() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> NeighborAdvertisement.parse(TEST_GRATUITOUS_NA_TRUNCATED,
+                                                  TEST_GRATUITOUS_NA_TRUNCATED.length));
+    }
+}
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 8f42a61..2735fb6 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -142,7 +142,11 @@
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.NetworkInformationShimImpl;
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.metrics.DataStallDetectionStats;
 import com.android.networkstack.metrics.DataStallStatsUtils;
 import com.android.networkstack.netlink.TcpSocketTracker;
@@ -261,6 +265,7 @@
     private static final String TEST_SPEED_TEST_URL = "https://speedtest.example.com";
     private static final String TEST_RELATIVE_URL = "/test/relative/gen_204";
     private static final String TEST_MCCMNC = "123456";
+    private static final String TEST_FRIENDLY_NAME = "Friendly Name";
     private static final String[] TEST_HTTP_URLS = {TEST_HTTP_OTHER_URL1, TEST_HTTP_OTHER_URL2};
     private static final String[] TEST_HTTPS_URLS = {TEST_HTTPS_OTHER_URL1, TEST_HTTPS_OTHER_URL2};
     private static final int TEST_TCP_FAIL_RATE = 99;
@@ -2537,6 +2542,59 @@
         verify(mCleartextDnsNetwork, times(4)).openConnection(any());
     }
 
+    @Test
+    public void testIsCaptivePortal_FromExternalSource() throws Exception {
+        assumeTrue(CaptivePortalDataShimImpl.isSupported());
+        assumeTrue(ShimUtils.isAtLeastS());
+        when(mDependencies.isFeatureEnabled(any(), eq(NAMESPACE_CONNECTIVITY),
+                eq(DISMISS_PORTAL_IN_VALIDATED_NETWORK), anyBoolean())).thenReturn(true);
+        final NetworkMonitor monitor = makeMonitor(WIFI_NOT_METERED_CAPABILITIES);
+
+        NetworkInformationShim networkShim = NetworkInformationShimImpl.newInstance();
+        CaptivePortalDataShim captivePortalData = new CaptivePortalDataShimImpl(
+                new CaptivePortalData.Builder().setCaptive(true).build());
+        final LinkProperties linkProperties = new LinkProperties(TEST_LINK_PROPERTIES);
+        networkShim.setCaptivePortalData(linkProperties, captivePortalData);
+        CaptivePortalDataShim captivePortalDataShim =
+                networkShim.getCaptivePortalData(linkProperties);
+
+        try {
+            // Set up T&C captive portal info from Passpoint
+            captivePortalData = captivePortalDataShim.withPasspointInfo(TEST_FRIENDLY_NAME,
+                    Uri.parse(TEST_VENUE_INFO_URL), Uri.parse(TEST_LOGIN_URL));
+        } catch (UnsupportedApiLevelException e) {
+            // Minimum API level for this test is 31
+            return;
+        }
+
+        networkShim.setCaptivePortalData(linkProperties, captivePortalData);
+        monitor.notifyLinkPropertiesChanged(linkProperties);
+        final NetworkCapabilities networkCapabilities =
+                new NetworkCapabilities(WIFI_NOT_METERED_CAPABILITIES);
+        monitor.notifyNetworkConnected(linkProperties, networkCapabilities);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .showProvisioningNotification(any(), any());
+        assertEquals(1, mRegisteredReceivers.size());
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+
+        // Force reevaluation and confirm that the network is still captive
+        HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
+        resetCallbacks();
+        monitor.forceReevaluation(Process.myUid());
+        assertEquals(monitor.getEvaluationState().getProbeCompletedResult(), 0);
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+
+        // Check that startCaptivePortalApp sends the expected intent.
+        monitor.launchCaptivePortalApp();
+
+        verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1)).startCaptivePortalApp(
+                argThat(network -> TEST_NETID == network.netId),
+                argThat(bundle -> bundle.getString(
+                        ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL).equals(TEST_LOGIN_URL)
+                        && TEST_NETID == ((Network) bundle.getParcelable(
+                        ConnectivityManager.EXTRA_NETWORK)).netId));
+    }
+
     private void setupResourceForMultipleProbes() {
         // Configure the resource to send multiple probe.
         when(mResources.getStringArray(R.array.config_captive_portal_https_urls))