[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 230e1fb647 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/15433152

Change-Id: I2648feec26ee30d2d923e92a15ba6ecb87e4fc80
diff --git a/Android.bp b/Android.bp
index cce44b0..367c542 100644
--- a/Android.bp
+++ b/Android.bp
@@ -45,10 +45,6 @@
     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",
@@ -57,9 +53,14 @@
 
 java_defaults {
     name: "NetworkStackReleaseApiLevel",
-    sdk_version: "system_30",
+    sdk_version: "module_31",
     min_sdk_version: "29",
-    target_sdk_version: "30",
+    target_sdk_version: "31",
+    libs: [
+        "framework-connectivity",
+        "framework-statsd",
+        "framework-wifi",
+    ]
 }
 
 // Libraries for the API shims
@@ -127,7 +128,6 @@
 // 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",
@@ -138,6 +138,27 @@
         "NetworkStackApi30Shims",
         "framework-connectivity",
     ],
+    sdk_version: "module_31",
+    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: "NetworkStackApi32Shims",
+    defaults: ["NetworkStackShimsDefaults"],
+    srcs: [
+        "apishim/32/**/*.java",
+    ],
+    libs: [
+        "NetworkStackShimsCommon",
+        "NetworkStackApi29Shims",
+        "NetworkStackApi30Shims",
+        "NetworkStackApi31Shims",
+        "framework-connectivity",
+    ],
     sdk_version: "module_current",
     visibility: ["//visibility:private"],
 }
@@ -147,13 +168,13 @@
 // called directly by the networkstack code.
 java_library {
     name: "NetworkStackApiCurrentShims",
-    enabled: enable_current_sdk_targets,
     defaults: ["NetworkStackShimsDefaults"],
     static_libs: [
         "NetworkStackShimsCommon",
         "NetworkStackApi29Shims",
         "NetworkStackApi30Shims",
         "NetworkStackApi31Shims",
+        "NetworkStackApi32Shims",
     ],
     sdk_version: "module_current",
     visibility: [
@@ -172,9 +193,10 @@
         "NetworkStackShimsCommon",
         "NetworkStackApi29Shims",
         "NetworkStackApi30Shims",
+        "NetworkStackApi31Shims",
     ],
     jarjar_rules: "apishim/jarjar-rules-compat.txt",
-    sdk_version: "system_30",
+    sdk_version: "module_31",
     visibility: [
         "//packages/modules/Connectivity/Tethering",
         "//packages/modules/Connectivity/tests/cts/net",
@@ -215,7 +237,6 @@
     ],
     static_libs: ["NetworkStackApiCurrentShims"],
     manifest: "AndroidManifestBase.xml",
-    enabled: enable_current_sdk_targets,
     visibility: [
         "//frameworks/base/tests/net/integration",
         "//packages/modules/Connectivity/Tethering/tests/integration",
@@ -290,8 +311,10 @@
     // The permission configuration *must* be included to ensure security of the device
     // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
     // the default CaptivePortalLogin.
-    required: ["PlatformNetworkPermissionConfig", "PlatformCaptivePortalLogin"],
-    enabled: enable_current_sdk_targets,
+    required: [
+        "PlatformNetworkPermissionConfig",
+        "PlatformCaptivePortalLogin",
+    ],
 }
 
 // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
@@ -299,8 +322,7 @@
     name: "NetworkStackNextManifestBase",
     defaults: ["NetworkStackAppDefaults", "NetworkStackDevApiLevel"],
     static_libs: ["NetworkStackApiCurrentLib"],
-    manifest: "AndroidManifest.xml",
-    enabled: enable_current_sdk_targets,
+    manifest: "AndroidManifest.xml"
 }
 
 // NetworkStack build targeting the current API release, for testing on in-development SDK
@@ -311,8 +333,10 @@
     certificate: "networkstack",
     manifest: "AndroidManifest_Next.xml",
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
-    enabled: enable_current_sdk_targets,
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack",
+    ],
 }
 
 // Updatable network stack for finalized API
@@ -323,7 +347,10 @@
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack",
+    ],
     updatable: true,
 }
 
@@ -400,7 +427,10 @@
     certificate: "networkstack",
     manifest: ":NetworkStackTestAndroidManifest",
     // The permission configuration *must* be included to ensure security of the device
-    required: ["NetworkPermissionConfig"],
+    required: [
+        "NetworkPermissionConfig",
+        "privapp_whitelist_com.android.networkstack",
+    ],
 }
 
 // When adding or modifying protos, the jarjar rules and possibly proguard rules need
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 55357a8..6a11b2c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,8 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.android.networkstack"
   android:sharedUserId="android.uid.networkstack"
-  android:versionCode="309999900"
-  android:versionName="r_aml_309999900"
+  android:versionCode="319999900"
+  android:versionName="s_aml_319999900"
 >
     <!-- Permissions must be defined here, and not in the base manifest, as the network stack
          running in the system server process does not need any permission, and having privileged
diff --git a/AndroidManifest_Next.xml b/AndroidManifest_Next.xml
index 02fcb64..9ad69ae 100644
--- a/AndroidManifest_Next.xml
+++ b/AndroidManifest_Next.xml
@@ -17,6 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.networkstack"
           android:sharedUserId="android.uid.networkstack"
-          android:versionCode="300000000"
-          android:versionName="R-next">
+          android:versionCode="320000000"
+          android:versionName="T-next">
 </manifest>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ed6a1a8..2bb0e61 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -40,7 +40,8 @@
     // We must specify at least one module here or the tests won't run. Use the same set as CTS
     // so in theory the infra would not need to reinstall/reboot devices to run both.
     {
-      "name": "NetworkStackTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+      // TODO: add back tethering when it is updatable in this branch
+      "name": "NetworkStackTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex]"
     }
   ],
   "mainline-postsubmit": [
diff --git a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/CaptivePortalDataShimImpl.java
similarity index 98%
rename from apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/CaptivePortalDataShimImpl.java
index 5af7412..5ae006b 100644
--- a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/CaptivePortalDataShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import android.net.CaptivePortalData;
 import android.net.Uri;
diff --git a/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/ConnectivityManagerShimImpl.java
similarity index 98%
rename from apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/ConnectivityManagerShimImpl.java
index 0e01c98..46de698 100644
--- a/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/ConnectivityManagerShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 
diff --git a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java b/apishim/31/com/android/networkstack/apishim/api31/ConstantsShim.java
similarity index 96%
rename from apishim/31/com/android/networkstack/apishim/ConstantsShim.java
rename to apishim/31/com/android/networkstack/apishim/api31/ConstantsShim.java
index 0184845..95ff072 100644
--- a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/ConstantsShim.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import androidx.annotation.VisibleForTesting;
 
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NetworkInformationShimImpl.java
similarity index 94%
rename from apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/NetworkInformationShimImpl.java
index d2615e6..a5c9a71 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/NetworkInformationShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -50,7 +50,7 @@
         if (!useApiAboveR()) {
             return com.android.networkstack.apishim.api30.NetworkInformationShimImpl.newInstance();
         }
-        return new com.android.networkstack.apishim.NetworkInformationShimImpl();
+        return new NetworkInformationShimImpl();
     }
 
     @Nullable
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NetworkRequestShimImpl.java
similarity index 87%
rename from apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/NetworkRequestShimImpl.java
index ccc8f70..2dc5d72 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/NetworkRequestShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 
@@ -58,6 +58,13 @@
     }
 
     @Override
+    public NetworkRequest.Builder setIncludeOtherUidNetworks(NetworkRequest.Builder builder,
+            boolean include) {
+        builder.setIncludeOtherUidNetworks(include);
+        return builder;
+    }
+
+    @Override
     public NetworkRequest.Builder newBuilder(@NonNull NetworkRequest request) {
         return new NetworkRequest.Builder(request);
     }
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/NetworkShimImpl.java
similarity index 95%
rename from apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/NetworkShimImpl.java
index 0c92391..eda8e27 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/NetworkShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import android.net.Network;
 
diff --git a/apishim/31/com/android/networkstack/apishim/SettingsShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/SettingsShimImpl.java
similarity index 87%
rename from apishim/31/com/android/networkstack/apishim/SettingsShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/SettingsShimImpl.java
index e15872f..1b5cbae 100644
--- a/apishim/31/com/android/networkstack/apishim/SettingsShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/SettingsShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 import android.content.Context;
 import android.os.Build;
@@ -48,9 +48,7 @@
     public boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
             @NonNull String callingPackage, @Nullable String callingAttributionTag,
             boolean throwException) {
-        // Since checkAndNoteWriteSettingsOperation with callingAttributionTag (S method) is not
-        // available in AOSP, calling R method (same as API 30 shim) temporary.
         return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,
-                throwException);
+                callingAttributionTag, throwException);
     }
 }
