Snap for 11174750 from 3255d59c03ace580cdfd13a4046a026c23b16cad to mainline-extservices-release

Change-Id: I1017d700b055b00725af9b8277f95f7e0db31f31
diff --git a/Android.bp b/Android.bp
index 61b4a1e..8ee5d6f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -89,7 +89,7 @@
     sdk_version: module_34_version,
     libs: [
         "framework-configinfrastructure",
-        "framework-connectivity",
+        "framework-connectivity.stubs.module_lib",
         "framework-connectivity-t",
         "framework-statsd",
         "framework-wifi",
@@ -311,6 +311,7 @@
         "androidx.annotation_annotation",
         "modules-utils-build_system",
         "modules-utils-preconditions",
+        "modules-utils-shell-command-handler",
         "modules-utils-statemachine",
         "netd_aidl_interface-lateststable-java",
         "networkstack-client",
diff --git a/OWNERS b/OWNERS
index c24680e..b0e134e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 31808
 set noparent
 file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
index e7f7b3d..2fcd8c6 100644
--- a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
@@ -100,7 +100,7 @@
     }
 
     /**
-     * @see NetworkCapabilites#getUnderlyingNetworks()
+     * @see NetworkCapabilities#getUnderlyingNetworks()
      */
     @Nullable
     default List<Network> getUnderlyingNetworks(@NonNull NetworkCapabilities nc) {
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 0571042..e056e3b 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -160,6 +160,10 @@
             version: "18",
             imports: ["ipmemorystore-aidl-interfaces-V10"],
         },
+        {
+            version: "19",
+            imports: ["ipmemorystore-aidl-interfaces-V10"],
+        },
 
     ],
 
@@ -174,7 +178,7 @@
     min_sdk_version: "30",
     static_libs: [
         "ipmemorystore-aidl-interfaces-V10-java",
-        "networkstack-aidl-interfaces-V18-java",
+        "networkstack-aidl-interfaces-V19-java",
     ],
     visibility: ["//packages/modules/NetworkStack:__subpackages__"],
     apex_available: [
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/.hash
new file mode 100644
index 0000000..5e94944
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/.hash
@@ -0,0 +1 @@
+ffc74fbac5dcfe825bc16b9a3a91b43251476361
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DataStallReportParcelable.aidl
new file mode 100644
index 0000000..771deda
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DataStallReportParcelable {
+  long timestampMillis = 0;
+  int detectionMethod = 1;
+  int tcpPacketFailRate = 2;
+  int tcpMetricsCollectionPeriodMillis = 3;
+  int dnsConsecutiveTimeouts = 4;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31f2194
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,44 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  @nullable String serverHostName;
+  @nullable String captivePortalApiUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..fb13c0c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitor.aidl
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkConnectedParcel(in android.net.networkstack.aidl.NetworkMonitorParameters params);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 0x01;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02;
+  const int NETWORK_VALIDATION_RESULT_SKIPPED = 0x04;
+  const int NETWORK_VALIDATION_PROBE_DNS = 0x04;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 0x08;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 0x20;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 0x40;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..36eda8e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+  oneway void showProvisioningNotification(String action, String packageName) = 3;
+  oneway void hideProvisioningNotification() = 4;
+  oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+  oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+  oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+  oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..8120ffc
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+  oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..0b6b778
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InformationElementParcelable.aidl
new file mode 100644
index 0000000..6103774
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InformationElementParcelable.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InformationElementParcelable {
+  int id;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..6a597e6
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2InformationParcelable.aidl
new file mode 100644
index 0000000..83796ee
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2InformationParcelable {
+  String l2Key;
+  String cluster;
+  android.net.MacAddress bssid;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..4b3fff5
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,39 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable Layer2PacketParcelable {
+  android.net.MacAddress dstMacAddress;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..18cf954
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NetworkTestResultParcelable.aidl
new file mode 100644
index 0000000..4d6d5a2
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable NetworkTestResultParcelable {
+  long timestampMillis;
+  int result;
+  int probesSucceeded;
+  int probesAttempted;
+  String redirectUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..ab62fe7
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(equals=true, toString=true)
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+  int privateDnsMode = (-1) /* -1 */;
+  String dohName = "";
+  String[] dohIps = {};
+  String dohPath = "";
+  int dohPort = (-1) /* -1 */;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..fba524b
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,63 @@
+/*
+**
+** 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.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ProvisioningConfigurationParcelable {
+  /**
+   * @deprecated use ipv4ProvisioningMode instead.
+   */
+  boolean enableIPv4;
+  /**
+   * @deprecated use ipv6ProvisioningMode instead.
+   */
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+  boolean enablePreconnection;
+  @nullable android.net.ScanResultInfoParcelable scanResultInfo;
+  @nullable android.net.Layer2InformationParcelable layer2Info;
+  @nullable List<android.net.networkstack.aidl.dhcp.DhcpOption> options;
+  int ipv4ProvisioningMode;
+  int ipv6ProvisioningMode;
+  boolean uniqueEui64AddressesOnly;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ScanResultInfoParcelable.aidl
new file mode 100644
index 0000000..94fc27f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable ScanResultInfoParcelable {
+  String ssid;
+  String bssid;
+  android.net.InformationElementParcelable[] informationElements;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..0e1c21c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaDerive(toString=true)
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpLeaseParcelable.aidl
new file mode 100644
index 0000000..3cd8860
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpLeaseParcelable.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpLeaseParcelable {
+  byte[] clientId;
+  byte[] hwAddr;
+  int netAddr;
+  int prefixLength;
+  long expTime;
+  String hostname;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..7997936
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,49 @@
+/**
+ *
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+  int singleClientAddr = 0;
+  boolean changePrefixOnDecline = false;
+  int leasesSubnetPrefixLength = 0;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpEventCallbacks.aidl
new file mode 100644
index 0000000..9312f47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpEventCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+interface IDhcpEventCallbacks {
+  oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases);
+  oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..1109f35
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb) = 0;
+  oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3;
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1;
+  oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2;
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..ab8577c
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.dhcp;
+/* @hide */
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..b81ec20
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClient.aidl
@@ -0,0 +1,59 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+  oneway void notifyPreconnectionComplete(boolean success);
+  oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info);
+  oneway void updateApfCapabilities(in android.net.apf.ApfCapabilities apfCapabilities);
+  const int PROV_IPV4_DISABLED = 0x00;
+  const int PROV_IPV4_STATIC = 0x01;
+  const int PROV_IPV4_DHCP = 0x02;
+  const int PROV_IPV6_DISABLED = 0x00;
+  const int PROV_IPV6_SLAAC = 0x01;
+  const int PROV_IPV6_LINKLOCAL = 0x02;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..9d36419
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,54 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ip;
+/* @hide */
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+  oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets);
+  oneway void onReachabilityFailure(in android.net.networkstack.aidl.ip.ReachabilityLossInfoParcelable lossInfo);
+  oneway void setMaxDtimMultiplier(int multiplier);
+  const int DTIM_MULTIPLIER_RESET = 0;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/NetworkMonitorParameters.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
new file mode 100644
index 0000000..2ab9db0
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/NetworkMonitorParameters.aidl
@@ -0,0 +1,41 @@
+/**
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl;
+@JavaDerive(equals=true, toString=true)
+parcelable NetworkMonitorParameters {
+  android.net.NetworkAgentConfig networkAgentConfig;
+  android.net.NetworkCapabilities networkCapabilities;
+  android.net.LinkProperties linkProperties;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/dhcp/DhcpOption.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
new file mode 100644
index 0000000..eea3e0d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/dhcp/DhcpOption.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.dhcp;
+@JavaDerive(toString=true)
+parcelable DhcpOption {
+  byte type;
+  @nullable byte[] value;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
new file mode 100644
index 0000000..bb88434
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossInfoParcelable.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.ip;
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable ReachabilityLossInfoParcelable {
+  String message;
+  android.net.networkstack.aidl.ip.ReachabilityLossReason reason;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
new file mode 100644
index 0000000..f9bb3c4
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/19/android/net/networkstack/aidl/ip/ReachabilityLossReason.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.networkstack.aidl.ip;
+@Backing(type="int")
+enum ReachabilityLossReason {
+  ROAM,
+  CONFIRM,
+  ORGANIC,
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
index 1457caf..ab62fe7 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/PrivateDnsConfigParcel.aidl
@@ -32,8 +32,13 @@
 // later when a module using the interface is updated, e.g., Mainline modules.
 
 package android.net;
-@JavaDerive(toString=true)
+@JavaDerive(equals=true, toString=true)
 parcelable PrivateDnsConfigParcel {
   String hostname;
   String[] ips;
+  int privateDnsMode = (-1) /* -1 */;
+  String dohName = "";
+  String[] dohIps = {};
+  String dohPath = "";
+  int dohPort = (-1) /* -1 */;
 }
diff --git a/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl
index 97bb697..e747d61 100644
--- a/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl
+++ b/common/networkstackclient/src/android/net/PrivateDnsConfigParcel.aidl
@@ -16,8 +16,60 @@
 
 package android.net;
 
-@JavaDerive(toString=true)
+@JavaDerive(equals=true, toString=true)
 parcelable PrivateDnsConfigParcel {
+    /**
+     * The hostname of private DNS provider.
+     */
     String hostname;
+
+    /**
+     * The DoT server IP addresses of `hostname`. They are not sorted.
+     */
     String[] ips;
+
+    /**
+     * The private DNS mode associated with this PrivateDnsConfigParcel.
+     * If it's set, the value must be one of the following constants defined in
+     * ConnectivitySettingsManager.
+     *   - PRIVATE_DNS_MODE_OFF (1)
+     *   - PRIVATE_DNS_MODE_OPPORTUNISTIC (2)
+     *   - PRIVATE_DNS_MODE_PROVIDER_HOSTNAME (3)
+     *
+     * For compatibility with old PrivateDnsConfigParcel, set the default value to -1 to indicate
+     * that the sender is using an old version of PrivateDnsConfigParcel and that the receiver
+     * cannot determine the private DNS mode by reading this field.
+     */
+    int privateDnsMode = -1;
+
+    /**
+     * The following fields with the prefix "doh" store the DoH3 information discovered from
+     * DDR. The similar fields are defined in DnsResolver as well. Although duplicating code
+     * is not a good idea, it avoids the complexity and confusion of having a parcelable
+     * containing a nested parcelable where the client and server could have a different version
+     * of the nested parcelable.
+     */
+
+    /**
+     * The DoH server hostname derived from TargetName field of a DNS SVCB response.
+     */
+    String dohName = "";
+
+    /**
+     * The DoH server IP addresses of `dohName`. They are not sorted.
+     */
+    String[] dohIps = {};
+
+    /**
+     * A part of the URI template used to construct the URL for DNS resolution.
+     * It's derived only from DNS SVCB SvcParamKey "dohpath".
+     * The URI template for DNS resolution is as follows:
+     *     https://<dohName>/<dohPath>
+     */
+    String dohPath = "";
+
+    /**
+     * The port used to reach the DoH servers.
+     */
+    int dohPort = -1;
 }
diff --git a/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java b/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
index 106ca1c..632d1d6 100644
--- a/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
+++ b/common/networkstackclient/src/android/net/shared/PrivateDnsConfig.java
@@ -16,9 +16,14 @@
 
 package android.net.shared;
 
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
 import static android.net.shared.ParcelableUtil.fromParcelableArray;
 import static android.net.shared.ParcelableUtil.toParcelableArray;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.PrivateDnsConfigParcel;
 import android.text.TextUtils;
 
@@ -27,50 +32,116 @@
 
 /** @hide */
 public class PrivateDnsConfig {
-    public final boolean useTls;
+    // These fields store the private DNS configuration from setting.
+    public final int mode;
+    @NonNull
     public final String hostname;
+
+    // Stores the DoT server IP addresses resolved from A/AAAA lookups.
+    @NonNull
     public final InetAddress[] ips;
 
+    // These fields store the DoH information discovered from SVCB lookups.
+    @NonNull
+    public final String dohName;
+    @NonNull
+    public final InetAddress[] dohIps;
+    @NonNull
+    public final String dohPath;
+    public final int dohPort;
+
+    /**
+     * A constructor for off mode private DNS configuration.
+     * TODO(b/261404136): Consider simplifying the constructors. One possible way is to
+     * use constants to represent private DNS modes:
+     *   public static PrivateDnsConfig OFF = new PrivateDnsConfig(false);
+     *   public static PrivateDnsConfig OPPORTUNISTIC = new PrivateDnsConfig(true);
+     *   public static PrivateDnsConfig STRICT = new PrivateDnsConfig(String hostname);
+     */
     public PrivateDnsConfig() {
         this(false);
     }
 
+    /**
+     * A constructor for off/opportunistic mode private DNS configuration depending on `useTls`.
+     */
     public PrivateDnsConfig(boolean useTls) {
-        this.useTls = useTls;
-        this.hostname = "";
-        this.ips = new InetAddress[0];
+        this(useTls ? PRIVATE_DNS_MODE_OPPORTUNISTIC : PRIVATE_DNS_MODE_OFF, null /* hostname */,
+                null /* ips */, null /* dohName */, null /* dohIps */, null /* dohPath */,
+                -1 /* dohPort */);
     }
 
-    public PrivateDnsConfig(String hostname, InetAddress[] ips) {
-        this.useTls = !TextUtils.isEmpty(hostname);
-        this.hostname = useTls ? hostname : "";
-        this.ips = (ips != null) ? ips : new InetAddress[0];
+    /**
+     * A constructor for off/strict mode private DNS configuration depending on `hostname`.
+     * If `hostname` is empty or null, this constructor creates a PrivateDnsConfig for off mode;
+     * otherwise, it creates a PrivateDnsConfig for strict mode.
+     */
+    public PrivateDnsConfig(@Nullable String hostname, @Nullable InetAddress[] ips) {
+        this(TextUtils.isEmpty(hostname) ? PRIVATE_DNS_MODE_OFF :
+                PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, hostname, ips, null /* dohName */,
+                null /* dohIps */, null /* dohPath */, -1 /* dohPort */);
+    }
+
+    /**
+     * A constructor for all kinds of private DNS configuration with given DoH information.
+     * It treats both null values and empty strings as equivalent. Similarly, treats null values
+     * and empty arrays as equivalent.
+     */
+    public PrivateDnsConfig(int mode, @Nullable String hostname, @Nullable InetAddress[] ips,
+            @Nullable String dohName, @Nullable InetAddress[] dohIps, @Nullable String dohPath,
+            int dohPort) {
+        this.mode = mode;
+        this.hostname = (hostname != null) ? hostname : "";
+        this.ips = (ips != null) ? ips.clone() : new InetAddress[0];
+        this.dohName = (dohName != null) ? dohName : "";
+        this.dohIps = (dohIps != null) ? dohIps.clone() : new InetAddress[0];
+        this.dohPath = (dohPath != null) ? dohPath : "";
+        this.dohPort = dohPort;
     }
 
     public PrivateDnsConfig(PrivateDnsConfig cfg) {
-        useTls = cfg.useTls;
+        mode = cfg.mode;
         hostname = cfg.hostname;
         ips = cfg.ips;
+        dohName = cfg.dohName;
+        dohIps = cfg.dohIps;
+        dohPath = cfg.dohPath;
+        dohPort = cfg.dohPort;
     }
 
     /**
      * Indicates whether this is a strict mode private DNS configuration.
      */
     public boolean inStrictMode() {
-        return useTls && !TextUtils.isEmpty(hostname);
+        return mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
     }
 
     /**
      * Indicates whether this is an opportunistic mode private DNS configuration.
      */
     public boolean inOpportunisticMode() {
-        return useTls && TextUtils.isEmpty(hostname);
+        return mode == PRIVATE_DNS_MODE_OPPORTUNISTIC;
     }
 
     @Override
     public String toString() {
         return PrivateDnsConfig.class.getSimpleName()
-                + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+                + "{" + modeAsString(mode) + ":" + hostname + "/" + Arrays.toString(ips)
+                + ", dohName=" + dohName
+                + ", dohIps=" + Arrays.toString(dohIps)
+                + ", dohPath=" + dohPath
+                + ", dohPort=" + dohPort
+                + "}";
+    }
+
+    @NonNull
+    private static String modeAsString(int mode) {
+        switch (mode) {
+            case PRIVATE_DNS_MODE_OFF: return "off";
+            case PRIVATE_DNS_MODE_OPPORTUNISTIC: return "opportunistic";
+            case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: return "strict";
+            default: return "unknown";
+        }
     }
 
     /**
@@ -81,7 +152,12 @@
         parcel.hostname = hostname;
         parcel.ips = toParcelableArray(
                 Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class);
-
+        parcel.privateDnsMode = mode;
+        parcel.dohName = dohName;
+        parcel.dohIps = toParcelableArray(
+                Arrays.asList(dohIps), IpConfigurationParcelableUtil::parcelAddress, String.class);
+        parcel.dohPath = dohPath;
+        parcel.dohPort = dohPort;
         return parcel;
     }
 
@@ -92,6 +168,26 @@
         InetAddress[] ips = new InetAddress[parcel.ips.length];
         ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress)
                 .toArray(ips);
-        return new PrivateDnsConfig(parcel.hostname, ips);
+
+        // For compatibility. If the sender (Tethering module) is using an old version (< 19) of
+        // NetworkStack AIDL that `privateDnsMode` field is not present, `privateDnsMode` will be
+        // assigned from the default value -1. Let `privateDnsMode` assigned based on the hostname.
+        // In this case, there is a harmless bug that the receiver (NetworkStack module) can't
+        // convert the parcel to a PrivateDnsConfig that indicates opportunistic mode.
+        // The bug is harmless because 1) the bug exists for years without any problems and
+        // 2) NetworkMonitor cares PrivateDnsConfig that indicates strict/off mode only.
+        // If the sender is using new version (>=19) while the receiver is using an old version,
+        // the above mentioned harmless bug will persist. Except for that harmless bug, there
+        // should be no other issues. New version's toParcel() doesn't change how the pre-existing
+        // fields `hostname` and `ips` are assigned.
+        if (parcel.privateDnsMode == -1) {
+            return new PrivateDnsConfig(parcel.hostname, ips);
+        }
+
+        InetAddress[] dohIps = new InetAddress[parcel.dohIps.length];
+        dohIps = fromParcelableArray(parcel.dohIps,
+                IpConfigurationParcelableUtil::unparcelAddress).toArray(dohIps);
+        return new PrivateDnsConfig(parcel.privateDnsMode, parcel.hostname, ips, parcel.dohName,
+                dohIps, parcel.dohPath, parcel.dohPort);
     }
 }
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 41c6726..3ab5e0e 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -17,6 +17,8 @@
 package android.net.apf;
 
 import static android.net.util.SocketUtils.makePacketSocketAddress;
+import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
+import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
 import static android.system.OsConstants.AF_PACKET;
 import static android.system.OsConstants.ARPHRD_ETHER;
 import static android.system.OsConstants.ETH_P_ARP;
@@ -54,6 +56,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.GuardedBy;
@@ -61,6 +64,7 @@
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.TokenBucket;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.ConnectivityUtils;
 import com.android.net.module.util.InterfaceParams;
@@ -112,6 +116,7 @@
         public int[] ethTypeBlackList;
         public int minRdnssLifetimeSec;
         public int acceptRaMinLft;
+        public boolean shouldHandleLightDoze;
     }
 
     /**
@@ -132,8 +137,6 @@
         PASSED_IPV4_UNICAST,
         PASSED_IPV6_ICMP,
         PASSED_IPV6_UNICAST_NON_ICMP,
-        PASSED_ARP_NON_IPV4,
-        PASSED_ARP_UNKNOWN,
         PASSED_ARP_UNICAST_REPLY,
         PASSED_NON_IP_UNICAST,
         PASSED_MDNS,
@@ -156,7 +159,9 @@
         DROPPED_IPV4_KEEPALIVE_ACK,
         DROPPED_IPV6_KEEPALIVE_ACK,
         DROPPED_IPV4_NATT_KEEPALIVE,
-        DROPPED_MDNS;
+        DROPPED_MDNS,
+        DROPPED_ARP_NON_IPV4,
+        DROPPED_ARP_UNKNOWN;
 
         // Returns the negative byte offset from the end of the APF data segment for
         // a given counter.
@@ -323,16 +328,48 @@
     // Tracks the value of /proc/sys/ipv6/conf/$iface/accept_ra_min_lft which affects router, RIO,
     // and PIO valid lifetimes.
     private final int mAcceptRaMinLft;
+    private final boolean mShouldHandleLightDoze;
+
+    private static boolean isDeviceIdleModeChangedAction(Intent intent) {
+        return ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction());
+    }
+
+    private boolean isDeviceLightIdleModeChangedAction(Intent intent) {
+        // The ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED only exist since T. For lower platform version,
+        // the check should return false. The explicit SDK check is needed to make linter happy
+        // about accessing ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED in this function.
+        if (!SdkLevel.isAtLeastT()) {
+            return false;
+        }
+        if (!mShouldHandleLightDoze) {
+            return false;
+        }
+        return ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(intent.getAction());
+    }
+
+    private boolean isDeviceLightIdleMode(@NonNull PowerManager powerManager) {
+        // The powerManager.isDeviceLightIdleMode() only exist since T. For lower platform version,
+        // the check should return false. The explicit SDK check is needed to make linter happy
+        // about accessing powerManager.isDeviceLightIdleMode() in this function.
+        if (!SdkLevel.isAtLeastT()) {
+            return false;
+        }
+        if (!mShouldHandleLightDoze) {
+            return false;
+        }
+
+        return powerManager.isDeviceLightIdleMode();
+    }
 
     // Detects doze mode state transitions.
     private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
-                PowerManager powerManager =
-                        (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-                final boolean deviceIdle = powerManager.isDeviceIdleMode();
+            final PowerManager powerManager = context.getSystemService(PowerManager.class);
+            if (isDeviceIdleModeChangedAction(intent)
+                    || isDeviceLightIdleModeChangedAction(intent)) {
+                final boolean deviceIdle = powerManager.isDeviceIdleMode()
+                        || isDeviceLightIdleMode(powerManager);
                 setDozeMode(deviceIdle);
             }
         }
@@ -346,9 +383,16 @@
     @GuardedBy("this")
     private int mIPv4PrefixLength;
 
-    @VisibleForTesting
+    private final Dependencies mDependencies;
+
     public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
             IpClientCallbacksWrapper ipClientCallback) {
+        this(context, config, ifParams, ipClientCallback, new Dependencies(context));
+    }
+
+    @VisibleForTesting
+    public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
+            IpClientCallbacksWrapper ipClientCallback, Dependencies dependencies) {
         mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mInterfaceParams = ifParams;
@@ -357,6 +401,8 @@
         mMinRdnssLifetimeSec = config.minRdnssLifetimeSec;
         mAcceptRaMinLft = config.acceptRaMinLft;
         mContext = context;
+        mShouldHandleLightDoze = config.shouldHandleLightDoze;
+        mDependencies = dependencies;
 
         if (mApfCapabilities.hasDataAccess()) {
             mCountAndPassLabel = "countAndPass";
@@ -384,8 +430,33 @@
         maybeStartFilter();
 
         // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
-        mContext.registerReceiver(mDeviceIdleReceiver,
-                new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+        mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldHandleLightDoze);
+    }
+
+    /**
+     * Dependencies class for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        private final Context mContext;
+        public Dependencies(final Context context) {
+            mContext = context;
+        }
+
+        /** Add receiver for detecting doze mode change */
+        public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver,
+                boolean shouldHandleLightDoze) {
+            final IntentFilter intentFilter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
+            if (SdkLevel.isAtLeastT() && shouldHandleLightDoze) {
+                intentFilter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
+            }
+            mContext.registerReceiver(receiver, intentFilter);
+        }
+
+        /** Remove broadcast receiver. */
+        public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver) {
+            mContext.unregisterReceiver(receiver);
+        }
     }
 
     public synchronized void setDataSnapshot(byte[] data) {
@@ -1360,9 +1431,9 @@
         // Here's a basic summary of what the ARP filter program does:
         //
         // if not ARP IPv4
-        //   pass
+        //   drop
         // if not ARP IPv4 reply or request
-        //   pass
+        //   drop
         // if ARP reply source ip is 0.0.0.0
         //   drop
         // if unicast ARP reply
@@ -1377,28 +1448,28 @@
 
         final String checkTargetIPv4 = "checkTargetIPv4";
 
-        // Pass if not ARP IPv4.
+        // Drop if not ARP IPv4.
         gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
-        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
+        maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4);
+        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndDropLabel);
 
-        // Pass if unknown ARP opcode.
+        // Drop if unknown ARP opcode.
         gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
         gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
-        maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
-        gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
+        maybeSetupCounter(gen, Counter.DROPPED_ARP_UNKNOWN);
+        gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndDropLabel);
 
         // Drop if ARP reply source IP is 0.0.0.0
         gen.addLoad32(Register.R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
         maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
         gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
 
-        // Pass if unicast reply.
+        // Pass if non-broadcast reply.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
         gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
 
-        // Either a unicast request, a unicast reply, or a broadcast reply.
+        // Either a request, or a broadcast reply.
         gen.defineLabel(checkTargetIPv4);
         if (mIPv4Address == null) {
             // When there is no IPv4 address, drop GARP replies (b/29404209).
@@ -2052,7 +2123,7 @@
             mReceiveThread = null;
         }
         mRas.clear();
-        mContext.unregisterReceiver(mDeviceIdleReceiver);
+        mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver);
     }
 
     public synchronized void setMulticastFilter(boolean isEnabled) {
@@ -2087,6 +2158,11 @@
         installNewProgramLocked();
     }
 
+    @VisibleForTesting
+    public synchronized boolean isInDozeMode() {
+        return mInDozeMode;
+    }
+
     /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
     private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
         LinkAddress ipv4Address = null;
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java
index 0460c83..e14364e 100644
--- a/src/android/net/apf/ApfGenerator.java
+++ b/src/android/net/apf/ApfGenerator.java
@@ -64,7 +64,12 @@
         EXT(21),   // Followed by immediate indicating ExtendedOpcodes.
         LDDW(22),  // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
         STDW(23),  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
-        WRITE(24); // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5"
+        WRITE(24),  // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5"
+        // Copy the data from input packet or APF data region to output buffer. Register bit is
+        // used to specify the source of data copy: R=0 means copy from packet, R=1 means copy
+        // from APF data region. The source offset is encoded in the first imm and the copy length
+        // is encoded in the second imm. "e.g. MEMCOPY(R=0), 5, 5"
+        MEMCOPY(25);
 
         final int value;
 
@@ -85,7 +90,13 @@
         TRANS(37), // Transmit buffer, "e.g. TRANS R0"
         EWRITE1(38), // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0"
         EWRITE2(39), // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0"
-        EWRITE4(40); // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0"
+        EWRITE4(40), // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0"
+        // Copy the data from input packet to output buffer. The source offset is encoded as [Rx
+        // + second imm]. The copy length is encoded in the third imm. "e.g. EPKTCOPY [R0 + 5], 5"
+        EPKTCOPY(41),
+        // Copy the data from APF data region to output buffer. The source offset is encoded as [Rx
+        // + second imm]. The copy length is encoded in the third imm. "e.g. EDATACOPY [R0 + 5], 5"
+        EDATACOPY(42);
 
         final int value;
 
@@ -118,12 +129,18 @@
             mSigned = signed;
             mImmSize = size;
         }
+
+        @Override
+        public String toString() {
+            return "Immediate{" + "mSigned=" + mSigned + ", mImmSize=" + mImmSize + ", mValue="
+                    + mValue + '}';
+        }
     }
 
     private class Instruction {
         private final byte mOpcode;   // A "Opcode" value.
         private final byte mRegister; // A "Register" value.
-        private final int mMaxSupportedImmediates;
+        private final int mMaxSupportedImms;
         public final List<Immediate> mImms = new ArrayList<>();
         // When mOpcode is a jump:
         private byte mTargetLabelSize;
@@ -136,13 +153,13 @@
         int offset;
 
         Instruction(Opcodes opcode, Register register) {
-            this(opcode, register, 1 /* mMaxSupportedImmediates */);
+            this(opcode, register, 1 /* maxSupportedImm */);
         }
 
-        Instruction(Opcodes opcode, Register register, int maxSupportedImm) {
+        Instruction(Opcodes opcode, Register register, int maxSupportedImms) {
             mOpcode = (byte) opcode.value;
             mRegister = (byte) register.value;
-            mMaxSupportedImmediates = maxSupportedImm;
+            mMaxSupportedImms = maxSupportedImms;
         }
 
         Instruction(Opcodes opcode) {
@@ -162,10 +179,10 @@
         }
 
         void addImm(Immediate imm) {
-            if (mImms.size() == mMaxSupportedImmediates) {
+            if (mImms.size() == mMaxSupportedImms) {
                 throw new IllegalArgumentException(
                         String.format("Opcode: %d only support at max: %d imms", mOpcode,
-                                mMaxSupportedImmediates));
+                                mMaxSupportedImms));
             }
             mImms.add(imm);
         }
@@ -201,9 +218,20 @@
                 return 0;
             }
             int size = 1;
-            size += mImms.size() * generatedImmSize();
+            byte maxImmSize = getMaxImmSize();
+            // For the copy opcode, the last imm is the length field is always 1 byte
+            if (isCopyOpCode()) {
+                if (mMaxSupportedImms != mImms.size()) {
+                    throw new IllegalStateException(
+                            "mImm size: " + mImms.size() + " doesn't match the mMaxSupportedImms: "
+                                    + mMaxSupportedImms);
+                }
+                size += (mImms.size() - 1) * maxImmSize + mImms.get(mImms.size() - 1).mImmSize;
+            } else {
+                size += mImms.size() * maxImmSize;
+            }
             if (mTargetLabel != null) {
-                size += generatedImmSize();
+                size += maxImmSize;
             }
             if (mCompareBytes != null) {
                 size += mCompareBytes.length;
@@ -233,7 +261,7 @@
          * Assemble value for instruction size field.
          */
         private byte generateImmSizeField() {
-            byte immSize = generatedImmSize();
+            byte immSize = getMaxImmSize();
             // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
             return immSize == 4 ? 3 : immSize;
         }
@@ -248,15 +276,15 @@
 
         /**
          * Write {@code value} at offset {@code writingOffset} into {@code bytecode}.
-         * {@link generatedImmSize} bytes are written. {@code value} is truncated to
-         * {@code generatedImmSize} bytes. {@code value} is treated simply as a
+         * {@code immSize} bytes are written. {@code value} is truncated to
+         * {@code immSize} bytes. {@code value} is treated simply as a
          * 32-bit value, so unsigned values should be zero extended and the truncation
          * should simply throw away their zero-ed upper bits, and signed values should
          * be sign extended and the truncation should simply throw away their signed
          * upper bits.
          */
-        private int writeValue(int value, byte[] bytecode, int writingOffset) {
-            for (int i = generatedImmSize() - 1; i >= 0; i--) {
+        private int writeValue(int value, byte[] bytecode, int writingOffset, byte immSize) {
+            for (int i = immSize - 1; i >= 0; i--) {
                 bytecode[writingOffset++] = (byte)((value >> (i * 8)) & 255);
             }
             return writingOffset;
@@ -271,11 +299,29 @@
             }
             int writingOffset = offset;
             bytecode[writingOffset++] = generateInstructionByte();
+            byte maxImmSize = getMaxImmSize();
             if (mTargetLabel != null) {
-                writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset);
+                writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset,
+                        maxImmSize);
             }
-            for (int i = 0; i < mImms.size(); ++i) {
-                writingOffset = writeValue(mImms.get(i).mValue, bytecode, writingOffset);
+            // For the copy opcode, the last imm is the length field is always 1 byte
+            if (isCopyOpCode()) {
+                if (mMaxSupportedImms != mImms.size()) {
+                    throw new IllegalStateException(
+                            "mImm size: " + mImms.size() + " doesn't match the mMaxSupportedImms: "
+                                    + mMaxSupportedImms);
+                }
+                int i;
+                for (i = 0; i < mImms.size() - 1; ++i) {
+                    writingOffset = writeValue(mImms.get(i).mValue, bytecode, writingOffset,
+                            maxImmSize);
+                }
+                writingOffset = writeValue(mImms.get(i).mValue, bytecode, writingOffset,
+                        mImms.get(i).mImmSize);
+            } else {
+                for (Immediate imm : mImms) {
+                    writingOffset = writeValue(imm.mValue, bytecode, writingOffset, maxImmSize);
+                }
             }
             if (mCompareBytes != null) {
                 System.arraycopy(mCompareBytes, 0, bytecode, writingOffset, mCompareBytes.length);
@@ -287,6 +333,20 @@
             }
         }
 
+        private boolean isCopyOpCode() {
+            if (mOpcode == Opcodes.MEMCOPY.value) {
+                return true;
+            }
+            if (mOpcode == Opcodes.EXT.value) {
+                int realOpcode = mImms.get(0).mValue;
+                if (realOpcode == ExtendedOpcodes.EPKTCOPY.value
+                        || realOpcode == ExtendedOpcodes.EDATACOPY.value) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         /**
          * Calculate the size of either the immediate fields or the target label field, if either is
          * present. Most instructions have either immediates or a target label field, but for the
@@ -295,7 +355,7 @@
          * byte, hence why this function simply takes the maximum of those sizes, so neither is
          * truncated.
          */
-        private byte generatedImmSize() {
+        private byte getMaxImmSize() {
             byte maxSize = mTargetLabelSize;
             for (int i = 0; i < mImms.size(); ++i) {
                 maxSize = (byte) Math.max(maxSize, mImms.get(i).mImmSize);
@@ -936,6 +996,116 @@
     }
 
     /**
+     * Add an instruction to the end of the program to copy data from APF data region to output
+     * buffer.
+     *
+     * @param srcOffset the offset inside the APF data region for where to start copy
+     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfGenerator object
+     * @throws IllegalInstructionException throws when imm size is incorrectly set.
+     */
+    public ApfGenerator addDataCopy(int srcOffset, int length)
+            throws IllegalInstructionException {
+        return addMemCopy(srcOffset, length, Register.R1);
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output buffer.
+     *
+     * @param srcOffset the offset inside the input packet for where to start copy
+     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfGenerator object
+     * @throws IllegalInstructionException throws when imm size is incorrectly set.
+     */
+    public ApfGenerator addPacketCopy(int srcOffset, int length)
+            throws IllegalInstructionException {
+        return addMemCopy(srcOffset, length, Register.R0);
+    }
+
+    private ApfGenerator addMemCopy(int srcOffset, int length, Register register)
+            throws IllegalInstructionException {
+        requireApfVersion(5);
+        checkCopyLength(length);
+        checkCopyOffset(srcOffset);
+        Instruction instruction = new Instruction(Opcodes.MEMCOPY,
+                register, 2 /* maxSupportedImms */);
+        // if the offset == 0, it should still be encoded with 1 byte size.
+        if (srcOffset == 0) {
+            instruction.addUnsignedImm(srcOffset, (byte) 1 /* size */);
+        } else {
+            instruction.addUnsignedImm(srcOffset);
+        }
+        instruction.addUnsignedImm(length, (byte) 1 /* size */);
+        addInstruction(instruction);
+        return this;
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF data region to output
+     * buffer.
+     *
+     * @param register the register that stored the base offset value.
+     * @param relativeOffset the offset inside the APF data region for where to start copy
+     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfGenerator object
+     * @throws IllegalInstructionException throws when imm size is incorrectly set.
+     */
+    public ApfGenerator addDataCopy(Register register, int relativeOffset, int length)
+            throws IllegalInstructionException {
+        return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EDATACOPY.value);
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output buffer.
+     *
+     * @param register the register that stored the base offset value.
+     * @param relativeOffset the offset inside the input packet for where to start copy
+     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfGenerator object
+     * @throws IllegalInstructionException throws when imm size is incorrectly set.
+     */
+    public ApfGenerator addPacketCopy(Register register, int relativeOffset, int length)
+            throws IllegalInstructionException {
+        return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EPKTCOPY.value);
+    }
+
+    private ApfGenerator addMemcopy(Register register, int relativeOffset, int length, int opcode)
+            throws IllegalInstructionException {
+        requireApfVersion(5);
+        checkCopyLength(length);
+        checkCopyOffset(relativeOffset);
+        Instruction instruction = new Instruction(Opcodes.EXT, register, 3 /* maxSupportedImms */);
+        instruction.addUnsignedImm(opcode);
+        // if the offset == 0, it should still be encoded with 1 byte size.
+        if (relativeOffset == 0) {
+            instruction.addUnsignedImm(relativeOffset, (byte) 1 /* size */);
+        } else {
+            instruction.addUnsignedImm(relativeOffset);
+        }
+        instruction.addUnsignedImm(length, (byte) 1 /* size */);
+        addInstruction(instruction);
+        return this;
+    }
+
+    private void checkCopyLength(int length) {
+        if (length < 0 || length > 255) {
+            throw new IllegalArgumentException(
+                    "copy length must between 0 to 255, length: " + length);
+        }
+    }
+
+    private void checkCopyOffset(int offset) {
+        if (offset < 0) {
+            throw new IllegalArgumentException(
+                    "offset must be non less than zero, offset: " + offset);
+        }
+    }
+
+    /**
      * Add an instruction to the end of the program to load 32 bits from the data memory into
      * {@code register}. The source address is computed by adding the signed immediate
      * @{code offset} to the other register.
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index b09a2b1..4353746 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -53,7 +53,6 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV4_CONFLICT_PROBE_NUM;
 import static com.android.net.module.util.SocketUtils.closeSocketQuietly;
 import static com.android.networkstack.util.NetworkStackUtils.DHCP_INIT_REBOOT_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION;
@@ -302,9 +301,7 @@
         if (isCapportApiEnabled()) {
             params.write(DHCP_CAPTIVE_PORTAL);
         }
-        if (isIPv6OnlyPreferredModeEnabled()) {
-            params.write(DHCP_IPV6_ONLY_PREFERRED);
-        }
+        params.write(DHCP_IPV6_ONLY_PREFERRED);
         // Customized DHCP options to be put in PRL.
         for (DhcpOption option : mConfiguration.options) {
             if (option.value == null) params.write(option.type);
@@ -567,16 +564,6 @@
     }
 
     /**
-     * check whether or not to support IPv6-only preferred option.
-     *
-     * IPv6-only preferred option is enabled by default if there is no experiment flag set to
-     * disable this feature explicitly.
-     */
-    public boolean isIPv6OnlyPreferredModeEnabled() {
-        return mDependencies.isFeatureNotChickenedOut(mContext, DHCP_IPV6_ONLY_PREFERRED_VERSION);
-    }
-
-    /**
      * Check whether to adopt slow DHCPREQUEST retransmission approach in Renewing/Rebinding state
      * suggested in RFC2131 section 4.4.5.
      */
@@ -648,7 +635,6 @@
     private byte[] getOptionsToSkip() {
         final ByteArrayOutputStream optionsToSkip = new ByteArrayOutputStream(2);
         if (!isCapportApiEnabled()) optionsToSkip.write(DHCP_CAPTIVE_PORTAL);
-        if (!isIPv6OnlyPreferredModeEnabled()) optionsToSkip.write(DHCP_IPV6_ONLY_PREFERRED);
         return optionsToSkip.toByteArray();
     }
 
@@ -1292,7 +1278,6 @@
     }
 
     private boolean maybeTransitionToIpv6OnlyWaitState(@NonNull final DhcpPacket packet) {
-        if (!isIPv6OnlyPreferredModeEnabled()) return false;
         if (packet.getIpv6OnlyWaitTimeMillis() == DhcpPacket.V6ONLY_PREFERRED_ABSENCE) return false;
 
         mIpv6OnlyWaitTimeMs = packet.getIpv6OnlyWaitTimeMillis();
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java
index 7359c7c..a107b9c 100644
--- a/src/android/net/dhcp6/Dhcp6Client.java
+++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -59,6 +59,7 @@
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.PacketReader;
 import com.android.net.module.util.netlink.NetlinkUtils;
+import com.android.net.module.util.structs.IaPrefixOption;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -66,6 +67,7 @@
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Random;
 import java.util.function.IntSupplier;
 
@@ -282,17 +284,27 @@
             scheduleKick();
         }
 
-        private void handleReceivedPacket(Dhcp6Packet packet) {
+        private void handleReceivedPacket(@NonNull final Dhcp6Packet packet) {
             // Technically it is valid for the server to not include a prefix in an IA in certain
             // scenarios (specifically in a reply to Renew / Rebind, which means: do not extend the
-            // prefix). However, while only supporting a single prefix, this never works well, so if
-            // the server decides to do so, ignore it.
-            // TODO: revisit this when adding multi-prefix support.
-            final boolean validIpo = packet.mPrefixDelegation.ipo != null
-                    && packet.mPrefixDelegation.ipo.isValid();
-            if (packet.isValid(mTransId, mClientDuid) && validIpo) {
-                receivePacket(packet);
+            // prefix, e.g. the list of prefix is empty). However, if prefix(es) do exist and all
+            // prefixes are invalid, then we should just ignore this packet.
+            if (!packet.isValid(mTransId, mClientDuid)) return;
+            if (!packet.mPrefixDelegation.ipos.isEmpty()) {
+                boolean allInvalidPrefixes = true;
+                for (IaPrefixOption ipo : packet.mPrefixDelegation.ipos) {
+                    if (ipo != null && ipo.isValid()) {
+                        allInvalidPrefixes = false;
+                        break;
+                    }
+                }
+                if (allInvalidPrefixes) {
+                    Log.w(TAG, "All IA_Prefix options included in the "
+                            + packet.getClass().getSimpleName() + " are invalid, ignore it.");
+                    return;
+                }
             }
+            receivePacket(packet);
         }
 
         @Override
@@ -377,17 +389,18 @@
         // prevent packet storms due to low timeouts.
         int renewTimeout = mReply.t1;
         int rebindTimeout = mReply.t2;
-        final long expirationTimeout = mReply.ipo.valid;
+        final long preferredTimeout = mReply.getMinimalPreferredLifetime();
+        final long expirationTimeout = mReply.getMinimalValidLifetime();
 
         // rfc8415#section-14.2: if t1 and / or t2 are 0, the client chooses an appropriate value.
         // rfc8415#section-21.21: Recommended values for T1 and T2 are 0.5 and 0.8 times the
         // shortest preferred lifetime of the prefixes in the IA_PD that the server is willing to
         // extend, respectively.
         if (renewTimeout == 0) {
-            renewTimeout = (int) (mReply.ipo.preferred * 0.5);
+            renewTimeout = (int) (preferredTimeout * 0.5);
         }
         if (rebindTimeout == 0) {
-            rebindTimeout = (int) (mReply.ipo.preferred * 0.8);
+            rebindTimeout = (int) (preferredTimeout * 0.8);
         }
 
         // Note: message validation asserts that the received t1 <= t2 if both t1 > 0 and t2 > 0.
@@ -461,16 +474,6 @@
         return transmitPacket(packet, "rebind");
     }
 
-    private ByteBuffer buildEmptyIaPdOption() {
-        return Dhcp6Packet.buildIaPdOption(IAID, 0 /* t1 */, 0 /* t2 */, 0 /* preferred */,
-                0 /* valid */, new byte[16] /* empty prefix */, (byte) RFC7421_PREFIX_LENGTH);
-    }
-
-    private ByteBuffer buildIaPdOption(@NonNull final PrefixDelegation pd) {
-        return Dhcp6Packet.buildIaPdOption(pd.iaid, pd.t1, pd.t2, pd.ipo.preferred, pd.ipo.valid,
-                pd.ipo.prefix, pd.ipo.prefixLen);
-    }
-
     /**
      * Parent state at which client does initialization of interface and packet handler, also
      * processes the CMD_STOP_DHCP6 command in this state which child states don't handle.
@@ -545,7 +548,12 @@
 
         @Override
         protected boolean sendPacket(int transId, long elapsedTimeMs) {
-            return sendSolicitPacket(transId, elapsedTimeMs, buildEmptyIaPdOption());
+            final IaPrefixOption hintOption = new IaPrefixOption((short) IaPrefixOption.LENGTH,
+                    0 /* preferred */, 0 /* valid */, (byte) RFC7421_PREFIX_LENGTH,
+                    new byte[16] /* empty prefix */);
+            final PrefixDelegation pd = new PrefixDelegation(IAID, 0 /* t1 */, 0 /* t2 */,
+                    Collections.singletonList(hintOption));
+            return sendSolicitPacket(transId, elapsedTimeMs, pd.build());
         }
 
         // TODO: support multiple prefixes.
@@ -585,7 +593,7 @@
 
         @Override
         protected boolean sendPacket(int transId, long elapsedTimeMs) {
-            return sendRequestPacket(transId, elapsedTimeMs, buildIaPdOption(mAdvertise));
+            return sendRequestPacket(transId, elapsedTimeMs, mAdvertise.build());
         }
 
         @Override
@@ -630,6 +638,33 @@
         }
     }
 
+    // Create an IPv6 address from the interface mac address with IFA_F_MANAGETEMPADDR
+    // flag, kernel will create another privacy IPv6 address on behalf of user space.
+    // We don't need to remember IPv6 addresses that need to extend the lifetime every
+    // time it enters BoundState.
+    private boolean addInterfaceAddress(@NonNull final Inet6Address address,
+            @NonNull final IaPrefixOption ipo) {
+        final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
+        final long now = SystemClock.elapsedRealtime();
+        final long deprecationTime = now + ipo.preferred;
+        final long expirationTime = now + ipo.valid;
+        final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
+                RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
+        if (!la.isGlobalPreferred()) {
+            Log.e(TAG, la + " is not a global preferred IPv6 address");
+            return false;
+        }
+        if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address,
+                (short) RFC7421_PREFIX_LENGTH,
+                flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
+                ipo.preferred, ipo.valid)) {
+            Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress()
+                    + "%" + mIface.index);
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound
      * state until T1 expires, and then transition to Renew state to extend the lease duration.
@@ -642,34 +677,21 @@
 
             // TODO: roll back to SOLICIT state after a delay if something wrong happens
             // instead of returning directly.
-            // The server may assign a prefix with length less than 64. To support automatic address
-            // generation (with IFA_F_MANAGETEMPADDR), we always set the address prefix length to
-            // 64, even if the delegated prefix length is less than 64. However, the unreachable
-            // route should still use the assigned prefix length.
-            final IpPrefix routePrefix = mReply.ipo.getIpPrefix();
-            final IpPrefix addressPrefix = new IpPrefix(routePrefix.getAddress(),
-                    RFC7421_PREFIX_LENGTH);
-            // Create EUI-64, so we don't need to remember IPv6 addresses that need to extend the
-            // lifetime every time it enters BoundState.
-            final Inet6Address address = createInet6AddressFromEui64(addressPrefix,
-                    macAddressToEui64(mIface.macAddr));
-            final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
-            final long now = SystemClock.elapsedRealtime();
-            final long deprecationTime = now + mReply.ipo.preferred;
-            final long expirationTime = now + mReply.ipo.valid;
-            final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
-                    RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
-            if (!la.isGlobalPreferred()) {
-                Log.e(TAG, la + " is not a global IPv6 address, ignoring");
-                return;
-            }
-            if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address,
-                    (short) RFC7421_PREFIX_LENGTH,
-                    flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
-                    mReply.ipo.preferred, mReply.ipo.valid)) {
-                Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress()
-                        + "%" + mIface.index);
-                return;
+            for (IaPrefixOption ipo : mReply.getValidIaPrefixes()) {
+                // TODO: The prefix with preferred/valid lifetime of 0 is valid, but client
+                // should stop using the prefix immediately. Actually kernel doesn't accept
+                // the address with valid lifetime of 0 and returns EINVAL when it sees that.
+                // We should send RTM_DELADDR netlink message to kernel to delete these addresses
+                // from the interface if any.
+                // Configure IPv6 addresses based on the delegated prefix(es) on the interface.
+                // We've checked that delegated prefix is valid upon receiving the response from
+                // DHCPv6 server, and the server may assign a prefix with length less than 64. So
+                // for SLAAC use case we always set the prefix length to 64 even if the delegated
+                // prefix length is less than 64.
+                final IpPrefix prefix = ipo.getIpPrefix();
+                final Inet6Address address = createInet6AddressFromEui64(prefix,
+                        macAddressToEui64(mIface.macAddr));
+                if (!addInterfaceAddress(address, ipo)) continue;
             }
             notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply);
         }