diff --git a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/SocketUtilsShimImpl.java
similarity index 94%
rename from apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
rename to apishim/31/com/android/networkstack/apishim/api31/SocketUtilsShimImpl.java
index 483bde0..f5aa80b 100644
--- a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/api31/SocketUtilsShimImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkstack.apishim;
+package com.android.networkstack.apishim.api31;
 
 /**
  * Implementation of {@link NetworkShim} for API 30.
diff --git a/apishim/32/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/32/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
new file mode 100644
index 0000000..2056b1b
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
@@ -0,0 +1,37 @@
+/*
+ * 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.net.CaptivePortalData;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+/**
+ * Compatibility implementation of {@link CaptivePortalDataShim}.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class CaptivePortalDataShimImpl
+        extends com.android.networkstack.apishim.api31.CaptivePortalDataShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    public CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
+        super(data);
+    }
+}
diff --git a/apishim/32/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java b/apishim/32/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
new file mode 100644
index 0000000..a7aa0c8
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
@@ -0,0 +1,36 @@
+/*
+ * 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.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+
+/**
+ * Compatibility implementation of {@link ConnectivityManagerShim}.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class ConnectivityManagerShimImpl
+        extends com.android.networkstack.apishim.api31.ConnectivityManagerShimImpl  {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected ConnectivityManagerShimImpl(Context context) {
+        super(context);
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java b/apishim/32/com/android/networkstack/apishim/ConstantsShim.java
similarity index 76%
copy from apishim/31/com/android/networkstack/apishim/ConstantsShim.java
copy to apishim/32/com/android/networkstack/apishim/ConstantsShim.java
index 0184845..0a5b555 100644
--- a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/32/com/android/networkstack/apishim/ConstantsShim.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -21,7 +21,7 @@
 /**
  * Utility class for defining and importing constants from the Android platform.
  */