@@ -701,11 +723,13 @@
         protected void receivePacket(Dhcp6Packet packet) {
             if (!(packet instanceof Dhcp6ReplyPacket)) return;
             final PrefixDelegation pd = packet.mPrefixDelegation;
-            if (!(Arrays.equals(pd.ipo.prefix, mReply.ipo.prefix)
-                    && pd.ipo.prefixLen == mReply.ipo.prefixLen)) {
-                Log.i(TAG, "Renewal prefix " + HexDump.toHexString(pd.ipo.prefix)
+            final IaPrefixOption request = mReply.ipos.get(0);
+            final IaPrefixOption response = pd.ipos.get(0);
+            if (!(Arrays.equals(request.prefix, response.prefix)
+                    && request.prefixLen == response.prefixLen)) {
+                Log.i(TAG, "Renewal prefix " + HexDump.toHexString(response.prefix)
                         + " does not match current prefix "
-                        + HexDump.toHexString(mReply.ipo.prefix));
+                        + HexDump.toHexString(request.prefix));
                 notifyPrefixDelegation(DHCP6_PD_PREFIX_CHANGED, null);
                 transitionTo(mSolicitState);
                 return;
@@ -748,7 +772,7 @@
 
         @Override
         protected boolean sendPacket(int transId, long elapsedTimeMs) {
-            return sendRenewPacket(transId, elapsedTimeMs, buildIaPdOption(mReply));
+            return sendRenewPacket(transId, elapsedTimeMs, mReply.build());
         }
     }
 
@@ -764,7 +788,7 @@
 
         @Override
         protected boolean sendPacket(int transId, long elapsedTimeMs) {
-            return sendRebindPacket(transId, elapsedTimeMs, buildIaPdOption(mReply));
+            return sendRebindPacket(transId, elapsedTimeMs, mReply.build());
         }
     }
 
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index 540a670..7a977e5 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -22,8 +22,8 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.structs.IaPdOption;
@@ -33,7 +33,11 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 import java.util.OptionalInt;
 
 /**
@@ -120,6 +124,11 @@
     protected PrefixDelegation mPrefixDelegation;
 
     /**
+     * DHCPv6 Optional Type: IA Prefix Option.
+     */
+    public static final byte DHCP6_IAPREFIX = 26;
+
+    /**
      * DHCPv6 Optional Type: SOL_MAX_RT.
      */
     public static final byte DHCP6_SOL_MAX_RT = 82;
@@ -155,6 +164,14 @@
     }
 
     /**
+     * Returns decoded IA_PD options associated with IA_ID.
+     */
+    @VisibleForTesting
+    public PrefixDelegation getPrefixDelegation() {
+        return mPrefixDelegation;
+    }
+
+    /**
      * Returns IA_ID associated to IA_PD.
      */
     public int getIaId() {
@@ -188,20 +205,23 @@
      * https://www.rfc-editor.org/rfc/rfc8415.html#section-21.21
      */
     public static class PrefixDelegation {
-        public int iaid;
-        public int t1;
-        public int t2;
-        public final IaPrefixOption ipo;
+        public final int iaid;
+        public final int t1;
+        public final int t2;
+        @NonNull
+        public final List<IaPrefixOption> ipos;
 
-        PrefixDelegation(int iaid, int t1, int t2, final IaPrefixOption ipo) {
+        public PrefixDelegation(int iaid, int t1, int t2,
+                @NonNull final List<IaPrefixOption> ipos) {
+            Objects.requireNonNull(ipos);
             this.iaid = iaid;
             this.t1 = t1;
             this.t2 = t2;
-            this.ipo = ipo;
+            this.ipos = ipos;
         }
 
         /**
-         * Check whether or not the delegated prefix in DHCPv6 packet is valid.
+         * Check whether or not the IA_PD option in DHCPv6 message is valid.
          *
          * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short.
          */
@@ -214,7 +234,6 @@
                 Log.e(TAG, "IA_PD option with invalid T1 " + t1 + " or T2 " + t2);
                 return false;
             }
-
             // Generally, t1 must be smaller or equal to t2 (except when t2 is 0).
             if (t2 != 0 && t1 > t2) {
                 Log.e(TAG, "IA_PD option with T1 " + t1 + " greater than T2 " + t2);
@@ -223,10 +242,95 @@
             return true;
         }
 
+        /**
+         * Decode an IA_PD option from the byte buffer.
+         */
+        public static PrefixDelegation decode(@NonNull final ByteBuffer buffer)
+                throws ParseException {
+            try {
+                final int iaid = buffer.getInt();
+                final int t1 = buffer.getInt();
+                final int t2 = buffer.getInt();
+                final List<IaPrefixOption> ipos = new ArrayList<IaPrefixOption>();
+                while (buffer.remaining() > 0) {
+                    final int original = buffer.position();
+                    final short optionType = buffer.getShort();
+                    final int optionLen = buffer.getShort() & 0xFFFF;
+                    switch (optionType) {
+                        case DHCP6_IAPREFIX:
+                            buffer.position(original);
+                            final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer);
+                            Log.d(TAG, "IA Prefix Option: " + ipo);
+                            ipos.add(ipo);
+                            break;
+                        // TODO: support DHCP6_STATUS_CODE option
+                        default:
+                            skipOption(buffer, optionLen);
+                    }
+                }
+                return new PrefixDelegation(iaid, t1, t2, ipos);
+            } catch (BufferUnderflowException e) {
+                throw new ParseException(e.getMessage());
+            }
+        }
+
+        /**
+         * Build an IA_PD option from given specific parameters, including IA_PREFIX options.
+         */
+        public ByteBuffer build() {
+            final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
+                    + Struct.getSize(IaPrefixOption.class) * ipos.size());
+            iapd.putInt(iaid);
+            iapd.putInt(t1);
+            iapd.putInt(t2);
+            for (IaPrefixOption ipo : ipos) {
+                ipo.writeToByteBuffer(iapd);
+            }
+            iapd.flip();
+            return iapd;
+        }
+
+        /**
+         * Return valid IA prefix options to be used and extended in the Reply message. It may
+         * return empty list if there isn't any valid IA prefix option in the Reply message.
+         *
+         * TODO: ensure that the prefix has a reasonable lifetime, and the timers aren't too short.
+         * and handle status code such as NoPrefixAvail.
+         */
+        public List<IaPrefixOption> getValidIaPrefixes() {
+            final List<IaPrefixOption> validIpos = new ArrayList<IaPrefixOption>();
+            for (IaPrefixOption ipo : ipos) {
+                if (!ipo.isValid()) continue;
+                validIpos.add(ipo);
+            }
+            return validIpos;
+        }
+
         @Override
         public String toString() {
             return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2
-                    + ", prefix " + ipo;
+                    + ", IA prefix options: " + ipos;
+        }
+
+        /**
+         * Compare the preferred lifetime in the IA prefix optin list and return the minimum one.
+         * TODO: exclude 0 preferred lifetime.
+         */
+        public long getMinimalPreferredLifetime() {
+            final IaPrefixOption ipo = Collections.min(ipos,
+                    (IaPrefixOption lhs, IaPrefixOption rhs) -> Long.compare(lhs.preferred,
+                            rhs.preferred));
+            return ipo.preferred;
+        }
+
+        /**
+         * Compare the valid lifetime in the IA prefix optin list and return the minimum one.
+         * TODO: exclude 0 valid lifetime.
+         */
+        public long getMinimalValidLifetime() {
+            final IaPrefixOption ipo = Collections.min(ipos,
+                    (IaPrefixOption lhs, IaPrefixOption rhs) -> Long.compare(lhs.valid, rhs.valid));
+            return ipo.valid;
         }
     }
 
@@ -293,8 +397,7 @@
      * |                                                               |
      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      */
-    @VisibleForTesting
-    static Dhcp6Packet decode(@NonNull final ByteBuffer packet) throws ParseException {
+    private static Dhcp6Packet decode(@NonNull final ByteBuffer packet) throws ParseException {
         int elapsedTime = 0;
         byte[] iapd = null;
         byte[] serverDuid = null;
@@ -303,6 +406,7 @@
         String statusMsg = null;
         boolean rapidCommit = false;
         int solMaxRt = 0;
+        PrefixDelegation pd = null;
 
         packet.order(ByteOrder.BIG_ENDIAN);
 
@@ -347,6 +451,7 @@
                         final byte[] bytes = new byte[expectedLen];
                         packet.get(bytes, 0 /* offset */, expectedLen);
                         iapd = bytes;
+                        pd = PrefixDelegation.decode(ByteBuffer.wrap(iapd));
                         break;
                     case DHCP6_RAPID_COMMIT:
                         expectedLen = 0;
@@ -410,14 +515,9 @@
                 throw new ParseException("Unimplemented DHCP6 message type %d" + messageType);
         }
 
-        if (iapd != null) {
-            final ByteBuffer buffer = ByteBuffer.wrap(iapd);
-            final int iaid = buffer.getInt();
-            final int t1 = buffer.getInt();
-            final int t2 = buffer.getInt();
-            final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer);
-            newPacket.mPrefixDelegation = new PrefixDelegation(iaid, t1, t2, ipo);
-            newPacket.mIaId = iaid;
+        if (pd != null) {
+            newPacket.mPrefixDelegation = pd;
+            newPacket.mIaId = pd.iaid;
         }
         newPacket.mStatusCode = statusCode;
         newPacket.mStatusMsg = statusMsg;
@@ -528,23 +628,6 @@
     }
 
     /**
-     * Build an IA_PD option from given specific parameters, including IA_PREFIX option.
-     */
-    public static ByteBuffer buildIaPdOption(int iaid, int t1, int t2, long preferred, long valid,
-            final byte[] prefix, byte prefixLen) {
-        final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
-                + Struct.getSize(IaPrefixOption.class));
-        iapd.putInt(iaid);
-        iapd.putInt(t1);
-        iapd.putInt(t2);
-        final ByteBuffer prefixOption = IaPrefixOption.build((short) IaPrefixOption.LENGTH,
-                preferred, valid, prefixLen, prefix);
-        iapd.put(prefixOption);
-        iapd.flip();
-        return iapd;
-    }
-
-    /**
      * Builds a DHCPv6 SOLICIT packet from the required specified parameters.
      */
     public static ByteBuffer buildSolicitPacket(int transId, long millisecs,
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 11cdd20..9747c2c 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -40,10 +40,12 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
 import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE;
+import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_FORCE_DISABLE;
+import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION;
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
@@ -122,6 +124,7 @@
 import com.android.net.module.util.arp.ArpPacket;
 import com.android.net.module.util.ip.InterfaceController;
 import com.android.net.module.util.netlink.NetlinkUtils;
+import com.android.net.module.util.structs.IaPrefixOption;
 import com.android.networkstack.R;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.networkstack.apishim.SocketUtilsShimImpl;
@@ -668,6 +671,9 @@
 
     // Experiment flag read from device config.
     private final boolean mDhcp6PrefixDelegationEnabled;
+    private final boolean mUseNewApfFilter;
+    private final boolean mEnableIpClientIgnoreLowRaLifetime;
+    private final boolean mApfShouldHandleLightDoze;
 
     private InterfaceParams mInterfaceParams;
 
@@ -822,8 +828,8 @@
          */
         public AndroidPacketFilter maybeCreateApfFilter(Context context,
                 ApfFilter.ApfConfiguration config, InterfaceParams ifParams,
-                IpClientCallbacksWrapper cb) {
-            if (isFeatureEnabled(context, NetworkStackUtils.APF_NEW_RA_FILTER_VERSION)) {
+                IpClientCallbacksWrapper cb, boolean useNewApfFilter) {
+            if (useNewApfFilter) {
                 return ApfFilter.maybeCreate(context, config, ifParams, cb);
             } else {
                 return LegacyApfFilter.maybeCreate(context, config, ifParams, cb);
@@ -884,6 +890,12 @@
                 CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME);
         mAcceptRaMinLft = mDependencies.getDeviceConfigPropertyInt(CONFIG_ACCEPT_RA_MIN_LFT,
                 DEFAULT_ACCEPT_RA_MIN_LFT);
+        mUseNewApfFilter = mDependencies.isFeatureEnabled(context, APF_NEW_RA_FILTER_VERSION);
+        mEnableIpClientIgnoreLowRaLifetime = mDependencies.isFeatureEnabled(context,
+                IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION);
+        // Light doze mode status checking API is only available at T or later releases.
+        mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut(
+                mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE);
 
         IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
                 mMinRdnssLifetimeSec);
@@ -1293,7 +1305,8 @@
         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println(mTag + " APF dump:");
         pw.increaseIndent();
-        if (apfFilter != null) {
+        if (apfFilter != null && apfCapabilities != null
+                && apfCapabilities.apfVersionSupported > 0) {
             if (apfCapabilities.hasDataAccess()) {
                 // Request a new snapshot, then wait for it.
                 mApfDataSnapshotComplete.close();
@@ -1721,24 +1734,24 @@
 
         // [4] Add in data from DHCPv6 Prefix Delegation, if available.
         if (mPrefixDelegation != null) {
-            try {
-                final IpPrefix destination =
-                        new IpPrefix(Inet6Address.getByAddress(mPrefixDelegation.ipo.prefix),
-                                mPrefixDelegation.ipo.prefixLen);
-                // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to this route
-                // based on the delegated prefix. To prevent the traffic loop between host and
-                // upstream delegated router. Because we specify the IFA_F_NOPREFIXROUTE when adding
-                // the IPv6 address, the kernel does not create a delegated prefix route, as a
-                // result, the user space won't receive any RTM_NEWROUTE message about the delegated
-                // prefix, we still need to install an unreachable route for the delegated prefix
-                // manually in LinkProperties to notify the caller this update.
-                // TODO: support RTN_BLACKHOLE in netd and use that on newer Android versions.
-                final RouteInfo route = new RouteInfo(destination, null /* gateway */,
-                        mInterfaceName, RTN_UNREACHABLE);
-                newLp.addRoute(route);
-            } catch (UnknownHostException e) {
-                Log.wtf(mTag, "Invalid delegated prefix "
-                        + HexDump.toHexString(mPrefixDelegation.ipo.prefix));
+            for (IaPrefixOption ipo : mPrefixDelegation.ipos) {
+                try {
+                    final IpPrefix destination =
+                            new IpPrefix(Inet6Address.getByAddress(ipo.prefix), ipo.prefixLen);
+                    // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to this route
+                    // based on the delegated prefix. To prevent the traffic loop between host and
+                    // upstream delegated router. Because we specify the IFA_F_NOPREFIXROUTE when
+                    // adding the IPv6 address, the kernel does not create a delegated prefix route,
+                    // as a result, the user space won't receive any RTM_NEWROUTE message about the
+                    // delegated prefix, we still need to install an unreachable route for the
+                    // delegated prefix manually in LinkProperties to notify the caller this update.
+                    // TODO: support RTN_BLACKHOLE in netd and use that on newer Android versions.
+                    final RouteInfo route = new RouteInfo(destination, null /* gateway */,
+                            mInterfaceName, RTN_UNREACHABLE);
+                    newLp.addRoute(route);
+                } catch (UnknownHostException e) {
+                    Log.wtf(mTag, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix));
+                }
             }
         }
 
@@ -2160,10 +2173,10 @@
                 setIpv6Sysctl(DAD_TRANSMITS, 0 /* dad_transmits */);
             }
         }
-        // Check chickened out flag first before reading IPv6 sysctl, which can prevent from
+        // Check the feature flag first before reading IPv6 sysctl, which can prevent from
         // triggering a potential kernel bug about the sysctl.
-        if (mDependencies.isFeatureNotChickenedOut(mContext,
-                IPCLIENT_IGNORE_LOW_RA_LIFETIME_FORCE_DISABLE)
+        // TODO: add unit test to check if the setIpv6Sysctl() is called or not.
+        if (mEnableIpClientIgnoreLowRaLifetime && mUseNewApfFilter
                 && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
             setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft);
         }
@@ -2244,8 +2257,7 @@
         setIpv6Sysctl(ACCEPT_RA, 2);
         setIpv6Sysctl(ACCEPT_RA_DEFRTR, 1);
         maybeRestoreDadTransmits();
-        if (mDependencies.isFeatureNotChickenedOut(mContext,
-                IPCLIENT_IGNORE_LOW_RA_LIFETIME_FORCE_DISABLE)
+        if (mUseNewApfFilter && mEnableIpClientIgnoreLowRaLifetime
                 && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
             setIpv6Sysctl(ACCEPT_RA_MIN_LFT, 0 /* sysctl default */);
         }
@@ -2353,8 +2365,9 @@
 
         apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec;
         apfConfig.acceptRaMinLft = mAcceptRaMinLft;
+        apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze;
         return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams,
-                mCallback);
+                mCallback, mUseNewApfFilter);
     }
 
     private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) {
@@ -2855,13 +2868,12 @@
                 Log.wtf(mTag, "PrefixDelegation shouldn't be null when DHCPv6 PD fails.");
                 return;
             }
+            final IaPrefixOption ipo = mPrefixDelegation.ipos.get(0);
             final IpPrefix prefix;
             try {
-                prefix = new IpPrefix(Inet6Address.getByAddress(mPrefixDelegation.ipo.prefix),
-                        RFC7421_PREFIX_LENGTH);
+                prefix = new IpPrefix(Inet6Address.getByAddress(ipo.prefix), RFC7421_PREFIX_LENGTH);
             } catch (UnknownHostException e) {
-                Log.wtf(TAG, "Invalid delegated prefix "
-                        + HexDump.toHexString(mPrefixDelegation.ipo.prefix));
+                Log.wtf(TAG, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix));
                 return;
             }
 
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
index c09f082..bd5e62b 100644
--- a/src/com/android/networkstack/metrics/stats.proto
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -188,3 +188,73 @@
     // NUD neighbor type, default gateway, DNS server or both.
     optional .android.stats.connectivity.NudNeighborType neighbor_type = 3;
 }
+
+/**
+ * Logs Ip client RA(Router Advertisement) info
+ * Logged from:
+ * packages/modules/NetworkStack/src/android/net/ip/IpClient.java
+ */
+message IpClientRaInfoReported {
+    // The maximum number of distinct RAs (Router Advertisements).
+    optional int32 max_number_of_distinct_ras = 1;
+
+    // The number of zero lifetime RAs (Router Advertisements).
+    optional int32 number_of_zero_lifetime_ras = 2;
+
+    // The number of parsing error for RAs (Router Advertisements).
+    optional int32 number_of_parsing_error_ras = 3;
+
+    // The lowest router lifetime in seconds.
+    optional int32 lowest_router_lifetime_seconds = 4;
+
+    // The lowest valid lifetime of PIO (Prefix Information Option) in seconds.
+    optional int32 lowest_pio_valid_lifetime_seconds = 5;
+
+    // The lowest route lifetime of RIO (Route Information Option) in seconds.
+    optional int32 lowest_rio_route_lifetime_seconds = 6;
+
+    // The lowest lifetime of RDNSS (Recursive DNS Server Option) in seconds.
+    optional int32 lowest_rdnss_lifetime_seconds = 7;
+}
+
+/**
+ * Logs value of the APF counter.
+ */
+message ApfCounter {
+    // The name of APF counter.
+    optional .android.stats.connectivity.CounterName counter_name = 1;
+
+    // The value of APF counter.
+    optional int32 counter_value = 2;
+}
+
+
+message ApfCounterList {
+    repeated ApfCounter apf_counter = 1;
+}
+
+/**
+ * Logs APF session information event.
+ * Logged from:
+ * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java or
+ * packages/modules/NetworkStack/src/android/net/apf/LegacyApfFilter.java
+ */
+message ApfSessionInfoReported {
+    // The version of APF, where version = -1 equals APF disable.
+    optional int32 version = 1;
+
+    // The memory size of APF module.
+    optional int32 memory_size = 2;
+
+    // The values of all APF counters.
+    optional ApfCounterList apf_counter_list = 3;
+
+    // The duration of ip client in milliseconds.
+    optional int32 ip_client_session_duration_ms = 4;
+
+    // Number of times APF program updated.
+    optional int32 num_of_times_apf_program_updated = 5;
+
+    // Record the maximum of program size.
+    optional int32 max_program_size = 6;
+}
diff --git a/src/com/android/networkstack/netlink/TcpInfo.java b/src/com/android/networkstack/netlink/TcpInfo.java
index 73d206e..de450e9 100644
--- a/src/com/android/networkstack/netlink/TcpInfo.java
+++ b/src/com/android/networkstack/netlink/TcpInfo.java
@@ -170,7 +170,8 @@
         }
     }
 
-    private static String decodeWscale(byte num) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static String decodeWscale(byte num) {
         return String.valueOf((num >> 4) & 0x0f)  + ":" + String.valueOf(num & 0x0f);
     }
 
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java
index d28f4b5..658fe8a 100644
--- a/src/com/android/networkstack/netlink/TcpSocketTracker.java
+++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -27,12 +27,14 @@
 import static android.system.OsConstants.SOL_SOCKET;
 import static android.system.OsConstants.SO_SNDTIMEO;
 
+import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED;
 import static com.android.net.module.util.NetworkStackConstants.DNS_OVER_TLS_PORT;
 import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
 import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
 import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
 import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+import static com.android.networkstack.util.NetworkStackUtils.IGNORE_TCP_INFO_FOR_BLOCKED_UIDS;
 import static com.android.networkstack.util.NetworkStackUtils.SKIP_TCP_POLL_IN_LIGHT_DOZE;
 
 import android.annotation.TargetApi;
@@ -40,10 +42,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.LinkProperties;
 import android.net.MarkMaskParcel;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.IBinder;
@@ -68,9 +72,9 @@
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.SocketUtils;
 import com.android.net.module.util.netlink.InetDiagMessage;
-import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructInetDiagMsg;
+import com.android.net.module.util.netlink.StructNlAttr;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.networkstack.apishim.NetworkShimImpl;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
@@ -129,6 +133,8 @@
     private int mMinPacketsThreshold = DEFAULT_DATA_STALL_MIN_PACKETS_THRESHOLD;
     private int mTcpPacketsFailRateThreshold = DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE;
 
+    // TODO: Remove doze mode solution since uid networking blocked traffic is filtered out by
+    //  the info provided by bpf maps.
     private final Object mDozeModeLock = new Object();
     @GuardedBy("mDozeModeLock")
     private boolean mInDozeMode = false;
@@ -139,8 +145,13 @@
     private boolean mInOpportunisticMode;
     @NonNull
     private LinkProperties mLinkProperties;
+    @NonNull
+    private NetworkCapabilities mNetworkCapabilities;
 
+    private final boolean mShouldDisableInDeepDoze;
     private final boolean mShouldDisableInLightDoze;
+    private final boolean mShouldIgnoreTcpInfoForBlockedUids;
+    private final ConnectivityManager mCm;
 
     @VisibleForTesting
     protected final DeviceConfig.OnPropertiesChangedListener mConfigListener =
@@ -158,8 +169,9 @@
                 }
             };
 
-    private static boolean isDeviceIdleModeChangedAction(Intent intent) {
-        return ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction());
+    private boolean isDeviceIdleModeChangedAction(Intent intent) {
+        return mShouldDisableInDeepDoze
+                && ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction());
     }
 
     @TargetApi(Build.VERSION_CODES.TIRAMISU)
@@ -180,7 +192,8 @@
                 // For tcp polling mechanism, there is no difference between deep doze mode and
                 // light doze mode. The deep doze mode and light doze mode block networking
                 // for uids in the same way, use single variable to control.
-                final boolean deviceIdle = powerManager.isDeviceIdleMode()
+                final boolean deviceIdle = (mShouldDisableInDeepDoze
+                        && powerManager.isDeviceIdleMode())
                         || (mShouldDisableInLightDoze && powerManager.isDeviceLightIdleMode());
                 setDozeMode(deviceIdle);
             }
@@ -191,7 +204,15 @@
         mDependencies = dps;
         mNetwork = network;
         mNetd = mDependencies.getNetd();
-        mShouldDisableInLightDoze = mDependencies.shouldDisableInLightDoze();
+        mShouldIgnoreTcpInfoForBlockedUids = mDependencies.shouldIgnoreTcpInfoForBlockedUids();
+
+        // Previous workarounds can be disabled if the device supports ignore blocked uids feature.
+        // To prevent inconsistencies and issues like broadcast receiver leaks, the feature flags
+        // are fixed after being read.
+        // TODO: Remove these workarounds when pre-T devices are no longer supported.
+        mShouldDisableInLightDoze = mDependencies.shouldDisableInLightDoze(
+                mShouldIgnoreTcpInfoForBlockedUids);
+        mShouldDisableInDeepDoze = !mShouldIgnoreTcpInfoForBlockedUids;
 
         // If the parcel is null, nothing should be matched which is achieved by the combination of
         // {@code NetlinkUtils#NULL_MASK} and {@code NetlinkUtils#UNKNOWN_MARK}.
@@ -205,7 +226,9 @@
                     family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family));
         }
         mDependencies.addDeviceConfigChangedListener(mConfigListener);
-        mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldDisableInLightDoze);
+        mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldDisableInDeepDoze,
+                mShouldDisableInLightDoze);
+        mCm = mDependencies.getContext().getSystemService(ConnectivityManager.class);
     }
 
     @Nullable
@@ -214,9 +237,9 @@
             final int netId = NetworkShimImpl.newInstance(mNetwork).getNetId();
             return mNetd.getFwmarkForNetwork(netId);
         } catch (UnsupportedApiLevelException e) {
-            log("Get netId is not available in this API level.");
+            logd("Get netId is not available in this API level.");
         } catch (RemoteException e) {
-            Log.e(TAG, "Error getting fwmark for network, ", e);
+            loge("Error getting fwmark for network, ", e);
         }
         return null;
     }
@@ -246,12 +269,13 @@
                 mDependencies.sendPollingRequest(fd, mSockDiagMsg.get(family));
                 while (parseMessage(mDependencies.recvMessage(fd),
                         family, newSocketInfoList, time)) {
-                    log("Pending info exist. Attempt to read more");
+                    logd("Pending info exist. Attempt to read more");
                 }
             }
 
             // Append TcpStats based on previous and current socket info.
             final TcpStat stat = new TcpStat();
+            final ArrayList<Integer> skippedBlockedUids = new ArrayList<>();
             mLatestReportedUids.clear();
             for (final SocketInfo newInfo : newSocketInfoList) {
                 final TcpStat diff = calculateLatestPacketsStat(newInfo,
@@ -271,11 +295,28 @@
                     continue;
                 }
 
+                if (mShouldIgnoreTcpInfoForBlockedUids) {
+                    // For backward-compatibility, NET_CAPABILITY_TEMPORARILY_NOT_METERED
+                    // is not referenced when deciding meteredness in NetworkPolicyManagerService.
+                    // Thus, whether to block metered networking should only be judged with
+                    // NET_CAPABILITY_NOT_METERED.
+                    final boolean metered = !mNetworkCapabilities.hasCapability(
+                            NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+                    final boolean uidBlocked = mCm.isUidNetworkingBlocked(newInfo.uid, metered);
+                    if (uidBlocked) {
+                        skippedBlockedUids.add(newInfo.uid);
+                        continue;
+                    }
+                }
+
                 if (diff != null) {
                     mLatestReportedUids.add(newInfo.uid);
                     stat.accumulate(diff);
                 }
             }
+            if (!skippedBlockedUids.isEmpty()) {
+                logd("Skip blocked uids: " + skippedBlockedUids);
+            }
 
             // Calculate mLatestReceiveCount, mSentSinceLastRecv and mLatestPacketFailPercentage.
             mSentSinceLastRecv = (stat.receivedCount == 0)
@@ -288,7 +329,7 @@
             cleanupSocketInfo(time);
             return true;
         } catch (ErrnoException | SocketException | InterruptedIOException e) {
-            Log.e(TAG, "Fail to get TCP info via netlink.", e);
+            loge("Fail to get TCP info via netlink.", e);
         } finally {
             SocketUtils.closeSocketQuietly(fd);
         }
@@ -302,11 +343,11 @@
 
     // Return true if there are more pending messages to read
     @VisibleForTesting
-    static boolean parseMessage(ByteBuffer bytes, int family,
+    boolean parseMessage(ByteBuffer bytes, int family,
             ArrayList<SocketInfo> outputSocketInfoList, long time) {
         if (!NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) {
             // This is unlikely to happen in real cases. Check this first for testing.
-            Log.e(TAG, "Size is less than header size. Ignored.");
+            loge("Size is less than header size. Ignored.");
             return false;
         }
 
@@ -340,12 +381,12 @@
                 outputSocketInfoList.add(info);
             } while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes));
         } catch (IllegalArgumentException | BufferUnderflowException e) {
-            Log.wtf(TAG, "Unexpected socket info parsing, family " + family
+            logwtf("Unexpected socket info parsing, family " + family
                     + " buffer:" + bytes + " "
                     + Base64.getEncoder().encodeToString(bytes.array()), e);
             return false;
         } catch (IllegalStateException e) {
-            Log.e(TAG, "Unexpected socket info parsing, family " + family
+            loge("Unexpected socket info parsing, family " + family
                     + " buffer:" + bytes + " "
                     + Base64.getEncoder().encodeToString(bytes.array()), e);
             return false;
@@ -354,21 +395,21 @@
         return true;
     }
 
-    private static int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) {
+    private int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) {
         final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes);
         if (nlmsghdr == null) {
-            Log.e(TAG, "Badly formatted data.");
+            loge("Badly formatted data.");
             return END_OF_PARSING;
         }
 
-        log("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit());
+        logd("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit());
         // End of the message. Stop parsing.
         if (nlmsghdr.nlmsg_type == NLMSG_DONE) {
             return END_OF_PARSING;
         }
 
         if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) {
-            Log.e(TAG, "Expect to get family " + family
+            loge("Expect to get family " + family
                     + " SOCK_DIAG_BY_FAMILY message but get "
                     + nlmsghdr.nlmsg_type);
             return END_OF_PARSING;
@@ -393,7 +434,7 @@
 
     /** Parse a {@code SocketInfo} from the given position of the given byte buffer. */
     @NonNull
-    private static SocketInfo parseSockInfo(@NonNull final ByteBuffer bytes, final int family,
+    private SocketInfo parseSockInfo(@NonNull final ByteBuffer bytes, final int family,
             final int nlmsgLen, final long time, final int uid, final long cookie,
             final int dstPort) {
         final int remainingDataSize = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE;
@@ -401,22 +442,17 @@
         int mark = NetlinkUtils.INIT_MARK_VALUE;
         // Get a tcp_info.
         while (bytes.position() < remainingDataSize) {
-            final RoutingAttribute rtattr =
-                    new RoutingAttribute(bytes.getShort(), bytes.getShort());
-            final short dataLen = rtattr.getDataLength();
-            if (rtattr.rtaType == NetlinkUtils.INET_DIAG_INFO) {
-                tcpInfo = TcpInfo.parse(bytes, dataLen);
-            } else if (rtattr.rtaType == NetlinkUtils.INET_DIAG_MARK) {
-                mark = bytes.getInt();
-            } else {
-                // Data provided by kernel will include both valid data and padding data. The data
-                // len provided from kernel indicates the valid data size. Readers must deduce the
-                // alignment by themselves.
-                skipRemainingAttributesBytesAligned(bytes, dataLen);
+            final StructNlAttr nlattr = StructNlAttr.parse(bytes);
+            if (nlattr == null) break;
+
+            if (nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) {
+                mark = nlattr.getValueAsInteger();
+            } else if (nlattr.nla_type == NetlinkUtils.INET_DIAG_INFO) {
+                tcpInfo = TcpInfo.parse(nlattr.getValueAsByteBuffer(), nlattr.getAlignedLength());
             }
         }
         final SocketInfo info = new SocketInfo(tcpInfo, family, mark, time, uid, cookie, dstPort);
-        log("parseSockInfo, " + info);
+        logd("parseSockInfo, " + info);
         return info;
     }
 
@@ -434,7 +470,7 @@
         }
         final boolean ret = (getLatestPacketFailPercentage() >= getTcpPacketsFailRateThreshold());
         if (ret) {
-            Log.d(TAG, "data stall suspected, uids: " + mLatestReportedUids.toString());
+            log("data stall suspected, uids: " + mLatestReportedUids.toString());
         }
         return ret;
     }
@@ -450,7 +486,7 @@
         }
 
         if (current.tcpInfo == null) {
-            log("Current tcpInfo is null.");
+            logd("Current tcpInfo is null.");
             return null;
         }
 
@@ -463,7 +499,7 @@
             stat.receivedCount -= previous.tcpInfo.mSegsIn;
             stat.retransCount -= previous.tcpInfo.mTotalRetrans;
         }
-        log("calculateLatestPacketsStat, stat:" + stat);
+        logd("calculateLatestPacketsStat, stat:" + stat);
         return stat;
     }
 
@@ -503,63 +539,31 @@
         return mTcpPacketsFailRateThreshold;
     }
 
-    /**
-     * Method to skip the remaining attributes bytes.
-     * Corresponds to NLMSG_NEXT in bionic/libc/kernel/uapi/linux/netlink.h.
-     *
-     * @param buffer the target ByteBuffer
-     * @param len the remaining length to skip.
-     */
-    private static void skipRemainingAttributesBytesAligned(@NonNull final ByteBuffer buffer,
-            final short len) {
-        // Data in {@Code RoutingAttribute} is followed after header with size {@Code NLA_ALIGNTO}
-        // bytes long for each block. Next attribute will start after the padding bytes if any.
-        // If all remaining bytes after header are valid in a data block, next attr will just start
-        // after valid bytes.
-        //
-        // E.g. With NLA_ALIGNTO(4), an attr struct with length 5 means 1 byte valid data remains
-        // after header and 3(4-1) padding bytes. Next attr with length 8 will start after the
-        // padding bytes and contain 4(8-4) valid bytes of data. The next attr start after the
-        // valid bytes, like:
-        //
-        // [HEADER(L=5)][   4-Bytes DATA      ][ HEADER(L=8) ][4 bytes DATA][Next attr]
-        // [ 5 valid bytes ][3 padding bytes  ][      8 valid bytes        ]   ...
-        final int cur = buffer.position();
-        buffer.position(cur + NetlinkConstants.alignedLengthOf(len));
+    private void logd(final String str) {
+        if (DBG) log(str);
     }
 
-    private static void log(final String str) {
-        if (DBG) Log.d(TAG, str);
+    private void log(final String s) {
+        Log.d(TAG + "/" + mNetwork.toString(), s);
+    }
+
+    private void loge(final String str) {
+        loge(str, null /* tr */);
+    }
+
+    private void loge(final String str, @Nullable Throwable tr) {
+        Log.e(TAG + "/" + mNetwork.toString(), str, tr);
+    }
+
+    private void logwtf(final String str, @Nullable Throwable tr) {
+        Log.wtf(TAG + "/" + mNetwork.toString(), str, tr);
     }
 
     /** Stops monitoring and releases resources. */
     public void quit() {
         mDependencies.removeDeviceConfigChangedListener(mConfigListener);
-        mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver);
-    }
-
-    /**
-     * Corresponds to {@code struct rtattr} from bionic/libc/kernel/uapi/linux/rtnetlink.h
-     *
-     * struct rtattr {
-     *    unsigned short rta_len;    // Length of option
-     *    unsigned short rta_type;   // Type of option
-     *    // Data follows
-     * };
-     */
-    static class RoutingAttribute {
-        public static final int HEADER_LENGTH = 4;
-
-        public final short rtaLen;  // The whole valid size of the struct.
-        public final short rtaType;
-
-        RoutingAttribute(final short len, final short type) {
-            rtaLen = len;
-            rtaType = type;
-        }
-        public short getDataLength() {
-            return (short) (rtaLen - HEADER_LENGTH);
-        }
+        mDependencies.removeBroadcastReceiver(mDeviceIdleReceiver,
+                mShouldDisableInDeepDoze, mShouldDisableInLightDoze);
     }
 
     /**
@@ -639,7 +643,7 @@
         synchronized (mDozeModeLock) {
             if (mInDozeMode == isEnabled) return;
             mInDozeMode = isEnabled;
-            log("Doze mode enabled=" + mInDozeMode);
+            logd("Doze mode enabled=" + mInDozeMode);
         }
     }
 
@@ -647,13 +651,17 @@
         if (mInOpportunisticMode == isEnabled) return;
         mInOpportunisticMode = isEnabled;
 
-        log("Private DNS Opportunistic mode enabled=" + mInOpportunisticMode);
+        logd("Private DNS Opportunistic mode enabled=" + mInOpportunisticMode);
     }
 
     public void setLinkProperties(@NonNull LinkProperties lp) {
         mLinkProperties = lp;
     }
 
+    public void setNetworkCapabilities(@NonNull NetworkCapabilities caps) {
+        mNetworkCapabilities = caps;
+    }
+
     /**
      * Dependencies class for testing.
      */
@@ -673,7 +681,7 @@
          */
         public FileDescriptor connectToKernel() throws ErrnoException, SocketException {
             final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket();
-            NetlinkUtils.connectSocketToNetlink(fd);
+            NetlinkUtils.connectToKernel(fd);
             Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO,
                     StructTimeval.fromMillis(IO_TIMEOUT_MS));
             return fd;
@@ -740,8 +748,14 @@
         /** Add receiver for detecting doze mode change to control TCP detection. */
         @TargetApi(Build.VERSION_CODES.TIRAMISU)
         public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver,
-                boolean shouldDisableInLightDoze) {
-            final IntentFilter intentFilter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
+                boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze) {
+            // No need to register receiver if no related feature is enabled.
+            if (!shouldDisableInDeepDoze && !shouldDisableInLightDoze) return;
+
+            final IntentFilter intentFilter = new IntentFilter();
+            if (shouldDisableInDeepDoze) {
+                intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
+            }
             if (shouldDisableInLightDoze) {
                 intentFilter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
             }
@@ -749,7 +763,9 @@
         }
 
         /** Remove broadcast receiver. */
-        public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver) {
+        public void removeBroadcastReceiver(@NonNull final BroadcastReceiver receiver,
+                boolean shouldDisableInDeepDoze, boolean shouldDisableInLightDoze) {
+            if (!shouldDisableInDeepDoze && !shouldDisableInLightDoze) return;
             mContext.unregisterReceiver(receiver);
         }
 
@@ -759,10 +775,27 @@
          * to deal with flag values changing at runtime.
          */
         @TargetApi(Build.VERSION_CODES.TIRAMISU)
-        public boolean shouldDisableInLightDoze() {
+        public boolean shouldDisableInLightDoze(boolean ignoreBlockedUidsSupported) {
             // Light doze mode status checking API is only available at T or later releases.
-            return SdkLevel.isAtLeastT() && DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(
+            if (!SdkLevel.isAtLeastT()) return false;
+
+            // Disable light doze mode design is replaced by ignoring blocked uids design.
+            if (ignoreBlockedUidsSupported) return false;
+
+            return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(
                     mContext, SKIP_TCP_POLL_IN_LIGHT_DOZE);
         }
+
+        /**
+         * Get whether the ignore Tcp info for blocked uids is supported. This method should
+         * only be called once in the constructor, to ensure that the code does not need
+         * to deal with flag values changing at runtime.
+         */
+        public boolean shouldIgnoreTcpInfoForBlockedUids() {
+            return SdkLevel.isAtLeastT() && DeviceConfigUtils.isFeatureSupported(
+                    mContext, FEATURE_IS_UID_NETWORKING_BLOCKED)
+                    && DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext,
+                    IGNORE_TCP_INFO_FOR_BLOCKED_UIDS);
+        }
     }
 }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index c9589e3..2dbf86e 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -34,7 +34,6 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.SocketException;
 import java.net.UnknownHostException;
 
 /**
@@ -152,21 +151,6 @@
             new String [] {"https://www.google.com/generate_204"};
 
     /**
-     * @deprecated Considering boolean experiment flag is likely to cause misconfiguration
-     *             particularly when NetworkStack module rolls back to previous version. It's
-     *             much safer to determine whether or not to enable one specific experimental
-     *             feature by comparing flag version with module version.
-     */
-    @Deprecated
-    public static final String DHCP_INIT_REBOOT_ENABLED = "dhcp_init_reboot_enabled";
-
-    /**
-     * @deprecated See above explanation.
-     */
-    @Deprecated
-    public static final String DHCP_RAPID_COMMIT_ENABLED = "dhcp_rapid_commit_enabled";
-
-    /**
      * Minimum module version at which to enable the DHCP INIT-REBOOT state.
      */
     public static final String DHCP_INIT_REBOOT_VERSION = "dhcp_init_reboot_version";
@@ -182,12 +166,6 @@
     public static final String DHCP_IP_CONFLICT_DETECT_VERSION = "dhcp_ip_conflict_detect_version";
 
     /**
-     * Minimum module version at which to enable the IPv6-Only preferred option.
-     */
-    public static final String DHCP_IPV6_ONLY_PREFERRED_VERSION =
-            "dhcp_ipv6_only_preferred_version";
-
-    /**
      * Minimum module version at which to enable slow DHCP retransmission approach in renew/rebind
      * state suggested in RFC2131 section 4.4.5.
      */
@@ -271,6 +249,12 @@
      * Experiment flag to enable new ra filter.
      */
     public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version";
+    /**
+     * Experiment flag to enable the feature of ignoring any individual RA section with lifetime
+     * below accept_ra_min_lft sysctl.
+     */
+    public static final String IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION =
+            "ipclient_ignore_low_ra_lifetime_version";
 
     /**** BEGIN Feature Kill Switch Flags ****/
 
@@ -282,11 +266,10 @@
             "ipclient_parse_netlink_events_force_disable";
 
     /**
-     * Kill switch flag to disable the feature of ignoring any individual RA section with lifetime
-     * below accept_ra_min_lft sysctl.
+     * Kill switch flag to disable the feature of handle light doze mode in Apf.
      */
-    public static final String IPCLIENT_IGNORE_LOW_RA_LIFETIME_FORCE_DISABLE =
-            "ipclient_ignore_low_ra_lifetime_force_disable";
+    public static final String APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE =
+            "apf_handle_light_doze_force_disable";
 
     /**
      * Kill switch flag to disable the feature of skipping Tcp socket info polling when light
@@ -294,6 +277,18 @@
      */
     public static final String SKIP_TCP_POLL_IN_LIGHT_DOZE = "skip_tcp_poll_in_light_doze_mode";
 
+    /**
+     * Kill switch flag to disable the feature of re-evaluate when network resumes.
+     */
+    public static final String REEVALUATE_WHEN_RESUME = "reevaluate_when_resume";
+
+    /**
+     * Kill switch flag to disable the feature of ignoring Tcp socket info for uids which
+     * networking are blocked.
+     */
+    public static final String IGNORE_TCP_INFO_FOR_BLOCKED_UIDS =
+            "ignore_tcp_info_for_blocked_uids";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java
index 368a6d4..40aee28 100644
--- a/src/com/android/server/NetworkStackService.java
+++ b/src/com/android/server/NetworkStackService.java
@@ -21,11 +21,15 @@
 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
 
 import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig;
+import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED;
+import static com.android.networkstack.util.NetworkStackUtils.IGNORE_TCP_INFO_FOR_BLOCKED_UIDS;
+import static com.android.networkstack.util.NetworkStackUtils.SKIP_TCP_POLL_IN_LIGHT_DOZE;
 import static com.android.server.util.PermissionUtil.checkDumpPermission;
 
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
 import android.net.IIpMemoryStore;
 import android.net.IIpMemoryStoreCallbacks;
 import android.net.INetd;
@@ -49,6 +53,7 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -59,6 +64,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.NetworkStackNotifier;
 import com.android.networkstack.R;
@@ -435,6 +442,20 @@
                 return;
             }
 