-public class ConstantsShim extends com.android.networkstack.apishim.api30.ConstantsShim {
+public class ConstantsShim extends com.android.networkstack.apishim.api31.ConstantsShim {
     /**
      * Constant that callers can use to determine what version of the shim they are using.
      * Must be the same as the version of the shims.
@@ -29,9 +29,5 @@
      * the shimmed objects and methods themselves.
      */
     @VisibleForTesting
-    public static final int VERSION = 31;
-
-    // When removing this shim, the version in NetworkMonitorUtils should be removed too.
-    // TODO: add TRANSPORT_TEST to system API in API 31 (it is only a test API as of R)
-    public static final int TRANSPORT_TEST = 7;
+    public static final int VERSION = 32;
 }
diff --git a/apishim/32/com/android/networkstack/apishim/NetworkInformationShimImpl.java b/apishim/32/com/android/networkstack/apishim/NetworkInformationShimImpl.java
new file mode 100644
index 0000000..28aa75c
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/NetworkInformationShimImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.NetworkInformationShim;
+
+/**
+ * Compatibility implementation of {@link NetworkInformationShim}.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class NetworkInformationShimImpl
+        extends com.android.networkstack.apishim.api31.NetworkInformationShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected NetworkInformationShimImpl() {}
+}
diff --git a/apishim/32/com/android/networkstack/apishim/NetworkRequestShimImpl.java b/apishim/32/com/android/networkstack/apishim/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..95ae5ba
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/NetworkRequestShimImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 31.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class NetworkRequestShimImpl
+        extends com.android.networkstack.apishim.api31.NetworkRequestShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected NetworkRequestShimImpl() {
+        super();
+    }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java b/apishim/32/com/android/networkstack/apishim/NetworkShimImpl.java
similarity index 70%
copy from apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
copy to apishim/32/com/android/networkstack/apishim/NetworkShimImpl.java
index 0c92391..2e31a78 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkShimImpl.java
+++ b/apishim/32/com/android/networkstack/apishim/NetworkShimImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -17,14 +17,17 @@
 package com.android.networkstack.apishim;
 
 import android.net.Network;
+import android.os.Build;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 
 /**
- * Implementation of {@link NetworkShim} for API 30.
+ * Compatibility implementation of {@link com.android.networkstack.apishim.common.NetworkShim}.
  */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
 public class NetworkShimImpl extends com.android.networkstack.apishim.api30.NetworkShimImpl {
-    // Currently, this is the same as the API 30 shim, so inherit everything from that.
+    // Currently, this is the same as the API 31 shim, so inherit everything from that.
     protected NetworkShimImpl(@NonNull Network network) {
         super(network);
     }
diff --git a/apishim/32/com/android/networkstack/apishim/SettingsShimImpl.java b/apishim/32/com/android/networkstack/apishim/SettingsShimImpl.java
new file mode 100644
index 0000000..46d2102
--- /dev/null
+++ b/apishim/32/com/android/networkstack/apishim/SettingsShimImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.networkstack.apishim.common.SettingsShim;
+
+/**
+ * Compatibility implementation of {@link SettingsShim} for API 31.
+ */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
+public class SettingsShimImpl
+        extends com.android.networkstack.apishim.api30.SettingsShimImpl {
+    // Currently identical to the API 31 shim, so inherit everything
+    protected SettingsShimImpl() { }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java b/apishim/32/com/android/networkstack/apishim/SocketUtilsShimImpl.java
similarity index 68%
copy from apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
copy to apishim/32/com/android/networkstack/apishim/SocketUtilsShimImpl.java
index 483bde0..2f4e500 100644
--- a/apishim/31/com/android/networkstack/apishim/SocketUtilsShimImpl.java
+++ b/apishim/32/com/android/networkstack/apishim/SocketUtilsShimImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -16,11 +16,16 @@
 
 package com.android.networkstack.apishim;
 
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
 /**
- * Implementation of {@link NetworkShim} for API 30.
+ * Implementation of {@link com.android.networkstack.apishim.common.SocketUtilsShim}.
  */
+@RequiresApi(Build.VERSION_CODES.S) // Change to T when version code available, and adding T methods
 public class SocketUtilsShimImpl
         extends com.android.networkstack.apishim.api30.SocketUtilsShimImpl {
-    // Currently, this is the same as the API 30 shim, so inherit everything from that.
+    // Currently, this is the same as the API 31 shim, so inherit everything from that.
     protected SocketUtilsShimImpl() {}
 }
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
index 7d6d7bf..d07d1ae 100644
--- a/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
@@ -37,6 +37,14 @@
             @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException;
 
     /**
+     * See android.net.NetworkRequest.Builder#setIncludeOtherUidNetworks.
+     */
+    default NetworkRequest.Builder setIncludeOtherUidNetworks(NetworkRequest.Builder builder,
+            boolean include) throws UnsupportedApiLevelException {
+        throw new UnsupportedApiLevelException("Not supported before API 31.");
+    }
+
+    /**
      * See android.net.NetworkRequest.Builder(NetworkRequest).
      * @throws UnsupportedApiLevelException if API is not available in the API level.
      */
diff --git a/apishim/jarjar-rules-compat.txt b/apishim/jarjar-rules-compat.txt
index dba2b49..4f34ccb 100644
--- a/apishim/jarjar-rules-compat.txt
+++ b/apishim/jarjar-rules-compat.txt
@@ -1,7 +1,7 @@
 # jarjar rules to use on API stable builds.
 # Use the latest stable apishim package as the main apishim package, to replace and avoid building
 # the unstable, non-compatibility shims.
-# Once API 31 is stable, apishim/31/com.android.networkstack.apishim should be moved to the
-# com.android.networkstack.apishim.api31 package, a new apishim/32/com.android.networkstack.apishim
-# package should be created, and this rule should reference api31.
-rule com.android.networkstack.apishim.api30.** com.android.networkstack.apishim.@1
\ No newline at end of file
+# Once API 32 is stable, apishim/32/com.android.networkstack.apishim should be moved to the
+# com.android.networkstack.apishim.api32 package, a new apishim/33/com.android.networkstack.apishim
+# package should be created, and this rule should reference api32.
+rule com.android.networkstack.apishim.api31.** com.android.networkstack.apishim.@1
\ No newline at end of file
diff --git a/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java b/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
index 2ba1dcc..8b388ad 100755
--- a/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/common/captiveportal/src/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -103,11 +103,22 @@
     }
 
     public boolean isSuccessful() {
-        return mHttpResponseCode == SUCCESS_CODE;
+        return isSuccessCode(mHttpResponseCode);
     }
 
     public boolean isPortal() {
-        return !isSuccessful() && (mHttpResponseCode >= 200) && (mHttpResponseCode <= 399);
+        return isPortalCode(mHttpResponseCode);
+    }
+
+    private static boolean isSuccessCode(int responseCode) {
+        return responseCode == SUCCESS_CODE;
+    }
+
+    /**
+     * @return Whether the specified HTTP return code indicates a captive portal.
+     */
+    public static boolean isPortalCode(int responseCode) {
+        return !isSuccessCode(responseCode) && (responseCode >= 200) && (responseCode <= 399);
     }
 
     public boolean isFailed() {
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 5d1c9e3..54f4b22 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -25,20 +25,15 @@
     name: "net-module-utils-srcs",
     srcs: [
         "src/android/net/util/SharedLog.java",
-        "src/android/net/shared/InitialConfiguration.java",
-        "src/android/net/shared/Layer2Information.java",
-        "src/android/net/shared/LinkPropertiesParcelableUtil.java",
-        "src/android/net/shared/ParcelableUtil.java",
         "src/android/net/shared/NetdUtils.java",
         "src/android/net/shared/NetworkMonitorUtils.java",
-        "src/android/net/shared/ParcelableUtil.java",
-        "src/android/net/shared/PrivateDnsConfig.java",
-        "src/android/net/shared/ProvisioningConfiguration.java",
         "src/android/net/shared/RouteUtils.java",
         "src/android/net/util/InterfaceParams.java",
     ],
     visibility: [
         "//frameworks/base/services/net",
+        "//frameworks/base/packages/Connectivity/service",
+        "//packages/modules/Connectivity/service",
     ]
 }
 
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
index 981a576..18138a7 100644
--- a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -36,6 +37,14 @@
     // TODO: use NetworkCapabilities.TRANSPORT_TEST once NetworkStack builds against API 31.
     private static final int TRANSPORT_TEST = 7;
 
+    // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
+    // NetworkStack shims, but at the same time cannot use non-system APIs.
+    // NET_CAPABILITY_NOT_VCN_MANAGED is system API as of S (so it is enforced to always be 28 and
+    // can't be changed).
+    // TODO: use NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED once NetworkStack builds against
+    //       API 31.
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
     // Network conditions broadcast constants
     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
             "android.net.conn.NETWORK_CONDITIONS_MEASURED";
@@ -59,11 +68,15 @@
     public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
         if (nc == null) return false;
 
+        final boolean isVcnManaged = !nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        final boolean isOemPaid = nc.hasCapability(NET_CAPABILITY_OEM_PAID)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+        final boolean isDefaultCapable = nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+
         // TODO: Consider requiring validation for DUN networks.
         if (nc.hasCapability(NET_CAPABILITY_INTERNET)
-                && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                && nc.hasCapability(NET_CAPABILITY_TRUSTED)) {
-            // Real networks
+                && (isVcnManaged || isOemPaid || isDefaultCapable)) {
             return true;
         }
 
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 96ff315..376ff89 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -72,7 +72,7 @@
         // For framework parcelables.
         "frameworks/base/core/java",
         // For API parcelables in connectivity
-        "frameworks/base/packages/Connectivity/framework/aidl-export",
+        "packages/modules/Connectivity/framework/aidl-export",
         "frameworks/native/aidl/binder", // For PersistableBundle.aidl
     ],
     srcs: [
@@ -145,25 +145,34 @@
 java_library {
     name: "networkstack-client",
     sdk_version: "system_current",
-    // this is part of updatable modules(NetworkStack) which targets 29(Q)
+    // this is part of updatable modules(NetworkStack) which runs on Q and above
     min_sdk_version: "29",
     srcs: [
         ":framework-annotations",
+        "src/android/net/ip/**/*.java",
+        "src/android/net/IpMemoryStore.java",
         "src/android/net/IpMemoryStoreClient.java",
         "src/android/net/ipmemorystore/**/*.java",
+        "src/android/net/NetworkMonitorManager.java",
         "src/android/net/networkstack/**/*.java",
         "src/android/net/networkstack/aidl/quirks/**/*.java",
         "src/android/net/shared/**/*.java",
+        "src/android/net/util/**/*.java",
+    ],
+    libs: [
+        "net-utils-framework-common",  // XXX for IpUtils.java only
     ],
     static_libs: [
         "ipmemorystore-aidl-interfaces-V10-java",
         "networkstack-aidl-interfaces-V10-java",
     ],
     visibility: [
-        "//frameworks/base/packages/Tethering",
+        "//frameworks/base/packages/Connectivity/service",
         "//packages/modules/Connectivity/Tethering",
+        "//packages/modules/Connectivity/service",
         "//frameworks/base/services/net",
         "//frameworks/opt/net/wifi/service",
+        "//packages/apps/Bluetooth",
         "//packages/modules/NetworkStack",
     ],
     apex_available: [
diff --git a/common/networkstackclient/src/android/net/IpMemoryStore.java b/common/networkstackclient/src/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..f2c1d35
--- /dev/null
+++ b/common/networkstackclient/src/android/net/IpMemoryStore.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Manager class used to communicate with the ip memory store service in the network stack,
+ * which is running in a separate module.
+ * @hide
+*/
+public class IpMemoryStore extends IpMemoryStoreClient {
+    private static final String TAG = IpMemoryStore.class.getSimpleName();
+    @NonNull private final CompletableFuture<IIpMemoryStore> mService;
+    @NonNull private final AtomicReference<CompletableFuture<IIpMemoryStore>> mTailNode;
+
+    public IpMemoryStore(@NonNull final Context context) {
+        super(context);
+        mService = new CompletableFuture<>();
+        mTailNode = new AtomicReference<CompletableFuture<IIpMemoryStore>>(mService);
+        getModuleNetworkStackClient(context).fetchIpMemoryStore(
+                new IIpMemoryStoreCallbacks.Stub() {
+                    @Override
+                    public void onIpMemoryStoreFetched(@NonNull final IIpMemoryStore memoryStore) {
+                        mService.complete(memoryStore);
+                    }
+
+                    @Override
+                    public int getInterfaceVersion() {
+                        return this.VERSION;
+                    }
+
+                    @Override
+                    public String getInterfaceHash() {
+                        return this.HASH;
+                    }
+                });
+    }
+
+    /*
+     *  If the IpMemoryStore is ready, this function will run the request synchronously.
+     *  Otherwise, it will enqueue the requests for execution immediately after the
+     *  service becomes ready. The requests are guaranteed to be executed in the order
+     *  they are sumbitted.
+     */
+    @Override
+    protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
+        mTailNode.getAndUpdate(future -> future.handle((store, exception) -> {
+            if (exception != null) {
+                // this should never happens since we also catch the exception below
+                Log.wtf(TAG, "Error fetching IpMemoryStore", exception);
+                return store;
+            }
+
+            try {
+                cb.accept(store);
+            } catch (Exception e) {
+                Log.wtf(TAG, "Exception occurred: " + e.getMessage());
+            }
+            return store;
+        }));
+    }
+
+    @VisibleForTesting
+    protected ModuleNetworkStackClient getModuleNetworkStackClient(Context context) {
+        return ModuleNetworkStackClient.getInstance(context);
+    }
+
+    /** Gets an instance of the memory store */
+    @NonNull
+    public static IpMemoryStore getMemoryStore(final Context context) {
+        return new IpMemoryStore(context);
+    }
+}
diff --git a/common/networkstackclient/src/android/net/NetworkMonitorManager.java b/common/networkstackclient/src/android/net/NetworkMonitorManager.java
new file mode 100644
index 0000000..0f66981
--- /dev/null
+++ b/common/networkstackclient/src/android/net/NetworkMonitorManager.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for INetworkMonitor.
+ *
+ * Wraps INetworkMonitor calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on INetworkMonitor are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+@Hide
+public class NetworkMonitorManager {
+
+    @NonNull private final INetworkMonitor mNetworkMonitor;
+    @NonNull private final String mTag;
+
+    public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager,
+            @NonNull String tag) {
+        mNetworkMonitor = networkMonitorManager;
+        mTag = tag;
+    }
+
+    public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager) {
+        this(networkMonitorManager, NetworkMonitorManager.class.getSimpleName());
+    }
+
+    private void log(String s, Throwable e) {
+        Log.e(mTag, s, e);
+    }
+
+    // CHECKSTYLE:OFF Generated code
+
+    public boolean start() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.start();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in start", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean launchCaptivePortalApp() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.launchCaptivePortalApp();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in launchCaptivePortalApp", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyCaptivePortalAppFinished(int response) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyCaptivePortalAppFinished(response);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyCaptivePortalAppFinished", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean setAcceptPartialConnectivity() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.setAcceptPartialConnectivity();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in setAcceptPartialConnectivity", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean forceReevaluation(int uid) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.forceReevaluation(uid);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in forceReevaluation", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyPrivateDnsChanged(config);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyPrivateDnsChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyDnsResponse(int returnCode) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyDnsResponse(returnCode);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyDnsResponse", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkConnected(lp, nc);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkConnected", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkDisconnected() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkDisconnected();
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkDisconnected", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyLinkPropertiesChanged(LinkProperties lp) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyLinkPropertiesChanged(lp);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyLinkPropertiesChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public boolean notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkMonitor.notifyNetworkCapabilitiesChanged(nc);
+            return true;
+        } catch (RemoteException e) {
+            log("Error in notifyNetworkCapabilitiesChanged", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // CHECKSTYLE:ON Generated code
+}
diff --git a/common/networkstackclient/src/android/net/ip/IpClientCallbacks.java b/common/networkstackclient/src/android/net/ip/IpClientCallbacks.java
new file mode 100644
index 0000000..b17fcaa
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IpClientCallbacks.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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 android.net.ip;
+
+import android.net.DhcpResultsParcelable;
+import android.net.Layer2PacketParcelable;
+import android.net.LinkProperties;
+
+import java.util.List;
+
+/**
+ * Callbacks for handling IpClient events.
+ *
+ * This is a convenience class to allow clients not to override all methods of IIpClientCallbacks,
+ * and avoid unparceling arguments.
+ * These methods are called asynchronously on a Binder thread, as IpClient lives in a different
+ * process.
+ * @hide
+ */
+public class IpClientCallbacks {
+
+    /**
+     * Callback called upon IpClient creation.
+     *
+     * @param ipClient The Binder token to communicate with IpClient.
+     */
+    public void onIpClientCreated(IIpClient ipClient) {}
+
+    /**
+     * Callback called prior to DHCP discovery/renewal.
+     *
+     * <p>In order to receive onPreDhcpAction(), call #withPreDhcpAction() when constructing a
+     * ProvisioningConfiguration.
+     *
+     * <p>Implementations of onPreDhcpAction() must call IpClient#completedPreDhcpAction() to
+     * indicate that DHCP is clear to proceed.
+      */
+    public void onPreDhcpAction() {}
+
+    /**
+     * Callback called after DHCP discovery/renewal.
+     */
+    public void onPostDhcpAction() {}
+
+    /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+        // In general callbacks would not use a parcelable directly (DhcpResultsParcelable), and
+        // would use a wrapper instead, because of the lack of safety of stable parcelables. But
+        // there are already two classes in the tree for DHCP information: DhcpInfo and DhcpResults,
+        // and neither of them exposes an appropriate API (they are bags of mutable fields and can't
+        // be changed because they are public API and @UnsupportedAppUsage, being no better than the
+        // stable parcelable). Adding a third class would cost more than the gain considering that
+        // the only client of this callback is WiFi, which will end up converting the results to
+        // DhcpInfo anyway.
+    }
+
+    /**
+     * Indicates that provisioning was successful.
+     */
+    public void onProvisioningSuccess(LinkProperties newLp) {}
+
+    /**
+     * Indicates that provisioning failed.
+     */
+    public void onProvisioningFailure(LinkProperties newLp) {}
+
+    /**
+     * Invoked on LinkProperties changes.
+     */
+    public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+    /**Called when the internal IpReachabilityMonitor (if enabled) has
+     * detected the loss of a critical number of required neighbors.
+     */
+    public void onReachabilityLost(String logMsg) {}
+
+    /**
+     * Called when the IpClient state machine terminates.
+     */
+    public void onQuit() {}
+
+    /**
+     * Called to indicate that a new APF program must be installed to filter incoming packets.
+     */
+    public void installPacketFilter(byte[] filter) {}
+
+    /**
+     * Called to indicate that the APF Program & data buffer must be read asynchronously from the
+     * wifi driver.
+     *
+     * <p>Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+     * buffer. In response to this request, the driver returns the data buffer asynchronously
+     * by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+     */
+    public void startReadPacketFilter() {}
+
+    /**
+     * If multicast filtering cannot be accomplished with APF, this function will be called to
+     * actuate multicast filtering using another means.
+     */
+    public void setFallbackMulticastFilter(boolean enabled) {}
+
+    /**
+     * Enabled/disable Neighbor Discover offload functionality. This is called, for example,
+     * whenever 464xlat is being started or stopped.
+     */
+    public void setNeighborDiscoveryOffload(boolean enable) {}
+
+    /**
+     * Invoked on starting preconnection process.
+     */
+    public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {}
+}
diff --git a/common/networkstackclient/src/android/net/ip/IpClientManager.java b/common/networkstackclient/src/android/net/ip/IpClientManager.java
new file mode 100644
index 0000000..b45405f
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IpClientManager.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2019 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 android.net.ip;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.ProxyInfo;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketDataParcelable;
+import android.net.shared.Layer2Information;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.KeepalivePacketDataUtil;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for IpClient.
+ *
+ * Wraps IIpClient calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on IIpClient are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+@Hide
+public class IpClientManager {
+    @NonNull private final IIpClient mIpClient;
+    @NonNull private final String mTag;
+
+    public IpClientManager(@NonNull IIpClient ipClient, @NonNull String tag) {
+        mIpClient = ipClient;
+        mTag = tag;
+    }
+
+    public IpClientManager(@NonNull IIpClient ipClient) {
+        this(ipClient, IpClientManager.class.getSimpleName());
+    }
+
+    private void log(String s, Throwable e) {
+        Log.e(mTag, s, e);
+    }
+
+    /**
+     * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+     * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+     * proceed.
+     */
+    public boolean completedPreDhcpAction() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.completedPreDhcpAction();
+            return true;
+        } catch (RemoteException e) {
+            log("Error completing PreDhcpAction", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Confirm the provisioning configuration.
+     */
+    public boolean confirmConfiguration() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.confirmConfiguration();
+            return true;
+        } catch (RemoteException e) {
+            log("Error confirming IpClient configuration", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Indicate that packet filter read is complete.
+     */
+    public boolean readPacketFilterComplete(byte[] data) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.readPacketFilterComplete(data);
+            return true;
+        } catch (RemoteException e) {
+            log("Error notifying IpClient of packet filter read", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Shut down this IpClient instance altogether.
+     */
+    public boolean shutdown() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.shutdown();
+            return true;
+        } catch (RemoteException e) {
+            log("Error shutting down IpClient", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Start provisioning with the provided parameters.
+     */
+    public boolean startProvisioning(ProvisioningConfiguration prov) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.startProvisioning(prov.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error starting IpClient provisioning", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Stop this IpClient.
+     *
+     * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+     */
+    public boolean stop() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.stop();
+            return true;
+        } catch (RemoteException e) {
+            log("Error stopping IpClient", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public boolean setTcpBufferSizes(String tcpBufferSizes) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setTcpBufferSizes(tcpBufferSizes);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting IpClient TCP buffer sizes", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public boolean setHttpProxy(ProxyInfo proxyInfo) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setHttpProxy(proxyInfo);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting IpClient proxy", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public boolean setMulticastFilter(boolean enabled) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setMulticastFilter(enabled);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting multicast filter", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Add a TCP keepalive packet filter before setting up keepalive offload.
+     */
+    public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketData pkt) {
+        return addKeepalivePacketFilter(slot, KeepalivePacketDataUtil.toStableParcelable(pkt));
+    }
+
+    /**
+     * Add a TCP keepalive packet filter before setting up keepalive offload.
+     * @deprecated This method is for use on pre-S platforms where TcpKeepalivePacketData is not
+     *             system API. On newer platforms use
+     *             addKeepalivePacketFilter(int, TcpKeepalivePacketData) instead.
+     */
+    @Deprecated
+    public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.addKeepalivePacketFilter(slot, pkt);
+            return true;
+        } catch (RemoteException e) {
+            log("Error adding Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Add a NAT-T keepalive packet filter before setting up keepalive offload.
+     */
+    public boolean addKeepalivePacketFilter(int slot, NattKeepalivePacketData pkt) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.addNattKeepalivePacketFilter(
+                    slot, KeepalivePacketDataUtil.toStableParcelable(pkt));
+            return true;
+        } catch (RemoteException e) {
+            log("Error adding NAT-T Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Remove a keepalive packet filter after stopping keepalive offload.
+     */
+    public boolean removeKeepalivePacketFilter(int slot) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.removeKeepalivePacketFilter(slot);
+            return true;
+        } catch (RemoteException e) {
+            log("Error removing Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the L2 key and group hint for storing info into the memory store.
+     */
+    public boolean setL2KeyAndGroupHint(String l2Key, String groupHint) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setL2KeyAndGroupHint(l2Key, groupHint);
+            return true;
+        } catch (RemoteException e) {
+            log("Failed setL2KeyAndGroupHint", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Notify IpClient that preconnection is complete and that the link is ready for use.
+     * The success parameter indicates whether the packets passed in by 'onPreconnectionStart'
+     * were successfully sent to the network or not.
+     */
+    public boolean notifyPreconnectionComplete(boolean success) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.notifyPreconnectionComplete(success);
+            return true;
+        } catch (RemoteException e) {
+            log("Error notifying IpClient Preconnection completed", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Update the bssid, L2 key and group hint layer2 information.
+     */
+    public boolean updateLayer2Information(Layer2Information info) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.updateLayer2Information(info.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error updating layer2 information", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/common/networkstackclient/src/android/net/ip/IpClientUtil.java b/common/networkstackclient/src/android/net/ip/IpClientUtil.java
new file mode 100644
index 0000000..1b55776
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ip/IpClientUtil.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2019 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 android.net.ip;
+
+import android.content.Context;
+import android.net.DhcpResultsParcelable;
+import android.net.Layer2PacketParcelable;
+import android.net.LinkProperties;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.os.ConditionVariable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+
+/**
+ * Utilities and wrappers to simplify communication with IpClient, which lives in the NetworkStack
+ * process.
+ *
+ * @hide
+ */
+public class IpClientUtil {
+    // TODO: remove with its callers
+    public static final String DUMP_ARG = "ipclient";
+
+    /**
+     * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
+     * complete with {@link WaitForProvisioningCallbacks#waitForProvisioning()}.
+     */
+    public static class WaitForProvisioningCallbacks extends IpClientCallbacks {
+        private final ConditionVariable mCV = new ConditionVariable();
+        private LinkProperties mCallbackLinkProperties;
+
+        /**
+         * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+         * {@link #onProvisioningFailure(LinkProperties)} is called.
+         */
+        public LinkProperties waitForProvisioning() {
+            mCV.block();
+            return mCallbackLinkProperties;
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCallbackLinkProperties = newLp;
+            mCV.open();
+        }
+
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCallbackLinkProperties = null;
+            mCV.open();
+        }
+    }
+
+    /**
+     * Create a new IpClient.
+     *
+     * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
+     * {@link IIpClientCallbacks}.
+     * @see {@link ModuleNetworkStackClient#makeIpClient(String, IIpClientCallbacks)}
+     */
+    public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
+        ModuleNetworkStackClient.getInstance(context)
+                .makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+    }
+
+    /**
+     * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+     */
+    private static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+        protected final IpClientCallbacks mCb;
+
+        /**
+         * Create a new IpClientCallbacksProxy.
+         */
+        IpClientCallbacksProxy(IpClientCallbacks cb) {
+            mCb = cb;
+        }
+
+        @Override
+        public void onIpClientCreated(IIpClient ipClient) {
+            mCb.onIpClientCreated(ipClient);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCb.onPreDhcpAction();
+        }
+
+        @Override
+        public void onPostDhcpAction() {
+            mCb.onPostDhcpAction();
+        }
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        @Override
+        public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+            mCb.onNewDhcpResults(dhcpResults);
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCb.onProvisioningSuccess(newLp);
+        }
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCb.onProvisioningFailure(newLp);
+        }
+
+        // Invoked on LinkProperties changes.
+        @Override
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            mCb.onLinkPropertiesChange(newLp);
+        }
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCb.onReachabilityLost(logMsg);
+        }
+
+        // Called when the IpClient state machine terminates.
+        @Override
+        public void onQuit() {
+            mCb.onQuit();
+        }
+
+        // Install an APF program to filter incoming packets.
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCb.installPacketFilter(filter);
+        }
+
+        // Asynchronously read back the APF program & data buffer from the wifi driver.
+        // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+        // buffer. In response to this request, the driver returns the data buffer asynchronously
+        // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+        @Override
+        public void startReadPacketFilter() {
+            mCb.startReadPacketFilter();
+        }
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCb.setFallbackMulticastFilter(enabled);
+        }
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCb.setNeighborDiscoveryOffload(enable);
+        }
+
+        // Invoked on starting preconnection process.
+        @Override
+        public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {
+            mCb.onPreconnectionStart(packets);
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
+    }
+
+    /**
+     * Dump logs for the specified IpClient.
+     * TODO: remove callers and delete
+     */
+    public static void dumpIpClient(
+            IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("IpClient logs have moved to dumpsys network_stack");
+    }
+}
diff --git a/common/moduleutils/src/android/net/shared/InitialConfiguration.java b/common/networkstackclient/src/android/net/shared/InitialConfiguration.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/InitialConfiguration.java
rename to common/networkstackclient/src/android/net/shared/InitialConfiguration.java
diff --git a/common/moduleutils/src/android/net/shared/Layer2Information.java b/common/networkstackclient/src/android/net/shared/Layer2Information.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/Layer2Information.java
rename to common/networkstackclient/src/android/net/shared/Layer2Information.java
diff --git a/common/moduleutils/src/android/net/shared/ParcelableUtil.java b/common/networkstackclient/src/android/net/shared/ParcelableUtil.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/ParcelableUtil.java
rename to common/networkstackclient/src/android/net/shared/ParcelableUtil.java
diff --git a/common/moduleutils/src/android/net/shared/PrivateDnsConfig.java b/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
similarity index 100%
rename from common/moduleutils/src/android/net/shared/PrivateDnsConfig.java
rename to common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
diff --git a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
similarity index 96%
rename from common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
rename to common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
index d3bc04d..6ee9b73 100644
--- a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
+++ b/common/networkstackclient/src/android/net/shared/ProvisioningConfiguration.java
@@ -21,7 +21,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.INetd;
 import android.net.InformationElementParcelable;
 import android.net.Network;
 import android.net.ProvisioningConfigurationParcelable;
@@ -76,6 +75,13 @@
     // allowing for 10% jitter.
     private static final int DEFAULT_TIMEOUT_MS = 18 * 1000;
 
+    // TODO: These cannot be imported from INetd.aidl, because networkstack-client cannot depend on
+    // INetd, as there are users of IpClient that depend on INetd directly (potentially at a
+    // different version, which is not allowed by the build system).
+    // Find a better way to express these constants.
+    public static final int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+    public static final int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+
     /**
      * Builder to create a {@link ProvisioningConfiguration}.
      */
@@ -180,7 +186,7 @@
          * Specify that IPv6 address generation should use a random MAC address.
          */
         public Builder withRandomMacAddress() {
-            mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+            mConfig.mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_EUI64;
             return this;
         }
 
@@ -188,7 +194,7 @@
          * Specify that IPv6 address generation should use a stable MAC address.
          */
         public Builder withStableMacAddress() {
-            mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+            mConfig.mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
             return this;
         }
 
@@ -437,7 +443,7 @@
     public StaticIpConfiguration mStaticIpConfig;
     public ApfCapabilities mApfCapabilities;
     public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
-    public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+    public int mIPv6AddrGenMode = IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
     public Network mNetwork = null;
     public String mDisplayName = null;
     public ScanResultInfo mScanResultInfo;
diff --git a/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java b/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java
new file mode 100644
index 0000000..5666985
--- /dev/null
+++ b/common/networkstackclient/src/android/net/util/KeepalivePacketDataUtil.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2019 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 android.net.util;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InvalidPacketException;
+import android.net.KeepalivePacketData;
+import android.net.NattKeepalivePacketData;
+import android.net.NattKeepalivePacketDataParcelable;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketDataParcelable;
+import android.os.Build;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.net.module.util.IpUtils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Utility class to convert to/from keepalive data parcelables.
+ *
+ * TODO: move to networkstack-client library when it is moved to frameworks/libs/net.
+ * This class cannot go into other shared libraries as it depends on NetworkStack AIDLs.
+ * @hide
+ */
+public final class KeepalivePacketDataUtil {
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int IPV6_HEADER_LENGTH = 40;
+    private static final int TCP_HEADER_LENGTH = 20;
+
+    private static final String TAG = KeepalivePacketDataUtil.class.getSimpleName();
+
+    /**
+     * Convert a NattKeepalivePacketData to a NattKeepalivePacketDataParcelable.
+     */
+    @NonNull
+    public static NattKeepalivePacketDataParcelable toStableParcelable(
+            @NonNull NattKeepalivePacketData pkt) {
+        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
+        final InetAddress srcAddress = pkt.getSrcAddress();
+        final InetAddress dstAddress = pkt.getDstAddress();
+        parcel.srcAddress = srcAddress.getAddress();
+        parcel.srcPort = pkt.getSrcPort();
+        parcel.dstAddress = dstAddress.getAddress();
+        parcel.dstPort = pkt.getDstPort();
+        return parcel;
+    }
+
+    /**
+     * Convert a TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable.
+     */
+    @NonNull
+    public static TcpKeepalivePacketDataParcelable toStableParcelable(
+            @NonNull TcpKeepalivePacketData pkt) {
+        final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
+        final InetAddress srcAddress = pkt.getSrcAddress();
+        final InetAddress dstAddress = pkt.getDstAddress();
+        parcel.srcAddress = srcAddress.getAddress();
+        parcel.srcPort = pkt.getSrcPort();
+        parcel.dstAddress = dstAddress.getAddress();
+        parcel.dstPort = pkt.getDstPort();
+        parcel.seq = pkt.getTcpSeq();
+        parcel.ack = pkt.getTcpAck();
+        parcel.rcvWnd = pkt.getTcpWindow();
+        parcel.rcvWndScale = pkt.getTcpWindowScale();
+        parcel.tos = pkt.getIpTos();
+        parcel.ttl = pkt.getIpTtl();
+        return parcel;
+    }
+
+    /**
+     * Factory method to create tcp keepalive packet structure.
+     * @hide
+     */
+    public static TcpKeepalivePacketData fromStableParcelable(
+            TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException {
+        final byte[] packet;
+        try {
+            if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null)
+                    && (tcpDetails.srcAddress.length == 4 /* V4 IP length */)
+                    && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) {
+                packet = buildV4Packet(tcpDetails);
+            } else {
+                // TODO: support ipv6
+                throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+            }
+            return new TcpKeepalivePacketData(
+                    InetAddress.getByAddress(tcpDetails.srcAddress),
+                    tcpDetails.srcPort,
+                    InetAddress.getByAddress(tcpDetails.dstAddress),
+                    tcpDetails.dstPort,
+                    packet,
+                    tcpDetails.seq, tcpDetails.ack, tcpDetails.rcvWnd, tcpDetails.rcvWndScale,
+                    tcpDetails.tos, tcpDetails.ttl);
+        } catch (UnknownHostException e) {
+            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+        }
+
+    }
+
+    /**
+     * Build ipv4 tcp keepalive packet, not including the link-layer header.
+     */
+    // TODO : if this code is ever moved to the network stack, factorize constants with the ones
+    // over there.
+    private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) {
+        final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH;
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.put((byte) 0x45);                       // IP version and IHL
+        buf.put((byte) tcpDetails.tos);             // TOS
+        buf.putShort((short) length);
+        buf.putInt(0x00004000);                     // ID, flags=DF, offset
+        buf.put((byte) tcpDetails.ttl);             // TTL
+        buf.put((byte) OsConstants.IPPROTO_TCP);
+        final int ipChecksumOffset = buf.position();
+        buf.putShort((short) 0);                    // IP checksum
+        buf.put(tcpDetails.srcAddress);
+        buf.put(tcpDetails.dstAddress);
+        buf.putShort((short) tcpDetails.srcPort);
+        buf.putShort((short) tcpDetails.dstPort);
+        buf.putInt(tcpDetails.seq);                 // Sequence Number
+        buf.putInt(tcpDetails.ack);                 // ACK
+        buf.putShort((short) 0x5010);               // TCP length=5, flags=ACK
+        buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale));   // Window size
+        final int tcpChecksumOffset = buf.position();
+        buf.putShort((short) 0);                    // TCP checksum
+        // URG is not set therefore the urgent pointer is zero.
+        buf.putShort((short) 0);                    // Urgent pointer
+
+        buf.putShort(ipChecksumOffset, com.android.net.module.util.IpUtils.ipChecksum(buf, 0));
+        buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
+                buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH));
+
+        return buf.array();
+    }
+
+    // TODO: add buildV6Packet.
+
+    /**
+     * Get a {@link TcpKeepalivePacketDataParcelable} from {@link KeepalivePacketData}, if the
+     * generic class actually contains TCP keepalive data.
+     *
+     * @deprecated This method is used on R platforms where android.net.TcpKeepalivePacketData was
+     * not yet system API. Newer platforms should use android.net.TcpKeepalivePacketData directly.
+     *
+     * @param data A {@link KeepalivePacketData} that may contain TCP keepalive data.
+     * @return A parcelable containing TCP keepalive data, or null if the input data does not
+     *         contain TCP keepalive data.
+     */
+    @Deprecated
+    @SuppressWarnings("AndroidFrameworkCompatChange") // API version check used to Log.wtf
+    @Nullable
+    public static TcpKeepalivePacketDataParcelable parseTcpKeepalivePacketData(
+            @Nullable KeepalivePacketData data) {
+        if (data == null) return null;
+
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
+            Log.wtf(TAG, "parseTcpKeepalivePacketData should not be used after R, use "
+                    + "TcpKeepalivePacketData instead.");
+        }
+
+        // Reconstruct TcpKeepalivePacketData from the packet contained in KeepalivePacketData
+        final ByteBuffer buffer = ByteBuffer.wrap(data.getPacket());
+        buffer.order(ByteOrder.BIG_ENDIAN);
+
+        // Most of the fields are accessible from the KeepalivePacketData superclass: instead of
+        // using Struct to parse everything, just extract the extra fields necessary for
+        // TcpKeepalivePacketData.
+        final int tcpSeq;
+        final int tcpAck;
+        final int wndSize;
+        final int ipTos;
+        final int ttl;
+        try {
+            // This only support IPv4, because TcpKeepalivePacketData only supports IPv4 for R and
+            // below, and this method should not be used on newer platforms.
+            tcpSeq = buffer.getInt(IPV4_HEADER_LENGTH + 4);
+            tcpAck = buffer.getInt(IPV4_HEADER_LENGTH + 8);
+            wndSize = buffer.getShort(IPV4_HEADER_LENGTH + 14);
+            ipTos = buffer.get(1);
+            ttl = buffer.get(8);
+        } catch (IndexOutOfBoundsException e) {
+            return null;
+        }
+
+        final TcpKeepalivePacketDataParcelable p = new TcpKeepalivePacketDataParcelable();
+        p.srcAddress = data.getSrcAddress().getAddress();
+        p.srcPort = data.getSrcPort();
+        p.dstAddress = data.getDstAddress().getAddress();
+        p.dstPort = data.getDstPort();
+        p.seq = tcpSeq;
+        p.ack = tcpAck;
+        // TcpKeepalivePacketData could actually use non-zero wndScale, but this does not affect
+        // actual functionality as generated packets will be the same (no wndScale option added)
+        p.rcvWnd = wndSize;
+        p.rcvWndScale = 0;
+        p.tos = ipTos;
+        p.ttl = ttl;
+        return p;
+    }
+}
diff --git a/lint-baseline-current-lib.xml b/lint-baseline-current-lib.xml
index f7b00f2..e8cfe3e 100644
--- a/lint-baseline-current-lib.xml
+++ b/lint-baseline-current-lib.xml
@@ -3,83 +3,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1032"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1034"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1087"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1089"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#any`"
-        errorLine1="        final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets,"
-        errorLine2="                                                               ~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1368"
-            column="64"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#toIntArray`"
-        errorLine1="                    CollectionUtils.toIntArray(mDnsReturnCode),"
-        errorLine2="                                    ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java"
-            line="302"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#toLongArray`"
-        errorLine1="                    CollectionUtils.toLongArray(mDnsTimeStamp),"
-        errorLine2="                                    ~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java"
-            line="303"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
         errorLine1="        newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix());"
         errorLine2="                                                   ~~~~~~~~~~~~~~">