+            pw.println("Device Configs:");
+            pw.increaseIndent();
+            pw.println("SKIP_TCP_POLL_IN_LIGHT_DOZE="
+                    + DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(
+                            mContext, SKIP_TCP_POLL_IN_LIGHT_DOZE));
+            pw.println("FEATURE_IS_UID_NETWORKING_BLOCKED=" + DeviceConfigUtils.isFeatureSupported(
+                            mContext, FEATURE_IS_UID_NETWORKING_BLOCKED));
+            pw.println("IGNORE_TCP_INFO_FOR_BLOCKED_UIDS="
+                    + DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext,
+                            IGNORE_TCP_INFO_FOR_BLOCKED_UIDS));
+            pw.decreaseIndent();
+            pw.println();
+
+
             pw.println("NetworkStack logs:");
             mLog.dump(fd, pw, args);
 
@@ -482,6 +503,69 @@
                     R.bool.config_no_sim_card_uses_neighbor_mcc, false));
         }
 
+        @Override
+        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+                @NonNull String[] args) {
+            return new ShellCmd().exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+                    err.getFileDescriptor(), args);
+        }
+
+        private class ShellCmd extends BasicShellCommandHandler {
+            @Override
+            public int onCommand(String cmd) {
+                if (cmd == null) {
+                    return handleDefaultCommands(cmd);
+                }
+                final PrintWriter pw = getOutPrintWriter();
+                try {
+                    switch (cmd) {
+                        case "is-uid-networking-blocked":
+                            if (!DeviceConfigUtils.isFeatureSupported(mContext,
+                                    FEATURE_IS_UID_NETWORKING_BLOCKED)) {
+                                pw.println("API is unsupported");
+                                return -1;
+                            }
+
+                            // Usage : cmd network_stack is-uid-networking-blocked <uid> <metered>
+                            // If no argument, get and display the usage help.
+                            if (getRemainingArgsCount() != 2) {
+                                onHelp();
+                                return -1;
+                            }
+                            final int uid;
+                            final boolean metered;
+                            // If any fail, throws and output to the stdout.
+                            // Let the caller handle it.
+                            uid = Integer.parseInt(getNextArg());
+                            metered = Boolean.parseBoolean(getNextArg());
+                            final ConnectivityManager cm =
+                                    mContext.getSystemService(ConnectivityManager.class);
+                            pw.println(cm.isUidNetworkingBlocked(
+                                    uid, metered /* isNetworkMetered */));
+                            return 0;
+                        default:
+                            return handleDefaultCommands(cmd);
+                    }
+                } catch (Exception e) {
+                    pw.println(e);
+                }
+                return -1;
+            }
+
+            @Override
+            public void onHelp() {
+                PrintWriter pw = getOutPrintWriter();
+                pw.println("NetworkStack service commands:");
+                pw.println("  help");
+                pw.println("    Print this help text.");
+                pw.println("  is-uid-networking-blocked <uid> <metered>");
+                pw.println("    Get whether the networking is blocked for given uid and metered.");
+                pw.println("    <uid>: The target uid.");
+                pw.println("    <metered>: [true|false], Whether the target network is metered.");
+            }
+        }
+
         /**
          * Dump version information of the module and detected system version.
          */
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 5b551a0..8c10138 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -36,6 +36,7 @@
 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.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.captiveportal.CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs;
 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
@@ -542,6 +543,7 @@
     private final boolean mPrivateIpNoInternetEnabled;
 
     private final boolean mMetricsEnabled;
+    private final boolean mReevaluateWhenResumeEnabled;
     @NonNull
     private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance();
 
@@ -628,6 +630,8 @@
         mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
         mMetricsEnabled = deps.isFeatureNotChickenedOut(context,
                 NetworkStackUtils.VALIDATION_METRICS_VERSION);
+        mReevaluateWhenResumeEnabled = deps.isFeatureNotChickenedOut(context,
+                NetworkStackUtils.REEVALUATE_WHEN_RESUME);
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
         mCaptivePortalFallbackSpecs =
@@ -938,6 +942,7 @@
                 // Initialization.
                 tst.setOpportunisticMode(false);
                 tst.setLinkProperties(mLinkProperties);
+                tst.setNetworkCapabilities(mNetworkCapabilities);
             }
             Log.d(TAG, "Starting on network " + mNetwork
                     + " with capport HTTPS URL " + Arrays.toString(mCaptivePortalHttpsUrls)
@@ -1101,16 +1106,8 @@
                     }
                     break;
                 case EVENT_NETWORK_CAPABILITIES_CHANGED:
-                    final NetworkCapabilities newCap = (NetworkCapabilities) message.obj;
-                    // Reevaluate network if underlying network changes on the validation required
-                    // VPN.
-                    if (isVpnUnderlyingNetworkChangeReevaluationRequired(
-                            newCap, mNetworkCapabilities)) {
-                        sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0);
-                    }
-
-                    mNetworkCapabilities = newCap;
-                    suppressNotificationIfNetworkRestricted();
+                    handleCapabilitiesChanged((NetworkCapabilities) message.obj,
+                            true /* reevaluateOnResume */);
                     break;
                 case EVENT_RESOURCE_CONFIG_CHANGED:
                     // RRO generation does not happen during package installation and instead after
@@ -1129,20 +1126,53 @@
             return HANDLED;
         }
 
-        private boolean isVpnUnderlyingNetworkChangeReevaluationRequired(
-                final NetworkCapabilities newCap, final NetworkCapabilities oldCap) {
-            return !newCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                    && isValidationRequired()
-                    && !Objects.equals(mInfoShim.getUnderlyingNetworks(newCap),
-                    mInfoShim.getUnderlyingNetworks(oldCap));
-        }
-
         @Override
         public void exit() {
             mContext.unregisterReceiver(mConfigurationReceiver);
         }
     }
 
+    private void handleCapabilitiesChanged(@NonNull final NetworkCapabilities newCap,
+            boolean reevaluateOnResume) {
+        // Go to EvaluatingState to reset the network re-evaluation timer when
+        // the network resumes from suspended.
+        // This is because the network is expected to be down
+        // when the device is suspended, and if the delay timer falls back to
+        // the maximum interval, re-evaluation will be triggered slowly after
+        // the network resumes.
+        // Suppress re-evaluation in validated state, if the network has been validated,
+        // then it's in the expected state.
+        // TODO(b/287183389): Evaluate once but do not re-evaluate when suspended, to make
+        //  exclamation mark visible by user but doesn't cause too much network traffic.
+        if (mReevaluateWhenResumeEnabled && reevaluateOnResume
+                && !mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && newCap.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)) {
+            // Interrupt if waiting for next probe.
+            sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 1 /* forceAccept */);
+        } else if (isVpnUnderlyingNetworkChangeReevaluationRequired(newCap, mNetworkCapabilities)) {
+            // If no re-evaluation is needed from the previous check, fall-through for lower
+            // priority checks.
+            // Reevaluate network if underlying network changes on the validation required
+            // VPN.
+            sendMessage(CMD_FORCE_REEVALUATION, NO_UID, 0 /* forceAccept */);
+        }
+        final TcpSocketTracker tst = getTcpSocketTracker();
+        if (tst != null) {
+            tst.setNetworkCapabilities(newCap);
+        }
+
+        mNetworkCapabilities = newCap;
+        suppressNotificationIfNetworkRestricted();
+    }
+
+    private boolean isVpnUnderlyingNetworkChangeReevaluationRequired(
+            final NetworkCapabilities newCap, final NetworkCapabilities oldCap) {
+        return !newCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                && isValidationRequired()
+                && !Objects.equals(mInfoShim.getUnderlyingNetworks(newCap),
+                mInfoShim.getUnderlyingNetworks(oldCap));
+    }
+
     // Being in the ValidatedState State indicates a Network is:
     // - Successfully validated, or
     // - Wanted "as is" by the user, or
@@ -1208,6 +1238,12 @@
                         sendTcpPollingEvent();
                     }
                     break;
+                case EVENT_NETWORK_CAPABILITIES_CHANGED:
+                    // The timer does not need to reset, and it won't need to re-evaluate if
+                    // the network is already validated when resumes.
+                    handleCapabilitiesChanged((NetworkCapabilities) message.obj,
+                            false /* reevaluateOnResume */);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -3456,6 +3492,11 @@
     }
 
     @VisibleForTesting
+    public int getReevaluationDelayMs() {
+        return mReevaluateDelayMs;
+    }
+
+    @VisibleForTesting
     protected boolean isDataStall() {
         if (!isDataStallDetectionRequired()) {
             return false;
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index c54906d..d239379 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -38,6 +38,7 @@
 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS;
+import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
 import static android.net.ip.IIpClientCallbacks.DTIM_MULTIPLIER_RESET;
 import static android.net.ip.IpClient.CONFIG_IPV6_AUTOCONF_TIMEOUT;
 import static android.net.ip.IpClient.CONFIG_ACCEPT_RA_MIN_LFT;
@@ -78,6 +79,7 @@
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
 import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
 import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
+import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
 import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -151,6 +153,7 @@
 import android.net.dhcp.DhcpRequestPacket;
 import android.net.dhcp6.Dhcp6Client;
 import android.net.dhcp6.Dhcp6Packet;
+import android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
 import android.net.dhcp6.Dhcp6RebindPacket;
 import android.net.dhcp6.Dhcp6RenewPacket;
 import android.net.dhcp6.Dhcp6RequestPacket;
@@ -199,6 +202,7 @@
 import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructNdOptPref64;
 import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.IaPrefixOption;
 import com.android.net.module.util.structs.Ipv6Header;
 import com.android.net.module.util.structs.LlaOption;
 import com.android.net.module.util.structs.PrefixInformationOption;
@@ -693,14 +697,11 @@
     }
 
     protected void setDhcpFeatures(final boolean isDhcpLeaseCacheEnabled,
-            final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled,
-            final boolean isIPv6OnlyPreferredEnabled) {
+            final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled) {
         setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled);
         setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled);
         setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION,
                 isDhcpIpConflictDetectEnabled);
-        setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION,
-                isIPv6OnlyPreferredEnabled);
     }
 
     private void setDeviceConfigForMaxDtimMultiplier() {
@@ -1201,9 +1202,9 @@
 
     private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
             final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled,
-            final boolean isDhcpIpConflictDetectEnabled, final boolean isIPv6OnlyPreferredEnabled,
-            final String displayName, final ScanResultInfo scanResultInfo,
-            final Layer2Information layer2Info) throws Exception {
+            final boolean isDhcpIpConflictDetectEnabled, final String displayName,
+            final ScanResultInfo scanResultInfo, final Layer2Information layer2Info)
+                    throws Exception {
         ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
                 .withLayer2Information(layer2Info == null
@@ -1216,7 +1217,7 @@
         if (scanResultInfo != null) prov.withScanResultInfo(scanResultInfo);
 
         setDhcpFeatures(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
-                isDhcpIpConflictDetectEnabled, isIPv6OnlyPreferredEnabled);
+                isDhcpIpConflictDetectEnabled);
 
         startIpClientProvisioning(prov.build());
         if (!isPreconnectionEnabled) {
@@ -1227,10 +1228,9 @@
 
     private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
             final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled,
-            final boolean isDhcpIpConflictDetectEnabled, final boolean isIPv6OnlyPreferredEnabled)
-            throws Exception {
+            final boolean isDhcpIpConflictDetectEnabled) throws Exception {
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
-                isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, isIPv6OnlyPreferredEnabled,
+                isPreconnectionEnabled, isDhcpIpConflictDetectEnabled,
                 null /* displayName */, null /* ScanResultInfo */, null /* layer2Info */);
     }
 
@@ -1284,13 +1284,12 @@
             final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled,
             final boolean shouldReplyRapidCommitAck, final int mtu,
             final boolean isDhcpIpConflictDetectEnabled,
-            final boolean isIPv6OnlyPreferredEnabled,
             final String captivePortalApiUrl, final String displayName,
             final ScanResultInfo scanResultInfo, final Layer2Information layer2Info)
             throws Exception {
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
                 false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
-                isIPv6OnlyPreferredEnabled, displayName, scanResultInfo, layer2Info);
+                displayName, scanResultInfo, layer2Info);
         return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
                 captivePortalApiUrl);
     }
@@ -1335,7 +1334,6 @@
             final boolean isDhcpIpConflictDetectEnabled) throws Exception {
         return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled,
                 isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled,
-                false /* isIPv6OnlyPreferredEnabled */,
                 null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */,
                 null /* layer2Info */);
     }
@@ -1392,7 +1390,7 @@
         }).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any());
         startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
                 false /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         return getNextDhcpPacket();
     }
 
@@ -1494,7 +1492,7 @@
 
         startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
                 shouldReplyRapidCommitAck, true /* isDhcpPreConnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart();
         final int preconnDiscoverTransId = packet.getTransactionId();
 
@@ -1674,7 +1672,7 @@
     public void testDhcpInit() throws Exception {
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
                 false /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         final DhcpPacket packet = getNextDhcpPacket();
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
@@ -1742,7 +1740,7 @@
     public void testRollbackFromRapidCommitOption() throws Exception {
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
                 true /* isDhcpRapidCommitEnabled */, false /* isPreConnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
 
         final List<DhcpPacket> discoverList = new ArrayList<DhcpPacket>();
         DhcpPacket packet;
@@ -1819,7 +1817,7 @@
     public void testDhcpClientRapidCommitEnabled() throws Exception {
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
                 true /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         final DhcpPacket packet = getNextDhcpPacket();
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
@@ -2545,7 +2543,7 @@
         // PreconnectionState instead of RunningState.
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
                 false /* shouldReplyRapidCommitAck */, true /* isDhcpPreConnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         assertDiscoverPacketOnPreconnectionStart();
 
         // Force to enter RunningState.
@@ -2671,7 +2669,7 @@
                 .withPreconnection()
                 .build();
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         startIpClientProvisioning(config);
         assertDiscoverPacketOnPreconnectionStart();
 
@@ -2703,7 +2701,7 @@
         // StoppedState.
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
                 false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         final DhcpPacket discover = getNextDhcpPacket();
         assertTrue(discover instanceof DhcpDiscoverPacket);
     }
@@ -2820,7 +2818,7 @@
             boolean serverSendsOption) throws Exception {
         startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
                 false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         final DhcpPacket discover = getNextDhcpPacket();
         assertTrue(discover instanceof DhcpDiscoverPacket);
         assertEquals(featureEnabled, discover.hasRequestedParam(DhcpPacket.DHCP_CAPTIVE_PORTAL));
@@ -2931,7 +2929,6 @@
                 TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
                 false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
                 false /* isDhcpIpConflictDetectEnabled */,
-                false /* isIPv6OnlyPreferredEnabled */,
                 null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */,
                 null /* layer2Info */);
         assertEquals(2, sentPackets.size());
@@ -3046,7 +3043,6 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */,
-                false /* isIPv6OnlyPreferredEnabled */,
                 null /* captivePortalApiUrl */, displayName, null /* scanResultInfo */,
                 layer2Info);
         verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
@@ -3199,7 +3195,7 @@
         // Enable rapid commit to accelerate DHCP handshake to shorten test duration,
         // not strictly necessary.
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         // Both signature and root tests can use this function to do dual-stack provisioning.
         if (useNetworkStackSignature()) {
             mIpc.startProvisioning(config);
@@ -3227,7 +3223,7 @@
         for (LinkAddress la : lp.getLinkAddresses()) {
             final InetAddress addr = la.getAddress();
             if ((addr instanceof Inet6Address) && !addr.isLinkLocalAddress()) {
-                return prefix.contains(addr);
+                if (prefix.contains(addr)) return true;
             }
         }
         return false;
@@ -3303,7 +3299,7 @@
                 .withoutIpReachabilityMonitor()
                 .build();
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, true /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         startIpClientProvisioning(config);
 
         final DhcpPacket packet =
@@ -3367,7 +3363,7 @@
                 .build();
 
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, true /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         startIpClientProvisioning(config);
 
         final DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart();
@@ -3422,7 +3418,7 @@
                 .build();
 
         setDhcpFeatures(true /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, true /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         startIpClientProvisioning(config);
 
         final DhcpPacket packet =
@@ -3556,7 +3552,7 @@
                 .withoutIPv6();
 
         setDhcpFeatures(isDhcpLeaseCacheEnabled, false /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
 
         startIpClientProvisioning(prov.build());
         verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true);
@@ -3927,7 +3923,7 @@
         // Enable rapid commit to accelerate DHCP handshake to shorten test duration,
         // not strictly necessary.
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
 
         // Disable gratuitious neighbor discovery feature manually, if the feature is enabled on
         // the DUT during experiment launch, that will send another two duplicate NA packets and
@@ -4294,8 +4290,7 @@
         mNetworkAgentThread.start();
 
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */,
-                false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         setFeatureEnabled(
                 NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION,
                 isIgnoreIncompleteIpv6DnsServerEnabled);
@@ -4471,7 +4466,7 @@
 
         // Speed up provisioning by enabling rapid commit. TODO: why is this necessary?
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         config = new ProvisioningConfiguration.Builder()
                 .build();
         startIpClientProvisioning(config);
@@ -4812,18 +4807,27 @@
         inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).setMaxDtimMultiplier(DTIM_MULTIPLIER_RESET);
     }
 
-    private void handleDhcp6Packets(final IpPrefix prefix, boolean shouldReplyRapidCommit)
-            throws Exception {
-        handleDhcp6Packets(prefix, 3600 /* t1 */, 4500 /* t2 */, 4500 /* preferred */,
-                7200 /* valid */, shouldReplyRapidCommit);
+    private IaPrefixOption buildIaPrefixOption(final IpPrefix prefix, int preferred,
+            int valid) {
+        return new IaPrefixOption((short) IaPrefixOption.LENGTH, preferred, valid,
+                (byte) RFC7421_PREFIX_LENGTH, prefix.getRawAddress() /* prefix */);
     }
 
-    private void handleDhcp6Packets(final IpPrefix prefix, int t1, int t2, int preferred, int valid,
+    private void handleDhcp6Packets(final IpPrefix prefix, boolean shouldReplyRapidCommit)
+            throws Exception {
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+                7200 /* valid */);
+        handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */,
+                shouldReplyRapidCommit);
+    }
+
+    private void handleDhcp6Packets(final List<IaPrefixOption> ipos, int t1, int t2,
             boolean shouldReplyRapidCommit) throws Exception {
+        ByteBuffer iapd;
         Dhcp6Packet packet;
         while ((packet = getNextDhcp6Packet()) != null) {
-            final ByteBuffer iapd = Dhcp6Packet.buildIaPdOption(packet.getIaId(), t1, t2,
-                    preferred, valid, prefix.getRawAddress(), (byte) prefix.getPrefixLength());
+            final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), t1, t2, ipos);
+            iapd = pd.build();
             if (packet instanceof Dhcp6SolicitPacket) {
                 if (shouldReplyRapidCommit) {
                     mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
@@ -4883,7 +4887,11 @@
     @Test
     public void testDhcp6Pd_longPrefixLength() throws Exception {
         prepareDhcp6PdTest();
-        handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), true /* shouldReplyRapidCommit */);
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/80");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */,
+                4000 /* valid */);
+        handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */,
+                true /* shouldReplyRapidCommit */);
         verify(mCb, never()).onProvisioningSuccess(any());
     }
 
@@ -4900,24 +4908,33 @@
     @Test
     public void testDhcp6Pd_T1GreaterThanT2() throws Exception {
         prepareDhcp6PdTest();
-        handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), 4500 /* t1 */, 3600 /* t2 */,
-                4500 /* preferred */, 7200 /* valid */, true /* shouldReplyRapidCommit */);
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */,
+                4000 /* valid */);
+        handleDhcp6Packets(Collections.singletonList(ipo), 4500 /* t1 */, 3600 /* t2 */,
+                true /* shouldReplyRapidCommit */);
         verify(mCb, never()).onProvisioningSuccess(any());
     }
 
     @Test
     public void testDhcp6Pd_preferredLifetimeGreaterThanValidLifetime() throws Exception {
         prepareDhcp6PdTest();
-        handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), 3600 /* t1 */, 4500 /* t2 */,
-                7200 /* preferred */, 4500 /* valid */, true /* shouldReplyRapidCommit */);
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 7200 /* preferred */,
+                4500 /* valid */);
+        handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */,
+                true /* shouldReplyRapidCommit */);
         verify(mCb, never()).onProvisioningSuccess(any());
     }
 
     @Test
     public void testDhcp6Pd_preferredLifetimeLessThanT2() throws Exception {
         prepareDhcp6PdTest();
-        handleDhcp6Packets(new IpPrefix("2001:db8:1::/80"), 3600 /* t1 */, 4500 /* t2 */,
-                3600 /* preferred */, 4000 /* valid */, true /* shouldReplyRapidCommit */);
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */,
+                4000 /* valid */);
+        handleDhcp6Packets(Collections.singletonList(ipo), 3600 /* t1 */, 4500 /* t2 */,
+                true /* shouldReplyRapidCommit */);
         verify(mCb, never()).onProvisioningSuccess(any());
     }
 
@@ -4936,7 +4953,7 @@
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .build();
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         startIpClientProvisioning(config);
 
         waitForRouterSolicitation();
@@ -4994,7 +5011,7 @@
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .build();
         setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
-                false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+                false /* isDhcpIpConflictDetectEnabled */);
         startIpClientProvisioning(config);
 
         waitForRouterSolicitation();
@@ -5017,6 +5034,44 @@
         ));
     }
 
+    @Test
+    public void testDhcp6Pd_multiplePrefixesWithInvalidPrefix() throws Exception {
+        final IpPrefix valid = new IpPrefix("2001:db8:1::/64");
+        final IpPrefix invalid = new IpPrefix("2001:db8:2::/64"); // preferred lft > valid lft
+        final IaPrefixOption validIpo = buildIaPrefixOption(valid, 4500 /* preferred */,
+                7200 /* valid */);
+        final IaPrefixOption invalidIpo = buildIaPrefixOption(invalid, 4500 /* preferred */,
+                3000 /* valid */);
+
+        prepareDhcp6PdTest();
+        handleDhcp6Packets(Arrays.asList(invalidIpo, validIpo), 3600 /* t1 */, 4500 /* t2 */,
+                true /* shouldReplyRapidCommit */);
+        final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+        final LinkProperties lp = captor.getValue();
+        assertTrue(hasIpv6AddressPrefixedWith(lp, valid));
+        assertFalse(hasIpv6AddressPrefixedWith(lp, invalid));
+    }
+
+    @Test
+    public void testDhcp6Pd_multiplePrefixesWithPrefixValidLifetimeOfZero() throws Exception {
+        final IpPrefix valid = new IpPrefix("2001:db8:1::/64");
+        final IpPrefix invalid = new IpPrefix("2001:db8:2::/64"); // preferred/valid lft 0
+        final IaPrefixOption validIpo = buildIaPrefixOption(valid, 4500 /* preferred */,
+                7200 /* valid */);
+        final IaPrefixOption invalidIpo = buildIaPrefixOption(invalid, 0 /* preferred */,
+                0 /* valid */);
+
+        prepareDhcp6PdTest();
+        handleDhcp6Packets(Arrays.asList(invalidIpo, validIpo), 3600 /* t1 */, 4500 /* t2 */,
+                true /* shouldReplyRapidCommit */);
+        final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+        final LinkProperties lp = captor.getValue();
+        assertTrue(hasIpv6AddressPrefixedWith(lp, valid));
+        assertFalse(hasIpv6AddressPrefixedWith(lp, invalid));
+    }
+
     private void prepareDhcp6PdRenewTest() throws Exception {
         final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
         prepareDhcp6PdTest();
@@ -5065,12 +5120,14 @@
         Dhcp6Packet packet = getNextDhcp6Packet();
         assertTrue(packet instanceof Dhcp6RenewPacket);
 
-        // Reply with a different prefix with requested one, check if all global IPv6 addresses
-        // will be deleted and loss the IPv6 provisioning.
+        // Reply with a different prefix with requested one, per RFC8415#section-18.2.10.1
+        // any new prefix should be added.
         final IpPrefix prefix1 = new IpPrefix("2001:db8:2::/64");
-        final ByteBuffer iapd = Dhcp6Packet.buildIaPdOption(packet.getIaId(), 3600 /* t1*/,
-                4500 /* t2 */, 4500 /* preferred */, 7200 /* valid */, prefix1.getRawAddress(),
-                (byte) 64 /* prefix length */);
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix1, 4500 /* preferred */,
+                7200 /* valid */);
+        final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */,
+                4500 /* t2 */, Collections.singletonList(ipo));
+        final ByteBuffer iapd = pd.build();
         mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
                 (Inet6Address) mClientIpAddress, false /* rapidCommit */));
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(any());
diff --git a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
index 8b1b046..b10e6e1 100644
--- a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
+++ b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
@@ -28,7 +28,9 @@
 import android.net.NetworkStatsIntegrationTest.Direction.UPLOAD
 import android.net.NetworkTemplate.MATCH_TEST
 import android.os.Build
+import android.os.ParcelFileDescriptor.AutoCloseInputStream
 import android.os.Process
+import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
@@ -42,6 +44,7 @@
 import fi.iki.elonen.NanoHTTPD
 import java.io.BufferedInputStream
 import java.io.BufferedOutputStream
+import java.io.BufferedReader
 import java.net.HttpURLConnection
 import java.net.HttpURLConnection.HTTP_OK
 import java.net.InetSocketAddress
@@ -49,6 +52,7 @@
 import java.nio.charset.Charset
 import kotlin.math.ceil
 import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 import org.junit.After
 import org.junit.Assume.assumeTrue
@@ -62,6 +66,7 @@
 @TargetApi(Build.VERSION_CODES.S)
 @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
 class NetworkStatsIntegrationTest {
+    private val TAG = NetworkStatsIntegrationTest::class.java.simpleName
     private val INTERNAL_V6ADDR =
         LinkAddress(InetAddresses.parseNumericAddress("2001:db8::1234"), 64)
     private val EXTERNAL_V6ADDR =
@@ -381,6 +386,7 @@
         val taggedUid = getUidDetail(iface, TEST_TAG)
         val trafficStatsIface = getTrafficStatsIface(iface)
         val trafficStatsUid = getTrafficStatsUid(Process.myUid())
+        val xtBpfStats = getXtBpfStatsInternal()
 
         private fun getUidDetail(iface: String, tag: Int): BareStats {
             return getNetworkStatsThat(iface, tag) { nsm, template ->
@@ -447,6 +453,40 @@
             TrafficStats.getUidTxBytes(uid),
             TrafficStats.getUidTxPackets(uid)
         )
+
+        private fun getXtBpfStatsInternal(): BareStats {
+            // The following pattern matches ip(6)tables-save -c output like below:
+            // [119:37802] -A bw_raw_PREROUTING -m bpf --object-pinned
+            //      /sys/fs/bpf/netd_shared/prog_netd_skfilter_ingress_xtbpf
+            // [141:26439] -A bw_mangle_POSTROUTING -m bpf --object-pinned
+            //      /sys/fs/bpf/netd_shared/prog_netd_skfilter_egress_xtbpf
+            val ingressRegex = Regex("""\[(?<rxPackets>\d+):(?<rxBytes>\d+)\]""" +
+                    """.*prog_netd_skfilter_ingress_xtbpf""")
+            val egressRegex = Regex("""\[(?<txPackets>\d+):(?<txBytes>\d+)\]""" +
+                    """.*prog_netd_skfilter_egress_xtbpf""")
+            val (v4Stats, v6Stats) = listOf("iptables-save -c", "ip6tables-save -c").map {
+                val output = runShellCommand(it)
+                val rxMatches = ingressRegex.find(output)
+                val txMatches = egressRegex.find(output)
+                assertNotNull(rxMatches)
+                assertNotNull(txMatches)
+
+                BareStats(
+                        rxBytes = rxMatches.groups["rxBytes"]!!.value.toLong(),
+                        rxPackets = rxMatches.groups["rxPackets"]!!.value.toLong(),
+                        txBytes = txMatches.groups["txBytes"]!!.value.toLong(),
+                        txPackets = txMatches.groups["txPackets"]!!.value.toLong()
+                )
+            }
+            return v4Stats.plus(v6Stats)
+        }
+
+        private fun runShellCommand(cmd: String): String {
+            return InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .executeShellCommand(cmd).use { pfd ->
+                        AutoCloseInputStream(pfd).bufferedReader().use(BufferedReader::readText)
+                    }
+        }
     }
 
     private fun assertAllStatsIncreases(
@@ -475,6 +515,15 @@
         lower: BareStats,
         upper: BareStats
     ) {
+        // XtBpf iptables hook counted traffic on all interfaces. Thus, this might see traffic
+        // on other interfaces as well. Also, other thread/process could reload the relevant
+        // iptables table. Thus, instead of asserting the readings, print logs when it is
+        // unexpected to provide more debug information when failing other items.
+        if (!checkInRange(before.xtBpfStats, after.xtBpfStats,
+                        lower + lower.reverse(), upper + upper.reverse())) {
+            Log.d(TAG, "Unexpected xtbpf stats: ${after.xtBpfStats} - ${before.xtBpfStats} " +
+                    "is not within range [$lower, $upper]")
+        }
         assertInRange(
             "Unexpected iface traffic stats",
             after.iface,
@@ -550,16 +599,24 @@
     ) {
         // Passing the value after operation and the value before operation to dump the actual
         // numbers if it fails.
-        val value = after - before
-        assertTrue(
-            value.rxBytes in lower.rxBytes..upper.rxBytes &&
-                    value.rxPackets in lower.rxPackets..upper.rxPackets &&
-                    value.txBytes in lower.txBytes..upper.txBytes &&
-                    value.txPackets in lower.txPackets..upper.txPackets,
+        assertTrue(checkInRange(before, after, lower, upper),
             "$tag on $iface: $after - $before is not within range [$lower, $upper]"
         )
     }
 
+    private fun checkInRange(
+            before: BareStats,
+            after: BareStats,
+            lower: BareStats,
+            upper: BareStats
+    ): Boolean {
+        val value = after - before
+        return value.rxBytes in lower.rxBytes..upper.rxBytes &&
+                value.rxPackets in lower.rxPackets..upper.rxPackets &&
+                value.txBytes in lower.txBytes..upper.txBytes &&
+                value.txPackets in lower.txPackets..upper.txPackets
+    }
+
     fun getRandomString(length: Long): String {
         val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
         return (1..length)
diff --git a/tests/unit/jni/apf_jni.cpp b/tests/unit/jni/apf_jni.cpp
index 5ae3ada..84c5c1a 100644
--- a/tests/unit/jni/apf_jni.cpp
+++ b/tests/unit/jni/apf_jni.cpp
@@ -41,7 +41,7 @@
                          filter_age);
   } else {
     return apf_run(program, program_len, ram_len, packet, packet_len,
-                         filter_age);
+                         filter_age << 14);
   }
 }
 
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 69c8917..6a54e1c 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -25,7 +25,8 @@
 import static android.net.apf.ApfTestUtils.MIN_PKT_SIZE;
 import static android.net.apf.ApfTestUtils.PASS;
 import static android.net.apf.ApfTestUtils.assertProgramEquals;
-import static android.system.OsConstants.AF_UNIX;
+import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
+import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
 import static android.system.OsConstants.ARPHRD_ETHER;
 import static android.system.OsConstants.ETH_P_ARP;
 import static android.system.OsConstants.ETH_P_IP;
@@ -34,7 +35,6 @@
 import static android.system.OsConstants.IPPROTO_IPV6;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_STREAM;
 
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
 
@@ -42,9 +42,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -54,14 +58,12 @@
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.ip.IIpClientCallbacks;
-import android.net.ip.IpClient.IpClientCallbacksWrapper;
-import android.os.ConditionVariable;
-import android.os.SystemClock;
+import android.net.apf.ApfTestUtils.MockIpClientCallback;
+import android.net.apf.ApfTestUtils.TestApfFilter;
+import android.os.Build;
+import android.os.PowerManager;
 import android.system.ErrnoException;
-import android.system.Os;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Pair;
 
@@ -71,27 +73,26 @@
 import com.android.internal.util.HexDump;
 import com.android.net.module.util.DnsPacket;
 import com.android.net.module.util.Inet4AddressUtils;
-import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.net.module.util.PacketBuilder;
-import com.android.net.module.util.SharedLog;
-import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.server.networkstack.tests.R;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
-import libcore.io.IoUtils;
 import libcore.io.Streams;
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -112,12 +113,13 @@
  *
  * The test cases will be executed by both APFv4 and APFv6 interpreter.
  */
-@RunWith(Parameterized.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 public class ApfTest {
-    private static final int TIMEOUT_MS = 500;
     private static final int MIN_APF_VERSION = 2;
 
+    @Rule
+    public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
     // Indicates which apf interpreter to run.
     @Parameterized.Parameter()
     public int mApfVersion;
@@ -127,10 +129,14 @@
         return Arrays.asList(4, 6);
     }
 
-    @Mock Context mContext;
+    @Mock private Context mContext;
+    @Mock
+    private ApfFilter.Dependencies mDependencies;
+    @Mock private PowerManager mPowerManager;
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
     }
 
     private static final String TAG = "ApfTest";
@@ -546,8 +552,8 @@
         // Test filter age pre-filled memory.
         gen = new ApfGenerator(MIN_APF_VERSION);
         gen.addLoadFromMemory(R0, gen.FILTER_AGE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
+        gen.addJumpIfR0Equals(123, gen.DROP_LABEL);
+        assertDrop(gen, new byte[MIN_PKT_SIZE], 123);
 
         // Test packet size pre-filled memory.
         gen = new ApfGenerator(MIN_APF_VERSION);
@@ -915,96 +921,6 @@
         apfFilter.shutdown();
     }
 
-    private class MockIpClientCallback extends IpClientCallbacksWrapper {
-        private final ConditionVariable mGotApfProgram = new ConditionVariable();
-        private byte[] mLastApfProgram;
-
-        MockIpClientCallback() {
-            super(mock(IIpClientCallbacks.class), mock(SharedLog.class),
-                    NetworkInformationShimImpl.newInstance());
-        }
-
-        @Override
-        public void installPacketFilter(byte[] filter) {
-            mLastApfProgram = filter;
-            mGotApfProgram.open();
-        }
-
-        public void resetApfProgramWait() {
-            mGotApfProgram.close();
-        }
-
-        public byte[] assertProgramUpdateAndGet() {
-            assertTrue(mGotApfProgram.block(TIMEOUT_MS));
-            return mLastApfProgram;
-        }
-
-        public void assertNoProgramUpdate() {
-            assertFalse(mGotApfProgram.block(TIMEOUT_MS));
-        }
-    }
-
-    private static class TestApfFilter extends ApfFilter {
-        public static final byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
-
-        private FileDescriptor mWriteSocket;
-        private long mCurrentTimeMs = SystemClock.elapsedRealtime();
-        private final MockIpClientCallback mMockIpClientCb;
-
-        public TestApfFilter(Context context, ApfConfiguration config,
-                MockIpClientCallback ipClientCallback) throws Exception {
-            super(context, config, InterfaceParams.getByName("lo"), ipClientCallback);
-            mMockIpClientCb = ipClientCallback;
-        }
-
-        // Pretend an RA packet has been received and show it to ApfFilter.
-        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
-            mMockIpClientCb.resetApfProgramWait();
-            // ApfFilter's ReceiveThread will be waiting to read this.
-            Os.write(mWriteSocket, packet, 0, packet.length);
-        }
-
-        // Simulate current time changes
-        public void increaseCurrentTimeSeconds(int delta) {
-            mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS;
-        }
-
-        @Override
-        protected int secondsSinceBoot() {
-            return (int) (mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS);
-        }
-
-        @Override
-        public synchronized void maybeStartFilter() {
-            mHardwareAddress = MOCK_MAC_ADDR;
-            installNewProgramLocked();
-
-            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
-            FileDescriptor readSocket = new FileDescriptor();
-            mWriteSocket = new FileDescriptor();
-            try {
-                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
-            } catch (ErrnoException e) {
-                fail();
-                return;
-            }
-            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
-            // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
-            mReceiveThread = new ReceiveThread(readSocket);
-            mReceiveThread.start();
-        }
-
-        @Override
-        public void shutdown() {
-            super.shutdown();
-            if (mReceiveThread != null) {
-                mReceiveThread.halt();
-                mReceiveThread = null;
-            }
-            IoUtils.closeQuietly(mWriteSocket);
-        }
-    }
-
     private static final int ETH_HEADER_LEN               = 14;
     private static final int ETH_DEST_ADDR_OFFSET         = 0;
     private static final int ETH_ETHERTYPE_OFFSET         = 12;
@@ -1134,17 +1050,6 @@
     private static final int IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2;
     private static final int MDNS_UDP_PORT = 5353;
 
-    // Helper to initialize a default apfFilter.
-    private ApfFilter setupApfFilter(
-            MockIpClientCallback ipClientCallback, ApfConfiguration config) throws Exception {
-        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(link);
-        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback);
-        apfFilter.setLinkProperties(lp);
-        return apfFilter;
-    }
-
     private static void setIpv4VersionFields(ByteBuffer packet) {
         packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
         packet.put(IP_HEADER_OFFSET, (byte) 0x45);
@@ -1800,20 +1705,60 @@
 
     @Test
     public void testApfFilterMulticastPingWhileDozing() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfFilter apfFilter = setupApfFilter(ipClientCallback, getDefaultConfig());
+        doTestApfFilterMulticastPingWhileDozing(false /* isLightDozing */);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApfFilterMulticastPingWhileLightDozing() throws Exception {
+        doTestApfFilterMulticastPingWhileDozing(true /* isLightDozing */);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testShouldHandleLightDozeKillSwitch() throws Exception {
+        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        final ApfConfiguration configuration = getDefaultConfig();
+        configuration.shouldHandleLightDoze = false;
+        final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback,
+                configuration, mDependencies);
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
+        final BroadcastReceiver receiver = receiverCaptor.getValue();
+        doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
+        receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        assertFalse(apfFilter.isInDozeMode());
+    }
+
+    private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception {
+        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        final ApfConfiguration configuration = getDefaultConfig();
+        configuration.shouldHandleLightDoze = true;
+        final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback,
+                configuration, mDependencies);
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
+        final BroadcastReceiver receiver = receiverCaptor.getValue();
 
         // Construct a multicast ICMPv6 ECHO request.
         final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
-        ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6);
+        final ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6);
         packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
         put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
 
         // Normally, we let multicast pings alone...
         assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
 
+        if (isLightDozing) {
+            doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        } else {
+            doReturn(true).when(mPowerManager).isDeviceIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
+        }
         // ...and even while dozing...
-        apfFilter.setDozeMode(true);
         assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
 
         // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
@@ -1829,7 +1774,13 @@
 
         // Now wake up from doze mode to ensure that we no longer drop the packets.
         // (The multicast filter is still enabled at this point).
-        apfFilter.setDozeMode(false);
+        if (isLightDozing) {
+            doReturn(false).when(mPowerManager).isDeviceLightIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        } else {
+            doReturn(false).when(mPowerManager).isDeviceIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
+        }
         assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
 
         apfFilter.shutdown();
@@ -1839,7 +1790,8 @@
     public void testApfFilter802_3() throws Exception {
         MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
+        ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+                mDependencies);
         byte[] program = ipClientCallback.assertProgramUpdateAndGet();
 
         // Verify empty packet of 100 zero bytes is passed
@@ -1859,7 +1811,8 @@
         ipClientCallback.resetApfProgramWait();
         apfFilter.shutdown();
         config.ieee802_3Filter = DROP_802_3_FRAMES;
-        apfFilter = setupApfFilter(ipClientCallback, config);
+        apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+                mDependencies);
         program = ipClientCallback.assertProgramUpdateAndGet();
 
         // Verify that IEEE802.3 frame is dropped
@@ -1886,7 +1839,8 @@
 
         MockIpClientCallback ipClientCallback = new MockIpClientCallback();
         ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = setupApfFilter(ipClientCallback, config);
+        ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+                mDependencies);
         byte[] program = ipClientCallback.assertProgramUpdateAndGet();
 
         // Verify empty packet of 100 zero bytes is passed
@@ -1906,7 +1860,8 @@
         ipClientCallback.resetApfProgramWait();
         apfFilter.shutdown();
         config.ethTypeBlackList = ipv4BlackList;
-        apfFilter = setupApfFilter(ipClientCallback, config);
+        apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+                mDependencies);
         program = ipClientCallback.assertProgramUpdateAndGet();
 
         // Verify that IPv4 frame will be dropped
@@ -1921,7 +1876,8 @@
         ipClientCallback.resetApfProgramWait();
         apfFilter.shutdown();
         config.ethTypeBlackList = ipv4Ipv6BlackList;
-        apfFilter = setupApfFilter(ipClientCallback, config);
+        apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+                mDependencies);
         program = ipClientCallback.assertProgramUpdateAndGet();
 
         // Verify that IPv4 frame will be dropped
diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java
index 555b92e..69c4079 100644
--- a/tests/unit/src/android/net/apf/ApfTestUtils.java
+++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -16,17 +16,43 @@
 package android.net.apf;
 
 import static android.net.apf.ApfJniUtils.apfSimulate;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
 
 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.mock;
+
+import android.content.Context;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.ip.IIpClientCallbacks;
+import android.net.ip.IpClient;
+import android.os.ConditionVariable;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.format.DateUtils;
 
 import com.android.internal.util.HexDump;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.apishim.NetworkInformationShimImpl;
 
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
 import java.util.Arrays;
 
 /**
  * The util class for calling the APF interpreter and check the return value
  */
 public class ApfTestUtils {
+    public static final int TIMEOUT_MS = 500;
     public static final int PASS = 1;
     public static final int DROP = 0;
     // Interpreter will just accept packets without link layer headers, so pad fake packet to at
@@ -137,6 +163,15 @@
         }
     }
 
+    /**
+     * Runs the APF program with customized data region and checks the return code.
+     */
+    public static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet,
+            byte[] data) {
+        assertReturnCodesEqual(expected,
+                apfSimulate(apfVersion, program, packet, data, 0 /* filterAge */));
+    }
+
     private static void assertVerdict(int apfVersion, int expected, ApfGenerator gen, byte[] packet,
             int filterAge) throws ApfGenerator.IllegalInstructionException {
         assertReturnCodesEqual(expected,
@@ -174,4 +209,134 @@
             throws ApfGenerator.IllegalInstructionException {
         assertVerdict(apfVersion, DROP, gen, new byte[MIN_PKT_SIZE], 0);
     }
+
+    /**
+     * The Mock ip client callback class.
+     */
+    public static class MockIpClientCallback extends IpClient.IpClientCallbacksWrapper {
+        private final ConditionVariable mGotApfProgram = new ConditionVariable();
+        private byte[] mLastApfProgram;
+
+        MockIpClientCallback() {
+            super(mock(IIpClientCallbacks.class), mock(SharedLog.class),
+                    NetworkInformationShimImpl.newInstance());
+        }
+
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mLastApfProgram = filter;
+            mGotApfProgram.open();
+        }
+
+        /**
+         * Reset the apf program and wait for the next update.
+         */
+        public void resetApfProgramWait() {
+            mGotApfProgram.close();
+        }
+
+        /**
+         * Assert the program is update within TIMEOUT_MS and return the program.
+         */
+        public byte[] assertProgramUpdateAndGet() {
+            assertTrue(mGotApfProgram.block(TIMEOUT_MS));
+            return mLastApfProgram;
+        }
+
+        /**
+         * Assert the program is not update within TIMEOUT_MS.
+         */
+        public void assertNoProgramUpdate() {
+            assertFalse(mGotApfProgram.block(TIMEOUT_MS));
+        }
+    }
+
+    /**
+     * The test apf filter class.
+     */
+    public static class TestApfFilter extends ApfFilter {
+        public static final byte[] MOCK_MAC_ADDR = {1, 2, 3, 4, 5, 6};
+        private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
+
+        private FileDescriptor mWriteSocket;
+        private long mCurrentTimeMs = SystemClock.elapsedRealtime();
+        private final MockIpClientCallback mMockIpClientCb;
+
+        public TestApfFilter(Context context, ApfConfiguration config,
+                MockIpClientCallback ipClientCallback) throws Exception {
+            this(context, config, ipClientCallback, new Dependencies(context));
+        }
+
+        public TestApfFilter(Context context, ApfConfiguration config,
+                MockIpClientCallback ipClientCallback, Dependencies dependencies) {
+            super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, dependencies);
+            mMockIpClientCb = ipClientCallback;
+        }
+
+        /**
+         * Create a new test ApfFiler.
+         */
+        public static ApfFilter createTestApfFilter(Context context,
+                MockIpClientCallback ipClientCallback, ApfConfiguration config,
+                ApfFilter.Dependencies dependencies) throws Exception {
+            LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+            LinkProperties lp = new LinkProperties();
+            lp.addLinkAddress(link);
+            TestApfFilter apfFilter = new TestApfFilter(context, config, ipClientCallback,
+                    dependencies);
+            apfFilter.setLinkProperties(lp);
+            return apfFilter;
+        }
+
+        /**
+         * Pretend an RA packet has been received and show it to ApfFilter.
+         */
+        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
+            mMockIpClientCb.resetApfProgramWait();
+            // ApfFilter's ReceiveThread will be waiting to read this.
+            Os.write(mWriteSocket, packet, 0, packet.length);
+        }
+
+        /**
+         * Simulate current time changes.
+         */
+        public void increaseCurrentTimeSeconds(int delta) {
+            mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS;
+        }
+
+        @Override
+        protected int secondsSinceBoot() {
+            return (int) (mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS);
+        }
+
+        @Override
+        public synchronized void maybeStartFilter() {
+            mHardwareAddress = MOCK_MAC_ADDR;
+            installNewProgramLocked();
+
+            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
+            FileDescriptor readSocket = new FileDescriptor();
+            mWriteSocket = new FileDescriptor();
+            try {
+                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+            } catch (ErrnoException e) {
+                fail();
+                return;
+            }
+            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
+            // This allows us to pretend RA packets have been received via pretendPacketReceived().
+            mReceiveThread = new ReceiveThread(readSocket);
+            mReceiveThread.start();
+        }
+
+        @Override
+        public void shutdown() {
+            super.shutdown();
+            if (mReceiveThread != null) {
+                mReceiveThread.halt();
+                mReceiveThread = null;
+            }
+            IoUtils.closeQuietly(mWriteSocket);
+        }
+    }
 }
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt
index d42396a..8cfa316 100644
--- a/tests/unit/src/android/net/apf/ApfV5Test.kt
+++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -71,6 +71,32 @@
                 "       0: write r0, 1",
                 "       2: write r0, 2",
                 "       4: write r0, 4"), ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(MIN_APF_VERSION)