@@ -102,50 +25,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="            mStatsBuilder.setIpv4LatencyMicros(ConnectivityUtils.saturatedCast(mIpv4Watch.stop()));"
-        errorLine2="                                                                 ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/IpProvisioningMetrics.java"
-            line="74"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="            mStatsBuilder.setIpv6LatencyMicros(ConnectivityUtils.saturatedCast(mIpv6Watch.stop()));"
-        errorLine2="                                                                 ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/IpProvisioningMetrics.java"
-            line="83"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#isEmpty`"
-        errorLine1="        if (isEmpty(mCaptivePortalFallbackSpecs)) {"
-        errorLine2="            ~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="2279"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#isIPv6ULA`"
-        errorLine1="                    || isIPv6ULA(address)) {"
-        errorLine2="                       ~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="2447"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level R (current min is 29): `android.telephony.NetworkRegistrationInfo#getCellIdentity`"
         errorLine1="                    nri == null ? null : nri.getCellIdentity());"
         errorLine2="                                             ~~~~~~~~~~~~~~~">
@@ -155,48 +34,4 @@
             column="46"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="        int latencyUs = ConnectivityUtils.saturatedCast(durationUs);"
-        errorLine2="                                          ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="198"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="                .setRemainingTtlSecs(ConnectivityUtils.saturatedCast(secondsRemaining))"
-        errorLine2="                                                       ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="209"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="                        ConnectivityUtils.saturatedCast(capportData.getByteLimit() / 1000))"
-        errorLine2="                                          ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="212"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="        mStatsBuilder.setLatencyMicros(ConnectivityUtils.saturatedCast(mWatch.stop()));"
-        errorLine2="                                                         ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="237"
-            column="58"/>
-    </issue>
-
 </issues>
diff --git a/lint-baseline-stable-lib.xml b/lint-baseline-stable-lib.xml
index f7b00f2..e8cfe3e 100644
--- a/lint-baseline-stable-lib.xml
+++ b/lint-baseline-stable-lib.xml
@@ -3,83 +3,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1032"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1034"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1087"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#addressAndPortToString`"
-        errorLine1="                        ConnectivityUtils.addressAndPortToString("
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1089"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#any`"
-        errorLine1="        final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets,"
-        errorLine2="                                                               ~~~">
-        <location
-            file="packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java"
-            line="1368"
-            column="64"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#toIntArray`"
-        errorLine1="                    CollectionUtils.toIntArray(mDnsReturnCode),"
-        errorLine2="                                    ~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java"
-            line="302"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#toLongArray`"
-        errorLine1="                    CollectionUtils.toLongArray(mDnsTimeStamp),"
-        errorLine2="                                    ~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/DataStallDetectionStats.java"
-            line="303"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level R (current min is 29): `android.net.LinkProperties#getNat64Prefix`"
         errorLine1="        newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix());"
         errorLine2="                                                   ~~~~~~~~~~~~~~">
@@ -102,50 +25,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="            mStatsBuilder.setIpv4LatencyMicros(ConnectivityUtils.saturatedCast(mIpv4Watch.stop()));"
-        errorLine2="                                                                 ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/IpProvisioningMetrics.java"
-            line="74"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="            mStatsBuilder.setIpv6LatencyMicros(ConnectivityUtils.saturatedCast(mIpv6Watch.stop()));"
-        errorLine2="                                                                 ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/IpProvisioningMetrics.java"
-            line="83"
-            column="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.CollectionUtils#isEmpty`"
-        errorLine1="        if (isEmpty(mCaptivePortalFallbackSpecs)) {"
-        errorLine2="            ~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="2279"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#isIPv6ULA`"
-        errorLine1="                    || isIPv6ULA(address)) {"
-        errorLine2="                       ~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java"
-            line="2447"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level R (current min is 29): `android.telephony.NetworkRegistrationInfo#getCellIdentity`"
         errorLine1="                    nri == null ? null : nri.getCellIdentity());"
         errorLine2="                                             ~~~~~~~~~~~~~~~">