+        gen.addDataCopy(1, 5)
+        gen.addPacketCopy(1000, 255)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(25, 1, 1), 1, 5,
+                encodeInstruction(25, 2, 0),
+                0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
+        ), program)
+        assertContentEquals(arrayOf(
+                "       0: dcopy 1, 5",
+                "       3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(MIN_APF_VERSION)
+        gen.addDataCopy(ApfGenerator.Register.R1, 0, 5)
+        gen.addPacketCopy(ApfGenerator.Register.R0, 1000, 255)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(21, 1, 1), 42, 0, 5,
+                encodeInstruction(21, 2, 0),
+                0, 41, 0x03.toByte(), 0xe8.toByte(), 0xff.toByte()
+        ), program)
+        assertContentEquals(arrayOf(
+                "       0: dcopy [r1+0], 5",
+                "       4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program))
     }
 
     private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
index de97ed4..00e480b 100644
--- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
+++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -20,7 +20,7 @@
 import androidx.test.runner.AndroidJUnit4
 import com.android.net.module.util.HexDump
 import com.android.testutils.assertThrows
-import java.nio.ByteBuffer
+import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -42,7 +42,7 @@
                 // IA prefix option(option_len=25)
                 "001A001900000000000000004000000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
-        val packet = Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
         assertTrue(packet is Dhcp6SolicitPacket)
     }
 
@@ -61,7 +61,7 @@
                 "001A001900000000000000004000000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
         assertThrows(Dhcp6Packet.ParseException::class.java) {
-                Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
+                Dhcp6Packet.decode(bytes, bytes.size)
         }
     }
 
@@ -80,7 +80,7 @@
                 "001A0019000000000000000040000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
         assertThrows(Dhcp6Packet.ParseException::class.java) {
-                Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
+                Dhcp6Packet.decode(bytes, bytes.size)
         }
     }
 
@@ -99,7 +99,7 @@
                 "001A001900000000000000004000000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
         assertThrows(Dhcp6Packet.ParseException::class.java) {
-                Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
+                Dhcp6Packet.decode(bytes, bytes.size)
         }
     }
 
@@ -119,7 +119,7 @@
                 // IA prefix option(option_len=25, prefix="fdfd:9ed6:7950:2::/64")
                 "001A00190000019F0000A8C040FDFD9ED6795000010000000000000000"
         val bytes = HexDump.hexStringToByteArray(advertiseHex)
-        val packet = Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
         assertTrue(packet is Dhcp6AdvertisePacket)
     }
 
@@ -142,7 +142,37 @@
                 "001A00190000019F0000A8C040FDFD9ED6795000010000000000000000"
         val bytes = HexDump.hexStringToByteArray(advertiseHex)
         // The unsupported option will be skipped normally and won't throw ParseException.
-        val packet = Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
         assertTrue(packet is Dhcp6AdvertisePacket)
     }
+
+    @Test
+    fun testDecodeDhcp6ReplyPacket() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // IA_PD option(option_len=70, including IA prefix option)
+            "0019004629cc56c7000000d300000152" +
+            // IA prefix option(option_len=25, prefix="2401:fa00:49c:412::/64", preferred=400,
+            // valid=1623)
+            "001a00190000019000000657402401fa00049c04120000000000000000" +
+            // IA prefix option(option_len=25, prefix="fdfd:9ed6:7950:2::/64", preferred=423,
+            // valid=43200)
+            "001a0019000001a70000a8c040fdfd9ed6795000020000000000000000"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
+        assertTrue(packet is Dhcp6ReplyPacket)
+        assertEquals(400, packet.prefixDelegation.minimalPreferredLifetime)
+        assertEquals(1623, packet.prefixDelegation.minimalValidLifetime)
+    }
 }
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index d8b2cc2..4e40fa2 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.clearInvocations;
@@ -86,6 +87,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -147,6 +150,8 @@
     @Mock private IpMemoryStoreService mIpMemoryStoreService;
     @Mock private InterfaceParams mInterfaceParams;
     @Mock private IpConnectivityLog mMetricsLog;
+    @Mock private FileDescriptor mFd;
+    @Mock private PrintWriter mWriter;
 
     private NetworkObserver mObserver;
     private InterfaceParams mIfParams;
@@ -702,7 +707,7 @@
         final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
                 ApfConfiguration.class);
         verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
-                any(), configCaptor.capture(), any(), any());
+                any(), configCaptor.capture(), any(), any(), anyBoolean());
 
         return configCaptor.getValue();
     }
@@ -771,7 +776,7 @@
         final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
                 ApfConfiguration.class);
         verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
-                any(), configCaptor.capture(), any(), any());
+                any(), configCaptor.capture(), any(), any(), anyBoolean());
         final ApfConfiguration actual = configCaptor.getValue();
         assertNotNull(actual);
         assertEquals(4, actual.apfCapabilities.apfVersionSupported);
@@ -782,6 +787,17 @@
     }
 
     @Test
+    public void testDumpApfFilter_withNoException() throws Exception {
+        final IpClient ipc = makeIpClient(TEST_IFNAME);
+        final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc,
+                false /* isApfSupported */);
+        assertNull(config.apfCapabilities);
+        clearInvocations(mDependencies);
+        ipc.dump(mFd, mWriter, null /* args */);
+        verifyShutdown(ipc);
+    }
+
+    @Test
     public void testApfUpdateCapabilities_nonNullInitialApfCapabilities() throws Exception {
         final IpClient ipc = makeIpClient(TEST_IFNAME);
         final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc,
@@ -793,7 +809,8 @@
                 8192 /* maxProgramSize */, 4 /* format */);
         ipc.updateApfCapabilities(newApfCapabilities);
         HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
-        verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any());
+        verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(),
+                anyBoolean());
         verifyShutdown(ipc);
     }
 
@@ -807,7 +824,8 @@
 
         ipc.updateApfCapabilities(null /* apfCapabilities */);
         HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
-        verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any());
+        verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(),
+                anyBoolean());
         verifyShutdown(ipc);
     }
 
diff --git a/tests/unit/src/android/net/shared/PrivateDnsConfigTest.java b/tests/unit/src/android/net/shared/PrivateDnsConfigTest.java
new file mode 100644
index 0000000..94f04d5
--- /dev/null
+++ b/tests/unit/src/android/net/shared/PrivateDnsConfigTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.shared;
+
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.PrivateDnsConfigParcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.InetAddress;
+
+@RunWith(JUnit4.class)
+public final class PrivateDnsConfigTest {
+    private static final int OFF_MODE = PRIVATE_DNS_MODE_OFF;
+    private static final int OPPORTUNISTIC_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+    private static final int STRICT_MODE = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+    private static final InetAddress[] TEST_ADDRS = new InetAddress[] {
+        InetAddress.parseNumericAddress("1.2.3.4"),
+        InetAddress.parseNumericAddress("2001:db8::2"),
+    };
+
+    private String[] toStringArray(InetAddress[] ips) {
+        String[] out = new String[ips.length];
+        int i = 0;
+        for (InetAddress ip : ips) {
+            out[i++] = ip.getHostAddress();
+        }
+        return out;
+    }
+
+    private void assertPrivateDnsConfigEquals(PrivateDnsConfig a, PrivateDnsConfig b) {
+        assertEquals(a.mode, b.mode);
+        assertEquals(a.hostname, b.hostname);
+        assertArrayEquals(a.ips, b.ips);
+        assertEquals(a.dohName, b.dohName);
+        assertArrayEquals(a.dohIps, b.dohIps);
+        assertEquals(a.dohPath, b.dohPath);
+        assertEquals(a.dohPort, b.dohPort);
+    }
+
+    private void assertParcelEquals(PrivateDnsConfig cfg, PrivateDnsConfigParcel parcel) {
+        assertEquals(parcel.privateDnsMode, cfg.mode);
+        assertEquals(parcel.hostname, cfg.hostname);
+        assertArrayEquals(parcel.ips, toStringArray(cfg.ips));
+        assertEquals(parcel.dohName, cfg.dohName);
+        assertEquals(parcel.dohPath, cfg.dohPath);
+        assertEquals(parcel.dohPort, cfg.dohPort);
+        assertArrayEquals(parcel.dohIps, toStringArray(cfg.dohIps));
+    }
+
+    // Tests both toParcel() and fromParcel() together.
+    private void testPrivateDnsConfigConversion(PrivateDnsConfig cfg) {
+        final PrivateDnsConfigParcel parcel = cfg.toParcel();
+        assertParcelEquals(cfg, parcel);
+
+        final PrivateDnsConfig convertedCfg = PrivateDnsConfig.fromParcel(parcel);
+        assertPrivateDnsConfigEquals(cfg, convertedCfg);
+    }
+
+    // Tests that a PrivateDnsConfig and a PrivateDnsConfig that is converted from
+    // PrivateDnsConfigParcel are equal.
+    @Test
+    public void testParcelableConversion() {
+        // Test the constructor: PrivateDnsConfig()
+        testPrivateDnsConfigConversion(new PrivateDnsConfig());
+
+        // Test the constructor: PrivateDnsConfig(boolean useTls)
+        testPrivateDnsConfigConversion(new PrivateDnsConfig(true));
+        testPrivateDnsConfigConversion(new PrivateDnsConfig(false));
+
+        // Test the constructor: PrivateDnsConfig(String hostname, InetAddress[] ips)
+        testPrivateDnsConfigConversion(new PrivateDnsConfig(null, null));
+        testPrivateDnsConfigConversion(new PrivateDnsConfig(null, TEST_ADDRS));
+        testPrivateDnsConfigConversion(new PrivateDnsConfig("dns.com", null));
+        testPrivateDnsConfigConversion(new PrivateDnsConfig("dns.com", TEST_ADDRS));
+
+        // Test the constructor:
+        // PrivateDnsConfig(int mode, String hostname, InetAddress[] ips,
+        //                  String dohName, InetAddress[] dohIps, String dohPath, int dohPort)
+        for (int mode : new int[] { OFF_MODE, OPPORTUNISTIC_MODE, STRICT_MODE }) {
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, null, null,
+                    null, null, null, -1));
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", null,
+                    null, null, null, -1));
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS,
+                    null, null, null, -1));
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS,
+                    "doh.com", null, null, -1));
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS,
+                    "doh.com", TEST_ADDRS, null, -1));
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS,
+                    "doh.com", TEST_ADDRS, "dohpath=/some-path{?dns}", -1));
+            testPrivateDnsConfigConversion(new PrivateDnsConfig(mode, "dns.com", TEST_ADDRS,
+                    "doh.com", TEST_ADDRS, "dohpath=/some-path{?dns}", 443));
+        }
+    }
+
+    @Test
+    public void testIpAddressArrayIsCopied() {
+        final InetAddress ip = InetAddress.parseNumericAddress("1.2.3.4");
+        final InetAddress[] ipArray = new InetAddress[] { ip };
+        final PrivateDnsConfig cfg = new PrivateDnsConfig(OPPORTUNISTIC_MODE, null /* hostname */,
+                ipArray /* ips */, null /* dohName */, ipArray /* dohIps */, null /* dohPath */,
+                -1 /* dohPort */);
+
+        ipArray[0] = InetAddress.parseNumericAddress("2001:db8::2");
+        assertArrayEquals(new InetAddress[] { ip }, cfg.ips);
+        assertArrayEquals(new InetAddress[] { ip }, cfg.dohIps);
+    }
+}
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
index 380f4ec..ff56f5f 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
@@ -242,4 +242,20 @@
 
         return info;
     }
+
+    @Test
+    public void testHashCode() {
+        final TcpInfo info = new TcpInfo(2, 1, 5);
+        final TcpInfo info2 = new TcpInfo(2, 1, 5);
+
+        assertEquals(info, info2);
+        assertEquals(info.hashCode(), info2.hashCode());
+    }
+
+    @Test
+    public void testDecodeWscale() {
+        assertEquals("0:0", TcpInfo.decodeWscale((byte) 0));
+        assertEquals("15:15", TcpInfo.decodeWscale((byte) 255));
+        assertEquals("15:0", TcpInfo.decodeWscale((byte) 240));
+    }
 }
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
index a697d8d..3857b04 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -16,8 +16,12 @@
 
 package com.android.networkstack.netlink;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.util.DataStallUtils.CONFIG_TCP_PACKETS_FAIL_PERCENTAGE;
 import static android.net.util.DataStallUtils.DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE;
+import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
 import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.system.OsConstants.AF_INET;
@@ -33,18 +37,22 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.IntDef;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.InetAddresses;
 import android.net.LinkProperties;
 import android.net.MarkMaskParcel;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.os.Build;
 import android.os.PowerManager;
 import android.util.Log;
@@ -57,6 +65,7 @@
 import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
 import libcore.util.HexEncoding;
@@ -71,6 +80,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -118,10 +129,22 @@
     private static final int TEST_NETID2_FWMARK = 0x1A85;
     private static final int NETID_MASK = 0xffff;
     private static final int TEST_UID1 = 1234;
+    private static final int TEST_UID2 = TEST_UID1 + 1;
     private static final short TEST_DST_PORT = 29113;
     private static final long TEST_COOKIE1 = 43387759684916L;
     private static final long TEST_COOKIE2 = TEST_COOKIE1 + 1;
     private static final InetAddress TEST_DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
+
+    private static final NetworkCapabilities CELL_METERED_CAPABILITIES =
+            new NetworkCapabilities()
+                    .addTransportType(TRANSPORT_CELLULAR)
+                    .addCapability(NET_CAPABILITY_INTERNET);
+
+    private static final NetworkCapabilities CELL_NOT_METERED_CAPABILITIES =
+            new NetworkCapabilities()
+                    .addTransportType(TRANSPORT_CELLULAR)
+                    .addCapability(NET_CAPABILITY_INTERNET)
+                    .addCapability(NET_CAPABILITY_NOT_METERED);
     @Mock private TcpSocketTracker.Dependencies mDependencies;
     @Mock private INetd mNetd;
     private final Network mNetwork = new Network(TEST_NETID1);
@@ -129,6 +152,7 @@
     private TerribleFailureHandler mOldWtfHandler;
     @Mock private Context mContext;
     @Mock private PowerManager mPowerManager;
+    @Mock private ConnectivityManager mCm;
 
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
@@ -146,11 +170,13 @@
                 eq(NAMESPACE_CONNECTIVITY),
                 eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE),
                 anyInt())).thenReturn(DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE);
-        when(mDependencies.shouldDisableInLightDoze()).thenReturn(true);
+        when(mDependencies.shouldDisableInLightDoze(anyBoolean())).thenReturn(true);
 
         when(mNetd.getFwmarkForNetwork(eq(TEST_NETID1)))
                 .thenReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID1_FWMARK));
+        doReturn(mContext).when(mDependencies).getContext();
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+        doReturn(mCm).when(mContext).getSystemService(ConnectivityManager.class);
     }
 
     @After
@@ -180,7 +206,8 @@
     public void testParseSockInfo() {
         final ByteBuffer buffer = getByteBuffer(SOCK_DIAG_TCP_INET_TEST_BYTES);
         final ArrayList<TcpSocketTracker.SocketInfo> infoList = new ArrayList<>();
-        TcpSocketTracker.parseMessage(buffer, AF_INET, infoList, 100L);
+        final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
+        tst.parseMessage(buffer, AF_INET, infoList, 100L);
         assertEquals(1, infoList.size());
         final TcpSocketTracker.SocketInfo parsed = infoList.get(0);
 
@@ -263,7 +290,7 @@
         testLp.addDnsServer(TEST_DNS1);
         tst.setLinkProperties(testLp);
         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
-                + composeSockDiagTcpHex(9, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2)
+                + composeSockDiagTcpHex(9, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1)
                 + NLMSG_DONE_HEX))
                 .when(mDependencies).recvMessage(any());
         assertTrue(tst.pollSocketsInfo());
@@ -281,7 +308,7 @@
         testLp.addValidatedPrivateDnsServer(TEST_DNS1);
         tst.setLinkProperties(testLp);
         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(10, 12)
-                + composeSockDiagTcpHex(11, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2)
+                + composeSockDiagTcpHex(11, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1)
                 + NLMSG_DONE_HEX))
                 .when(mDependencies).recvMessage(any());
         assertTrue(tst.pollSocketsInfo());
@@ -295,7 +322,7 @@
         // polling cycle.
         tst.setOpportunisticMode(false);
         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(11, 14)
-                + composeSockDiagTcpHex(13, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2)
+                + composeSockDiagTcpHex(13, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID1)
                 + NLMSG_DONE_HEX))
                 .when(mDependencies).recvMessage(any());
         assertTrue(tst.pollSocketsInfo());
@@ -305,6 +332,101 @@
         assertFalse(tst.isDataStallSuspected());
     }
 
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    @Test
+    public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_beforeT() throws Exception {
+        doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled();
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @Test
+    public void testPollSocketsInfo_ignoreBlockedUid_featureDisabled_TOrAbove() throws Exception {
+        doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled();
+        verify(mCm, never()).isUidNetworkingBlocked(anyInt(), anyBoolean());
+    }
+
+    private void doTestPollSocketsInfo_ignoreBlockedUid_featureDisabled() throws Exception {
+        doReturn(false).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
+        final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
+        // Simulate 1 message with data stall happened.
+        doReturn(getByteBufferFromHexString(
+                composeSockDiagTcpHex(4, 10) + NLMSG_DONE_HEX))
+                .when(mDependencies).recvMessage(any());
+        assertTrue(tst.pollSocketsInfo());
+        // 4 retran / 10 sent = 40 percent.
+        assertEquals(40, tst.getLatestPacketFailPercentage());
+        assertEquals(10, tst.getSentSinceLastRecv());
+        assertFalse(tst.isDataStallSuspected());
+
+        // With the feature disabled, append another message with blocked uid, verify the
+        // traffic of networking-blocked uid is not filtered.
+        doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
+                + composeSockDiagTcpHex(5, 10, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2)
+                + NLMSG_DONE_HEX))
+                .when(mDependencies).recvMessage(any());
+        assertTrue(tst.pollSocketsInfo());
+        // 5 + 5 retrans / 10 sent = 100 percent.
+        assertEquals(100, tst.getLatestPacketFailPercentage());
+        assertEquals(20, tst.getSentSinceLastRecv());
+        assertTrue(tst.isDataStallSuspected());
+    }
+
+    // The feature is not enabled on pre-T device, because it needs bpf support.
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @Test
+    public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled() throws Exception {
+        doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
+        final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
+        tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES);
+        doReturn(true).when(mCm).isUidNetworkingBlocked(TEST_UID2, false /* metered */);
+        // With the feature enabled, append another message with blocked uid, verify the
+        // traffic of networking-blocked uid is filtered out.
+        doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10)
+                + composeSockDiagTcpHex(6, 12, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2)
+                + NLMSG_DONE_HEX))
+                .when(mDependencies).recvMessage(any());
+        assertTrue(tst.pollSocketsInfo());
+        assertEquals(40, tst.getLatestPacketFailPercentage());
+        assertEquals(10, tst.getSentSinceLastRecv());
+        assertFalse(tst.isDataStallSuspected());
+
+        // Unblock traffic of the uid, verify the traffic of the uid is not filtered.
+        doReturn(false).when(mCm).isUidNetworkingBlocked(TEST_UID2, false /* metered */);
+        doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(4, 10)
+                + composeSockDiagTcpHex(8, 14, DNS_OVER_TLS_PORT, TEST_COOKIE2, TEST_UID2)
+                + NLMSG_DONE_HEX))
+                .when(mDependencies).recvMessage(any());
+        assertTrue(tst.pollSocketsInfo());
+        // Lost 2 / 2 sent = 100 percent.
+        assertEquals(100, tst.getLatestPacketFailPercentage());
+        assertEquals(12, tst.getSentSinceLastRecv());
+        assertTrue(tst.isDataStallSuspected());
+    }
+
+    // The feature is not enabled on pre-T device, because it needs bpf support.
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @Test
+    public void testPollSocketsInfo_ignoreBlockedUid_featureEnabled_dataSaver() throws Exception {
+        doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
+        final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
+
+        tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES);
+        final ByteBuffer mockMessage = getByteBufferFromHexString(composeSockDiagTcpHex(4, 10)
+                + NLMSG_DONE_HEX);
+        doReturn(mockMessage).when(mDependencies).recvMessage(any());
+        assertTrue(tst.pollSocketsInfo());
+        verify(mCm).isUidNetworkingBlocked(TEST_UID1, false /* metered */);
+
+        // Verify the metered parameter will be correctly passed to ConnectivityManager.
+        tst.setNetworkCapabilities(CELL_METERED_CAPABILITIES);
+        mockMessage.rewind(); // Reset read position to 0 since the same buffer is used.
+        assertTrue(tst.pollSocketsInfo());
+        verify(mCm).isUidNetworkingBlocked(TEST_UID1, true /* metered */);
+
+        // Correctness of the logic which handling different blocked status is
+        // verified in other tests, see {@code testPollSocketsInfo_ignoreBlockedUid_featureEnabled}.
+    }
+
     @Test
     public void testTcpInfoParsingWithMultipleMsgs() throws Exception {
         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
@@ -469,133 +591,160 @@
     }
 
     private static String composeSockDiagTcpHex(int retrans, int sent) {
-        return composeSockDiagTcpHex(retrans, sent, TEST_DST_PORT, TEST_COOKIE1);
+        return composeSockDiagTcpHex(retrans, sent, TEST_DST_PORT, TEST_COOKIE1, TEST_UID1);
     }
 