@@ -155,48 +34,4 @@
             column="46"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="        int latencyUs = ConnectivityUtils.saturatedCast(durationUs);"
-        errorLine2="                                          ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="198"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="                .setRemainingTtlSecs(ConnectivityUtils.saturatedCast(secondsRemaining))"
-        errorLine2="                                                       ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="209"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="                        ConnectivityUtils.saturatedCast(capportData.getByteLimit() / 1000))"
-        errorLine2="                                          ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="212"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 29): `com.android.net.module.util.ConnectivityUtils#saturatedCast`"
-        errorLine1="        mStatsBuilder.setLatencyMicros(ConnectivityUtils.saturatedCast(mWatch.stop()));"
-        errorLine2="                                                         ~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkValidationMetrics.java"
-            line="237"
-            column="58"/>
-    </issue>
-
 </issues>
diff --git a/res/values/config.xml b/res/values/config.xml
index d6a11ab..805ca04 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -105,4 +105,20 @@
          increased until reaching the config_max_retry_timer. -->
     <integer name="config_evaluating_bandwidth_min_retry_timer_ms"></integer>
     <integer name="config_evaluating_bandwidth_max_retry_timer_ms"></integer>
+
+    <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+         Those frames are identified by the field Eth-type having values
+         less than 0x600 -->
+    <bool name="config_apfDrop802_3Frames">true</bool>
+
+    <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+         will be dropped
+         TODO: need to put proper values, these are for testing purposes only -->
+    <integer-array name="config_apfEthTypeDenyList">
+        <item>0x88A2</item>
+        <item>0x88A4</item>
+        <item>0x88B8</item>
+        <item>0x88CD</item>
+        <item>0x88E3</item>
+    </integer-array>
 </resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b2967b9..bfb450e 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -77,6 +77,13 @@
             <item type="integer" name="config_evaluating_bandwidth_timeout_ms"/>
             <item type="integer" name="config_evaluating_bandwidth_min_retry_timer_ms"/>
             <item type="integer" name="config_evaluating_bandwidth_max_retry_timer_ms"/>
+
+            <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+            Those frames are identified by the field Eth-type having values less than 0x600 -->
+            <item type="bool" name="config_apfDrop802_3Frames"/>
+            <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+            will be dropped -->
+            <item type="array" name="config_apfEthTypeDenyList"/>
         </policy>
     </overlayable>
 </resources>
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index b03d653..a57e99d 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -36,6 +36,7 @@
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
 import android.net.INetd;
@@ -94,6 +95,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.networkstack.R;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.networkstack.apishim.SocketUtilsShimImpl;
 import com.android.networkstack.apishim.common.NetworkInformationShim;
@@ -605,6 +607,16 @@
             return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
                     defaultEnabled);
         }
+
+        /**
+         * Create an APF filter if apfCapabilities indicates support for packet filtering using
+         * APF programs.
+         * @see ApfFilter#maybeCreate
+         */
+        public ApfFilter maybeCreateApfFilter(Context context, ApfFilter.ApfConfiguration config,
+                InterfaceParams ifParams, IpClientCallbacksWrapper cb) {
+            return ApfFilter.maybeCreate(context, config, ifParams, cb);
+        }
     }
 
     public IpClient(Context context, String ifName, IIpClientCallbacks callback,
@@ -2195,10 +2207,19 @@
             apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
             apfConfig.multicastFilter = mMulticastFiltering;
             // Get the Configuration for ApfFilter from Context
-            apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
-            apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            // Resource settings were moved from ApfCapabilities APIs to NetworkStack resources in S
+            if (ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) {
+                final Resources res = mContext.getResources();
+                apfConfig.ieee802_3Filter = res.getBoolean(R.bool.config_apfDrop802_3Frames);
+                apfConfig.ethTypeBlackList = res.getIntArray(R.array.config_apfEthTypeDenyList);
+            } else {
+                apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames();
+                apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList();
+            }
+
             apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec;
-            mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback);
+            mApfFilter = mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams,
+                    mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 7bc6a49..8950938 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -33,6 +33,7 @@
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs;
 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
@@ -671,6 +672,7 @@
                 (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj;
         mLinkProperties = attrs.first;
         mNetworkCapabilities = attrs.second;
+        suppressNotificationIfNetworkRestricted();
     }
 
     /**
@@ -735,6 +737,12 @@
         return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities);
     }
 
+    private void suppressNotificationIfNetworkRestricted() {
+        if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+            mDontDisplaySigninNotification = true;
+        }
+    }
+
     private void notifyNetworkTested(NetworkTestResultParcelable result) {
         try {
             if (mCallbackVersion <= 5) {
@@ -984,6 +992,7 @@
                     break;
                 case EVENT_NETWORK_CAPABILITIES_CHANGED:
                     mNetworkCapabilities = (NetworkCapabilities) message.obj;
+                    suppressNotificationIfNetworkRestricted();
                     break;
                 default:
                     break;
@@ -2541,8 +2550,17 @@
 
         final CaptivePortalProbeResult probeResult;
         if (probeSpec == null) {
+            if (CaptivePortalProbeResult.isPortalCode(httpResponseCode)
+                    && TextUtils.isEmpty(redirectUrl)
+                    && ShimUtils.isAtLeastS()) {
+                // If a portal is a non-redirect portal (often portals that return HTTP 200 with a
+                // login page for all HTTP requests), report the probe URL as the login URL starting
+                // from S (b/172048052). This avoids breaking assumptions that
+                // [is a portal] is equivalent to [there is a login URL].
+                redirectUrl = url.toString();
+            }
             probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl,
-                    url.toString(),   1 << probeType);
+                    url.toString(), 1 << probeType);
         } else {
             probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
         }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 43a0575..7a2a471 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -87,7 +87,6 @@
     certificate: "networkstack",
     platform_apis: true,
     test_suites: ["device-tests"],
-    enabled: false, // Disabled in mainline-prod
 }
 
 // The static lib needs to be jarjared by each module so they do not conflict with each other
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index e308b96..64da600 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -36,7 +36,10 @@
         "android.test.base",
         "android.test.mock",
     ],
-    defaults: ["libnetworkstackutilsjni_deps"],
+    defaults: [
+        "framework-connectivity-test-defaults",
+        "libnetworkstackutilsjni_deps"
+    ],
     jni_libs: [
         // For mockito extended
         "libdexmakerjvmtiagent",
@@ -54,11 +57,13 @@
     min_sdk_version: "29",
     srcs: [], // TODO: tests that only apply to the current, non-stable API can be added here
     test_suites: ["general-tests"],
-    test_mainline_modules: ["CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex"],
+    test_mainline_modules: [
+        "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex",
+        "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex"
+    ],
     defaults: ["NetworkStackTestsDefaults"],
     static_libs: ["NetworkStackApiCurrentLib"],
     compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit
-    enabled: false, // Disabled in mainline-prod
 }
 
 // Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -71,8 +76,8 @@
     static_libs: ["NetworkStackApiStableLib"],
     visibility: [
         "//packages/modules/NetworkStack/tests/integration",
-        "//frameworks/base/packages/Tethering/tests/integration",
-        "//packages/modules/Connectivity/Tethering/tests/integration",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
     ]
 }
 
@@ -81,7 +86,10 @@
     min_sdk_version: "29",
     target_sdk_version: "30",
     test_suites: ["general-tests", "mts"],
-    test_mainline_modules: ["CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex"],
+    test_mainline_modules: [
+        "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex",
+        "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex"
+    ],
     defaults: ["NetworkStackTestsDefaults"],
     static_libs: ["NetworkStackApiStableLib"],
     compile_multilib: "both",
diff --git a/tests/unit/lint-baseline.xml b/tests/unit/lint-baseline.xml
index ededbea..0812c2d 100644
--- a/tests/unit/lint-baseline.xml
+++ b/tests/unit/lint-baseline.xml
@@ -3,105 +3,6 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="    private static final NetworkCapabilities CELL_METERED_CAPABILITIES = new NetworkCapabilities()"
-        errorLine2="                                                                         ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="297"
-            column="74"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="            new NetworkCapabilities()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="302"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="            new NetworkCapabilities()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="308"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="            new NetworkCapabilities().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="314"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="        final NetworkCapabilities nc = new NetworkCapabilities()"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="670"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="        final NetworkCapabilities nc = new NetworkCapabilities()"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="690"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="        final NetworkCapabilities meteredCap = new NetworkCapabilities()"
-        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="726"
-            column="48"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="        final NetworkCapabilities nonMeteredCap = new NetworkCapabilities()"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="746"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `new android.net.NetworkCapabilities`"
-        errorLine1="        final NetworkCapabilities nc = new NetworkCapabilities()"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java"
-            line="2149"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="NewApi"
         message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
         errorLine1="    private val EMPTY_CAPABILITIES = NetworkCapabilities()"
         errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~">
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index e991ea7..7260332 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -18,12 +18,14 @@
 
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -48,22 +50,30 @@
 import android.net.MacAddress;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.RouteInfo;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.InitialConfiguration;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
+import android.os.Build;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.networkstack.R;
 import com.android.server.NetworkObserver;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -85,6 +95,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpClientTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final String VALID = "VALID";
     private static final String INVALID = "INVALID";
     private static final String TEST_IFNAME = "test_wlan0";
@@ -631,6 +644,71 @@
         return out;
     }
 
+    private ApfConfiguration verifyApfFilterCreatedOnStart(IpClient ipc) {
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIPv4()
+                .withoutIpReachabilityMonitor()
+                .withInitialConfiguration(
+                        conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
+                .withApfCapabilities(new ApfCapabilities(
+                        4 /* version */, 4096 /* maxProgramSize */, 4 /* format */))
+                .build();
+
+        ipc.startProvisioning(config);
+        final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
+                ApfConfiguration.class);
+        verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
+                any(), configCaptor.capture(), any(), any());
+
+        return configCaptor.getValue();
+    }
+
+    @Test @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testApfConfiguration_R() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertEquals(ApfCapabilities.getApfDrop8023Frames(), config.ieee802_3Filter);
+        assertArrayEquals(ApfCapabilities.getApfEtherTypeBlackList(), config.ethTypeBlackList);
+
+        verify(mResources, never()).getBoolean(R.bool.config_apfDrop802_3Frames);
+        verify(mResources, never()).getIntArray(R.array.config_apfEthTypeDenyList);
+
+        verifyShutdown(ipc);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testApfConfiguration() throws Exception {
+        doReturn(true).when(mResources).getBoolean(R.bool.config_apfDrop802_3Frames);
+        final int[] ethTypeDenyList = new int[] { 0x88A2, 0x88A4 };
+        doReturn(ethTypeDenyList).when(mResources).getIntArray(
+                R.array.config_apfEthTypeDenyList);
+
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertTrue(config.ieee802_3Filter);
+        assertArrayEquals(ethTypeDenyList, config.ethTypeBlackList);
+
+        verifyShutdown(ipc);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testApfConfiguration_NoApfDrop8023Frames() throws Exception {
+        doReturn(false).when(mResources).getBoolean(R.bool.config_apfDrop802_3Frames);
+        final int[] ethTypeDenyList = new int[] { 0x88A3, 0x88A5 };
+        doReturn(ethTypeDenyList).when(mResources).getIntArray(
+                R.array.config_apfEthTypeDenyList);
+
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc);
+
+        assertFalse(config.ieee802_3Filter);
+        assertArrayEquals(ethTypeDenyList, config.ethTypeBlackList);
+
+        verifyShutdown(ipc);
+    }
+
     interface Fn<A,B> {
         B call(A a) throws Exception;
     }
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index f010007..3a1bde4 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -28,6 +28,8 @@
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -91,6 +93,7 @@
 import static java.util.stream.Collectors.toList;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -112,6 +115,7 @@
 import android.net.Uri;
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
+import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.SharedLog;
 import android.net.wifi.WifiInfo;