-    private static String composeSockDiagTcpHex(int retrans, int sent, short dstPort, long cookie) {
+    private static String composeSockDiagTcpHex(int retrans, int sent, short dstPort,
+            long cookie, int uid) {
         return // struct nlmsghdr.
-                "14010000" +        // length = 276
-                "1400" +            // type = SOCK_DIAG_BY_FAMILY
-                "0301" +            // flags = NLM_F_REQUEST | NLM_F_DUMP
-                "00000000" +        // seqno
-                "00000000" +        // pid (0 == kernel)
+                "14010000"          // length = 276
+                + "1400"            // type = SOCK_DIAG_BY_FAMILY
+                + "0301"            // flags = NLM_F_REQUEST | NLM_F_DUMP
+                + "00000000"        // seqno
+                + "00000000"        // pid (0 == kernel)
                 // struct inet_diag_req_v2
-                "02" +              // family = AF_INET
-                "06" +              // state
-                "00" +              // timer
-                "00" +              // retrans
+                + "02"              // family = AF_INET
+                + "06"              // state
+                + "00"              // timer
+                + "00"              // retrans
                 // inet_diag_sockid: ports and addresses are always in big endian,
                 // see StructInetDiagSockId.
-                "DEA5" +                                               // idiag_sport = 56997
-                getHexStringFromShort(dstPort, ByteOrder.BIG_ENDIAN) + // idiag_dport
-                "0a006402000000000000000000000000" +                   // idiag_src = 10.0.100.2
-                "08080808000000000000000000000000" +                   // idiag_dst = 8.8.8.8
-                "00000000" +                                           // idiag_if
-                getHexStringFromLong(cookie) +                         // idiag_cookie
-                "00000000" +                                           // idiag_expires
-                "00000000" +                                           // idiag_rqueue
-                "00000000" +                                           // idiag_wqueue
-                getHexStringFromInt(TEST_UID1) +                       // idiag_uid
-                "00000000" +                                           // idiag_inode
+                + "DEA5"                                                // idiag_sport = 56997
+                + getHexStringFromShort(dstPort, ByteOrder.BIG_ENDIAN)  // idiag_dport
+                + "0a006402000000000000000000000000"                    // idiag_src = 10.0.100.2
+                + "08080808000000000000000000000000"                    // idiag_dst = 8.8.8.8
+                + "00000000"                                            // idiag_if
+                + getHexStringFromLong(cookie)                          // idiag_cookie
+                + "00000000"                                            // idiag_expires
+                + "00000000"                                            // idiag_rqueue
+                + "00000000"                                            // idiag_wqueue
+                + getHexStringFromInt(uid)                              // idiag_uid
+                + "00000000"                                            // idiag_inode
                 // rtattr
-                "0500" +            // len = 5
-                "0800" +            // type = 8
-                "00000000" +        // data
-                "0800" +            // len = 8
-                "0F00" +            // type = 15(INET_DIAG_MARK)
-                "850A0C00" +        // data, socket mark=789125
-                "AC00" +            // len = 172
-                "0200" +            // type = 2(INET_DIAG_INFO)
+                + "0500"            // len = 5
+                + "0800"            // type = 8
+                + "00000000"        // data
+                + "0800"            // len = 8
+                + "0F00"            // type = 15(INET_DIAG_MARK)
+                + "850A0C00"        // data, socket mark=789125
+                + "AC00"            // len = 172
+                + "0200"            // type = 2(INET_DIAG_INFO)
                 // tcp_info
-                "01" +              // state = TCP_ESTABLISHED
-                "00" +              // ca_state = TCP_CA_OPEN
-                "05" +              // retransmits = 5
-                "00" +              // probes = 0
-                "00" +              // backoff = 0
-                "07" +              // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
-                "88" +              // wscale = 8
-                "00" +              // delivery_rate_app_limited = 0
-                "4A911B00" +        // rto = 1806666
-                "00000000" +        // ato = 0
-                "2E050000" +        // sndMss = 1326
-                "18020000" +        // rcvMss = 536
-                "00000000" +        // unsacked = 0
-                "00000000" +        // acked = 0
-                "00000000" +        // lost
-                "00000000" +        // retrans = 0
-                "00000000" +        // fackets = 0
-                "BB000000" +        // lastDataSent = 187
-                "00000000" +        // lastAckSent = 0
-                "BB000000" +        // lastDataRecv = 187
-                "BB000000" +        // lastDataAckRecv = 187
-                "DC050000" +        // pmtu = 1500
-                "30560100" +        // rcvSsthresh = 87600
-                "3E2C0900" +        // rttt = 601150
-                "1F960400" +        // rttvar = 300575
-                "78050000" +        // sndSsthresh = 1400
-                "0A000000" +        // sndCwnd = 10
-                "A8050000" +        // advmss = 1448
-                "03000000" +        // reordering = 3
-                "00000000" +        // rcvrtt = 0
-                "30560100" +        // rcvspace = 87600
-                getHexStringFromInt(retrans) +   // totalRetrans
-                "53AC000000000000" +    // pacingRate = 44115
-                "FFFFFFFFFFFFFFFF" +    // maxPacingRate = 18446744073709551615
-                "0100000000000000" +    // bytesAcked = 1
-                "0000000000000000" +    // bytesReceived = 0
-                getHexStringFromInt(sent) + // SegsOut
-                "00000000" +        // SegsIn = 0
-                "00000000" +        // NotSentBytes = 0
-                "3E2C0900" +        // minRtt = 601150
-                "00000000" +        // DataSegsIn = 0
-                "00000000" +        // DataSegsOut = 0
-                "0000000000000000"; // deliverRate = 0
+                + "01"              // state = TCP_ESTABLISHED
+                + "00"              // ca_state = TCP_CA_OPEN
+                + "05"              // retransmits = 5
+                + "00"              // probes = 0
+                + "00"              // backoff = 0
+                + "07"              // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
+                + "88"              // wscale = 8
+                + "00"              // delivery_rate_app_limited = 0
+                + "4A911B00"        // rto = 1806666
+                + "00000000"        // ato = 0
+                + "2E050000"        // sndMss = 1326
+                + "18020000"        // rcvMss = 536
+                + "00000000"        // unsacked = 0
+                + "00000000"        // acked = 0
+                + "00000000"        // lost
+                + "00000000"        // retrans = 0
+                + "00000000"        // fackets = 0
+                + "BB000000"        // lastDataSent = 187
+                + "00000000"        // lastAckSent = 0
+                + "BB000000"        // lastDataRecv = 187
+                + "BB000000"        // lastDataAckRecv = 187
+                + "DC050000"        // pmtu = 1500
+                + "30560100"        // rcvSsthresh = 87600
+                + "3E2C0900"        // rttt = 601150
+                + "1F960400"        // rttvar = 300575
+                + "78050000"        // sndSsthresh = 1400
+                + "0A000000"        // sndCwnd = 10
+                + "A8050000"        // advmss = 1448
+                + "03000000"        // reordering = 3
+                + "00000000"        // rcvrtt = 0
+                + "30560100"        // rcvspace = 87600
+                + getHexStringFromInt(retrans)   // totalRetrans
+                + "53AC000000000000"    // pacingRate = 44115
+                + "FFFFFFFFFFFFFFFF"    // maxPacingRate = 18446744073709551615
+                + "0100000000000000"    // bytesAcked = 1
+                + "0000000000000000"    // bytesReceived = 0
+                + getHexStringFromInt(sent) // SegsOut
+                + "00000000"        // SegsIn = 0
+                + "00000000"        // NotSentBytes = 0
+                + "3E2C0900"        // minRtt = 601150
+                + "00000000"        // DataSegsIn = 0
+                + "00000000"        // DataSegsOut = 0
+                + "0000000000000000"; // deliverRate = 0
     }
 
+    private static final int DEEP_DOZE = 0;
+    private static final int LIGHT_DOZE = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            DEEP_DOZE,
+            LIGHT_DOZE
+    })
+    private @interface DozeModeType {}
+
     @Test
-    public void testTcpInfoParsingWithDozeMode() throws Exception {
-        final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
-        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
+    public void testTcpInfoParsingWithDozeMode_enabled() throws Exception {
+        doReturn(false).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
+        doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
+        doTestTcpInfoDisableParsingWithDozeMode(DEEP_DOZE, true /* featureEnabled */);
+    }
 
-        verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
-        setupNormalTestTcpInfo();
-        assertTrue(tst.pollSocketsInfo());
-
-        // Lower the threshold.
-        when(mDependencies.getDeviceConfigPropertyInt(any(), eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE),
-                anyInt())).thenReturn(40);
-
-        // Trigger a config update.
-        tst.mConfigListener.onPropertiesChanged(null /* properties */);
-        assertEquals(10, tst.getSentSinceLastRecv());
-        assertEquals(50, tst.getLatestPacketFailPercentage());
-        assertTrue(tst.isDataStallSuspected());
-
-        // Enable doze mode, verify counters are not updated.
-        doReturn(true).when(mPowerManager).isDeviceIdleMode();
-        final BroadcastReceiver receiver = receiverCaptor.getValue();
-        receiver.onReceive(mContext, new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
-        assertFalse(tst.pollSocketsInfo());
-        assertEquals(10, tst.getSentSinceLastRecv());
-        assertEquals(50, tst.getLatestPacketFailPercentage());
-        assertFalse(tst.isDataStallSuspected());
+    // Ignore blocked uids is supported on T. Thus, for pre-T device this feature is always
+    // needed since there is no replacement.
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @Test
+    public void testTcpInfoParsingWithDozeMode_disabled() throws Exception {
+        doReturn(true).when(mDependencies).shouldIgnoreTcpInfoForBlockedUids();
+        doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
+        doTestTcpInfoDisableParsingWithDozeMode(DEEP_DOZE, false /* featureEnabled */);
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     public void testTcpInfoDisableParsingWithLightDozeMode_enabled() throws Exception {
+        doReturn(true).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
+        doTestTcpInfoDisableParsingWithDozeMode(LIGHT_DOZE, true /* featureEnabled */);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testTcpInfoDisableParsingWithLightDozeMode_disabled() throws Exception {
+        doReturn(false).when(mDependencies).shouldDisableInLightDoze(anyBoolean());
+        doTestTcpInfoDisableParsingWithDozeMode(LIGHT_DOZE, false /* featureEnabled */);
+    }
+
+    private void doTestTcpInfoDisableParsingWithDozeMode(@DozeModeType int dozeModeType,
+            boolean featureEnabled) throws Exception {
         final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
+        tst.setNetworkCapabilities(CELL_NOT_METERED_CAPABILITIES);
         final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
-        // Enable light doze mode with 1 netlink message.
-        verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
+        // Enable doze mode with 1 netlink message.
+        verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(),
+                anyBoolean(), anyBoolean());
         final BroadcastReceiver receiver = receiverCaptor.getValue();
-        doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
-        receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        if (dozeModeType == DEEP_DOZE) {
+            doReturn(true).when(mPowerManager).isDeviceIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
+        } else {
+            doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        }
         doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
                 + NLMSG_DONE_HEX)).when(mDependencies).recvMessage(any());
 
+        if (!featureEnabled) {
+            // Verify TcpInfo is still processed.
+            assertTrue(tst.pollSocketsInfo());
+            assertEquals(10, tst.getSentSinceLastRecv());
+            // Lost 4 + default 5 retrans / 10 sent.
+            assertEquals(90, tst.getLatestPacketFailPercentage());
+            assertTrue(tst.isDataStallSuspected());
+            return;
+        }
+
         // Verify counters are not updated.
         assertFalse(tst.pollSocketsInfo());
         assertEquals(0, tst.getSentSinceLastRecv());
@@ -603,9 +752,14 @@
         assertEquals(-1, tst.getLatestPacketFailPercentage());
         assertFalse(tst.isDataStallSuspected());
 
-        // Disable light doze mode, verify polling are processed and counters are updated.
-        doReturn(false).when(mPowerManager).isDeviceLightIdleMode();
-        receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        // Disable deep/light doze mode, verify polling are processed and counters are updated.
+        if (dozeModeType == DEEP_DOZE) {
+            doReturn(false).when(mPowerManager).isDeviceIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
+        } else {
+            doReturn(false).when(mPowerManager).isDeviceLightIdleMode();
+            receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        }
         assertTrue(tst.pollSocketsInfo());
         assertEquals(10, tst.getSentSinceLastRecv());
         // Lost 4 + default 5 retrans / 10 sent.
@@ -613,28 +767,6 @@
         assertTrue(tst.isDataStallSuspected());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
-    public void testTcpInfoDisableParsingWithLightDozeMode_disabled() throws Exception {
-        when(mDependencies.shouldDisableInLightDoze()).thenReturn(false);
-        final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork);
-        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-
-        // Enable light doze mode with 1 netlink message.
-        verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
-        final BroadcastReceiver receiver = receiverCaptor.getValue();
-        doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
-        receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
-        doReturn(getByteBufferFromHexString(composeSockDiagTcpHex(9, 10)
-                + NLMSG_DONE_HEX)).when(mDependencies).recvMessage(any());
-
-        // Verify TcpInfo is still processed.
-        assertTrue(tst.pollSocketsInfo());
-        assertEquals(10, tst.getSentSinceLastRecv());
-        assertEquals(90, tst.getLatestPacketFailPercentage());
-        assertTrue(tst.isDataStallSuspected());
-    }
-
     private void setupNormalTestTcpInfo() throws Exception {
         final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES);
         final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES);
@@ -643,28 +775,28 @@
 
     private static final String BAD_DIAG_MSG_HEX =
         // struct nlmsghdr.
-            "00000058" +      // length = 1476395008
-            "1400" +         // type = SOCK_DIAG_BY_FAMILY
-            "0301" +         // flags = NLM_F_REQUEST | NLM_F_DUMP
-            "00000000" +     // seqno
-            "00000000" +     // pid (0 == kernel)
+            "00000058"      // length = 1476395008
+            + "1400"         // type = SOCK_DIAG_BY_FAMILY
+            + "0301"         // flags = NLM_F_REQUEST | NLM_F_DUMP
+            + "00000000"     // seqno
+            + "00000000"     // pid (0 == kernel)
             // struct inet_diag_req_v2
-            "02" +           // family = AF_INET
-            "06" +           // state
-            "00" +           // timer
-            "00" +           // retrans
+            + "02"           // family = AF_INET
+            + "06"           // state
+            + "00"           // timer
+            + "00"           // retrans
             // inet_diag_sockid
-            "DEA5" +         // idiag_sport = 42462
-            "71B9" +         // idiag_dport = 47473
-            "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
-            "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
-            "00000000" +    // idiag_if
-            "34ED000076270000" + // idiag_cookie = 43387759684916
-            "00000000" +    // idiag_expires
-            "00000000" +    // idiag_rqueue
-            "00000000" +    // idiag_wqueue
-            "00000000" +    // idiag_uid
-            "00000000";    // idiag_inode
+            + "DEA5"         // idiag_sport = 42462
+            + "71B9"         // idiag_dport = 47473
+            + "0a006402000000000000000000000000" // idiag_src = 10.0.100.2
+            + "08080808000000000000000000000000" // idiag_dst = 8.8.8.8
+            + "00000000"    // idiag_if
+            + "34ED000076270000" // idiag_cookie = 43387759684916
+            + "00000000"    // idiag_expires
+            + "00000000"    // idiag_rqueue
+            + "00000000"    // idiag_wqueue
+            + "00000000"    // idiag_uid
+            + "00000000";    // idiag_inode
     private static final byte[] BAD_SOCK_DIAG_MSG_BYTES =
         HexEncoding.decode(BAD_DIAG_MSG_HEX.toCharArray(), false);
 
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 3f2ff31..5be2573 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -32,6 +32,7 @@
 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_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
@@ -62,6 +63,7 @@
 import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
 import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT;
 import static com.android.networkstack.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.REEVALUATE_WHEN_RESUME;
 import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS;
 import static com.android.server.connectivity.NetworkMonitor.extractCharset;
 
@@ -319,10 +321,17 @@
         return lp;
     }
 
-    private static final NetworkCapabilities CELL_METERED_CAPABILITIES = new NetworkCapabilities()
+    private static final NetworkCapabilities CELL_SUSPENDED_METERED_CAPABILITIES =
+            new NetworkCapabilities()
             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
             .addCapability(NET_CAPABILITY_INTERNET);
 
+    private static final NetworkCapabilities CELL_METERED_CAPABILITIES =
+            new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NET_CAPABILITY_INTERNET)
+            .addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+
     private static final NetworkCapabilities CELL_NOT_METERED_CAPABILITIES =
             new NetworkCapabilities()
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
@@ -554,6 +563,7 @@
         initHttpConnection(mHttpConnection);
         initHttpConnection(mHttpsConnection);
         initHttpConnection(mFallbackConnection);
+        initHttpConnection(mOtherFallbackConnection);
 
         mFakeDns = new FakeDns();
         mFakeDns.startMocking();
@@ -2290,6 +2300,64 @@
     }
 
     @Test
+    public void testReevaluationInterval_networkResume() throws Exception {
+        // Setup nothing and expect validation to fail.
+        doReturn(true).when(mDependencies)
+                .isFeatureNotChickenedOut(any(), eq(REEVALUATE_WHEN_RESUME));
+        final NetworkMonitor nm = runFailedNetworkTest();
+        verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */,
+                1 /* interactions */);
+        // Reevaluation delay doubled right after 1st validation failure.
+        assertEquals(INITIAL_REEVALUATE_DELAY_MS * 2, nm.getReevaluationDelayMs());
+
+        // Suspend the network. Verify re-evaluation count does not increase.
+        setNetworkCapabilities(nm, CELL_SUSPENDED_METERED_CAPABILITIES);
+        verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */,
+                1 /* interactions */);
+        // Verify the count does not increase.
+        assertEquals(INITIAL_REEVALUATE_DELAY_MS * 2, nm.getReevaluationDelayMs());
+
+        // Resume the network, verify re-evaluation runs immediately and the timer resets.
+        setNetworkCapabilities(nm, CELL_METERED_CAPABILITIES);
+        // Wait for another idle to prevent from flaky because the handler fires another message
+        // to re-evaluate.
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        assertEquals(INITIAL_REEVALUATE_DELAY_MS, nm.getReevaluationDelayMs());
+        verifyNetworkTested(VALIDATION_RESULT_INVALID, 0 /* probesSucceeded */,
+                2 /* interactions */);
+    }
+
+    @Test
+    public void testReevaluationInterval_verifiedNetwork() throws Exception {
+        final WrappedNetworkMonitor wnm = prepareValidatedStateNetworkMonitor(
+                CELL_METERED_CAPABILITIES);
+        assertEquals(INITIAL_REEVALUATE_DELAY_MS, wnm.getReevaluationDelayMs());
+
+        // Suspend the network. Verify re-evaluation count does not increase.
+        setNetworkCapabilities(wnm, CELL_SUSPENDED_METERED_CAPABILITIES);
+        verifyNetworkTestedValidFromHttps(1 /* interactions */);
+        assertEquals(INITIAL_REEVALUATE_DELAY_MS, wnm.getReevaluationDelayMs());
+
+        // Resume the network. Verify re-evaluation count does not increase.
+        setNetworkCapabilities(wnm, CELL_METERED_CAPABILITIES);
+        verifyNetworkTestedValidFromHttps(1 /* interactions */);
+        assertEquals(INITIAL_REEVALUATE_DELAY_MS, wnm.getReevaluationDelayMs());
+    }
+
+    @Test
+    public void testTcpSocketTracker_setCapabilities() throws Exception {
+        setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
+        final InOrder inOrder = inOrder(mTst);
+        final WrappedNetworkMonitor wnm = prepareValidatedStateNetworkMonitor(
+                CELL_METERED_CAPABILITIES);
+        inOrder.verify(mTst).setNetworkCapabilities(eq(CELL_METERED_CAPABILITIES));
+
+        // Suspend the network. Verify the capabilities would be passed to TcpSocketTracker.
+        setNetworkCapabilities(wnm, CELL_SUSPENDED_METERED_CAPABILITIES);
+        inOrder.verify(mTst).setNetworkCapabilities(eq(CELL_SUSPENDED_METERED_CAPABILITIES));
+    }
+
+    @Test
     public void testDataStall_setOpportunisticMode() {
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
@@ -2328,7 +2396,7 @@
     private void testDataStall_StallDnsSuspectedAndSendMetrics(int transport,
             NetworkCapabilities nc) throws Exception {
         // NM suspects data stall from DNS signal and sends data stall metrics.
-        final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
+        final WrappedNetworkMonitor nm = prepareValidatedStateNetworkMonitor(nc);
         makeDnsTimeoutEvent(nm, 5);
         // Trigger a dns signal to start evaluate data stall and upload metrics.
         nm.notifyDnsResponse(RETURN_CODE_DNS_TIMEOUT);
@@ -2338,7 +2406,7 @@
 
     @Test
     public void testDataStall_NoStallSuspectedAndSendMetrics() throws Exception {
-        final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(
+        final WrappedNetworkMonitor nm = prepareValidatedStateNetworkMonitor(
                 CELL_METERED_CAPABILITIES);
         // Setup no data stall dns signal.
         makeDnsTimeoutEvent(nm, 3);
@@ -2360,12 +2428,11 @@
 
     private void testDataStall_StallTcpSuspectedAndSendMetrics(NetworkCapabilities nc)
             throws Exception {
-        assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
         setupTcpDataStall();
-        setTcpPollingInterval(0);
+        setTcpPollingInterval(1);
         // NM suspects data stall from TCP signal and sends data stall metrics.
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
-        final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
+        final WrappedNetworkMonitor nm = prepareValidatedStateNetworkMonitor(nc);
         // Trigger a tcp event immediately.
         nm.sendTcpPollingEvent();
         // Allow only one transport type in the context of this test for simplification.
@@ -2374,7 +2441,7 @@
         verifySendDataStallDetectionStats(nm, DATA_STALL_EVALUATION_TYPE_TCP, transports[0]);
     }
 
-    private WrappedNetworkMonitor prepareNetworkMonitorForVerifyDataStall(NetworkCapabilities nc)
+    private WrappedNetworkMonitor prepareValidatedStateNetworkMonitor(NetworkCapabilities nc)
             throws Exception {
         // Connect a VALID network to simulate the data stall detection because data stall
         // evaluation will only start from validated state.
@@ -2411,7 +2478,9 @@
                 ArgumentCaptor.forClass(CaptivePortalProbeResult.class);
         final ArgumentCaptor<DataStallDetectionStats> statsCaptor =
                 ArgumentCaptor.forClass(DataStallDetectionStats.class);
-        verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(1))
+        // TCP data stall detection may be triggered more than once because NM stays in the
+        // ValidatedState and polling timer is set to 0.
+        verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).atLeast(1))
                 .writeDataStallDetectionStats(statsCaptor.capture(), probeResultCaptor.capture());
         // Ensure probe will not stop due to rate-limiting mechanism.
         nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
@@ -2566,26 +2635,22 @@
 
     @Test
     public void testCollectDataStallMetrics_TcpWithCellular() {
-        assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
         testDataStallMetricsWithCellular(DATA_STALL_EVALUATION_TYPE_TCP);
     }
 
     @Test
     public void testCollectDataStallMetrics_TcpWithWiFi() {
-        assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
         testDataStallMetricsWithWiFi(DATA_STALL_EVALUATION_TYPE_TCP);
     }
 
     @Test
     public void testCollectDataStallMetrics_TcpAndDnsWithWifi() {
-        assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
         testDataStallMetricsWithWiFi(
                 DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS);
     }
 
     @Test
     public void testCollectDataStallMetrics_TcpAndDnsWithCellular() {
-        assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
         testDataStallMetricsWithCellular(
                 DATA_STALL_EVALUATION_TYPE_TCP | DATA_STALL_EVALUATION_TYPE_DNS);
     }
@@ -2672,19 +2737,17 @@
 
     @Test
     public void testNotifyNetwork_WithforceReevaluation() throws Exception {
+        // Set validated result for both HTTP and HTTPS probes.
         setValidProbes();
         final NetworkMonitor nm = runValidatedNetworkTest();
         // Verify forceReevaluation will not reset the validation result but only probe result until
         // getting the validation result.
         setSslException(mHttpsConnection);
-        setStatus(mHttpConnection, 500);
-        setStatus(mFallbackConnection, 204);
         nm.forceReevaluation(Process.myUid());
         // Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
-        verifyNetworkTested(VALIDATION_RESULT_INVALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK,
+        verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
                 1 /* interactions */);
-        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
     }
 
     @Test