@@ -155,6 +159,7 @@
 import com.android.server.connectivity.nano.DnsEvent;
 import com.android.server.connectivity.nano.WifiData;
 import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
 
@@ -201,6 +206,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressLint("NewApi")  // Uses hidden APIs, which the linter would identify as missing APIs.
 public class NetworkMonitorTest {
     private static final String LOCATION_HEADER = "location";
     private static final String CONTENT_TYPE_HEADER = "Content-Type";
@@ -315,6 +321,14 @@
     private static final NetworkCapabilities CELL_NO_INTERNET_CAPABILITIES =
             new NetworkCapabilities().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
 
+    private static final NetworkCapabilities WIFI_OEM_PAID_CAPABILITIES =
+            new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_METERED)
+                .addCapability(NET_CAPABILITY_OEM_PAID)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+
     /**
      * Fakes DNS responses.
      *
@@ -1068,6 +1082,34 @@
         runPortalNetworkTest();
     }
 
+    @Test
+    public void testIsCaptivePortal_Http200EmptyResponse() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 200);
+        // Invalid if there is no content (can't login to an empty page)
+        runNetworkTest(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null);
+        verify(mCallbacks, never()).showProvisioningNotification(any(), any());
+    }
+
+    private void doCaptivePortal200ResponseTest(String expectedRedirectUrl) throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 200);
+        doReturn(100L).when(mHttpConnection).getContentLengthLong();
+        // Redirect URL was null before S
+        runNetworkTest(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, expectedRedirectUrl);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).showProvisioningNotification(any(), any());
+    }
+
+    @Test @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testIsCaptivePortal_HttpProbeIs200Portal_R() throws Exception {
+        doCaptivePortal200ResponseTest(null);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testIsCaptivePortal_HttpProbeIs200Portal() throws Exception {
+        doCaptivePortal200ResponseTest(TEST_HTTP_URL);
+    }
+
     private void setupPrivateIpResponse(String privateAddr) throws Exception {
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
@@ -1758,6 +1800,47 @@
         verify(mCleartextDnsNetwork, never()).openConnection(any());
     }
 
+    private NetworkCapabilities getVcnUnderlyingCarrierWifiCaps() {
+        // Must be called from within the test because NOT_VCN_MANAGED is an invalid capability
+        // value up to Android R. Thus, this must be guarded by an SDK check in tests that use this.
+        return new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .removeCapability(NetworkMonitorUtils.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+    }
+
+    @Test
+    public void testVcnUnderlyingNetwork() throws Exception {
+        assumeTrue(ShimUtils.isAtLeastS());
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+
+        final NetworkMonitor nm = runNetworkTest(
+                TEST_LINK_PROPERTIES, getVcnUnderlyingCarrierWifiCaps(),
+                NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
+                null /* redirectUrl */);
+        assertEquals(NETWORK_VALIDATION_RESULT_VALID,
+                nm.getEvaluationState().getEvaluationResult());
+    }
+
+    @Test
+    public void testVcnUnderlyingNetworkBadNetwork() throws Exception {
+        assumeTrue(ShimUtils.isAtLeastS());
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+
+        final NetworkMonitor nm = runNetworkTest(
+                TEST_LINK_PROPERTIES, getVcnUnderlyingCarrierWifiCaps(),
+                VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
+        assertEquals(VALIDATION_RESULT_INVALID,
+                nm.getEvaluationState().getEvaluationResult());
+    }
+
     @Test
     public void testLaunchCaptivePortalApp() throws Exception {
         setSslException(mHttpsConnection);
@@ -2604,6 +2687,61 @@
                         ConnectivityManager.EXTRA_NETWORK)).netId));
     }
 
+    @Test
+    public void testOemPaidNetworkValidated() throws Exception {
+        setValidProbes();
+
+        final NetworkMonitor nm = runNetworkTest(TEST_LINK_PROPERTIES,
+                WIFI_OEM_PAID_CAPABILITIES,
+                NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
+                null /* redirectUrl */);
+        assertEquals(NETWORK_VALIDATION_RESULT_VALID,
+                nm.getEvaluationState().getEvaluationResult());
+    }
+
+    @Test
+    public void testOemPaidNetwork_AllProbesFailed() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+
+        runNetworkTest(TEST_LINK_PROPERTIES,
+                WIFI_OEM_PAID_CAPABILITIES,
+                VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */, null /* redirectUrl */);
+    }
+
+    @Test
+    public void testOemPaidNetworkNoInternetCapabilityValidated() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+
+        final NetworkCapabilities networkCapabilities =
+                new NetworkCapabilities(WIFI_OEM_PAID_CAPABILITIES);
+        networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
+        runNetworkTest(TEST_LINK_PROPERTIES, networkCapabilities,
+                NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */, null /* redirectUrl */);
+
+        verify(mCleartextDnsNetwork, never()).openConnection(any());
+        verify(mHttpsConnection, never()).getResponseCode();
+        verify(mHttpConnection, never()).getResponseCode();
+        verify(mFallbackConnection, never()).getResponseCode();
+    }
+
+    @Test
+    public void testOemPaidNetwork_CaptivePortalNotLaunched() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mFallbackConnection, 404);
+        setPortal302(mHttpConnection);
+
+        runNetworkTest(TEST_LINK_PROPERTIES, WIFI_OEM_PAID_CAPABILITIES,
+                VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
+                TEST_LOGIN_URL);
+
+        verify(mCallbacks, never()).showProvisioningNotification(any(), any());
+    }
+
     private void setupResourceForMultipleProbes() {
         // Configure the resource to send multiple probe.
         when(mResources.getStringArray(R.array.config_captive_portal_https_urls))