Rename groupHint to cluster. am: c6ad18a80d am: 3453e859fe am: 9dc17ef6bc

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

Change-Id: I5db8a596a049c2ed4c92a075208461047ea147e4
diff --git a/Android.bp b/Android.bp
index ccdaf7a..37e001c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -87,11 +87,13 @@
     libs: ["unsupportedappusage"],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-V3-java",
+        "netd_aidl_interface-java",
         "netlink-client",
         "networkstack-client",
+        "net-utils-framework-common",
+        // See note on statsprotos when adding/updating proto build rules
         "datastallprotosnano",
-        "networkstackprotosnano",
+        "statsprotos",
         "captiveportal-lib",
     ],
     plugins: ["java_api_finder"],
@@ -126,7 +128,7 @@
     defaults: ["NetworkStackReleaseApiLevel"],
     srcs: [
         "src/**/*.java",
-        ":statslog-networkstack-java-gen-q",
+        ":statslog-networkstack-java-gen",
     ],
     // API stable uses a jarjared version of the shims
     static_libs: [
@@ -214,7 +216,10 @@
     defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
     static_libs: ["NetworkStackApiStableLib"],
     manifest: "AndroidManifestBase.xml",
-    visibility: ["//frameworks/base/tests/net/integration"],
+    visibility: [
+        "//frameworks/base/tests/net/integration",
+        "//cts/tests/tests/net",
+    ],
 }
 
 cc_library_shared {
@@ -249,15 +254,6 @@
 }
 
 genrule {
-    name: "statslog-networkstack-java-gen-q",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
-         " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
-         " --compileQ",
-    out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
-}
-
-genrule {
     name: "statslog-networkstack-java-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
@@ -288,3 +284,19 @@
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
 }
+
+// When adding or modifying protos, the jarjar rules and possibly proguard rules need
+// to be updated: proto libraries may pull additional static libraries.
+java_library_static {
+    name: "statsprotos",
+    proto: {
+        type: "lite",
+    },
+    srcs: [
+        "src/com/android/networkstack/metrics/stats.proto",
+    ],
+    static_libs: [
+        "networkstackprotos",
+    ],
+    defaults: ["NetworkStackReleaseApiLevel"],
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
index 0c75f27..5639386 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -93,6 +93,16 @@
     }
 
     @Override
+    public long getByteLimit() {
+        return mData.getByteLimit();
+    }
+
+    @Override
+    public long getExpiryTimeMillis() {
+        return mData.getExpiryTimeMillis();
+    }
+
+    @Override
     public Uri getUserPortalUrl() {
         return mData.getUserPortalUrl();
     }
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
index eb4fc07..27fd745 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
@@ -37,4 +37,9 @@
             DataStallReport.DETECTION_METHOD_DNS_EVENTS;
     public static final int DETECTION_METHOD_TCP_METRICS =
             DataStallReport.DETECTION_METHOD_TCP_METRICS;
+
+    /**
+     * @see android.net.NetworkCapabilities
+     */
+    public static final int TRANSPORT_TEST = 7;
 }
diff --git a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java b/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
index a328720..0184845 100644
--- a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
@@ -30,4 +30,8 @@
      */
     @VisibleForTesting
     public static final int VERSION = 31;
+
+    // When removing this shim, the version in NetworkMonitorUtils should be removed too.
+    // TODO: add TRANSPORT_TEST to system API in API 31 (it is only a test API as of R)
+    public static final int TRANSPORT_TEST = 7;
 }
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
index fe99c13..a18ba49 100644
--- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -30,6 +30,16 @@
     boolean isCaptive();
 
     /**
+     * @see android.net.CaptivePortalData#getByteLimit()
+     */
+    long getByteLimit();
+
+    /**
+     * @see android.net.CaptivePortalData#getExpiryTimeMillis()
+     */
+    long getExpiryTimeMillis();
+
+    /**
      * @see android.net.CaptivePortalData#getUserPortalUrl()
      */
     Uri getUserPortalUrl();
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 93ee00c..9dfec42 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -22,7 +22,6 @@
     srcs: [
         "src/android/net/util/SharedLog.java",
         "src/android/net/shared/InitialConfiguration.java",
-        "src/android/net/shared/IpConfigurationParcelableUtil.java",
         "src/android/net/shared/Layer2Information.java",
         "src/android/net/shared/LinkPropertiesParcelableUtil.java",
         "src/android/net/shared/ParcelableUtil.java",
diff --git a/common/moduleutils/src/android/net/shared/NetdUtils.java b/common/moduleutils/src/android/net/shared/NetdUtils.java
index 0137bb7..5fa29c9 100644
--- a/common/moduleutils/src/android/net/shared/NetdUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetdUtils.java
@@ -17,6 +17,7 @@
 package android.net.shared;
 
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.system.OsConstants.EBUSY;
 
 import android.net.INetd;
 import android.net.IpPrefix;
@@ -24,6 +25,8 @@
 import android.net.TetherConfigParcel;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -33,6 +36,8 @@
  * @hide
  */
 public class NetdUtils {
+    private static final String TAG = NetdUtils.class.getSimpleName();
+
     /** Start tethering. */
     public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy,
             final String[] dhcpRange) throws RemoteException, ServiceSpecificException {
@@ -45,14 +50,46 @@
     /** Setup interface for tethering. */
     public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
             throws RemoteException, ServiceSpecificException {
-        netd.tetherInterfaceAdd(iface);
+        tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+    }
 
-        netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+    /** Setup interface with configurable retries for tethering. */
+    public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
+            int maxAttempts, int pollingIntervalMs)
+            throws RemoteException, ServiceSpecificException {
+        netd.tetherInterfaceAdd(iface);
+        networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
         List<RouteInfo> routes = new ArrayList<>();
         routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
         RouteUtils.addRoutesToLocalNetwork(netd, iface, routes);
     }
 
+    /**
+     * Retry Netd#networkAddInterface for EBUSY error code.
+     * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode.
+     * There can be a race where puts the interface into the local network but interface is still
+     * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
+     * See b/158269544 for detail.
+     */
+    private static void networkAddInterface(final INetd netd, final String iface,
+            int maxAttempts, int pollingIntervalMs)
+            throws ServiceSpecificException, RemoteException {
+        for (int i = 1; i <= maxAttempts; i++) {
+            try {
+                netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+                return;
+            } catch (ServiceSpecificException e) {
+                if (e.errorCode == EBUSY && i < maxAttempts) {
+                    SystemClock.sleep(pollingIntervalMs);
+                    continue;
+                }
+
+                Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e);
+                throw e;
+            }
+        }
+    }
+
     /** Reset interface for tethering. */
     public static void untetherInterface(final INetd netd, String iface)
             throws RemoteException, ServiceSpecificException {
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
index 8983d00..981a576 100644
--- a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -20,11 +20,21 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.net.NetworkCapabilities;
 
 /** @hide */
 public class NetworkMonitorUtils {
+    // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
+    // NetworkStack shims, but at the same time cannot use non-system APIs.
+    // TRANSPORT_TEST is test API as of R (so it is enforced to always be 7 and can't be changed),
+    // and it is being added as a system API in S.
+    // TODO: use NetworkCapabilities.TRANSPORT_TEST once NetworkStack builds against API 31.
+    private static final int TRANSPORT_TEST = 7;
 
     // Network conditions broadcast constants
     public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
@@ -47,11 +57,33 @@
      * @param nc Network capabilities of the network to test.
      */
     public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
+        if (nc == null) return false;
+
         // TODO: Consider requiring validation for DUN networks.
-        return nc != null
-                && nc.hasCapability(NET_CAPABILITY_INTERNET)
+        if (nc.hasCapability(NET_CAPABILITY_INTERNET)
                 && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+                && nc.hasCapability(NET_CAPABILITY_TRUSTED)) {
+            // Real networks
+            return true;
+        }
+
+        // TODO: once TRANSPORT_TEST is @SystemApi in S and S SDK is stable (so constant shims can
+        // be replaced with the SDK constant that will be inlined), replace isTestNetwork with
+        // hasTransport(TRANSPORT_TEST)
+
+        // Test networks that also have one of the major transport types are attempting to replicate
+        // that transport on a test interface (for example, test ethernet networks with
+        // EthernetManager#setIncludeTestInterfaces). Run validation on them for realistic tests.
+        // See also comments on EthernetManager#setIncludeTestInterfaces and on TestNetworkManager.
+        if (nc.hasTransport(TRANSPORT_TEST) && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) && (
+                nc.hasTransport(TRANSPORT_WIFI)
+                || nc.hasTransport(TRANSPORT_CELLULAR)
+                || nc.hasTransport(TRANSPORT_BLUETOOTH)
+                || nc.hasTransport(TRANSPORT_ETHERNET))) {
+            return true;
+        }
+
+        return false;
     }
 
     /**
diff --git a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
index cb613fb..5f5699c 100644
--- a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
+++ b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
@@ -68,12 +68,12 @@
     // TODO: Delete this default timeout once those callers that care are
     // fixed to pass in their preferred timeout.
     //
-    // We pick 36 seconds so we can send DHCP requests at
+    // We pick 18 seconds so we can send DHCP requests at
     //
-    //     t=0, t=2, t=6, t=14, t=30
+    //     t=0, t=1, t=3, t=7, t=16
     //
     // allowing for 10% jitter.
-    private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+    private static final int DEFAULT_TIMEOUT_MS = 18 * 1000;
 
     /**
      * Builder to create a {@link ProvisioningConfiguration}.
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 43d482f..b5a3e95 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -44,6 +44,7 @@
         "4",
         "5",
         "6",
+        "7",
     ],
     visibility: [
         "//system/tools/aidl/build",
@@ -123,6 +124,7 @@
         "src/android/net/IpMemoryStoreClient.java",
         "src/android/net/ipmemorystore/**/*.java",
         "src/android/net/networkstack/**/*.java",
+        "src/android/net/shared/**/*.java",
     ],
     static_libs: [
         "ipmemorystore-aidl-interfaces-java",
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/.hash b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/.hash
new file mode 100644
index 0000000..786a6f7
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/.hash
@@ -0,0 +1 @@
+31826566143ef882d67fac9f24566f73df4907b4
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..bf7a26d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 IIpMemoryStore {
+  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
+  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
+  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
+  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
+  oneway void factoryReset();
+  oneway void delete(String l2Key, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
+  oneway void deleteCluster(String cluster, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..2024391
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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 IIpMemoryStoreCallbacks {
+  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..8a1b57e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+parcelable Blob {
+  byte[] data;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..e711272
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnBlobRetrievedListener {
+  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..4abecb9
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnL2KeyResponseListener {
+  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..05c48b3
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnNetworkAttributesRetrievedListener {
+  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..0bc8c5e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnSameL3NetworkResponseListener {
+  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..cf30fa1
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnStatusAndCountListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status, int count);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..e71de47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnStatusListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..92a570d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+parcelable NetworkAttributesParcelable {
+  byte[] assignedV4Address;
+  long assignedV4AddressExpiry;
+  String cluster;
+  android.net.ipmemorystore.Blob[] dnsAddresses;
+  int mtu;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..eca0987
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+parcelable SameL3NetworkResponseParcelable {
+  String l2Key1;
+  String l2Key2;
+  float confidence;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..7554608
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
index ac87e59..bf7a26d 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
@@ -25,4 +25,6 @@
   oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
   oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
   oneway void factoryReset();
+  oneway void delete(String l2Key, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
+  oneway void deleteCluster(String cluster, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
 }
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..cf30fa1
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files 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.ipmemorystore;
+/* @hide */
+interface IOnStatusAndCountListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status, int count);
+}
diff --git a/common/networkstackclient/src/android/net/IIpMemoryStore.aidl b/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
index add221a..3bb58bf 100644
--- a/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
+++ b/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
@@ -22,6 +22,7 @@
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
 import android.net.ipmemorystore.IOnStatusListener;
 
 /** {@hide} */
@@ -39,8 +40,7 @@
      * @param attributes The attributes for this network.
      * @param listener A listener that will be invoked to inform of the completion of this call,
      *                 or null if the client is not interested in learning about success/failure.
-     * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
-     *         If the call failed, the L2 key will be null.
+     * @return (through the listener) A status to indicate success or failure.
      */
     void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
             IOnStatusListener listener);
@@ -115,4 +115,41 @@
      * Delete all data because a factory reset operation is in progress.
      */
     void factoryReset();
+
+    /**
+     * Delete a single entry.
+     *
+     * @param l2key The L2 key of the entry to delete.
+     * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+     *                 operation vastly more expensive as the database files will have to be copied
+     *                 and created again from the old files (see sqlite3 VACUUM operation for
+     *                 details) and makes no functional difference; only pass true if security or
+     *                 privacy demands this data must be removed from disk immediately.
+     *                 Note that this can fail for storage reasons. The passed listener will then
+     *                 receive an appropriate error status with the number of deleted rows.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * @return (through the listener) A status to indicate success and the number of deleted records
+     */
+    void delete(String l2Key, boolean needWipe, IOnStatusAndCountListener listener);
+
+    /**
+     * Delete all entries in a cluster.
+     *
+     * This method will delete all entries in the memory store that have the cluster attribute
+     * passed as an argument.
+     *
+     * @param cluster The cluster to delete.
+     * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+     *                 operation vastly more expensive as the database files will have to be copied
+     *                 and created again from the old files (see sqlite3 VACUUM operation for
+     *                 details) and makes no functional difference; only pass true if security or
+     *                 privacy demands this data must be removed from disk immediately.
+     *                 Note that this can fail for storage reasons. The passed listener will then
+     *                 receive an appropriate error status with the number of deleted rows.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * @return (through the listener) A status to indicate success and the number of deleted records
+     */
+    void deleteCluster(String cluster, boolean needWipe, IOnStatusAndCountListener listener);
 }
diff --git a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
index 014b528..a1c5694 100644
--- a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
+++ b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
@@ -22,6 +22,7 @@
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.OnBlobRetrievedListener;
+import android.net.ipmemorystore.OnDeleteStatusListener;
 import android.net.ipmemorystore.OnL2KeyResponseListener;
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
@@ -91,6 +92,7 @@
                     () -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
                             OnStatusListener.toAIDL(listener))));
         } catch (ExecutionException m) {
+            if (null == listener) return;
             ignoringRemoteException("Error storing network attributes",
                     () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
         }
@@ -115,6 +117,7 @@
                     () -> service.storeBlob(l2Key, clientId, name, data,
                             OnStatusListener.toAIDL(listener))));
         } catch (ExecutionException m) {
+            if (null == listener) return;
             ignoringRemoteException("Error storing blob",
                     () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
         }
@@ -142,7 +145,8 @@
                             OnL2KeyResponseListener.toAIDL(listener))));
         } catch (ExecutionException m) {
             ignoringRemoteException("Error finding L2 Key",
-                    () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
+                    () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN),
+                            null /* l2Key */));
         }
     }
 
@@ -163,7 +167,8 @@
                             OnSameL3NetworkResponseListener.toAIDL(listener))));
         } catch (ExecutionException m) {
             ignoringRemoteException("Error checking for network sameness",
-                    () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
+                    () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN),
+                            null /* response */));
         }
     }
 
@@ -185,7 +190,7 @@
         } catch (ExecutionException m) {
             ignoringRemoteException("Error retrieving network attributes",
                     () -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
-                            null, null));
+                            null /* l2Key */, null /* attributes */));
         }
     }
 
@@ -209,7 +214,67 @@
         } catch (ExecutionException m) {
             ignoringRemoteException("Error retrieving blob",
                     () -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
-                            null, null, null));
+                            null /* l2Key */, null /* name */, null /* blob */));
+        }
+    }
+
+    /**
+     * Delete a single entry.
+     *
+     * @param l2Key The L2 key of the entry to delete.
+     * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+     *                 operation vastly more expensive as the database files will have to be copied
+     *                 and created again from the old files (see sqlite3 VACUUM operation for
+     *                 details) and makes no functional difference; only pass true if security or
+     *                 privacy demands this data must be removed from disk immediately.
+     *                 Note that this can fail for storage reasons. The passed listener will then
+     *                 receive an appropriate error status with the number of deleted rows.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * returns (through the listener) A status to indicate success and the number of deleted records
+     */
+    public void delete(@NonNull final String l2Key, final boolean needWipe,
+            @Nullable final OnDeleteStatusListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(() ->
+                    service.delete(l2Key, needWipe, OnDeleteStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            if (null == listener) return;
+            ignoringRemoteException("Error deleting from the memory store",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN),
+                            0 /* deletedRecords */));
+        }
+    }
+
+    /**
+     * Delete all entries in a cluster.
+     *
+     * This method will delete all entries in the memory store that have the cluster attribute
+     * passed as an argument.
+     *
+     * @param cluster The cluster to delete.
+     * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+     *                 operation vastly more expensive as the database files will have to be copied
+     *                 and created again from the old files (see sqlite3 VACUUM operation for
+     *                 details) and makes no functional difference; only pass true if security or
+     *                 privacy demands this data must be removed from disk immediately.
+     *                 Note that this can fail for storage reasons. The passed listener will then
+     *                 receive an appropriate error status with the number of deleted rows.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * returns (through the listener) A status to indicate success and the number of deleted records
+     */
+    public void deleteCluster(@NonNull final String cluster, final boolean needWipe,
+            @Nullable final OnDeleteStatusListener listener) {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.deleteCluster(cluster, needWipe,
+                            OnDeleteStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            if (null == listener) return;
+            ignoringRemoteException("Error deleting from the memory store",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN),
+                            0 /* deletedRecords */));
         }
     }
 
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..c19b5c1
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package android.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusAndCountListener {
+    /**
+     * The operation has completed with the specified status, and supplied the passed count
+     * as call-specific additional data.
+     */
+     void onComplete(in StatusParcelable status, int count);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
index 104ac79..2e444fe 100644
--- a/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
+++ b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
@@ -218,6 +218,22 @@
         private Integer mMtu;
 
         /**
+         * Constructs a new Builder.
+         */
+        public Builder() {}
+
+        /**
+         * Constructs a Builder from the passed NetworkAttributes.
+         */
+        public Builder(@NonNull final NetworkAttributes attributes) {
+            mAssignedAddress = attributes.assignedV4Address;
+            mAssignedAddressExpiry = attributes.assignedV4AddressExpiry;
+            mCluster = attributes.cluster;
+            mDnsAddresses = new ArrayList<>(attributes.dnsAddresses);
+            mMtu = attributes.mtu;
+        }
+
+        /**
          * Set the assigned address.
          * @param assignedV4Address The assigned address.
          * @return This builder.
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnDeleteStatusListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnDeleteStatusListener.java
new file mode 100644
index 0000000..7138877
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnDeleteStatusListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A listener for the IpMemoryStore to return a status to a client.
+ * @hide
+ */
+public interface OnDeleteStatusListener {
+    /**
+     * The operation has completed with the specified status, and deleted the specified
+     * number of records. The operation can fail with a non-zero count of deleted rows as
+     * wipe requests may fail for lack of storage. See the documentation of each deletion
+     * method for details.
+     */
+    void onComplete(Status status, int deletedRecords);
+
+    /** Converts this OnDeleteStatusListener to a parcelable object */
+    @NonNull
+    static IOnStatusAndCountListener toAIDL(@Nullable final OnDeleteStatusListener listener) {
+        return new IOnStatusAndCountListener.Stub() {
+            @Override
+            public void onComplete(final StatusParcelable statusParcelable, int deletedRecords) {
+                if (null != listener) {
+                    listener.onComplete(new Status(statusParcelable), deletedRecords);
+                }
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+
+            @Override
+            public String getInterfaceHash() {
+                return this.HASH;
+            }
+        };
+    }
+}
diff --git a/common/networkstackclient/src/android/net/shared/IpConfigurationParcelableUtil.java b/common/networkstackclient/src/android/net/shared/IpConfigurationParcelableUtil.java
new file mode 100644
index 0000000..e8152cf
--- /dev/null
+++ b/common/networkstackclient/src/android/net/shared/IpConfigurationParcelableUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import java.net.InetAddress;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
+ * configuration classes.
+ * @hide
+ */
+public final class IpConfigurationParcelableUtil {
+
+    /**
+     * Convert InetAddress to String.
+     * TODO: have an InetAddressParcelable
+     */
+    public static String parcelAddress(@Nullable InetAddress addr) {
+        if (addr == null) return null;
+        return addr.getHostAddress();
+    }
+
+    /**
+     * Convert String to InetAddress.
+     * TODO: have an InetAddressParcelable
+     */
+    public static InetAddress unparcelAddress(@Nullable String addr) {
+        if (addr == null) return null;
+        return InetAddresses.parseNumericAddress(addr);
+    }
+}
diff --git a/jarjar-rules-shared.txt b/jarjar-rules-shared.txt
index 7346b1a..048c976 100644
--- a/jarjar-rules-shared.txt
+++ b/jarjar-rules-shared.txt
@@ -1,10 +1,14 @@
-rule com.android.internal.util.** android.net.networkstack.util.@1
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.util.@1
 
-rule android.net.shared.Inet4AddressUtils* android.net.networkstack.shared.Inet4AddressUtils@1
-rule android.net.shared.InetAddressUtils* android.net.networkstack.shared.InetAddressUtils@1
+rule com.android.internal.util.** android.net.networkstack.util.@1
+rule com.google.protobuf.** com.android.networkstack.protobuf.@1
+
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.util.@1
 
 # Ignore DhcpResultsParcelable, but jarjar DhcpResults
 # TODO: move DhcpResults into services.net and delete from here
 rule android.net.DhcpResultsParcelable* @0
 rule android.net.DhcpResults* android.net.networkstack.DhcpResults@1
-rule android.net.LocalLog* android.net.networkstack.LocalLog@1
+rule android.util.LocalLog* android.net.networkstack.util.LocalLog@1
diff --git a/proguard.flags b/proguard.flags
index c60f6c3..af4262a 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -7,3 +7,10 @@
     static final int CMD_*;
     static final int EVENT_*;
 }
+
+# The lite proto runtime uses reflection to access fields based on the names in
+# the schema, keep all the fields.
+# This replicates the base proguard rule used by the build by default
+# (proguard_basic_keeps.flags), but needs to be specified here because the
+# com.google.protobuf package is jarjared to the below package.
+-keepclassmembers class * extends com.android.networkstack.protobuf.MessageLite { <fields>; }
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..7edeae2
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Kontroleringsportaalstawing"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Kennisgewings word gewys wanneer die toestel suksesvol vir \'n kontroleringsportaalnetwerk gestaaf het"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Netwerkplekinligting"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Kennisgewings word gewys om aan te dui dat die netwerk \'n plekinligtingbladsy het"</string>
+    <string name="connected" msgid="4563643884927480998">"Gekoppel"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Is gekoppel / Tik om webwerf te bekyk"</string>
+    <string name="application_label" msgid="1322847171305285454">"Netwerkbestuurder"</string>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..a5c349e
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"የተያዥ መግቢያ ማረጋገጥ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"መሣሪያው በተሳካ ሁኔታ ከአንድ ተያዥ መግቢያ አውታረ መረብ ጋር ሲረጋገጥ ማሳወቂያዎች ይታያሉ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"የአውታረ መረብ መድረክ መረጃ"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"አውታረ መረቡ የመድረክ መረጃ ግገጽ እንዳለው ለማመልከት ማሳወቂያዎች ይታያሉ"</string>
+    <string name="connected" msgid="4563643884927480998">"ተገናኝቷል"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"ተገናኝቷል / የድር ጣቢያን ለማየት መታ ያድርጉ"</string>
+    <string name="application_label" msgid="1322847171305285454">"የአውታረ መረብ አስተዳዳሪ"</string>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..fe60a49
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"مصادقة مدخل مشروط الوصول إليه"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"يتم عرض الإشعارات عندما تتم مصادقة الجهاز بنجاح مع شبكة مدخل مشروط الوصول إليه."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"معلومات عن المكان الذي يوفّر الشبكة"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"الإشعارات التي يتم عرضها لتشير إلى أن الشبكة تحتوي على صفحة معلومات عن المكان."</string>
+    <string name="connected" msgid="4563643884927480998">"الجهاز متصل الآن بالإنترنت."</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"متصل بالشبكة، انقر للانتقال إلى الموقع الإلكتروني"</string>
+    <string name="application_label" msgid="1322847171305285454">"إدارة الشبكة"</string>
+</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
new file mode 100644
index 0000000..2a51122
--- /dev/null
+++ b/res/values-as/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"কেপটিভ প’ৰ্টেলৰ প্ৰমাণীকৰণ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ডিভাইচটোৱে কোনো কেপটিভ প’ৰ্টেল নেটৱর্কত সফলতাৰে বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰিলে জাননীসমূহ দেখুওৱা হয়"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"নেটৱৰ্ক স্থলীৰ তথ্য"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"নেটৱৰ্কৰ এখন স্থলীৰ তথ্য পৃষ্ঠা থকাটো দৰ্শাবলৈ জাননীসমূহ দেখুওৱা হয়"</string>
+    <string name="connected" msgid="4563643884927480998">"সংযোগ কৰা হৈছে"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"সংযুক্ত / ৱেবছাইট চাবলৈ টিপক"</string>
+    <string name="application_label" msgid="1322847171305285454">"নেটৱৰ্ক মেনেজাৰ"</string>
+</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
new file mode 100644
index 0000000..0dfd623
--- /dev/null
+++ b/res/values-az/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Avtorizasiya tələb edən portalda doğrulama"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Cihaz daxili portal şəbəkəsində uğurla doğrulandıqda bildirişlər göstərilir"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Şəbəkənin məkan məlumatı"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Şəbəkədə məkan məlumatı səhifəsi olduğunu göstərən bildirişlər"</string>
+    <string name="connected" msgid="4563643884927480998">"Qoşulub"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Qoşulub / Veb sayta baxmaq üçün toxunun"</string>
+    <string name="application_label" msgid="1322847171305285454">"Şəbəkə meneceri"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..e53b2c9
--- /dev/null
+++ b/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Potvrda identiteta na ulaznom portalu"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obaveštenja koja se prikazuju kada se potvrdi identitet uređaja na mreži ulaznog portala"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacije o mestu na mreži"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obaveštenja koja se prikazuju da bi pokazala da mreža ima stranicu sa informacijama o mestu"</string>
+    <string name="connected" msgid="4563643884927480998">"Povezano"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Povezano/dodirnite da biste pregledali veb-sajt"</string>
+    <string name="application_label" msgid="1322847171305285454">"Menadžer mreže"</string>
+</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000..dc0d108
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Аўтэнтыфікацыя на старонцы аўтарызацыі"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Апавяшчэнні, якія паказваюцца, калі прылада паспяхова прайшла аўтэнтыфікацыю на вэб-старонцы аўтарызацыі ў сетцы"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Інфармацыя пра месцазнаходжанне сеткі"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Апавяшчэнні, якія паказваюцца, калі ў сетцы ёсць старонка з інфармацыяй пра яе месцазнаходжанне"</string>
+    <string name="connected" msgid="4563643884927480998">"Падключана"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Падключана / Націсніце для прагляду вэб-сайта"</string>
+    <string name="application_label" msgid="1322847171305285454">"Менеджар сетак"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..28118b5
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Удостоверяване в портал за целта"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Известия, които се показват, когато устройството е успешно удостоверено в мрежа с портал за удостоверяване"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Информация за мястото на мрежата"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Известия, показвани, за да укажат, че има страница с информация за мястото, където е реализирана мрежата"</string>
+    <string name="connected" msgid="4563643884927480998">"Установена е връзка"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Установена е връзка/докоснете за преглед на сайта"</string>
+    <string name="application_label" msgid="1322847171305285454">"Мрежови мениджър"</string>
+</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
new file mode 100644
index 0000000..917f2a9
--- /dev/null
+++ b/res/values-bn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ক্যাপটিভ পোর্টালের যাচাইকরণ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ক্যাপটিভ পোর্টাল নেটওয়ার্কে ডিভাইস সফলভাবে যাচাই করা হলে এই বিজ্ঞপ্তি দেখানো হয়"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"নেটওয়ার্ক ভেন্যু সংক্রান্ত তথ্য"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"নেটওয়ার্কে একটি ভেন্যুর তথ্য সংক্রান্ত পৃষ্ঠা রয়েছে তা বোঝানোর জন্য বিজ্ঞপ্তি দেখানো হয়েছে"</string>
+    <string name="connected" msgid="4563643884927480998">"কানেক্ট করা হয়েছে"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"কানেক্ট করা আছে / ওয়েবসাইট দেখতে ট্যাপ করুন"</string>
+    <string name="application_label" msgid="1322847171305285454">"নেটওয়ার্ক ম্যানেজার"</string>
+</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
new file mode 100644
index 0000000..ed85f6a
--- /dev/null
+++ b/res/values-bs/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentifikacija na zaštitnom portalu"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obavještenja koja se prikazuju kada se uređaj uspješno autentificira na mreži sa zaštitnim portalom"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Mrežne informacije o mjestu događaja"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obavještenja koja se prikazuju da pokažu da mreža ima stranicu s informacijama o mjestu događaja"</string>
+    <string name="connected" msgid="4563643884927480998">"Povezano"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Povezano/Dodirnite da vidite web lokaciju"</string>
+    <string name="application_label" msgid="1322847171305285454">"Upravitelj mreže"</string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..4e8d3ec
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticació del portal captiu"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Les notificacions es mostren quan el dispositiu s\'ha autenticat correctament a una xarxa de portal captiu"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informació sobre el lloc de la xarxa"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Les notificacions es mostren per indicar que la xarxa té una pàgina d\'informació del lloc"</string>
+    <string name="connected" msgid="4563643884927480998">"Connectat"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connectat / Toca per veure el lloc web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Administrador de xarxes"</string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..a55fe72
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Ověření captive portálu"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Oznámení zobrazovaná po úspěšném ověření zařízení v síti s captive portálem"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Síť s informacemi o místě"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Oznámení zobrazovaná na znamení, že síť obsahuje stránku s informacemi o místě"</string>
+    <string name="connected" msgid="4563643884927480998">"Připojeno"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Připojeno / Klepnutím zobrazíte web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Správce sítí"</string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..e54f11c
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Godkendelse til loginportal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"De notifikationer, der vises, når enheden er blevet godkendt til et netværk via en loginportal"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Oplysninger om netværksplacering"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifikationer, der vises for at indikere, at netværket har en side med oplysninger om placeringen"</string>
+    <string name="connected" msgid="4563643884927480998">"Der er oprettet forbindelse"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Der er oprettet forbindelse/tryk for at se website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Netværksadministrator"</string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..502a3b1
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive Portal-Authentifizierung"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Die Benachrichtigungen werden angezeigt, wenn das Gerät bei einem Captive Portal-Netzwerk authentifiziert wurde"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informationen zum Netzwerkstandort"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Die Benachrichtigungen werden angezeigt, um auf ein Netzwerk mit einer Seite hinzuweisen, die weitere Informationen zum Netzwerkstandort enthält"</string>
+    <string name="connected" msgid="4563643884927480998">"Verbunden"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Verbunden/Zum Ansehen der Website tippen"</string>
+    <string name="application_label" msgid="1322847171305285454">"Netzwerkmanager"</string>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..32d423b
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Έλεγχος ταυτότητας πύλης υποδοχής"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Εμφανίζονται ειδοποιήσεις όταν η συσκευή έχει πραγματοποιήσει επιτυχώς έλεγχο ταυτότητας σε δίκτυο με πύλη υποδοχής."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Πληροφορίες τόπου δικτύου"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Εμφανίζονται ειδοποιήσεις για να υποδείξουν ότι το δίκτυο διαθέτει σελίδα πληροφοριών για τον τόπο."</string>
+    <string name="connected" msgid="4563643884927480998">"Συνδέθηκε"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Συνδέθηκε/Πατήστε για προβολή του ιστοτόπου"</string>
+    <string name="application_label" msgid="1322847171305285454">"Διαχειριστής δικτύου"</string>
+</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rAU/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+    <string name="connected" msgid="4563643884927480998">"Connected"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+    <string name="connected" msgid="4563643884927480998">"Connected"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+    <string name="connected" msgid="4563643884927480998">"Connected"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+    <string name="connected" msgid="4563643884927480998">"Connected"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..c6a7270
--- /dev/null
+++ b/res/values-en-rXC/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎Captive portal authentication‎‏‎‎‏‎"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎Notifications shown when the device has successfully authenticated to a captive portal network‎‏‎‎‏‎"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎Network venue information‎‏‎‎‏‎"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎Notifications shown to indicate the network has a venue information page‎‏‎‎‏‎"</string>
+    <string name="connected" msgid="4563643884927480998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎Connected‎‏‎‎‏‎"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‏‎Connected / Tap to view website‎‏‎‎‏‎"</string>
+    <string name="application_label" msgid="1322847171305285454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎Network manager‎‏‎‎‏‎"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..915dfc5
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticación de portal cautivo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificaciones que se muestran cuando el dispositivo se autenticó correctamente en una red de portal cautivo"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Información del lugar de la red"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificaciones que se muestran para indicar que la red tiene una página de información sobre el lugar"</string>
+    <string name="connected" msgid="4563643884927480998">"Se estableció conexión"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Conectado/presiona para ver el sitio web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Administrador de redes"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..b99fab8
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticación en el portal cautivo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificaciones que se muestran cuando el dispositivo se autentica correctamente en la red de un portal cautivo"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Información sobre el lugar de la red"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificaciones que se muestran para indicar que la red tiene una página de información sobre el lugar"</string>
+    <string name="connected" msgid="4563643884927480998">"Conectado"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Conectado: toca para ver el sitio web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Administrador de redes"</string>
+</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
new file mode 100644
index 0000000..2ffaff8
--- /dev/null
+++ b/res/values-et/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Hõiveportaali autentimine"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Märguanded, mis kuvatakse siis, kui seadmel õnnestub hõiveportaaliga autentida"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Võrgu asukoha teave"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Märguanded, mis kuvatakse juhul, kui võrgul on asukoha teabeleht"</string>
+    <string name="connected" msgid="4563643884927480998">"Ühendatud"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Ühendatud / puudutage veebisaidi vaatamiseks"</string>
+    <string name="application_label" msgid="1322847171305285454">"Võrguhaldur"</string>
+</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
new file mode 100644
index 0000000..281a68b
--- /dev/null
+++ b/res/values-eu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Sare-zerbitzuen atariko autentifikazioa"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Gailua sare-zerbitzuaren atari batean behar bezala autentifikatzen denean agertuko dira jakinarazpenak"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Sareari dagokion tokiaren informazioa"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Jakinarazpenak agertuko dira sareari dagokion tokiak informazio-orri bat duela adierazteko"</string>
+    <string name="connected" msgid="4563643884927480998">"Konektatuta"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Konektatuta / Sakatu webgunea ikusteko"</string>
+    <string name="application_label" msgid="1322847171305285454">"Sare-kudeatzailea"</string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..b595a9e
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"راستی‌آزمایی درگاه مهمان"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"اعلان‌هایی که پس از راستی‌آزمایی موفق دستگاه در یک شبکه درگاه مهمان نشان داده می‌شوند"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"اطلاعات محل شبکه"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"اعلان‌هایی که نمایش داده می‌شوند تا نشان دهند شبکه صفحه اطلاعات محل دارد"</string>
+    <string name="connected" msgid="4563643884927480998">"متصل"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"متصل شد / برای مشاهده وب‌سایت ضربه بزنید"</string>
+    <string name="application_label" msgid="1322847171305285454">"مدیر شبکه"</string>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..975e7d2
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal ‑todennus"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Ilmoitukset, jotka näkyvät, kun laite on todennettu ja yhteydessä captive portal ‑verkkoon"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Verkon paikkatiedot"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Ilmoitukset, jotka kertovat, että verkolla on paikkatietoja sisältävä sivu"</string>
+    <string name="connected" msgid="4563643884927480998">"Yhdistetty"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Yhdistetty / Näytä sivusto napauttamalla"</string>
+    <string name="application_label" msgid="1322847171305285454">"Verkkojen hallinta"</string>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..5bd6d20
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Authentification du portail captif"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications affichées lorsque l\'appareil s\'est authentifié auprès d\'un réseau doté d\'un portail captif"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Information sur le lieu du réseau"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications indiquant que le réseau dispose d\'une page d\'information à propos du lieu"</string>
+    <string name="connected" msgid="4563643884927480998">"Connecté"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connecté : touchez pour afficher le site Web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Gestionnaire de réseaux"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..20b737e
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Authentification sur le portail captif"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications affichées lorsque l\'appareil a bien été connecté et authentifié sur un réseau avec portail captif"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informations sur un lieu lié au réseau"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications affichées pour indiquer que le réseau comporte une page d\'informations sur le lieu auquel il est associé"</string>
+    <string name="connected" msgid="4563643884927480998">"Connecté"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Connecté/Appuyer pour afficher le site Web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Gestionnaire de réseaux"</string>
+</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
new file mode 100644
index 0000000..6c6db88
--- /dev/null
+++ b/res/values-gl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticación de portal cativo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Móstranse notificacións cando o dispositivo se autentica correctamente nunha rede de portal cativo."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Rede con información sobre o lugar"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Móstranse notificacións para indicar que a rede ten unha páxina de información sobre o lugar"</string>
+    <string name="connected" msgid="4563643884927480998">"Dispositivo conectado á rede"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Conectado. Toca para ver o sitio web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Xestor de redes"</string>
+</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
new file mode 100644
index 0000000..78ebb30
--- /dev/null
+++ b/res/values-gu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"કૅપ્ટિવ પોર્ટલ માટેનું પ્રમાણીકરણ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"જ્યારે કૅપ્ટિવ પોર્ટલ નેટવર્ક પર ડિવાઇસનું સફળતાપૂર્વક પ્રમાણીકરણ થઈ જાય, ત્યારે બતાવવામાં આવતા નોટિફિકેશન"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"નેટવર્કના સ્થળની માહિતી"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"નેટવર્ક સ્થળની માહિતીનું પેજ ધરાવે છે તે સૂચવવા માટે બતાવવામાં આવતા નોટિફિકેશન"</string>
+    <string name="connected" msgid="4563643884927480998">"કનેક્ટ કર્યું છે"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"કનેક્ટેડ / વેબસાઇટ જોવા માટે ટૅપ કરો"</string>
+    <string name="application_label" msgid="1322847171305285454">"નેટવર્ક મેનેજર"</string>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..730bb2a
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"कैप्टिव पोर्टल की मदद से पुष्टि करना"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"डिवाइस के कैप्टिव पोर्टल नेटवर्क से पुष्टि होने के बाद सूचनाएं दिखती हैं"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्क पर उस जगह के बारे में जानकारी"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"किसी नेटवर्क पर उस जगह के बारे में बताने वाले पेज की जानकारी दिखाने वाली सूचनाएं"</string>
+    <string name="connected" msgid="4563643884927480998">"नेटवर्क से कनेक्ट हो गया है"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"कनेक्ट है / वेबसाइट देखने के लिए टैप करें"</string>
+    <string name="application_label" msgid="1322847171305285454">"नेटवर्क मैनेजर"</string>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..eaf0b2e
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentifikacija na zaštitnom portalu"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obavijesti koje se prikazuju kada uređaj uspješno prođe provjeru autentičnosti na mreži zaštitnog portala"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Podaci o mrežnom mjestu"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obavijesti koje se prikazuju kada mreža ima stranicu s podacima o mjestu događaja"</string>
+    <string name="connected" msgid="4563643884927480998">"Povezano"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Povezano/dodirnite da bi se prikazala web-lokacija"</string>
+    <string name="application_label" msgid="1322847171305285454">"Upravitelj mreža"</string>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..801961c
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Bejelentkezés hitelesítési portálon"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Értesítés jelenik meg, ha az eszköz sikeresen bejelentkezett egy hitelesítési portált használó hálózaton"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Információk a hálózat helyéről"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Értesítés jelenik meg, amely jelzi, hogy a hálózat a helyszínről szóló információs oldalt is tartalmaz"</string>
+    <string name="connected" msgid="4563643884927480998">"Csatlakozva"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Csatlakozva / Koppintson a webhely megtekintéséhez"</string>
+    <string name="application_label" msgid="1322847171305285454">"Hálózatkezelő"</string>
+</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
new file mode 100644
index 0000000..df1ba31
--- /dev/null
+++ b/res/values-hy/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Նույնականացում մուտքի էջում"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Ծանուցումներ, որոնք ցուցադրվում են, երբ ցանցի մուտքի էջում հաջողվում է նույնականացնել սարքը"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Տվյալներ ցանցի հասանելիության տեղի մասին"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Ծանուցումներ, որոնք ցույց են տալիս այն էջը, որում առկա են տեղեկություններ սարքի հասանելիության վայրի մասին"</string>
+    <string name="connected" msgid="4563643884927480998">"Միացված է"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Միացված է / Հպեք՝ կայքը դիտելու համար"</string>
+    <string name="application_label" msgid="1322847171305285454">"Ցանցի կառավարիչ"</string>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..195391c
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentikasi captive portal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifikasi ditampilkan saat perangkat telah berhasil diautentikasi ke jaringan captive portal"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informasi tempat jaringan"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifikasi ditampilkan untuk menandakan bahwa jaringan memiliki halaman informasi tempat"</string>
+    <string name="connected" msgid="4563643884927480998">"Terhubung"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Terhubung / Ketuk untuk melihat situs"</string>
+    <string name="application_label" msgid="1322847171305285454">"Pengelola jaringan"</string>
+</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
new file mode 100644
index 0000000..8dcaeb7
--- /dev/null
+++ b/res/values-is/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Auðkenning á innskráningarsíðu"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Tilkynningar eru sýndar þegar tæki hefur fengið auðkenningu á neti með innskráningarsíðu"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Upplýsingar nets um vettvang"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Tilkynningar eru sýndar til að gefa til kynna að net innihaldi upplýsingasíðu um vettvang"</string>
+    <string name="connected" msgid="4563643884927480998">"Tengt"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Tengt / Ýttu til að skoða vefsvæði"</string>
+    <string name="application_label" msgid="1322847171305285454">"Netstjórnun"</string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..7e0fc28
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticazione a captive portal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifiche mostrate in seguito all\'autenticazione del dispositivo su una rete captive portal"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Info sull\'attività che offre la rete"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifiche mostrate per indicare che la rete ha una pagina di informazioni su un luogo"</string>
+    <string name="connected" msgid="4563643884927480998">"Connesso"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Rete collegata/Tocca per visualizzare il sito web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..4772691
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"אימות של פורטל שבוי"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"התראות המוצגות כשהמכשיר אומת בהצלחה וחובר לרשת של פורטל שבוי"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"מידע על מקום רשת"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"התראות המוצגות כדי לציין שלרשת יש דף מידע על מקום"</string>
+    <string name="connected" msgid="4563643884927480998">"מחובר"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"מחוברת / יש להקיש כדי להציג את האתר"</string>
+    <string name="application_label" msgid="1322847171305285454">"ניהול רשתות"</string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..e1f76e1
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"キャプティブ ポータルの認証"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"デバイスが認証されてキャプティブ ポータル ネットワークにアクセスできるようになると、通知が表示されます"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ネットワークの場所に関する情報"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"場所に関する情報のページがネットワークにあることを示す通知が表示されます"</string>
+    <string name="connected" msgid="4563643884927480998">"接続完了"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"接続しました。タップしてウェブサイトをご覧ください"</string>
+    <string name="application_label" msgid="1322847171305285454">"ネットワーク マネージャ"</string>
+</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
new file mode 100644
index 0000000..a1d79dd
--- /dev/null
+++ b/res/values-ka/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ავთენტიკაცია ავტორიზაციის პორტალზე"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"შეტყობინებები გამოჩნდება ავტორიზაციის პორტალის ქსელში მოწყობილობის წარმატებული ავთენტიკაციის შემდეგ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ქსელის ადგილის შესახებ ინფორმაცია"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"შეტყობინებები გამოჩნდება იმის საჩვენებლად, რომ ქსელს აქვს ადგილის შესახებ ინფორმაციის გვერდი"</string>
+    <string name="connected" msgid="4563643884927480998">"დაკავშირებულია"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"დაკავშირებულია / შეეხეთ ვებსაიტის სანახავად"</string>
+    <string name="application_label" msgid="1322847171305285454">"ქსელის მმართველი"</string>
+</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
new file mode 100644
index 0000000..ce501d2
--- /dev/null
+++ b/res/values-kk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Адаптивті портал аутентификациясы"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Құрылғы адаптивті портал желісінде аутентификацияланған кезде, хабарландыру көрсетіледі."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Желінің орын туралы ақпараты"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Желінің орын туралы ақпарат беті барын көрсету үшін хабарландыру көрсетіледі."</string>
+    <string name="connected" msgid="4563643884927480998">"Байланыстырылды."</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Байланыстырылған/Веб-сайтты көру үшін түртіңіз."</string>
+    <string name="application_label" msgid="1322847171305285454">"Желі менеджері"</string>
+</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
new file mode 100644
index 0000000..c043152
--- /dev/null
+++ b/res/values-km/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ការផ្ទៀងផ្ទាត់ច្រកចូលប្រើបណ្ដាញ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ការជូនដំណឹង​បង្ហាញ នៅពេល​ឧបករណ៍​បានផ្ទៀងផ្ទាត់​បណ្ដាញ​ច្រកចូលប្រើ​ដោយជោគជ័យ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ព័ត៌មាន​អំពីទីតាំងបណ្ដាញ"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ការជូនដំណឹងបង្ហាញ​ ដើម្បី​បង្ហាញ​ថាបណ្ដាញ​មានទំព័រព័ត៌មានអំពីទីតាំង"</string>
+    <string name="connected" msgid="4563643884927480998">"បានភ្ជាប់"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"បានភ្ជាប់ / ចុច​ដើម្បីមើលគេហទំព័រ"</string>
+    <string name="application_label" msgid="1322847171305285454">"កម្មវិធី​គ្រប់គ្រង​បណ្ដាញ"</string>
+</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
new file mode 100644
index 0000000..1e7f7f8
--- /dev/null
+++ b/res/values-kn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ವೈ-ಫೈ ಪ್ರಾರಂಭ ಪೋರ್ಟಲ್‌ನ ದೃಢೀಕರಣ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ಸಾಧನವು ವೈ-ಫೈ ಪ್ರಾರಂಭ ಪೋರ್ಟಲ್‌ಗೆ ಯಶಸ್ವಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆದಾಗ ಮತ್ತು ದೃಢೀಕರಿಸಿದಾಗ ತೋರಿಸಲಾಗುವ ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ನೆಟ್‌ವರ್ಕ್ ಸ್ಥಳದ ಮಾಹಿತಿ"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಸ್ಥಳದ ಮಾಹಿತಿ ನೀಡುವ ಪುಟವಿದೆ ಎಂಬುದನ್ನು ಸೂಚಿಸಲು ಅಧಿಸೂಚನೆಗಳನ್ನು ತೋರಿಸಲಾಗುತ್ತದೆ"</string>
+    <string name="connected" msgid="4563643884927480998">"ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"ಸಂಪರ್ಕಿಸಲಾಗಿದೆ / ವೆಬ್‌ಸೈಟ್ ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="application_label" msgid="1322847171305285454">"ನೆಟ್‌ವರ್ಕ್ ಮ್ಯಾನೇಜರ್"</string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..03fb285
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"종속 포털 인증"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"기기가 종속 포털 네트워크에 액세스하도록 인증되었을 때 표시되는 알림입니다."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"네트워크 장소 정보"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"네트워크에 장소 정보 페이지가 있음을 나타내기 위해 표시되는 알림입니다."</string>
+    <string name="connected" msgid="4563643884927480998">"연결됨"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"연결됨 / 탭하여 웹사이트 보기"</string>
+    <string name="application_label" msgid="1322847171305285454">"네트워크 관리자"</string>
+</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
new file mode 100644
index 0000000..9aec881
--- /dev/null
+++ b/res/values-ky/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Кирүү бетинин аутентификациясы"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Түзмөк тармактын кирүү бети аркылуу аутентификациядан ийгиликтүү өткөндө билдирмелер көрсөтүлөт"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Тармактын жайгашуусу жөнүндө маалымат"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Тармак маалымат барагына киргенде билдирме көрсөтүлөт"</string>
+    <string name="connected" msgid="4563643884927480998">"Туташты"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Туташты / Вебсайтты көрүү үчүн таптаңыз"</string>
+    <string name="application_label" msgid="1322847171305285454">"Тармактарды башкаргыч"</string>
+</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
new file mode 100644
index 0000000..58e4bc2
--- /dev/null
+++ b/res/values-lo/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ການພິສູດຢືນຢັນຊ່ອງ​ທາງ​ເຂົ້າ​ຮັກ​ສາ​ໄວ້"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ສະແດງການແຈ້ງເຕືອນເມື່ອອຸປະກອນໄດ້ພິສູດຢືນຢັນຫາເຄືອຂ່າຍຊ່ອງ​ທາງ​ເຂົ້າ​ຮັກ​ສາ​ໄວ້ສຳເລັດ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ຂໍ້ມູນສະຖານທີ່ເຄືອຂ່າຍ"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ສະແດງການແຈ້ງເຕືອນເພື່ອລະບຸວ່າເຄືອຂ່າຍມີໜ້າຂໍ້ມູນສະຖານທີ່"</string>
+    <string name="connected" msgid="4563643884927480998">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"ເຊື່ອມຕໍ່ແລ້ວ / ແຕະເພື່ອເບິ່ງເວັບໄຊ"</string>
+    <string name="application_label" msgid="1322847171305285454">"ຕົວຈັດການເຄືອຂ່າຍ"</string>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..18c8921
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Fiksuotojo portalo autentifikavimas"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Pranešimai rodomi, kai įrenginys sėkmingai autentifikuotas naudoti fiksuotųjų portalų tinklą"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Tinklo vietos informacija"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Pranešimai rodomi informuojant, kad tinkle yra vietos informacijos puslapis"</string>
+    <string name="connected" msgid="4563643884927480998">"Prisijungta"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Prisijungta / žr. svetainę palietę"</string>
+    <string name="application_label" msgid="1322847171305285454">"Tinklo tvarkymo įrankis"</string>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..8ecaada
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentifikācija caurlaides lapā"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Paziņojumi, kas tiek rādīti, kad ierīcē ir sekmīgi veikta autentifikācija caurlaides lapas tīklā"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Norises vietas informācija"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Paziņojumi par to, ka tīklam ir norises vietas informācijas lapa"</string>
+    <string name="connected" msgid="4563643884927480998">"Izveidots savienojums"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Izveidots savienojums / piesk., lai skatītu vietni"</string>
+    <string name="application_label" msgid="1322847171305285454">"Tīkla pārzinis"</string>
+</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
new file mode 100644
index 0000000..44dfed5
--- /dev/null
+++ b/res/values-mk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Проверка преку портал за проверка"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Се прикажуваат известувања кога уредот успешно ќе изврши проверка на мрежата на порталот за проверка"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Мрежни информации за местото на настанот"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Се прикажуваат известувања за да означат дека мрежата има страница со информации за местото на настанот"</string>
+    <string name="connected" msgid="4563643884927480998">"Поврзан"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Поврзано/допрете за да се прикаже веб-сајтот"</string>
+    <string name="application_label" msgid="1322847171305285454">"Управувач со мрежата"</string>
+</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
new file mode 100644
index 0000000..5eee05b
--- /dev/null
+++ b/res/values-ml/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ക്യാപ്റ്റീവ് പോർട്ടൽ പരിശോധിക്കൽ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ക്യാപ്റ്റീവ് പോർട്ടൽ നെറ്റ്‌വർക്കിൽ ഉപകരണം പരിശോധിച്ചുറപ്പിച്ചാൽ അറിയിപ്പുകൾ കാണിക്കും"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"നെറ്റ്‌വർക്കിലെ സ്ഥല വിവരങ്ങൾ"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"നെറ്റ്‌വർക്കിന് സ്ഥല വിവരങ്ങൾക്കുള്ള പേജ് ഉണ്ടെന്ന് സൂചിപ്പിക്കാൻ അറിയിപ്പുകൾ കാണിക്കും"</string>
+    <string name="connected" msgid="4563643884927480998">"കണക്റ്റ് ചെയ്തു"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"കണക്‌റ്റ് ചെയ്‌തു / വെബ്‌സെെറ്റ് കാണാൻ ടാപ്പ് ചെയ്യൂ"</string>
+    <string name="application_label" msgid="1322847171305285454">"നെറ്റ്‌വർക്ക് മാനേജർ"</string>
+</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
new file mode 100644
index 0000000..10e7870
--- /dev/null
+++ b/res/values-mn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Дамжих порталын нотолгоо"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Төхөөрөмжийг дамжих порталын сүлжээнд амжилттай баталгаажуулсан үед харуулдаг мэдэгдлүүд"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Сүлжээний байршлын мэдээлэл"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Сүлжээнд байршлын мэдээллийн хуудас байгааг заах зорилгоор харуулдаг мэдэгдэл"</string>
+    <string name="connected" msgid="4563643884927480998">"Холбогдсон"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Холбогдсон / Вэб сайтыг үзэхийн тулд товшино уу"</string>
+    <string name="application_label" msgid="1322847171305285454">"Сүлжээний менежер"</string>
+</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
new file mode 100644
index 0000000..ab23825
--- /dev/null
+++ b/res/values-mr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"कॅप्टिव्ह पोर्टल ऑथेंटिकेशन"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"डिव्हाइस यशस्वीरीत्या कॅप्टिव्ह पोर्टल नेटवर्कमध्ये ऑथेंटिकेट केल्यावर दाखवल्या जाणाऱ्या सूचना"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्कच्या स्थळासंबंधित माहिती"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"नेटवर्कचे स्थळासंबंधित माहितीचे पेज असल्याचे दाखवण्याकरिता सूचना दाखवल्या जातात"</string>
+    <string name="connected" msgid="4563643884927480998">"कनेक्ट केले आहे"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"कनेक्ट केले / वेबसाइट पाहण्यासाठी टॅप करा"</string>
+    <string name="application_label" msgid="1322847171305285454">"नेटवर्क व्यवस्थापक"</string>
+</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
new file mode 100644
index 0000000..e3b7121
--- /dev/null
+++ b/res/values-ms/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Pengesahan portal terbolot"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Pemberitahuan ditunjukkan apabila peranti telah berjaya disahkan ke rangkaian portal terbolot"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Maklumat tempat rangkaian"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Pemberitahuan dipaparkan untuk menunjukkan rangkaian mempunyai halaman maklumat tempat"</string>
+    <string name="connected" msgid="4563643884927480998">"Disambungkan"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Disambungkan / Ketik untuk melihat tapak web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Pengurus rangkaian"</string>
+</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
new file mode 100644
index 0000000..1cd147c
--- /dev/null
+++ b/res/values-my/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal အထောက်အထားစိစစ်ခြင်း"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Captive portal ကွန်ရက်တွင် စက်ကို အောင်မြင်စွာ အထောက်အထားစိစစ်ပြီးသောအခါ အကြောင်းကြားချက်များ ပြသည်"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ကွန်ရက်နေရာအချက်အလက်"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ကွန်ရက်တွင် နေရာအချက်အလက် စာမျက်နှာ ရှိကြောင်း ညွှန်ပြရန် အကြောင်းကြားချက်များ ပြသည်"</string>
+    <string name="connected" msgid="4563643884927480998">"ချိတ်ဆက်ထားသည်"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"ချိတ်ဆက်ထားသည် / ဝဘ်ဆိုက်ကိုကြည့်ရန် တို့ပါ"</string>
+    <string name="application_label" msgid="1322847171305285454">"ကွန်ရက်မန်နေဂျာ"</string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..9f2a576
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentisering med obligatorisk side"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Varsler som vises når enheten er blitt autentisert på en obligatorisk side for et nettverk"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Nettverkets stedsInformasjon"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Varsler vises for å indikere at nettverket har en side med stedsinformasjon"</string>
+    <string name="connected" msgid="4563643884927480998">"Tilkoblet"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Tilkoblet / trykk for å se nettstedet"</string>
+    <string name="application_label" msgid="1322847171305285454">"Nettverksadministrator"</string>
+</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
new file mode 100644
index 0000000..500d584
--- /dev/null
+++ b/res/values-ne/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"क्याप्टिभ पोर्टलको प्रमाणीकरण"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"यन्त्र क्याप्टिभ पोर्टल नेटवर्कमा सफलतापूर्वक जोडिएको कुरा प्रमाणित भएपछि देखाइने सूचनाहरू"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्कको स्थानसम्बन्धी जानकारी"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"नेटवर्कको स्थानसम्बन्धी जानकारी भएको पृष्ठ रहेको सङ्केत गर्न देखाइने सूचनाहरू"</string>
+    <string name="connected" msgid="4563643884927480998">"जोडिएको छ"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"जोडियो / वेबसाइट हेर्न ट्याप गर्नुहोस्"</string>
+    <string name="application_label" msgid="1322847171305285454">"नेटवर्क व्यवस्थापक"</string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..527d895
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Verificatie van captive portal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Er worden meldingen weergegeven als het apparaat is geverifieerd voor een captive portal-netwerk"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Netwerklocatie-informatie"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Er worden meldingen weergegeven om aan te geven dat het netwerk een locatie-informatiepagina heeft"</string>
+    <string name="connected" msgid="4563643884927480998">"Verbonden"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Verbonden / Tik om de website te bekijken"</string>
+    <string name="application_label" msgid="1322847171305285454">"Netwerkbeheer"</string>
+</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
new file mode 100644
index 0000000..a067683
--- /dev/null
+++ b/res/values-or/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"କ୍ୟାପ୍ଟିଭ୍ ପୋର୍ଟାଲ୍ ପ୍ରାମାଣିକତା"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ଡିଭାଇସଟି ଏକ କ୍ୟାପ୍ଟିଭ୍ ପୋର୍ଟାଲ୍ ନେଟୱାର୍କରେ ସଫଳତାର ସହିତ ପ୍ରାମାଣିକୃତ ହେଲେ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାଯାଏ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ନେଟୱାର୍କ ଭେନ୍ୟୁ ସୂଚନା"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ନେଟୱାର୍କରେ ଏକ ଭେନ୍ୟୁ ସୂଚନା ପୃଷ୍ଠା ଅଛି ବୋଲି ସୂଚିତ କରିବାକୁ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାଯାଏ"</string>
+    <string name="connected" msgid="4563643884927480998">"ସଂଯୋଗ କରାଯାଇଛି"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"ସଂଯୁକ୍ତ ଅଛି / ୱେବସାଇଟ୍ ଦେଖିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
+    <string name="application_label" msgid="1322847171305285454">"ନେଟୱାର୍କ ପରିଚାଳକ"</string>
+</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
new file mode 100644
index 0000000..6b9e743
--- /dev/null
+++ b/res/values-pa/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"ਕੈਪਟਿਵ ਪੋਰਟਲ ਪ੍ਰਮਾਣੀਕਰਨ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"ਡੀਵਾਈਸ ਦੇ ਸਫਲਤਾਪੂਰਵਕ ਕਨੈਕਟ ਹੋਣ ਅਤੇ ਕੈਪਟਿਵ ਪੋਰਟਲ ਨੈੱਟਵਰਕ \'ਤੇ ਪ੍ਰਮਾਣਿਤ ਹੋਣ \'ਤੇ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ਨੈੱਟਵਰਕ ਸਥਾਨ ਬਾਰੇ ਜਾਣਕਾਰੀ"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ਇਹ ਦੱਸਣ ਲਈ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ ਕਿ ਨੈੱਟਵਰਕ ਕੋਲ ਸਥਾਨ ਬਾਰੇ ਜਾਣਕਾਰੀ ਵਾਲਾ ਪੰਨਾ ਹੈ"</string>
+    <string name="connected" msgid="4563643884927480998">"ਕਨੈਕਟ ਹੈ"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ / ਵੈੱਬਸਾਈਟ ਨੂੰ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="application_label" msgid="1322847171305285454">"ਨੈੱਟਵਰਕ ਪ੍ਰਬੰਧਕ"</string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..7db7b79
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Uwierzytelnianie – portal przechwytujący"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Powiadomienia są wyświetlane, gdy urządzenie zostanie uwierzytelnione w sieci portalu przechwytującego"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Sieć z informacjami o miejscach spotkań"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Powiadomienia są wyświetlane, aby poinformować, że sieć posiada stronę z informacjami o miejscach wydarzeń"</string>
+    <string name="connected" msgid="4563643884927480998">"Połączono"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Połączono / Kliknij, aby wyświetlić stronę"</string>
+    <string name="application_label" msgid="1322847171305285454">"Menedżer sieci"</string>
+</resources>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..e1f090b
--- /dev/null
+++ b/res/values-pt-rBR/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticação de portal cativo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificações mostradas quando o dispositivo é autenticado em uma rede de portal cativo"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informações sobre o local da rede"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificações mostradas para indicar que a rede tem uma página de informações sobre o local"</string>
+    <string name="connected" msgid="4563643884927480998">"Conectado"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Conectado / Toque para ver o site"</string>
+    <string name="application_label" msgid="1322847171305285454">"Gerenciador de rede"</string>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..5049b5f
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticação no portal cativo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificações apresentadas quando o dispositivo tiver sido autenticado com êxito numa rede de portal cativo."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informações sobre o local da rede"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"As notificações são apresentadas para indicar que a rede tem uma página de informações sobre o local."</string>
+    <string name="connected" msgid="4563643884927480998">"Ligado"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Ligado/Toque para ver o Website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Gestor de redes"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..e1f090b
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticação de portal cativo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificações mostradas quando o dispositivo é autenticado em uma rede de portal cativo"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informações sobre o local da rede"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificações mostradas para indicar que a rede tem uma página de informações sobre o local"</string>
+    <string name="connected" msgid="4563643884927480998">"Conectado"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Conectado / Toque para ver o site"</string>
+    <string name="application_label" msgid="1322847171305285454">"Gerenciador de rede"</string>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..feda547
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentificare cu portal captiv"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificări afișate atunci când dispozitivul se autentifică la o rețea cu portal captiv"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informații despre locația rețelei"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificări afișate pentru a indica faptul că o rețea are o pagină cu informații despre locație"</string>
+    <string name="connected" msgid="4563643884927480998">"Conectată"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Conectat / atingeți pentru a vedea site-ul"</string>
+    <string name="application_label" msgid="1322847171305285454">"Manager de rețea"</string>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..399d025
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Аутентификация на странице входа"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Уведомления, которые появляются при успешной аутентификации устройства на странице входа в сеть."</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Информация о месте, где доступна сеть"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Уведомления о наличии страницы с информацией о месте, где доступна сеть."</string>
+    <string name="connected" msgid="4563643884927480998">"Подключение выполнено"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Подключено. Нажмите, чтобы открыть сайт."</string>
+    <string name="application_label" msgid="1322847171305285454">"Менеджер сетей"</string>
+</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
new file mode 100644
index 0000000..cc91f09
--- /dev/null
+++ b/res/values-si/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"පිවිසුම් දොරටු සත්‍යාපනය"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"උපාංගය සාර්ථකව පිවිසුම් දොරටු ජාලයකට සත්‍යාපනය කර ඇති විට දැනුම් දීම් පෙන්වයි"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ජාල ස්ථාන තොරතුරු"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ජාලයට ස්ථාන තොරතුරු පිටුවක් ඇති බව දැක්වීමට දැනුම් දීම් පෙන්වයි"</string>
+    <string name="connected" msgid="4563643884927480998">"සම්බන්ධ විය"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"සම්බන්ධිතයි / වෙබ් අඩවිය බැලීමට තට්ටු කරන්න"</string>
+    <string name="application_label" msgid="1322847171305285454">"ජාල කළමනාකරු"</string>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..819fc57
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Overenie v prihlasovacom portáli"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Zobrazované upozornenia oznamujú, že bolo zariadenie úspešne pripojené k sieti prihlasovacieho portálu a overené"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informácie o mieste konania v sieti"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Zobrazované upozornenia oznamujú, že sieť má stránku s informáciami o mieste konania"</string>
+    <string name="connected" msgid="4563643884927480998">"Pripojená"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Pripojené / klepnutím zobrazíte web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Správca siete"</string>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..98b1d9f
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Preverjanje pristnosti za prestrezni portal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obvestila, ki so prikazana, ko naprava uspešno vzpostavi povezavo z omrežjem prestreznega portala in preveri pristnost"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacije o prizorišču v omrežju"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obvestila, ki so prikazana, ko ima omrežje stran z informacijami o prizorišču"</string>
+    <string name="connected" msgid="4563643884927480998">"Povezava je vzpostavljena"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Povezano / Dotaknite se za ogled spletnega mesta"</string>
+    <string name="application_label" msgid="1322847171305285454">"Upravitelj omrežij"</string>
+</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
new file mode 100644
index 0000000..a590ee6
--- /dev/null
+++ b/res/values-sq/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Vërtetimi i portalit të izoluar"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Njoftimet shfaqen kur pajisja është vërtetuar me sukses në një rrjet të një portali të izoluar"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacionet për vendin e rrjetit"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Njoftimet e shfaqura për të treguar se rrjeti ka një faqe me informacione për vendin"</string>
+    <string name="connected" msgid="4563643884927480998">"U lidh"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Lidhur / Trokit për të parë faqen e internetit"</string>
+    <string name="application_label" msgid="1322847171305285454">"Menaxheri i rrjetit"</string>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..e791575
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Потврда идентитета на улазном порталу"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Обавештења која се приказују када се потврди идентитет уређаја на мрежи улазног портала"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Информације о месту на мрежи"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Обавештења која се приказују да би показала да мрежа има страницу са информацијама о месту"</string>
+    <string name="connected" msgid="4563643884927480998">"Повезано"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Повезано/додирните да бисте прегледали веб-сајт"</string>
+    <string name="application_label" msgid="1322847171305285454">"Менаџер мреже"</string>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..dcdc771
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentisering för infångstportal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Aviseringar visas när enheten har autentiserats i ett nätverk med infångstportal"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Information för nätverkets plats"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Aviseringar visas när det finns en informationssida för nätverkets plats"</string>
+    <string name="connected" msgid="4563643884927480998">"Ansluten"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Ansluten – tryck för att visa webbplatsen"</string>
+    <string name="application_label" msgid="1322847171305285454">"Nätverksadministratör"</string>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..480d89c
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Uthibitishaji wa ukurasa wa mwanzo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Arifa zinaonyeshwa wakati kifaa kimethibitishwa kwenye mtandao wenye ukurasa wa mwanzo"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Maelezo ya mtandao wa mahali"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Arifa zinaonyeshwa ili kuashiria kuwa mtandao una ukurasa wa maelezo ya mahali"</string>
+    <string name="connected" msgid="4563643884927480998">"Umeunganisha"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Imeunganishwa / Gusa ili uangalie tovuti"</string>
+    <string name="application_label" msgid="1322847171305285454">"Kidhibiti cha mtandao"</string>
+</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
new file mode 100644
index 0000000..22b3547
--- /dev/null
+++ b/res/values-ta/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"கேப்டிவ் போர்டல் அங்கீகரிப்பு"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"கேப்டிவ் போர்டல் நெட்வொர்க்கில் சாதனம் வெற்றிகரமாக அங்கீகரிக்கப்பட்டதும் அறிவிப்புகள் காட்டப்படும்"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"நெட்வொர்க் முகவரித் தகவல்"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"நெட்வொர்க்கிற்கு முகவரித் தகவல் பக்கம் இருப்பதைக் குறிப்பிட அறிவிப்புகள் காட்டப்படும்"</string>
+    <string name="connected" msgid="4563643884927480998">"இணைக்கப்பட்டது"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"இணைக்கப்பட்டுள்ளது/இணையதளத்தைப் பார்க்க, தட்டவும்"</string>
+    <string name="application_label" msgid="1322847171305285454">"நெட்வொர்க் நிர்வாகி"</string>
+</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
new file mode 100644
index 0000000..8e85ac7
--- /dev/null
+++ b/res/values-te/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"క్యాప్టివ్ పోర్టల్ ప్రామాణీకరణ"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"క్యాప్టివ్ పోర్టల్ నెట్‌వర్క్‌కి పరికరం విజయవంతంగా ప్రామాణీకరించబడినప్పుడు చూపించబడే నోటిఫికేషన్‌లు"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"నెట్‌వర్క్ వేదిక సమాచారం"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"నెట్‌వర్క్‌లో వేదిక సమాచార పేజీ ఉందని సూచించడానికి నోటిఫికేషన్‌లు చూపబడతాయి"</string>
+    <string name="connected" msgid="4563643884927480998">"కనెక్ట్ చేయబడింది"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"కనెక్టెడ్ / వెబ్‌సైట్‌ను చూడటానికి ట్యాప్ చేయండి."</string>
+    <string name="application_label" msgid="1322847171305285454">"నెట్‌వర్క్ మేనేజర్"</string>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..3b053aa
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"การตรวจสอบสิทธิ์แคพทีฟพอร์ทัล"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"การแจ้งเตือนที่แสดงเมื่ออุปกรณ์ผ่านการตรวจสอบสิทธิ์สำหรับการเข้าถึงเครือข่ายแคพทีฟพอร์ทัลได้สำเร็จ"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ข้อมูลสถานที่ของเครือข่าย"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"การแจ้งเตือนที่แสดงเพื่อบ่งชี้ว่าเครือข่ายมีหน้าข้อมูลสถานที่"</string>
+    <string name="connected" msgid="4563643884927480998">"เชื่อมต่อแล้ว"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"เชื่อมต่อแล้ว/แตะเพื่อดูเว็บไซต์"</string>
+    <string name="application_label" msgid="1322847171305285454">"ตัวจัดการเครือข่าย"</string>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..912ec8a
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Pag-authenticate ng captive portal"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Ipapakita ang mga notification kapag matagumpay na na-authenticate ang device sa isang captive portal network"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Impormasyon ng venue ng network"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Nagpapakita ng mga notification para isaad na may page ng impormasyon ng venue ang network"</string>
+    <string name="connected" msgid="4563643884927480998">"Nakakonekta"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Nakakonekta / I-tap para makita ang website"</string>
+    <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..f89016a
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Giriş portalı kimlik doğrulaması"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Cihaz, giriş portalı ağında başarıyla kimlik doğrulaması yaptığında gösterilen bildirimler"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Ağ mekan bilgileri"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Ağın bir mekan bilgisi sayfası olduğunu gösteren bildirimler"</string>
+    <string name="connected" msgid="4563643884927480998">"Bağlandı"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Bağlı / Web sitesini görüntülemek için dokunun"</string>
+    <string name="application_label" msgid="1322847171305285454">"Ağ yöneticisi"</string>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..5ea1b1f
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Автентифікація на адаптивному порталі"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Сповіщення, які з\'являються, коли пристрій авторизується в мережі з адаптивним порталом"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Інформація про місце в мережі"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Сповіщення з\'являються, якщо в мережі є сторінка з інформацією про місце"</string>
+    <string name="connected" msgid="4563643884927480998">"Підключено"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Підключено. Торкніться, щоб переглянути веб-сайт"</string>
+    <string name="application_label" msgid="1322847171305285454">"Менеджер мереж"</string>
+</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
new file mode 100644
index 0000000..ae54a54
--- /dev/null
+++ b/res/values-ur/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"کیپٹو پورٹل کی تصدیق"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"جب آلہ کامیابی کے ساتھ ایک کیپٹو پورٹل نیٹ ورک پر تصدیق کرتا ہے تو اطلاعات کو دکھایا جاتا ہے"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"نیٹ ورک کے مقام کی معلومات"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"نیٹ ورک میں مقام سے متعلق معلومات کا صفحہ موجود ہے کی نشاندہی کے لیے اطلاعات کو دکھایا جاتا ہے"</string>
+    <string name="connected" msgid="4563643884927480998">"منسلک ہے"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"منسلک ہے / ویب سائٹ ملاحظہ کرنے کیلئے تھپتھپائیں"</string>
+    <string name="application_label" msgid="1322847171305285454">"نیٹ ورک مینیجر"</string>
+</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
new file mode 100644
index 0000000..068b2d9
--- /dev/null
+++ b/res/values-uz/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Kirish portali autentifikatsiyasi"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Qurilma tarmoqqa kirish portaliga muvaffaqiyatli autentifikatsiya qilinganida chiqadigan bildirishnomalar"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Qayerda tarmoq mavjudligi haqida axborot"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Tarmoq bor joy haqida axborot sahifasi borligini bildiruvchi bildirishnomalar"</string>
+    <string name="connected" msgid="4563643884927480998">"Ulandi"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Ulangan / Saytni ochish uchun bosing"</string>
+    <string name="application_label" msgid="1322847171305285454">"Tarmoqlar menejeri"</string>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..40584e2
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Xác thực qua trang xác thực"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Thông báo hiển thị khi thiết bị đã xác thực thành công và kết nối với mạng của trang xác thực"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Thông tin về địa điểm trong mạng"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Thông báo hiển thị để cho biết mạng này có trang thông tin về địa điểm"</string>
+    <string name="connected" msgid="4563643884927480998">"Đã kết nối"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Đã kết nối/Nhấn để xem trang web"</string>
+    <string name="application_label" msgid="1322847171305285454">"Trình quản lý mạng"</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..1157f38
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"强制门户身份验证"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"当设备成功通过强制门户网络的身份验证时显示的通知"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"网络包含场地信息页面"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"为说明网络包含场地信息页面而显示的通知"</string>
+    <string name="connected" msgid="4563643884927480998">"已连接"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"已连接/点按即可查看网站"</string>
+    <string name="application_label" msgid="1322847171305285454">"网络管理器"</string>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..b08ea64
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"強制網絡入口驗證"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"當裝置成功通過強制網絡入口網絡驗證時,系統就會顯示通知"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"網絡場地資料"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"為說明網絡具有場地資料頁面而顯示的通知"</string>
+    <string name="connected" msgid="4563643884927480998">"已連線"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"已連接/輕按即可查看網站"</string>
+    <string name="application_label" msgid="1322847171305285454">"網絡管理員"</string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..882f4bf
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"網頁認證入口驗證"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"當裝置成功通過網頁認證入口網路驗證時,系統就會顯示這類通知"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"網路驗證資訊"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"當網路有驗證資訊頁面時,系統就會顯示這類通知"</string>
+    <string name="connected" msgid="4563643884927480998">"已連線"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"已連線/輕觸即可查看網站"</string>
+    <string name="application_label" msgid="1322847171305285454">"網路管理工具"</string>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..0e24519
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="notification_channel_name_connected" msgid="1795068343200033922">"Ukugunyaza iphothali yabathunjiweyo"</string>
+    <string name="notification_channel_description_connected" msgid="7239184168268014518">"Izaziso eziboniswa lapho idivayisi igunyazwe ngokuphumelelayo kunethiwekhi yephothali yabathunjiweyo"</string>
+    <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Ulwazi lwendawo yenethiwekhi"</string>
+    <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Izaziso eziboniswa ukubonisa ukuthi inethiwekhi inekhasi lolwazi lwendawo"</string>
+    <string name="connected" msgid="4563643884927480998">"Ixhunyiwe"</string>
+    <string name="tap_for_info" msgid="6849746325626883711">"Ixhunyiwe / Thepha ukuze ubuke iwebhusayithi"</string>
+    <string name="application_label" msgid="1322847171305285454">"Umphathi wenethiwekhi"</string>
+</resources>
diff --git a/src/android/net/captiveportal/CapportApiProbeResult.java b/src/android/net/captiveportal/CapportApiProbeResult.java
index f693bed..e35b791 100644
--- a/src/android/net/captiveportal/CapportApiProbeResult.java
+++ b/src/android/net/captiveportal/CapportApiProbeResult.java
@@ -25,11 +25,12 @@
  * @hide
  */
 public class CapportApiProbeResult extends CaptivePortalProbeResult {
-    @NonNull
+    // CaptivePortalData may be null if the capport API does not send any valid reply.
+    @Nullable
     private final CaptivePortalDataShim mCapportData;
 
     public CapportApiProbeResult(@NonNull CaptivePortalProbeResult result,
-            @NonNull CaptivePortalDataShim capportData) {
+            @Nullable CaptivePortalDataShim capportData) {
         this(result.mHttpResponseCode, result.redirectUrl, result.detectUrl, capportData,
                 result.probeType);
     }
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 011cbaf..4fedf30 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -80,6 +80,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.stats.connectivity.DhcpFeature;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.EventLog;
@@ -101,6 +102,7 @@
 import com.android.networkstack.apishim.SocketUtilsShimImpl;
 import com.android.networkstack.apishim.common.ShimUtils;
 import com.android.networkstack.arp.ArpPacket;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -151,9 +153,10 @@
 
     // Timers and timeouts.
     private static final int SECONDS = 1000;
-    private static final int FIRST_TIMEOUT_MS         =   2 * SECONDS;
-    private static final int MAX_TIMEOUT_MS           = 128 * SECONDS;
+    private static final int FIRST_TIMEOUT_MS         =   1 * SECONDS;
+    private static final int MAX_TIMEOUT_MS           = 512 * SECONDS;
     private static final int IPMEMORYSTORE_TIMEOUT_MS =   1 * SECONDS;
+    private static final int DHCP_INITREBOOT_TIMEOUT_MS = 5 * SECONDS;
 
     // The waiting time to restart the DHCP configuration process after broadcasting a
     // DHCPDECLINE message, (RFC2131 3.1.5 describes client SHOULD wait a minimum of 10
@@ -200,8 +203,8 @@
 
     // This is not strictly needed, since the client is asynchronous and implements exponential
     // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
-    // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
-    // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
+    // a blocking operation with a 30-second timeout. We pick 18 seconds so we can send packets at
+    // t=0, t=1, t=3, t=7, t=16, allowing for 10% jitter.
     private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;
 
     // DhcpClient uses IpClient's handler.
@@ -304,6 +307,8 @@
     private final Context mContext;
     private final Random mRandom;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+    @NonNull
+    private final IpProvisioningMetrics mMetrics;
 
     // We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
     // be off-link as well as on-link).
@@ -377,9 +382,11 @@
      */
     public static class Dependencies {
         private final NetworkStackIpMemoryStore mNetworkStackIpMemoryStore;
+        private final IpProvisioningMetrics mMetrics;
 
-        public Dependencies(NetworkStackIpMemoryStore store) {
+        public Dependencies(NetworkStackIpMemoryStore store, IpProvisioningMetrics metrics) {
             mNetworkStackIpMemoryStore = store;
+            mMetrics = metrics;
         }
 
         /**
@@ -406,6 +413,13 @@
         }
 
         /**
+         * Get a IpProvisioningMetrics instance.
+         */
+        public IpProvisioningMetrics getIpProvisioningMetrics() {
+            return mMetrics;
+        }
+
+        /**
          * Return whether a feature guarded by a feature flag is enabled.
          * @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
          */
@@ -443,6 +457,7 @@
         mController = controller;
         mIfaceName = iface;
         mIpMemoryStore = deps.getIpMemoryStore();
+        mMetrics = deps.getIpProvisioningMetrics();
 
         // CHECKSTYLE:OFF IndentationCheck
         addState(mStoppedState);
@@ -483,6 +498,7 @@
         final boolean sendHostname = deps.getSendHostnameOption(context);
         mHostname = sendHostname ? new HostnameTransliterator().transliterate(
                 deps.getDeviceName(mContext)) : null;
+        mMetrics.setHostnameTransinfo(sendHostname, mHostname != null);
     }
 
     public void registerForPreDhcpNotification() {
@@ -502,9 +518,9 @@
      * check whether or not to support caching the last lease info and INIT-REBOOT state.
      *
      * INIT-REBOOT state is supported on Android R by default if there is no experiment flag set to
-     * disable this feature explicitly, meanwhile we still hope to be able to control this feature
-     * on/off by pushing experiment flag for A/B testing and metrics collection on both of Android
-     * Q and R version, however it's disbled on Android Q by default.
+     * disable this feature explicitly, meanwhile turning this feature on/off by pushing experiment
+     * flag makes it possible to do A/B test and metrics collection on both of Android Q and R, but
+     * it's disabled on Android Q by default.
      */
     public boolean isDhcpLeaseCacheEnabled() {
         final boolean defaultEnabled =
@@ -528,6 +544,15 @@
                 false /* defaultEnabled */);
     }
 
+    private void recordMetricEnabledFeatures() {
+        if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
+        if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
+        if (isDhcpIpConflictDetectEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+        if (mConfiguration.isPreconnectionEnabled) {
+            mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_FILS);
+        }
+    }
+
     private void confirmDhcpLease(DhcpPacket packet, DhcpResults results) {
         setDhcpLeaseExpiry(packet);
         acceptDhcpResults(results, "Confirmed");
@@ -609,6 +634,7 @@
                     EventLog.writeEvent(snetTagId, bugId, uid, data);
                 }
                 mMetricsLog.log(mIfaceName, new DhcpErrorEvent(e.errorCode));
+                mMetrics.addDhcpErrorCode(e.errorCode);
             }
         }
 
@@ -686,6 +712,7 @@
         final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
                 DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
                 DO_UNICAST, getRequestedParams(), isDhcpRapidCommitEnabled(), mHostname);
+        mMetrics.incrementCountForDiscover();
         return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
     }
 
@@ -704,6 +731,7 @@
         String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
                              " request=" + requestedAddress.getHostAddress() +
                              " serverid=" + serverStr;
+        mMetrics.incrementCountForRequest();
         return transmitPacket(packet, description, encap, to);
     }
 
@@ -936,6 +964,7 @@
                     } else {
                         startInitRebootOrInit();
                     }
+                    recordMetricEnabledFeatures();
                     return HANDLED;
                 default:
                     return NOT_HANDLED;
@@ -1421,6 +1450,7 @@
             try {
                 final ArpPacket packet = ArpPacket.parseArpPacket(recvbuf, length);
                 if (hasIpAddressConflict(packet, mTargetIp)) {
+                    mMetrics.incrementCountForIpConflict();
                     sendMessage(EVENT_IP_CONFLICT);
                 }
             } catch (ArpPacket.ParseException e) {
@@ -1814,6 +1844,7 @@
     class DhcpInitRebootState extends DhcpRequestingState {
         @Override
         public void enter() {
+            mTimeout = DHCP_INITREBOOT_TIMEOUT_MS;
             super.enter();
             startNewTransaction();
         }
diff --git a/src/android/net/dhcp/DhcpLease.java b/src/android/net/dhcp/DhcpLease.java
index 3226f28..0b9cd7c 100644
--- a/src/android/net/dhcp/DhcpLease.java
+++ b/src/android/net/dhcp/DhcpLease.java
@@ -16,7 +16,7 @@
 
 package android.net.dhcp;
 
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 
 import android.net.MacAddress;
 import android.os.SystemClock;
diff --git a/src/android/net/dhcp/DhcpLeaseRepository.java b/src/android/net/dhcp/DhcpLeaseRepository.java
index b7a2572..8420996 100644
--- a/src/android/net/dhcp/DhcpLeaseRepository.java
+++ b/src/android/net/dhcp/DhcpLeaseRepository.java
@@ -18,10 +18,10 @@
 
 import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
 import static android.net.dhcp.DhcpLease.inet4AddrToString;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
 
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS;
 
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
index d022973..3915740 100644
--- a/src/android/net/dhcp/DhcpPacket.java
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -22,7 +22,6 @@
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.metrics.DhcpErrorEvent;
-import android.net.shared.Inet4AddressUtils;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.system.OsConstants;
@@ -31,6 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.net.module.util.Inet4AddressUtils;
 import com.android.networkstack.apishim.common.ShimUtils;
 
 import java.io.UnsupportedEncodingException;
diff --git a/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java b/src/android/net/dhcp/DhcpResultsParcelableUtil.java
similarity index 68%
rename from common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java
rename to src/android/net/dhcp/DhcpResultsParcelableUtil.java
index 7ef764b..7075925 100644
--- a/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java
+++ b/src/android/net/dhcp/DhcpResultsParcelableUtil.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,22 +14,22 @@
  * limitations under the License.
  */
 
-package android.net.shared;
+package android.net.dhcp;
 
-import android.annotation.Nullable;
+import static android.net.shared.IpConfigurationParcelableUtil.parcelAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress;
+
 import android.net.DhcpResults;
 import android.net.DhcpResultsParcelable;
-import android.net.InetAddresses;
+
+import androidx.annotation.Nullable;
 
 import java.net.Inet4Address;
-import java.net.InetAddress;
 
 /**
- * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
- * configuration classes.
- * @hide
+ * A utility class to convert DhcpResults to DhcpResultsParcelable.
  */
-public final class IpConfigurationParcelableUtil {
+public final class DhcpResultsParcelableUtil {
     /**
      * Convert DhcpResults to a DhcpResultsParcelable.
      */
@@ -60,22 +60,4 @@
         results.captivePortalApiUrl = p.captivePortalApiUrl;
         return results;
     }
-
-    /**
-     * Convert InetAddress to String.
-     * TODO: have an InetAddressParcelable
-     */
-    public static String parcelAddress(@Nullable InetAddress addr) {
-        if (addr == null) return null;
-        return addr.getHostAddress();
-    }
-
-    /**
-     * Convert String to InetAddress.
-     * TODO: have an InetAddressParcelable
-     */
-    public static InetAddress unparcelAddress(@Nullable String addr) {
-        if (addr == null) return null;
-        return InetAddresses.parseNumericAddress(addr);
-    }
 }
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index 55b1f28..6c95b5a 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -23,8 +23,6 @@
 import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.system.OsConstants.AF_INET;
@@ -36,6 +34,8 @@
 import static android.system.OsConstants.SO_REUSEADDR;
 
 import static com.android.internal.util.TrafficStatsConstants.TAG_SYSTEM_DHCP_SERVER;
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
diff --git a/src/android/net/dhcp/DhcpServingParams.java b/src/android/net/dhcp/DhcpServingParams.java
index 77cdf7a..97db6ae 100644
--- a/src/android/net/dhcp/DhcpServingParams.java
+++ b/src/android/net/dhcp/DhcpServingParams.java
@@ -16,9 +16,8 @@
 
 package android.net.dhcp;
 
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
 import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
 import static com.android.server.util.NetworkStackConstants.IPV4_MIN_MTU;
@@ -27,12 +26,13 @@
 
 import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.shared.Inet4AddressUtils;
 import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.net.module.util.Inet4AddressUtils;
+
 import java.net.Inet4Address;
 import java.util.Arrays;
 import java.util.Collections;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 4860ff3..c4f46ae 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -17,7 +17,7 @@
 package android.net.ip;
 
 import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.server.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
@@ -60,11 +60,11 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
+import android.stats.connectivity.DisconnectCode;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
-import android.util.Patterns;
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
@@ -80,12 +80,15 @@
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.networkstack.apishim.common.NetworkInformationShim;
 import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -128,6 +131,7 @@
     private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
     private final NetworkStackIpMemoryStore mIpMemoryStore;
     private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
+    private final IpProvisioningMetrics mIpProvisioningMetrics = new IpProvisioningMetrics();
 
     /**
      * Dump all state machine and connectivity packet logs to the specified writer.
@@ -385,6 +389,9 @@
     private static final int CMD_COMPLETE_PRECONNECTION = 16;
     private static final int CMD_UPDATE_L2INFORMATION = 17;
 
+    private static final int ARG_LINKPROP_CHANGED_LINKSTATE_DOWN = 0;
+    private static final int ARG_LINKPROP_CHANGED_LINKSTATE_UP = 1;
+
     // Internal commands to use instead of trying to call transitionTo() inside
     // a given State's enter() method. Calling transitionTo() from enter/exit
     // encounters a Log.wtf() that can cause trouble on eng builds.
@@ -481,6 +488,7 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
     private MacAddress mCurrentBssid;
+    private boolean mHasDisabledIPv6OnProvLoss;
 
     /**
      * Reading the snapshot is an asynchronous operation initiated by invoking
@@ -525,8 +533,8 @@
          * Get a DhcpClient Dependencies instance.
          */
         public DhcpClient.Dependencies getDhcpClientDependencies(
-                NetworkStackIpMemoryStore ipMemoryStore) {
-            return new DhcpClient.Dependencies(ipMemoryStore);
+                NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
+            return new DhcpClient.Dependencies(ipMemoryStore, metrics);
         }
 
         /**
@@ -591,7 +599,9 @@
         mLinkObserver = new IpClientLinkObserver(
                 mContext, getHandler(),
                 mInterfaceName,
-                () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED),
+                (ifaceUp) -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED, ifaceUp
+                        ? ARG_LINKPROP_CHANGED_LINKSTATE_UP
+                        : ARG_LINKPROP_CHANGED_LINKSTATE_DOWN),
                 config, mLog) {
             @Override
             public void onInterfaceAdded(String iface) {
@@ -814,9 +824,12 @@
      * Stop this IpClient.
      *
      * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+     *    The message "arg1" parameter is used to record the disconnect code metrics.
+     *    Usually this method is called by the peer (e.g. wifi) intentionally to stop IpClient,
+     *    consider that's the normal user termination.
      */
     public void stop() {
-        sendMessage(CMD_STOP);
+        sendMessage(CMD_STOP, DisconnectCode.DC_NORMAL_TERMINATION.getNumber());
     }
 
     /**
@@ -1070,6 +1083,12 @@
         mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
     }
 
+    // Record the DisconnectCode and transition to StoppingState.
+    private void transitionToStoppingState(final DisconnectCode code) {
+        mIpProvisioningMetrics.setDisconnectCode(code);
+        transitionTo(mStoppingState);
+    }
+
     // For now: use WifiStateMachine's historical notion of provisioned.
     @VisibleForTesting
     static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
@@ -1136,9 +1155,9 @@
         // Note that we can still be disconnected by IpReachabilityMonitor
         // if the IPv6 default gateway (but not the IPv6 DNS servers; see
         // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss =
-                mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
-                && !mCm.shouldAvoidBadWifi();
+        final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIPv6OnProvLoss
+                || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+                        && !mCm.shouldAvoidBadWifi());
 
         // Additionally:
         //
@@ -1162,7 +1181,23 @@
         // IPv6 default route then also consider the loss of that default route
         // to be a loss of provisioning. See b/27962810.
         if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
-            delta = PROV_CHANGE_LOST_PROVISIONING;
+            // Although link properties have lost IPv6 default route in this case, if IPv4 is still
+            // working with appropriate routes and DNS servers, we can keep the current connection
+            // without disconnecting from the network, just disable IPv6 on that given network until
+            // to the next provisioning. Disabling IPv6 will result in all IPv6 connectivity torn
+            // down and all IPv6 sockets being closed, the non-routable IPv6 DNS servers will be
+            // stripped out, so applications will be able to reconnect immediately over IPv4. See
+            // b/131781810.
+            if (newLp.isIpv4Provisioned()) {
+                mInterfaceCtrl.disableIPv6();
+                mHasDisabledIPv6OnProvLoss = true;
+                delta = PROV_CHANGE_STILL_PROVISIONED;
+                if (DBG) {
+                    mLog.log("Disable IPv6 stack completely when the default router has gone");
+                }
+            } else {
+                delta = PROV_CHANGE_LOST_PROVISIONING;
+            }
         }
 
         return delta;
@@ -1263,9 +1298,9 @@
             }
 
             final String capportUrl = mDhcpResults.captivePortalApiUrl;
-            // Uri.parse does no syntax check; do a simple regex check to eliminate garbage.
+            // Uri.parse does no syntax check; do a simple check to eliminate garbage.
             // If the URL is still incorrect data fetching will fail later, which is fine.
-            if (capportUrl != null && Patterns.WEB_URL.matcher(capportUrl).matches()) {
+            if (isParseableUrl(capportUrl)) {
                 NetworkInformationShimImpl.newInstance()
                         .setCaptivePortalApiUrl(newLp, Uri.parse(capportUrl));
             }
@@ -1303,6 +1338,19 @@
         return newLp;
     }
 
+    private static boolean isParseableUrl(String url) {
+        // Verify that a URL has a reasonable format that can be parsed as per the URL constructor.
+        // This does not use Patterns.WEB_URL as that pattern excludes URLs without TLDs, such as on
+        // localhost.
+        if (url == null) return false;
+        try {
+            new URL(url);
+            return true;
+        } catch (MalformedURLException e) {
+            return false;
+        }
+    }
+
     private static void addAllReachableDnsServers(
             LinkProperties lp, Iterable<InetAddress> dnses) {
         // TODO: Investigate deleting this reachability check.  We should be
@@ -1321,6 +1369,12 @@
         if (Objects.equals(newLp, mLinkProperties)) {
             return true;
         }
+
+        // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
+        // wait for the provisioning completion and record the latency.
+        mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
+        mIpProvisioningMetrics.setIPv6ProvisionedLatencyOnFirstTime(newLp.isIpv6Provisioned());
+
         final int delta = setLinkProperties(newLp);
         // Most of the attributes stored in the memory store are deduced from
         // the link properties, therefore when the properties update the memory
@@ -1416,10 +1470,10 @@
         }
         mCallback.onNewDhcpResults(null);
 
-        handleProvisioningFailure();
+        handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_FAIL);
     }
 
-    private void handleProvisioningFailure() {
+    private void handleProvisioningFailure(final DisconnectCode code) {
         final LinkProperties newLp = assembleLinkProperties();
         int delta = setLinkProperties(newLp);
         // If we've gotten here and we're still not provisioned treat that as
@@ -1436,7 +1490,7 @@
 
         dispatchCallback(delta, newLp);
         if (delta == PROV_CHANGE_LOST_PROVISIONING) {
-            transitionTo(mStoppingState);
+            transitionToStoppingState(code);
         }
     }
 
@@ -1577,6 +1631,7 @@
         @Override
         public void enter() {
             stopAllIP();
+            mHasDisabledIPv6OnProvLoss = false;
 
             mLinkObserver.clearInterfaceParams();
             resetLinkProperties();
@@ -1691,7 +1746,7 @@
     private void startDhcpClient() {
         // Start DHCPv4.
         mDhcpClient = mDependencies.makeDhcpClient(mContext, IpClient.this, mInterfaceParams,
-                mDependencies.getDhcpClientDependencies(mIpMemoryStore));
+                mDependencies.getDhcpClientDependencies(mIpMemoryStore, mIpProvisioningMetrics));
 
         // If preconnection is enabled, there is no need to ask Wi-Fi to disable powersaving
         // during DHCP, because the DHCP handshake will happen during association. In order to
@@ -1712,7 +1767,8 @@
             if (mInterfaceParams == null) {
                 logError("Failed to find InterfaceParams for " + mInterfaceName);
                 doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
-                deferMessage(obtainMessage(CMD_STOP));
+                deferMessage(obtainMessage(CMD_STOP,
+                        DisconnectCode.DC_INTERFACE_NOT_FOUND.getNumber()));
                 return;
             }
 
@@ -1740,7 +1796,7 @@
                 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
                     handleLinkPropertiesUpdate(NO_CALLBACKS);
                     if (readyToProceed()) {
-                        transitionTo(mRunningState);
+                        transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState);
                     }
                     break;
 
@@ -1804,6 +1860,7 @@
     class StartedState extends State {
         @Override
         public void enter() {
+            mIpProvisioningMetrics.reset();
             mStartTimeMillis = SystemClock.elapsedRealtime();
             if (mConfiguration.mProvisioningTimeoutMs > 0) {
                 final long alarmTime = SystemClock.elapsedRealtime()
@@ -1815,13 +1872,17 @@
         @Override
         public void exit() {
             mProvisioningTimeoutAlarm.cancel();
+
+            // Record metrics information once this provisioning has completed due to certain
+            // reason (normal termination, provisioning timeout, lost provisioning and etc).
+            mIpProvisioningMetrics.statsWrite();
         }
 
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case CMD_STOP:
-                    transitionTo(mStoppingState);
+                    transitionToStoppingState(DisconnectCode.forNumber(msg.arg1));
                     break;
 
                 case CMD_UPDATE_L2KEY_CLUSTER: {
@@ -1843,7 +1904,7 @@
                     break;
 
                 case EVENT_PROVISIONING_TIMEOUT:
-                    handleProvisioningFailure();
+                    handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_TIMEOUT);
                     break;
 
                 default:
@@ -1880,13 +1941,13 @@
 
             if (mConfiguration.mEnableIPv6 && !startIPv6()) {
                 doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
-                enqueueJumpToStoppingState();
+                enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6);
                 return;
             }
 
             if (mConfiguration.mEnableIPv4 && !isUsingPreconnection() && !startIPv4()) {
                 doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
-                enqueueJumpToStoppingState();
+                enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV4);
                 return;
             }
 
@@ -1894,14 +1955,14 @@
             if ((config != null) && !applyInitialConfig(config)) {
                 // TODO introduce a new IpManagerEvent constant to distinguish this error case.
                 doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-                enqueueJumpToStoppingState();
+                enqueueJumpToStoppingState(DisconnectCode.DC_INVALID_PROVISIONING);
                 return;
             }
 
             if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
                 doImmediateProvisioningFailure(
                         IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
-                enqueueJumpToStoppingState();
+                enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPREACHABILITYMONITOR);
                 return;
             }
         }
@@ -1933,8 +1994,8 @@
             resetLinkProperties();
         }
 
-        private void enqueueJumpToStoppingState() {
-            deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
+        private void enqueueJumpToStoppingState(final DisconnectCode code) {
+            deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING, code.getNumber()));
         }
 
         private ConnectivityPacketTracker createPacketTracker() {
@@ -1969,7 +2030,7 @@
             switch (msg.what) {
                 case CMD_JUMP_RUNNING_TO_STOPPING:
                 case CMD_STOP:
-                    transitionTo(mStoppingState);
+                    transitionToStoppingState(DisconnectCode.forNumber(msg.arg1));
                     break;
 
                 case CMD_START:
@@ -1996,8 +2057,16 @@
                     break;
 
                 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    // EVENT_NETLINK_LINKPROPERTIES_CHANGED message will be received in both of
+                    // provisioning loss and normal user termination cases (e.g. turn off wifi or
+                    // switch to another wifi ssid), hence, checking the current interface link
+                    // state (down or up) helps distinguish the two cases: if the link state is
+                    // down, provisioning is only lost because the link is being torn down (for
+                    // example when turning off wifi), so treat it as a normal termination.
                     if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppingState);
+                        final boolean linkStateUp = (msg.arg1 == ARG_LINKPROP_CHANGED_LINKSTATE_UP);
+                        transitionToStoppingState(linkStateUp ? DisconnectCode.DC_PROVISIONING_FAIL
+                                : DisconnectCode.DC_NORMAL_TERMINATION);
                     }
                     break;
 
@@ -2077,7 +2146,7 @@
                     } else {
                         logError("Failed to set IPv4 address.");
                         dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, mLinkProperties);
-                        transitionTo(mStoppingState);
+                        transitionToStoppingState(DisconnectCode.DC_PROVISIONING_FAIL);
                     }
                     break;
                 }
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index dcbca94..82f8d5d 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -89,8 +89,13 @@
     public interface Callback {
         /**
          * Called when some properties of the link were updated.
+         *
+         * @param linkState Whether the interface link state is up as per the latest
+         *                  {@link #onInterfaceLinkStateChanged(String, boolean)} callback. This
+         *                  should only be used for metrics purposes, as it could be inconsistent
+         *                  with {@link #getLinkProperties()} in particular.
          */
-        void update();
+        void update(boolean linkState);
     }
 
     /** Configuration parameters for IpClientLinkObserver. */
@@ -105,6 +110,7 @@
     private final String mInterfaceName;
     private final Callback mCallback;
     private final LinkProperties mLinkProperties;
+    private boolean mInterfaceLinkState;
     private DnsServerRepository mDnsServerRepository;
     private final AlarmManager mAlarmManager;
     private final Configuration mConfig;
@@ -121,6 +127,7 @@
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mInterfaceName);
         mConfig = config;
+        mInterfaceLinkState = true; // Assume up by default
         mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
@@ -148,8 +155,22 @@
             // now empty. Note that from the moment that the interface is removed, any further
             // interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
             // code that parses them will not be able to resolve the ifindex to an interface name.
-            clearLinkProperties();
-            mCallback.update();
+            final boolean linkState;
+            synchronized (this) {
+                clearLinkProperties();
+                linkState = getInterfaceLinkStateLocked();
+            }
+            mCallback.update(linkState);
+        }
+    }
+
+    @Override
+    public void onInterfaceLinkStateChanged(String iface, boolean state) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
+            synchronized (this) {
+                setInterfaceLinkStateLocked(state);
+            }
         }
     }
 
@@ -157,12 +178,14 @@
     public void onInterfaceAddressUpdated(LinkAddress address, String iface) {
         if (mInterfaceName.equals(iface)) {
             maybeLog("addressUpdated", iface, address);
-            boolean changed;
+            final boolean changed;
+            final boolean linkState;
             synchronized (this) {
                 changed = mLinkProperties.addLinkAddress(address);
+                linkState = getInterfaceLinkStateLocked();
             }
             if (changed) {
-                mCallback.update();
+                mCallback.update(linkState);
             }
         }
     }
@@ -171,12 +194,14 @@
     public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
         if (mInterfaceName.equals(iface)) {
             maybeLog("addressRemoved", iface, address);
-            boolean changed;
+            final boolean changed;
+            final boolean linkState;
             synchronized (this) {
                 changed = mLinkProperties.removeLinkAddress(address);
+                linkState = getInterfaceLinkStateLocked();
             }
             if (changed) {
-                mCallback.update();
+                mCallback.update(linkState);
             }
         }
     }
@@ -185,12 +210,14 @@
     public void onRouteUpdated(RouteInfo route) {
         if (mInterfaceName.equals(route.getInterface())) {
             maybeLog("routeUpdated", route);
-            boolean changed;
+            final boolean changed;
+            final boolean linkState;
             synchronized (this) {
                 changed = mLinkProperties.addRoute(route);
+                linkState = getInterfaceLinkStateLocked();
             }
             if (changed) {
-                mCallback.update();
+                mCallback.update(linkState);
             }
         }
     }
@@ -199,12 +226,14 @@
     public void onRouteRemoved(RouteInfo route) {
         if (mInterfaceName.equals(route.getInterface())) {
             maybeLog("routeRemoved", route);
-            boolean changed;
+            final boolean changed;
+            final boolean linkState;
             synchronized (this) {
                 changed = mLinkProperties.removeRoute(route);
+                linkState = getInterfaceLinkStateLocked();
             }
             if (changed) {
-                mCallback.update();
+                mCallback.update(linkState);
             }
         }
     }
@@ -213,12 +242,14 @@
     public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
         if (mInterfaceName.equals(iface)) {
             maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
-            boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+            final boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+            final boolean linkState;
             if (changed) {
                 synchronized (this) {
                     mDnsServerRepository.setDnsServersOn(mLinkProperties);
+                    linkState = getInterfaceLinkStateLocked();
                 }
-                mCallback.update();
+                mCallback.update(linkState);
             }
         }
     }
@@ -243,6 +274,14 @@
         mLinkProperties.setInterfaceName(mInterfaceName);
     }
 
+    private boolean getInterfaceLinkStateLocked() {
+        return mInterfaceLinkState;
+    }
+
+    private void setInterfaceLinkStateLocked(boolean state) {
+        mInterfaceLinkState = state;
+    }
+
     /** Notifies this object of new interface parameters. */
     public void setInterfaceParams(InterfaceParams params) {
         mNetlinkMonitor.setIfindex(params.index);
@@ -355,7 +394,7 @@
                 cancelPref64Alarm();
             }
 
-            mCallback.update();
+            mCallback.update(getInterfaceLinkStateLocked());
         }
 
         private void processPref64Option(StructNdOptPref64 opt, final long now) {
diff --git a/src/android/net/util/DataStallUtils.java b/src/android/net/util/DataStallUtils.java
index 3391a71..2c5de57 100644
--- a/src/android/net/util/DataStallUtils.java
+++ b/src/android/net/util/DataStallUtils.java
@@ -96,7 +96,7 @@
     /**
      * Default polling interval to observe the tcp health.
      */
-    public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 10_000;
+    public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 20_000;
 
     /**
      * Default tcp packets fail rate to suspect as a data stall.
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 99563ee..94de7c3 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -226,6 +226,15 @@
     public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION =
             "dns_probe_private_ip_no_internet";
 
+    /**
+     * Experiment flag to enable validation metrics sent by NetworkMonitor.
+     *
+     * Metrics are sent by default. They can be disabled by setting the flag to a number greater
+     * than the APK version (for example 999999999).
+     * @see #isFeatureEnabled(Context, String, String, boolean)
+     */
+    public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
@@ -348,6 +357,9 @@
      * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
      * with current version of property. If this property version is valid, the corresponding
      * experimental feature would be enabled, otherwise disabled.
+     *
+     * This is useful to ensure that if a module install is rolled back, flags are not left fully
+     * rolled out on a version where they have not been well tested.
      * @param context The global context information about an app environment.
      * @param namespace The namespace containing the property to look up.
      * @param name The name of the property to look up.
@@ -363,6 +375,9 @@
      * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
      * with current version of property. If this property version is valid, the corresponding
      * experimental feature would be enabled, otherwise disabled.
+     *
+     * This is useful to ensure that if a module install is rolled back, flags are not left fully
+     * rolled out on a version where they have not been well tested.
      * @param context The global context information about an app environment.
      * @param namespace The namespace containing the property to look up.
      * @param name The name of the property to look up.
@@ -436,4 +451,22 @@
         return addr instanceof Inet6Address
                 && ((addr.getAddress()[0] & 0xfe) == 0xfc);
     }
+
+    /**
+     * Returns the {@code int} nearest in value to {@code value}.
+     *
+     * @param value any {@code long} value
+     * @return the same value cast to {@code int} if it is in the range of the {@code int}
+     * type, {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if
+     * it is too small
+     */
+    public static int saturatedCast(long value) {
+        if (value > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        }
+        if (value < Integer.MIN_VALUE) {
+            return Integer.MIN_VALUE;
+        }
+        return (int) value;
+    }
 }
diff --git a/src/android/net/util/Stopwatch.java b/src/android/net/util/Stopwatch.java
index 07618e9..88e523e 100644
--- a/src/android/net/util/Stopwatch.java
+++ b/src/android/net/util/Stopwatch.java
@@ -49,6 +49,15 @@
     }
 
     /**
+     * Restart the Stopwatch.
+     */
+    public Stopwatch restart() {
+        mStartTimeNs = SystemClock.elapsedRealtimeNanos();
+        mStopTimeNs = 0;
+        return this;
+    }
+
+    /**
      * Stop the Stopwatch.
      * @return the total time recorded, in microseconds, or 0 if not started.
      */
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index a330c70..dbb62b1 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -16,6 +16,8 @@
 
 package com.android.networkstack;
 
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -142,7 +144,19 @@
                 resources.getString(title),
                 importance);
         channel.setDescription(resources.getString(description));
-        mNotificationManager.createNotificationChannel(channel);
+        getNotificationManagerForChannels().createNotificationChannel(channel);
+    }
+
+    /**
+     * Get the NotificationManager to use to query channels, as opposed to posting notifications.
+     *
+     * Although notifications are posted as USER_ALL, notification channels are always created
+     * based on the UID calling NotificationManager, regardless of the context UserHandle.
+     * When querying notification channels, using a USER_ALL context would return no channel: the
+     * default context (as UserHandle 0 for NetworkStack) must be used.
+     */
+    private NotificationManager getNotificationManagerForChannels() {
+        return mContext.getSystemService(NotificationManager.class);
     }
 
     /**
@@ -225,7 +239,7 @@
                     .setContentText(res.getString(R.string.tap_for_info))
                     .setContentIntent(mDependencies.getActivityPendingIntent(
                             getContextAsUser(mContext, UserHandle.CURRENT),
-                            infoIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+                            infoIntent, PendingIntent.FLAG_IMMUTABLE));
 
             networkStatus.mShownNotification = NOTE_VENUE_INFO;
         } else if (showValidated) {
@@ -238,7 +252,7 @@
                     .setContentIntent(mDependencies.getActivityPendingIntent(
                             getContextAsUser(mContext, UserHandle.CURRENT),
                             new Intent(Settings.ACTION_WIFI_SETTINGS),
-                            PendingIntent.FLAG_UPDATE_CURRENT));
+                            PendingIntent.FLAG_IMMUTABLE));
 
             networkStatus.mShownNotification = NOTE_CONNECTED;
         } else {
@@ -284,7 +298,11 @@
     }
 
     private boolean isVenueInfoNotificationEnabled() {
-        return mNotificationManager.getNotificationChannel(CHANNEL_VENUE_INFO) != null;
+        final NotificationChannel channel = getNotificationManagerForChannels()
+                .getNotificationChannel(CHANNEL_VENUE_INFO);
+        if (channel == null) return false;
+
+        return channel.getImportance() != IMPORTANCE_NONE;
     }
 
     private static String getNotificationTag(@NonNull Network network) {
diff --git a/src/com/android/networkstack/metrics/DataStallDetectionStats.java b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
index d294e04..4de2ec0 100644
--- a/src/com/android/networkstack/metrics/DataStallDetectionStats.java
+++ b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
@@ -261,11 +261,13 @@
             if (info == null) return DataStallEventProto.AP_BAND_UNKNOWN;
 
             int freq = info.getFrequency();
-            // Refer to ScanResult.is5GHz() and ScanResult.is24GHz().
-            if (freq > 4900 && freq < 5900) {
+            // Refer to ScanResult.is5GHz(), ScanResult.is24GHz() and ScanResult.is6GHz().
+            if (freq >= 5160 && freq <= 5865) {
                 return DataStallEventProto.AP_BAND_5GHZ;
-            } else if (freq > 2400 && freq < 2500) {
+            } else if (freq >= 2412 && freq <= 2484) {
                 return DataStallEventProto.AP_BAND_2GHZ;
+            } else if (freq >= 5945 && freq <= 7105) {
+                return DataStallEventProto.AP_BAND_6GHZ;
             } else {
                 return DataStallEventProto.AP_BAND_UNKNOWN;
             }
diff --git a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
new file mode 100644
index 0000000..64e173d
--- /dev/null
+++ b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.net.util.NetworkStackUtils;
+import android.net.util.Stopwatch;
+import android.stats.connectivity.DhcpErrorCode;
+import android.stats.connectivity.DhcpFeature;
+import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.HostnameTransResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class to record the network IpProvisioning into statsd.
+ * 1. Fill in NetworkIpProvisioningReported proto.
+ * 2. Write the NetworkIpProvisioningReported proto into statsd.
+ * 3. This class is not thread-safe, and should always be accessed from the same thread.
+ * @hide
+ */
+
+public class IpProvisioningMetrics {
+    private static final String TAG = IpProvisioningMetrics.class.getSimpleName();
+    private final NetworkIpProvisioningReported.Builder mStatsBuilder =
+            NetworkIpProvisioningReported.newBuilder();
+    private final DhcpSession.Builder mDhcpSessionBuilder = DhcpSession.newBuilder();
+    private final Stopwatch mIpv4Watch = new Stopwatch().start();
+    private final Stopwatch mIpv6Watch = new Stopwatch().start();
+    private final Stopwatch mWatch = new Stopwatch().start();
+    private final Set<DhcpFeature> mDhcpFeatures = new HashSet<DhcpFeature>();
+
+    // Define a maximum number of the DhcpErrorCode.
+    public static final int MAX_DHCP_ERROR_COUNT = 20;
+
+    /**
+     *  reset this all metrics members
+     */
+    public void reset() {
+        mStatsBuilder.clear();
+        mDhcpSessionBuilder.clear();
+        mDhcpFeatures.clear();
+        mIpv4Watch.restart();
+        mIpv6Watch.restart();
+        mWatch.restart();
+    }
+
+    /**
+     * Write the TransportType into mStatsBuilder.
+     * TODO: implement this
+     */
+    public void setTransportType() {}
+
+    /**
+     * Write the IPv4Provisioned latency into mStatsBuilder.
+     */
+    public void setIPv4ProvisionedLatencyOnFirstTime(final boolean isIpv4Provisioned) {
+        if (isIpv4Provisioned && !mStatsBuilder.hasIpv4LatencyMicros()) {
+            mStatsBuilder.setIpv4LatencyMicros(NetworkStackUtils.saturatedCast(mIpv4Watch.stop()));
+        }
+    }
+
+    /**
+     * Write the IPv6Provisioned latency into mStatsBuilder.
+     */
+    public void setIPv6ProvisionedLatencyOnFirstTime(final boolean isIpv6Provisioned) {
+        if (isIpv6Provisioned && !mStatsBuilder.hasIpv6LatencyMicros()) {
+            mStatsBuilder.setIpv6LatencyMicros(NetworkStackUtils.saturatedCast(mIpv6Watch.stop()));
+        }
+    }
+
+    /**
+     * Write the DhcpFeature proto into mStatsBuilder.
+     */
+    public void setDhcpEnabledFeature(final DhcpFeature feature) {
+        if (feature == DhcpFeature.DF_UNKNOWN) return;
+        mDhcpFeatures.add(feature);
+    }
+
+    /**
+     * Write the DHCPDISCOVER transmission count into DhcpSession.
+     */
+    public void incrementCountForDiscover() {
+        mDhcpSessionBuilder.setDiscoverCount(mDhcpSessionBuilder.getDiscoverCount() + 1);
+    }
+
+    /**
+     * Write the DHCPREQUEST transmission count into DhcpSession.
+     */
+    public void incrementCountForRequest() {
+        mDhcpSessionBuilder.setRequestCount(mDhcpSessionBuilder.getRequestCount() + 1);
+    }
+
+    /**
+     * Write the IPv4 address conflict count into DhcpSession.
+     */
+    public void incrementCountForIpConflict() {
+        mDhcpSessionBuilder.setConflictCount(mDhcpSessionBuilder.getConflictCount() + 1);
+    }
+
+    /**
+     * Write the hostname transliteration result into DhcpSession.
+     */
+    public void setHostnameTransinfo(final boolean isOptionEnabled, final boolean transSuccess) {
+        mDhcpSessionBuilder.setHtResult(!isOptionEnabled ? HostnameTransResult.HTR_DISABLE :
+                transSuccess ? HostnameTransResult.HTR_SUCCESS : HostnameTransResult.HTR_FAILURE);
+    }
+
+    private static DhcpErrorCode dhcpErrorFromNumberSafe(int number) {
+        // See DhcpErrorCode.errorCodeWithOption
+        // TODO: add a DhcpErrorCode method to extract the code;
+        //       or replace legacy error codes with the new metrics.
+        final DhcpErrorCode error = DhcpErrorCode.forNumber(number & 0xFFFF0000);
+        if (error == null) return DhcpErrorCode.ET_UNKNOWN;
+        return error;
+    }
+
+    /**
+     * write the DHCP error code into DhcpSession.
+     */
+    public void addDhcpErrorCode(final int errorCode) {
+        if (mDhcpSessionBuilder.getErrorCodeCount() >= MAX_DHCP_ERROR_COUNT) return;
+        mDhcpSessionBuilder.addErrorCode(dhcpErrorFromNumberSafe(errorCode));
+    }
+
+    /**
+     * Write the IP provision disconnect code into DhcpSession.
+     */
+    public void setDisconnectCode(final DisconnectCode disconnectCode) {
+        if (mStatsBuilder.hasDisconnectCode()) return;
+        mStatsBuilder.setDisconnectCode(disconnectCode);
+    }
+
+    /**
+     * Write the NetworkIpProvisioningReported proto into statsd.
+     */
+    public NetworkIpProvisioningReported statsWrite() {
+        if (!mWatch.isStarted()) return null;
+        for (DhcpFeature feature : mDhcpFeatures) {
+            mDhcpSessionBuilder.addUsedFeatures(feature);
+        }
+        mStatsBuilder.setDhcpSession(mDhcpSessionBuilder);
+        mStatsBuilder.setProvisioningDurationMicros(mWatch.stop());
+        mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
+        final NetworkIpProvisioningReported Stats = mStatsBuilder.build();
+        final byte[] DhcpSession = Stats.getDhcpSession().toByteArray();
+        NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_IP_PROVISIONING_REPORTED,
+                Stats.getTransportType().getNumber(),
+                Stats.getIpv4LatencyMicros(),
+                Stats.getIpv6LatencyMicros(),
+                Stats.getProvisioningDurationMicros(),
+                Stats.getDisconnectCode().getNumber(),
+                DhcpSession,
+                Stats.getRandomNumber());
+        mWatch.reset();
+        return Stats;
+    }
+}
diff --git a/src/com/android/networkstack/metrics/NetworkValidationMetrics.java b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
new file mode 100644
index 0000000..f27a939
--- /dev/null
+++ b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+package com.android.networkstack.metrics;
+
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+
+import static java.lang.System.currentTimeMillis;
+
+import android.net.INetworkMonitor;
+import android.net.NetworkCapabilities;
+import android.net.captiveportal.CaptivePortalProbeResult;
+import android.net.metrics.ValidationProbeEvent;
+import android.net.util.NetworkStackUtils;
+import android.net.util.Stopwatch;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
+import android.stats.connectivity.TransportType;
+import android.stats.connectivity.ValidationResult;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+/**
+ * Class to record the network validation into statsd.
+ * 1. Fill in NetworkValidationReported proto.
+ * 2. Write the NetworkValidationReported proto into statsd.
+ * @hide
+ */
+
+public class NetworkValidationMetrics {
+    private final NetworkValidationReported.Builder mStatsBuilder =
+            NetworkValidationReported.newBuilder();
+    private final ProbeEvents.Builder mProbeEventsBuilder = ProbeEvents.newBuilder();
+    private final CapportApiData.Builder mCapportApiDataBuilder = CapportApiData.newBuilder();
+    private final Stopwatch mWatch = new Stopwatch();
+    private int mValidationIndex = 0;
+    // Define a maximum size that can store events.
+    public static final int MAX_PROBE_EVENTS_COUNT = 20;
+
+    /**
+     * Reset this NetworkValidationMetrics and start collecting timing and metrics.
+     *
+     * <p>This must be called when validation starts.
+     */
+    public void startCollection(@Nullable NetworkCapabilities nc) {
+        mStatsBuilder.clear();
+        mProbeEventsBuilder.clear();
+        mCapportApiDataBuilder.clear();
+        mWatch.restart();
+        mStatsBuilder.setTransportType(getTransportTypeFromNC(nc));
+        mValidationIndex++;
+    }
+
+    /**
+     * Returns the enum TransportType.
+     *
+     * <p>This method only supports a limited set of common transport type combinations that can be
+     * measured through metrics, and will return {@link TransportType#TT_UNKNOWN} for others. This
+     * ensures that, for example, metrics for a TRANSPORT_NEW_UNKNOWN | TRANSPORT_ETHERNET network
+     * cannot get aggregated with / compared with a "normal" TRANSPORT_ETHERNET network without
+     * noticing.
+     *
+     * @param nc Capabilities to extract transport type from.
+     * @return the TransportType which is defined in
+     * core/proto/android/stats/connectivity/network_stack.proto
+     */
+    @VisibleForTesting
+    public static TransportType getTransportTypeFromNC(@Nullable NetworkCapabilities nc) {
+        if (nc == null) return TransportType.TT_UNKNOWN;
+
+        final int trCount = nc.getTransportTypes().length;
+        boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR);
+        boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI);
+        boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH);
+        boolean hasEthernet = nc.hasTransport(TRANSPORT_ETHERNET);
+        boolean hasVpn = nc.hasTransport(TRANSPORT_VPN);
+        boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE);
+        boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN);
+
+        // VPN networks are not subject to validation and should not see validation stats, but
+        // metrics could be added to measure private DNS probes only.
+        if (trCount == 3 && hasCellular && hasWifi && hasVpn) {
+            return TransportType.TT_WIFI_CELLULAR_VPN;
+        }
+
+        if (trCount == 2 && hasVpn) {
+            if (hasWifi) return TransportType.TT_WIFI_VPN;
+            if (hasCellular) return TransportType.TT_CELLULAR_VPN;
+            if (hasBT) return TransportType.TT_BLUETOOTH_VPN;
+            if (hasEthernet) return TransportType.TT_ETHERNET_VPN;
+        }
+
+        if (trCount == 1) {
+            if (hasWifi) return TransportType.TT_WIFI;
+            if (hasCellular) return TransportType.TT_CELLULAR;
+            if (hasBT) return TransportType.TT_BLUETOOTH;
+            if (hasEthernet) return TransportType.TT_ETHERNET;
+            if (hasWifiAware) return TransportType.TT_WIFI_AWARE;
+            if (hasLopan) return TransportType.TT_LOWPAN;
+            // TODO: consider having a TT_VPN for VPN-only transport
+        }
+
+        return TransportType.TT_UNKNOWN;
+    }
+
+    /**
+     * Map {@link ValidationProbeEvent} to {@link ProbeType}.
+     */
+    public static ProbeType probeTypeToEnum(final int probeType) {
+        switch(probeType) {
+            case ValidationProbeEvent.PROBE_DNS:
+                return ProbeType.PT_DNS;
+            case ValidationProbeEvent.PROBE_HTTP:
+                return ProbeType.PT_HTTP;
+            case ValidationProbeEvent.PROBE_HTTPS:
+                return ProbeType.PT_HTTPS;
+            case ValidationProbeEvent.PROBE_PAC:
+                return ProbeType.PT_PAC;
+            case ValidationProbeEvent.PROBE_FALLBACK:
+                return ProbeType.PT_FALLBACK;
+            case ValidationProbeEvent.PROBE_PRIVDNS:
+                return ProbeType.PT_PRIVDNS;
+            default:
+                return ProbeType.PT_UNKNOWN;
+        }
+    }
+
+    /**
+     * Map {@link CaptivePortalProbeResult} to {@link ProbeResult}.
+     */
+    public static ProbeResult httpProbeResultToEnum(final CaptivePortalProbeResult result) {
+        if (result == null) return ProbeResult.PR_UNKNOWN;
+
+        if (result.isSuccessful()) {
+            return ProbeResult.PR_SUCCESS;
+        } else if (result.isDnsPrivateIpResponse()) {
+            return ProbeResult.PR_PRIVATE_IP_DNS;
+        } else if (result.isFailed()) {
+            return ProbeResult.PR_FAILURE;
+        } else if (result.isPortal()) {
+            return ProbeResult.PR_PORTAL;
+        } else {
+            return ProbeResult.PR_UNKNOWN;
+        }
+    }
+
+    /**
+     * Map  validation result (as per INetworkMonitor) to {@link ValidationResult}.
+     */
+    @VisibleForTesting
+    public static ValidationResult validationResultToEnum(int result, String redirectUrl) {
+        // TODO: consider adding a VR_PARTIAL_SUCCESS field to track cases where users accepted
+        // partial connectivity
+        if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) {
+            return ValidationResult.VR_SUCCESS;
+        } else if (redirectUrl != null) {
+            return ValidationResult.VR_PORTAL;
+        } else if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL) != 0) {
+            return ValidationResult.VR_PARTIAL;
+        } else {
+            return ValidationResult.VR_FAILURE;
+        }
+    }
+
+    /**
+     * Add a network probe event to the metrics builder.
+     */
+    public void addProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result,
+            @Nullable final CaptivePortalDataShim capportData) {
+        // When the number of ProbeEvents of mProbeEventsBuilder exceeds
+        // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent.
+        // TODO: consider recording the total number of probes in a separate field to know how
+        // many probes are skipped.
+        if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return;
+
+        int latencyUs = NetworkStackUtils.saturatedCast(durationUs);
+
+        final ProbeEvent.Builder probeEventBuilder = ProbeEvent.newBuilder()
+                .setLatencyMicros(latencyUs)
+                .setProbeType(type)
+                .setProbeResult(result);
+
+        if (capportData != null) {
+            final long secondsRemaining =
+                    (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000;
+            mCapportApiDataBuilder
+                .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining))
+                // TODO: rename this field to setRemainingKBytes, or use a long
+                .setRemainingBytes(
+                        NetworkStackUtils.saturatedCast(capportData.getByteLimit() / 1000))
+                .setHasPortalUrl((capportData.getUserPortalUrl() != null))
+                .setHasVenueInfo((capportData.getVenueInfoUrl() != null));
+            probeEventBuilder.setCapportApiData(mCapportApiDataBuilder);
+        }
+
+        mProbeEventsBuilder.addProbeEvent(probeEventBuilder);
+    }
+
+    /**
+     * Write the network validation info to mStatsBuilder.
+     */
+    public void setValidationResult(int result, String redirectUrl) {
+        mStatsBuilder.setValidationResult(validationResultToEnum(result, redirectUrl));
+    }
+
+    /**
+     * Write the NetworkValidationReported proto to statsd.
+     *
+     * <p>This is a no-op if {@link #startCollection(NetworkCapabilities)} was not called since the
+     * last call to this method.
+     */
+    public NetworkValidationReported maybeStopCollectionAndSend() {
+        if (!mWatch.isStarted()) return null;
+        mStatsBuilder.setProbeEvents(mProbeEventsBuilder);
+        mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop()));
+        mStatsBuilder.setValidationIndex(mValidationIndex);
+        // write a random value(0 ~ 999) for sampling.
+        mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
+        final NetworkValidationReported stats = mStatsBuilder.build();
+        final byte[] probeEvents = stats.getProbeEvents().toByteArray();
+
+        NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED,
+                stats.getTransportType().getNumber(),
+                probeEvents,
+                stats.getValidationResult().getNumber(),
+                stats.getLatencyMicros(),
+                stats.getValidationIndex(),
+                stats.getRandomNumber());
+        mWatch.reset();
+        return stats;
+    }
+}
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
new file mode 100644
index 0000000..8a94db9
--- /dev/null
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package com.android.networkstack.metrics;
+
+import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto";
+
+message CapportApiData {
+    // The TTL of the network connection provided by captive portal
+    optional int32 remaining_ttl_secs = 1;
+
+    // The limit traffic data of the network connection provided by captive portal
+    optional int32 remaining_bytes = 2;
+
+    // Is portal url option included in the DHCP packet (Yes, No)
+    optional bool has_portal_url = 3;
+
+    // Is venue info (e.g. store info, maps, flight status) included (Yes, No)
+    optional bool has_venue_info = 4;
+}
+
+message ProbeEvent {
+    // The probe type (http or https, or captive portal API...)
+    optional .android.stats.connectivity.ProbeType probe_type = 1;
+
+    // The latency in microseconds of the probe event
+    optional int32 latency_micros = 2;
+
+    // The result of the probe event
+    optional .android.stats.connectivity.ProbeResult probe_result = 3;
+
+    // The CaptivePortal API info
+    optional CapportApiData capport_api_data = 4;
+}
+
+message ProbeEvents {
+    // Record probe event during the validation
+    repeated ProbeEvent probe_event = 1;
+}
+
+/**
+ * The DHCP (Dynamic Host Configuration Protocol) session info
+ * Logged from:
+ * src/android/net/dhcp/DhcpClient.java
+ */
+message DhcpSession {
+    // The DHCP Feature(s) enabled in this session
+    repeated .android.stats.connectivity.DhcpFeature used_features = 1;
+
+    // The discover packet (re)transmit count
+    optional int32 discover_count = 2;
+
+    // The request packet (re)transmit count
+    optional int32 request_count = 3;
+
+    // The IPv4 address conflict count
+    // (only be meaningful when duplicate address detection is enabled)
+    optional int32 conflict_count = 4;
+
+    // The DHCP packet parsing error code in this session
+    // (defined in android.net.metrics.DhcpErrorEvent)
+    repeated .android.stats.connectivity.DhcpErrorCode error_code = 5;
+
+    // The result of DHCP hostname transliteration
+    optional .android.stats.connectivity.HostnameTransResult ht_result = 6;
+}
+
+/**
+ * Logs Network IP provisioning event
+ * Logged from:
+ * src/com/android/networkstack/metrics/NetworkIpProvisioningMetrics.java
+ */
+message NetworkIpProvisioningReported {
+    // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+    optional .android.stats.connectivity.TransportType transport_type = 1;
+
+    // The latency in microseconds of IP Provisioning over IPV4
+    optional int32 ipv4_latency_micros = 2;
+
+    // The latency in microseconds of IP Provisioning over IPV6
+    optional int32 ipv6_latency_micros = 3;
+
+    // The time duration between provisioning start and end (success or failure)
+    optional int64 provisioning_duration_micros = 4;
+
+    // The specific disconnect reason for this IP provisioning
+    optional .android.stats.connectivity.DisconnectCode disconnect_code = 5;
+
+    // Log DHCP session info (Only valid for IPv4)
+    optional DhcpSession dhcp_session = 6;
+
+    // The random number between 0 ~ 999 for sampling
+    optional int32 random_number = 7;
+}
+
+/**
+ * Logs Network DHCP Renew event
+ * Logged from:
+ * src/android/net/dhcp/DhcpClient.java
+ */
+message NetworkDhcpRenewReported {
+    // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+    optional .android.stats.connectivity.TransportType transport_type = 1;
+
+    // The request packet (re)transmit count
+    optional int32 request_count = 2;
+
+    // The latency in microseconds of DHCP Renew
+    optional int32 latency_micros = 3;
+
+    // The DHCP error code is defined in android.net.metrics.DhcpErrorEvent
+    optional .android.stats.connectivity.DhcpErrorCode error_code = 4;
+
+    // The result of DHCP renew
+    optional .android.stats.connectivity.DhcpRenewResult renew_result = 5;
+
+    // The random number between 0 ~ 999 for sampling
+    optional int32 random_number = 6;
+}
+
+/**
+ * Logs Network Validation event
+ * Logged from:
+ * src/com/android/server/connectivity/NetworkMonitor.java
+ */
+message NetworkValidationReported {
+    // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+    optional .android.stats.connectivity.TransportType transport_type = 1;
+
+    // Record each probe event
+    optional ProbeEvents probe_events = 2;
+
+    // The result of the network validation
+    optional .android.stats.connectivity.ValidationResult validation_result = 3;
+
+    // The latency in microseconds of network validation
+    optional int32 latency_micros = 4;
+
+    // The validation index (the first validation attempt or second, third...)
+    optional int32 validation_index = 5;
+
+    // The random number between 0 ~ 999 for sampling
+    optional int32 random_number = 6;
+}
+
+/**
+ * Logs NetworkStack Quirk event
+ * Logged from:
+ * src/com/android/networkstack/
+ * This will be defined as count metrics on server side
+ */
+message NetworkStackQuirkReported {
+    // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+    optional .android.stats.connectivity.TransportType transport_type = 1;
+
+    // Record each Quirk event
+    optional .android.stats.connectivity.NetworkQuirkEvent event = 2;
+}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 8a1b0c7..40de26e 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -83,6 +83,7 @@
 
 import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS;
 import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS;
+import static com.android.networkstack.apishim.ConstantsShim.TRANSPORT_TEST;
 import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
 import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
 
@@ -128,6 +129,8 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CellIdentityNr;
 import android.telephony.CellInfo;
@@ -154,6 +157,7 @@
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.RingBufferIndices;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -167,6 +171,7 @@
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.metrics.DataStallDetectionStats;
 import com.android.networkstack.metrics.DataStallStatsUtils;
+import com.android.networkstack.metrics.NetworkValidationMetrics;
 import com.android.networkstack.netlink.TcpSocketTracker;
 import com.android.networkstack.util.DnsUtils;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -387,7 +392,8 @@
     private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24;
 
     // Start mReevaluateDelayMs at this value and double.
-    private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
+    @VisibleForTesting
+    static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
     private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
     // Default timeout of evaluating network bandwidth.
     private static final int DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS = 10_000;
@@ -444,7 +450,14 @@
     protected boolean mIsCaptivePortalCheckEnabled;
 
     private boolean mUseHttps;
-    // The total number of captive portal detection attempts for this NetworkMonitor instance.
+    /**
+     * The total number of completed validation attempts (network validated or a captive portal was
+     * detected) for this NetworkMonitor instance.
+     * This does not include attempts that were interrupted, retried or finished with a result that
+     * is not success or portal. See {@code mValidationIndex} in {@link NetworkValidationMetrics}
+     * for a count of all attempts.
+     * TODO: remove when removing legacy metrics.
+     */
     private int mValidations = 0;
 
     // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
@@ -498,6 +511,18 @@
 
     private final boolean mPrivateIpNoInternetEnabled;
 
+    private final boolean mMetricsEnabled;
+
+    // The validation metrics are accessed by individual probe threads, and by the StateMachine
+    // thread. All accesses must be synchronized to make sure the StateMachine thread can see
+    // reports from all probes.
+    // TODO: as that most usage is in the StateMachine thread and probes only add their probe
+    // events, consider having probes return their stats to the StateMachine, and only access this
+    // member on the StateMachine thread without synchronization.
+    @GuardedBy("mNetworkValidationMetrics")
+    private final NetworkValidationMetrics mNetworkValidationMetrics =
+            new NetworkValidationMetrics();
+
     private int getCallbackVersion(INetworkMonitorCallbacks cb) {
         int version;
         try {
@@ -561,6 +586,8 @@
 
         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
         mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
+        mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
+                NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */);
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
         mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
@@ -773,6 +800,51 @@
         }
     }
 
+    private void startMetricsCollection() {
+        if (!mMetricsEnabled) return;
+        try {
+            synchronized (mNetworkValidationMetrics) {
+                mNetworkValidationMetrics.startCollection(mNetworkCapabilities);
+            }
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error resetting validation metrics", e);
+        }
+    }
+
+    private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result,
+            CaptivePortalDataShim capportData) {
+        if (!mMetricsEnabled) return;
+        try {
+            synchronized (mNetworkValidationMetrics) {
+                mNetworkValidationMetrics.addProbeEvent(type, latencyMicros, result, capportData);
+            }
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error recording probe event", e);
+        }
+    }
+
+    private void recordValidationResult(int result, String redirectUrl) {
+        if (!mMetricsEnabled) return;
+        try {
+            synchronized (mNetworkValidationMetrics) {
+                mNetworkValidationMetrics.setValidationResult(result, redirectUrl);
+            }
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error recording validation result", e);
+        }
+    }
+
+    private void maybeStopCollectionAndSendMetrics() {
+        if (!mMetricsEnabled) return;
+        try {
+            synchronized (mNetworkValidationMetrics) {
+                mNetworkValidationMetrics.maybeStopCollectionAndSend();
+            }
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error sending validation stats", e);
+        }
+    }
+
     // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
     // does not entail any real state (hence no enter() or exit() routines).
     private class DefaultState extends State {
@@ -785,11 +857,17 @@
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_NETWORK_DISCONNECTED:
+                    maybeStopCollectionAndSendMetrics();
                     logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
                     quit();
                     return HANDLED;
                 case CMD_FORCE_REEVALUATION:
                 case CMD_CAPTIVE_PORTAL_RECHECK:
+                    if (getCurrentState() == mDefaultState) {
+                        // Before receiving CMD_NETWORK_CONNECTED (when still in mDefaultState),
+                        // requests to reevaluate are not valid: drop them.
+                        return HANDLED;
+                    }
                     String msg = "Forcing reevaluation for UID " + message.arg1;
                     final DnsStallDetector dsd = getDnsStallDetector();
                     if (dsd != null) {
@@ -931,6 +1009,7 @@
             initSocketTrackingIfRequired();
             // start periodical polling.
             sendTcpPollingEvent();
+            maybeStopCollectionAndSendMetrics();
         }
 
         private void initSocketTrackingIfRequired() {
@@ -950,6 +1029,9 @@
                     transitionTo(mValidatedState);
                     break;
                 case CMD_EVALUATE_PRIVATE_DNS:
+                    // TODO: this causes reevaluation of a single probe that is not counted in
+                    // metrics. Add support for such reevaluation probes in metrics, and log them
+                    // separately.
                     transitionTo(mEvaluatingPrivateDnsState);
                     break;
                 case EVENT_DNS_NOTIFICATION:
@@ -1090,7 +1172,7 @@
                     final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
                     // Use redirect URL from AP if exists.
                     final String portalUrl =
-                            (useRedirectUrlForPortal() && probeRes.redirectUrl != null)
+                            (useRedirectUrlForPortal() && makeURL(probeRes.redirectUrl) != null)
                             ? probeRes.redirectUrl : probeRes.detectUrl;
                     appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl);
                     if (probeRes.probeSpec != null) {
@@ -1278,6 +1360,7 @@
             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
             mValidations++;
+            maybeStopCollectionAndSendMetrics();
         }
 
         @Override
@@ -1309,6 +1392,12 @@
                                 notifyPrivateDnsConfigResolved();
                             } else {
                                 handlePrivateDnsEvaluationFailure();
+                                // The private DNS probe fails-fast if the server hostname cannot
+                                // be resolved. Record it as a failure with zero latency.
+                                // TODO: refactor this together with the probe recorded in
+                                // sendPrivateDnsProbe, so logging is symmetric / easier to follow.
+                                recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */,
+                                        ProbeResult.PR_FAILURE, null /* capportData */);
                                 break;
                             }
                         }
@@ -1418,6 +1507,8 @@
                 validationLog(PROBE_PRIVDNS, host,
                         String.format("%dus - Error: %s", time, uhe.getMessage()));
             }
+            recordProbeEventMetrics(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS :
+                    ProbeResult.PR_FAILURE, null /* capportData */);
             logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
             return success;
         }
@@ -1428,14 +1519,23 @@
 
         @Override
         public void enter() {
+            // When starting a full probe cycle here, record any pending stats (for example if
+            // CMD_FORCE_REEVALUATE was called before evaluation finished, as can happen in
+            // EvaluatingPrivateDnsState).
+            maybeStopCollectionAndSendMetrics();
+            // Restart the metrics collection timers. Metrics will be stopped and sent when the
+            // validation attempt finishes (as success, failure or portal), or if it is interrupted
+            // (by being restarted or if NetworkMonitor stops).
+            startMetricsCollection();
             if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
                 //Don't continue to blame UID forever.
                 TrafficStats.clearThreadStatsUid();
             }
 
             final int token = ++mProbeToken;
+            final EvaluationThreadDeps deps = new EvaluationThreadDeps(mNetworkCapabilities);
             mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
-                    isCaptivePortal())));
+                    isCaptivePortal(deps))));
             mThread.start();
         }
 
@@ -1508,6 +1608,9 @@
     private class WaitingForNextProbeState extends State {
         @Override
         public void enter() {
+            // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be
+            // restarted when the next probe starts.
+            maybeStopCollectionAndSendMetrics();
             scheduleNextProbe();
         }
 
@@ -1728,6 +1831,7 @@
         }
         try {
             final List<CellInfo> cells = mTelephonyManager.getAllCellInfo();
+            if (cells == null) return null;
             final Map<String, Integer> countryCodeMap = new HashMap<>();
             int maxCount = 0;
             for (final CellInfo cell : cells) {
@@ -2147,8 +2251,25 @@
         return mCaptivePortalFallbackSpecs[idx];
     }
 
-    @VisibleForTesting
-    protected CaptivePortalProbeResult isCaptivePortal() {
+    /**
+     * Parameters that can be accessed by the evaluation thread in a thread-safe way.
+     *
+     * Parameters such as LinkProperties and NetworkCapabilities cannot be accessed by the
+     * evaluation thread directly, as they are managed in the state machine thread and not
+     * synchronized. This class provides a copy of the required data that is not modified and can be
+     * used safely by the evaluation thread.
+     */
+    private static class EvaluationThreadDeps {
+        // TODO: add parameters that are accessed in a non-thread-safe way from the evaluation
+        // thread (read from LinkProperties, NetworkCapabilities, useHttps, validationStage)
+        private final boolean mIsTestNetwork;
+
+        EvaluationThreadDeps(NetworkCapabilities nc) {
+            this.mIsTestNetwork = nc.hasTransport(TRANSPORT_TEST);
+        }
+    }
+
+    private CaptivePortalProbeResult isCaptivePortal(EvaluationThreadDeps deps) {
         if (!mIsCaptivePortalCheckEnabled) {
             validationLog("Validation disabled.");
             return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN);
@@ -2196,11 +2317,11 @@
             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
         } else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) {
             // Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes.
-            result = sendHttpAndHttpsParallelWithFallbackProbes(
-                    proxyInfo, httpsUrls[0], httpUrls[0]);
+            result = sendHttpAndHttpsParallelWithFallbackProbes(deps, proxyInfo,
+                    httpsUrls[0], httpUrls[0]);
         } else if (mUseHttps) {
             // Support result aggregation from multiple Urls.
-            result = sendMultiParallelHttpAndHttpsProbes(proxyInfo, httpsUrls, httpUrls);
+            result = sendMultiParallelHttpAndHttpsProbes(deps, proxyInfo, httpsUrls, httpUrls);
         } else {
             result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP);
             reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
@@ -2239,6 +2360,8 @@
         // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe.
         if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP
                 && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) {
+            recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
+                    0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */);
             return CaptivePortalProbeResult.PRIVATE_IP;
         }
         return sendHttpProbe(url, probeType, null);
@@ -2270,6 +2393,9 @@
             result = ValidationProbeEvent.DNS_FAILURE;
         }
         final long latency = watch.stop();
+        recordProbeEventMetrics(ProbeType.PT_DNS, latency,
+                (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS :
+                ProbeResult.PR_FAILURE, null /* capportData */);
         logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
         return addresses;
     }
@@ -2387,12 +2513,17 @@
         }
         logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
 
+        final CaptivePortalProbeResult probeResult;
         if (probeSpec == null) {
-            return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString(),
-                    1 << probeType);
+            probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl,
+                    url.toString(),   1 << probeType);
         } else {
-            return probeSpec.getResult(httpResponseCode, redirectUrl);
+            probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
         }
+        recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
+                probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult),
+                null /* capportData */);
+        return probeResult;
     }
 
     @VisibleForTesting
@@ -2482,12 +2613,12 @@
         private final CountDownLatch mLatch;
         private final Probe mProbe;
 
-        ProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, int probeType,
-                Uri captivePortalApiUrl) {
+        ProbeThread(CountDownLatch latch, EvaluationThreadDeps deps, ProxyInfo proxy, URL url,
+                int probeType, Uri captivePortalApiUrl) {
             mLatch = latch;
             mProbe = (probeType == ValidationProbeEvent.PROBE_HTTPS)
-                    ? new HttpsProbe(proxy, url, captivePortalApiUrl)
-                    : new HttpProbe(proxy, url, captivePortalApiUrl);
+                    ? new HttpsProbe(deps, proxy, url, captivePortalApiUrl)
+                    : new HttpProbe(deps, proxy, url, captivePortalApiUrl);
             mResult = CaptivePortalProbeResult.failed(probeType);
         }
 
@@ -2512,11 +2643,14 @@
     }
 
     private abstract static class Probe {
+        protected final EvaluationThreadDeps mDeps;
         protected final ProxyInfo mProxy;
         protected final URL mUrl;
         protected final Uri mCaptivePortalApiUrl;
 
-        protected Probe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+        protected Probe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url,
+                Uri captivePortalApiUrl) {
+            mDeps = deps;
             mProxy = proxy;
             mUrl = url;
             mCaptivePortalApiUrl = captivePortalApiUrl;
@@ -2526,8 +2660,8 @@
     }
 
     final class HttpsProbe extends Probe {
-        HttpsProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
-            super(proxy, url, captivePortalApiUrl);
+        HttpsProbe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+            super(deps, proxy, url, captivePortalApiUrl);
         }
 
         @Override
@@ -2537,18 +2671,24 @@
     }
 
     final class HttpProbe extends Probe {
-        HttpProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
-            super(proxy, url, captivePortalApiUrl);
+        HttpProbe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+            super(deps, proxy, url, captivePortalApiUrl);
         }
 
-        private CaptivePortalDataShim tryCapportApiProbe() {
-            if (mCaptivePortalApiUrl == null) return null;
+        private CaptivePortalDataShim sendCapportApiProbe() {
+            // TODO: consider adding metrics counters for each case returning null in this method
+            // (cases where the API is not implemented properly).
             validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl);
 
             final String apiContent;
             try {
                 final URL url = new URL(mCaptivePortalApiUrl.toString());
-                if (!"https".equals(url.getProtocol())) {
+                // Protocol must be HTTPS
+                // (as per https://www.ietf.org/id/draft-ietf-capport-api-07.txt, #4).
+                // Only allow HTTP on localhost, for testing.
+                final boolean isTestLocalhostHttp = mDeps.mIsTestNetwork
+                        && "localhost".equals(url.getHost()) && "http".equals(url.getProtocol());
+                if (!"https".equals(url.getProtocol()) && !isTestLocalhostHttp) {
                     validationLog("Invalid captive portal API protocol: " + url.getProtocol());
                     return null;
                 }
@@ -2576,26 +2716,38 @@
 
             try {
                 final JSONObject info = new JSONObject(apiContent);
-                return CaptivePortalDataShimImpl.fromJson(info);
+                final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info);
+                if (capportData != null && capportData.isCaptive()
+                        && capportData.getUserPortalUrl() == null) {
+                    validationLog("Missing user-portal-url from capport response");
+                    return null;
+                }
+                return capportData;
             } catch (JSONException e) {
                 validationLog("Could not parse capport API JSON: " + e.getMessage());
                 return null;
             } catch (UnsupportedApiLevelException e) {
+                // This should never happen because LinkProperties would not have a capport URL
+                // before R.
                 validationLog("Platform API too low to support capport API");
                 return null;
             }
         }
 
+        private CaptivePortalDataShim tryCapportApiProbe() {
+            if (mCaptivePortalApiUrl == null) return null;
+            final Stopwatch capportApiWatch = new Stopwatch().start();
+            final CaptivePortalDataShim capportData = sendCapportApiProbe();
+            recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(),
+                    capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS,
+                    capportData);
+            return capportData;
+        }
+
         @Override
         protected CaptivePortalProbeResult sendProbe() {
             final CaptivePortalDataShim capportData = tryCapportApiProbe();
             if (capportData != null && capportData.isCaptive()) {
-                if (capportData.getUserPortalUrl() == null) {
-                    validationLog("Missing user-portal-url from capport response");
-                    return new CapportApiProbeResult(
-                            sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP),
-                            capportData);
-                }
                 final String loginUrlString = capportData.getUserPortalUrl().toString();
                 // Starting from R (where CaptivePortalData was introduced), the captive portal app
                 // delegates to NetworkMonitor for verifying when the network validates instead of
@@ -2631,8 +2783,9 @@
                         && captivePortalApiUrl == null);
     }
 
-    private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(@NonNull ProxyInfo proxy,
-            @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) {
+    private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(
+            @NonNull EvaluationThreadDeps deps, @Nullable ProxyInfo proxy, @NonNull URL[] httpsUrls,
+            @NonNull URL[] httpUrls) {
         // If multiple URLs are required to ensure the correctness of validation, send parallel
         // probes to explore the result in separate probe threads and aggregate those results into
         // one as the final result for either HTTP or HTTPS.
@@ -2657,13 +2810,13 @@
             // TODO: Have the capport probe as a different probe for cleanliness.
             final URL urlMaybeWithCapport = httpUrls[0];
             for (final URL url : httpUrls) {
-                futures.add(ecs.submit(() -> new HttpProbe(proxy, url,
+                futures.add(ecs.submit(() -> new HttpProbe(deps, proxy, url,
                         url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe()));
             }
 
             for (final URL url : httpsUrls) {
-                futures.add(
-                        ecs.submit(() -> new HttpsProbe(proxy, url, capportApiUrl).sendProbe()));
+                futures.add(ecs.submit(() -> new HttpsProbe(deps, proxy, url, capportApiUrl)
+                        .sendProbe()));
             }
 
             final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>();
@@ -2759,15 +2912,15 @@
     }
 
     private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes(
-            ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
+            EvaluationThreadDeps deps, ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
         // Number of probes to wait for. If a probe completes with a conclusive answer
         // it shortcuts the latch immediately by forcing the count to 0.
         final CountDownLatch latch = new CountDownLatch(2);
 
         final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties);
-        final ProbeThread httpsProbe = new ProbeThread(latch, proxy, httpsUrl,
+        final ProbeThread httpsProbe = new ProbeThread(latch, deps, proxy, httpsUrl,
                 ValidationProbeEvent.PROBE_HTTPS, capportApiUrl);
-        final ProbeThread httpProbe = new ProbeThread(latch, proxy, httpUrl,
+        final ProbeThread httpProbe = new ProbeThread(latch, deps, proxy, httpUrl,
                 ValidationProbeEvent.PROBE_HTTP, capportApiUrl);
 
         try {
@@ -3308,6 +3461,7 @@
             p.redirectUrl = redirectUrl;
             p.timestampMillis = SystemClock.elapsedRealtime();
             notifyNetworkTested(p);
+            recordValidationResult(result, redirectUrl);
         }
 
         @VisibleForTesting
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index be338e5..9b1be06 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -16,8 +16,8 @@
 
 package com.android.server.connectivity.ipmemorystore;
 
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -335,21 +335,19 @@
     // Returns the expiry date of the specified row, or one of the error codes above if the
     // row is not found or some other error
     static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+        try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
                 EXPIRY_COLUMN, // columns
                 SELECT_L2KEY, // selection
                 new String[] { key }, // selectionArgs
                 null, // groupBy
                 null, // having
-                null // orderBy
-        );
-        // L2KEY is the primary key ; it should not be possible to get more than one
-        // result here. 0 results means the key was not found.
-        if (cursor.getCount() != 1) return EXPIRY_ERROR;
-        cursor.moveToFirst();
-        final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
-        cursor.close();
-        return result;
+                null)) { // orderBy
+            // L2KEY is the primary key ; it should not be possible to get more than one
+            // result here. 0 results means the key was not found.
+            if (cursor.getCount() != 1) return EXPIRY_ERROR;
+            cursor.moveToFirst();
+            return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+        }
     }
 
     static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
@@ -399,20 +397,19 @@
     @Nullable
     static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db,
             @NonNull final String key) {
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+        try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
                 null, // columns, null means everything
                 NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection
                 new String[] { key }, // selectionArgs
                 null, // groupBy
                 null, // having
-                null); // orderBy
-        // L2KEY is the primary key ; it should not be possible to get more than one
-        // result here. 0 results means the key was not found.
-        if (cursor.getCount() != 1) return null;
-        cursor.moveToFirst();
-        final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
-        cursor.close();
-        return attributes;
+                null)) { // orderBy
+            // L2KEY is the primary key ; it should not be possible to get more than one
+            // result here. 0 results means the key was not found.
+            if (cursor.getCount() != 1) return null;
+            cursor.moveToFirst();
+            return readNetworkAttributesLine(cursor);
+        }
     }
 
     private static final String[] DATA_COLUMN = new String[] {
@@ -422,7 +419,7 @@
     @Nullable
     static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
             @NonNull final String clientId, @NonNull final String name) {
-        final Cursor cursor = db.query(PrivateDataContract.TABLENAME,
+        try (Cursor cursor = db.query(PrivateDataContract.TABLENAME,
                 DATA_COLUMN, // columns
                 PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection
                 + PrivateDataContract.COLNAME_CLIENT + " = ? AND "
@@ -430,14 +427,13 @@
                 new String[] { key, clientId, name }, // selectionArgs
                 null, // groupBy
                 null, // having
-                null); // orderBy
-        // The query above is querying by (composite) primary key, so it should not be possible to
-        // get more than one result here. 0 results means the key was not found.
-        if (cursor.getCount() != 1) return null;
-        cursor.moveToFirst();
-        final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
-        cursor.close();
-        return result;
+                null)) { // orderBy
+            // The query above is querying by (composite) primary key, so it should not be possible
+            // to get more than one result here. 0 results means the key was not found.
+            if (cursor.getCount() != 1) return null;
+            cursor.moveToFirst();
+            return cursor.getBlob(0); // index in the DATA_COLUMN array
+        }
     }
 
     /**
@@ -449,7 +445,7 @@
             try {
                 db.delete(NetworkAttributesContract.TABLENAME, null, null);
                 db.delete(PrivateDataContract.TABLENAME, null, null);
-                final Cursor cursorNetworkAttributes = db.query(
+                try (Cursor cursorNetworkAttributes = db.query(
                         // table name
                         NetworkAttributesContract.TABLENAME,
                         // column name
@@ -459,13 +455,10 @@
                         null, // groupBy
                         null, // having
                         null, // orderBy
-                        "1"); // limit
-                if (0 != cursorNetworkAttributes.getCount()) {
-                    cursorNetworkAttributes.close();
-                    continue;
+                        "1")) { // limit
+                    if (0 != cursorNetworkAttributes.getCount()) continue;
                 }
-                cursorNetworkAttributes.close();
-                final Cursor cursorPrivateData = db.query(
+                try (Cursor cursorPrivateData = db.query(
                         // table name
                         PrivateDataContract.TABLENAME,
                         // column name
@@ -475,14 +468,10 @@
                         null, // groupBy
                         null, // having
                         null, // orderBy
-                        "1"); // limit
-                if (0 != cursorPrivateData.getCount()) {
-                    cursorPrivateData.close();
-                    continue;
+                        "1")) { // limit
+                    if (0 != cursorPrivateData.getCount()) continue;
                 }
-                cursorPrivateData.close();
                 db.setTransactionSuccessful();
-                return;
             } catch (SQLiteException e) {
                 Log.e(TAG, "Could not wipe the data in database", e);
             } finally {
@@ -575,7 +564,7 @@
 
         final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
                 + sj.toString() + ")";
-        final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+        try (Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
                 false, // distinct
                 NetworkAttributesContract.TABLENAME,
                 null, // columns, null means everything
@@ -584,22 +573,81 @@
                 null, // groupBy
                 null, // having
                 null, // orderBy
-                null); // limit
-        if (cursor.getCount() <= 0) return null;
-        cursor.moveToFirst();
-        String bestKey = null;
-        float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
-        while (!cursor.isAfterLast()) {
-            final NetworkAttributes read = readNetworkAttributesLine(cursor);
-            final float confidence = read.getNetworkGroupSamenessConfidence(attr);
-            if (confidence > bestMatchConfidence) {
-                bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
-                bestMatchConfidence = confidence;
+                null)) { // limit
+            if (cursor.getCount() <= 0) return null;
+            cursor.moveToFirst();
+            String bestKey = null;
+            float bestMatchConfidence =
+                    GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+            while (!cursor.isAfterLast()) {
+                final NetworkAttributes read = readNetworkAttributesLine(cursor);
+                final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+                if (confidence > bestMatchConfidence) {
+                    bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+                    bestMatchConfidence = confidence;
+                }
+                cursor.moveToNext();
             }
-            cursor.moveToNext();
+            return bestKey;
         }
-        cursor.close();
-        return bestKey;
+    }
+
+    /**
+     * Delete a single entry by key.
+     *
+     * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
+     * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
+     * maintenance window.
+     * Note that wiping data is a very expensive operation. This is meant for clients that need
+     * this data gone from disk immediately for security reasons. Functionally it makes no
+     * difference at all.
+     */
+    static StatusAndCount delete(@NonNull final SQLiteDatabase db, @NonNull final String l2key,
+            final boolean needWipe) {
+        return deleteEntriesWithColumn(db,
+                NetworkAttributesContract.COLNAME_L2KEY, l2key, needWipe);
+    }
+
+    /**
+     * Delete all entries that have a particular cluster value.
+     *
+     * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
+     * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
+     * maintenance window.
+     * Note that wiping data is a very expensive operation. This is meant for clients that need
+     * this data gone from disk immediately for security reasons. Functionally it makes no
+     * difference at all.
+     */
+    static StatusAndCount deleteCluster(@NonNull final SQLiteDatabase db,
+            @NonNull final String cluster, final boolean needWipe) {
+        return deleteEntriesWithColumn(db,
+                NetworkAttributesContract.COLNAME_CLUSTER, cluster, needWipe);
+    }
+
+    // Delete all entries where the given column has the given value.
+    private static StatusAndCount deleteEntriesWithColumn(@NonNull final SQLiteDatabase db,
+            @NonNull final String column, @NonNull final String value, final boolean needWipe) {
+        db.beginTransaction();
+        int deleted = 0;
+        try {
+            deleted = db.delete(NetworkAttributesContract.TABLENAME,
+                    column + "= ?", new String[] { value });
+            db.setTransactionSuccessful();
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Could not delete from the memory store", e);
+            // Unclear what might have happened ; deleting records is not supposed to be able
+            // to fail barring a syntax error in the SQL query.
+            return new StatusAndCount(Status.ERROR_UNKNOWN, 0);
+        } finally {
+            db.endTransaction();
+        }
+
+        if (needWipe) {
+            final int vacuumStatus = vacuum(db);
+            // This is a problem for the client : return the failure
+            if (Status.SUCCESS != vacuumStatus) return new StatusAndCount(vacuumStatus, deleted);
+        }
+        return new StatusAndCount(Status.SUCCESS, deleted);
     }
 
     // Drops all records that are expired. Relevance has decayed to zero of these records. Returns
@@ -637,20 +685,21 @@
         }
 
         // Queries number of NetworkAttributes that start from the lowest expiryDate.
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+        final long expiryDate;
+        try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
                 new String[] {NetworkAttributesContract.COLNAME_EXPIRYDATE}, // columns
                 null, // selection
                 null, // selectionArgs
                 null, // groupBy
                 null, // having
                 NetworkAttributesContract.COLNAME_EXPIRYDATE, // orderBy
-                Integer.toString(number)); // limit
-        if (cursor == null || cursor.getCount() <= 0) return Status.ERROR_GENERIC;
-        cursor.moveToLast();
+                Integer.toString(number))) { // limit
+            if (cursor == null || cursor.getCount() <= 0) return Status.ERROR_GENERIC;
+            cursor.moveToLast();
 
-        //Get the expiryDate from last record.
-        final long expiryDate = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, 0);
-        cursor.close();
+            // Get the expiryDate from last record.
+            expiryDate = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, 0);
+        }
 
         db.beginTransaction();
         try {
@@ -678,15 +727,16 @@
 
     static int getTotalRecordNumber(@NonNull final SQLiteDatabase db) {
         // Query the total number of NetworkAttributes
-        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+        try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
                 new String[] {"COUNT(*)"}, // columns
                 null, // selection
                 null, // selectionArgs
                 null, // groupBy
                 null, // having
-                null); // orderBy
-        cursor.moveToFirst();
-        return cursor == null ? 0 : cursor.getInt(0);
+                null)) { // orderBy
+            cursor.moveToFirst();
+            return cursor == null ? 0 : cursor.getInt(0);
+        }
     }
 
     // Helper methods
@@ -708,4 +758,13 @@
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
     }
+    private static int vacuum(@NonNull final SQLiteDatabase db) {
+        try {
+            db.execSQL("VACUUM");
+            return Status.SUCCESS;
+        } catch (SQLiteException e) {
+            // Vacuuming may fail from lack of storage, because it makes a copy of the database.
+            return Status.ERROR_STORAGE;
+        }
+    }
 }
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index cd29e0d..ae9c875 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -33,6 +33,7 @@
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -116,7 +117,11 @@
         // TODO : investigate replacing this scheme with a scheme where each thread has its own
         // instance of the database, as it may be faster. It is likely however that IpMemoryStore
         // operations are mostly IO-bound anyway, and additional contention is unlikely to bring
-        // benefits. Alternatively, a read-write lock might increase throughput.
+        // benefits. Alternatively, a read-write lock might increase throughput. Also if doing
+        // this work, care must be taken around the privacy-preserving VACUUM operations as
+        // VACUUM will fail if there are other open transactions at the same time, and using
+        // multiple threads will open the possibility of this failure happening, threatening
+        // the privacy guarantees.
         mExecutor = Executors.newSingleThreadExecutor();
         RegularMaintenanceJobService.schedule(mContext, this);
     }
@@ -405,6 +410,56 @@
     }
 
     /**
+     * Delete a single entry.
+     *
+     * @param l2Key The L2 key of the entry to delete.
+     * @param needWipe Whether the data must be wiped from disk immediately for security reasons.
+     *                 This is very expensive and makes no functional difference ; only pass
+     *                 true if security requires this data must be removed from disk immediately.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * returns (through the listener) A status to indicate success and the number of deleted records
+     */
+    public void delete(@NonNull final String l2Key, final boolean needWipe,
+            @Nullable final IOnStatusAndCountListener listener) {
+        mExecutor.execute(() -> {
+            try {
+                final StatusAndCount res = IpMemoryStoreDatabase.delete(mDb, l2Key, needWipe);
+                if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
+    }
+
+    /**
+     * Delete all entries in a cluster.
+     *
+     * This method will delete all entries in the memory store that have the cluster attribute
+     * passed as an argument.
+     *
+     * @param cluster The cluster to delete.
+     * @param needWipe Whether the data must be wiped from disk immediately for security reasons.
+     *                 This is very expensive and makes no functional difference ; only pass
+     *                 true if security requires this data must be removed from disk immediately.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * returns (through the listener) A status to indicate success and the number of deleted records
+     */
+    public void deleteCluster(@NonNull final String cluster, final boolean needWipe,
+            @Nullable final IOnStatusAndCountListener listener) {
+        mExecutor.execute(() -> {
+            try {
+                final StatusAndCount res =
+                        IpMemoryStoreDatabase.deleteCluster(mDb, cluster, needWipe);
+                if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
+    }
+
+    /**
      * Wipe the data in IpMemoryStore database upon network factory reset.
      */
     @Override
diff --git a/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java
new file mode 100644
index 0000000..2cbe843
--- /dev/null
+++ b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.ipmemorystore;
+
+/**
+ * Small data class to wrap a Status and an int.
+ */
+public class StatusAndCount {
+    public final int status;
+    public final int count;
+    public StatusAndCount(final int status, final int count) {
+        this.status = status;
+        this.count = count;
+    }
+}
diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java
index dbba7f3..6ecc84c 100644
--- a/src/com/android/server/util/NetworkStackConstants.java
+++ b/src/com/android/server/util/NetworkStackConstants.java
@@ -16,7 +16,7 @@
 
 package com.android.server.util;
 
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 
 import java.net.Inet4Address;
 
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index e924d3a..af63f0e 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -82,6 +82,18 @@
     test_suites: ["device-tests"],
 }
 
+// The static lib needs to be jarjared by each module so they do not conflict with each other
+// (e.g. wifi, system server, network stack need to use different package names when including it).
+// Apply NetworkStack jarjar rules to the tests as well so classes in NetworkStaticLibTests have the
+// same package names as in module code.
+android_library {
+    name: "NetworkStackStaticLibTestsLib",
+    platform_apis: true,
+    min_sdk_version: "29",
+    jarjar_rules: ":NetworkStackJarJarRules",
+    static_libs: ["NetworkStaticLibTestsLib"],
+}
+
 // Special version of the network stack tests that includes all tests necessary for code coverage
 // purposes. This is currently the union of NetworkStackTests and NetworkStackIntegrationTests.
 android_test {
@@ -92,7 +104,11 @@
     test_suites: ["device-tests", "mts"],
     test_config: "AndroidTest_Coverage.xml",
     defaults: ["NetworkStackIntegrationTestsJniDefaults"],
-    static_libs: ["NetworkStackTestsLib", "NetworkStackIntegrationTestsLib"],
+    static_libs: [
+        "NetworkStackTestsLib",
+        "NetworkStackIntegrationTestsLib",
+        "NetworkStackStaticLibTestsLib",
+    ],
     compile_multilib: "both",
     manifest: "AndroidManifest_coverage.xml",
 }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 3e38a00..38eb84e 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -24,15 +24,15 @@
 import static android.net.dhcp.DhcpPacket.ENCAP_L2;
 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
 import static android.net.ipmemorystore.Status.SUCCESS;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
 import static android.system.OsConstants.ETH_P_IPV6;
 import static android.system.OsConstants.IFA_F_TEMPORARY;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
 import static android.system.OsConstants.IPPROTO_TCP;
 
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
 import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
 import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
@@ -136,6 +136,7 @@
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.ShimUtils;
 import com.android.networkstack.arp.ArpPacket;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
 import com.android.server.NetworkObserver;
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -171,6 +172,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -317,8 +319,8 @@
 
         @Override
         public DhcpClient.Dependencies getDhcpClientDependencies(
-                NetworkStackIpMemoryStore ipMemoryStore) {
-            return new DhcpClient.Dependencies(ipMemoryStore) {
+                NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
+            return new DhcpClient.Dependencies(ipMemoryStore, metrics) {
                 @Override
                 public boolean isFeatureEnabled(final Context context, final String name,
                         final boolean defaultEnabled) {
@@ -689,13 +691,12 @@
                 false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
                 isHostnameConfigurationEnabled, hostname, displayName, scanResultInfo);
         return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
-                isDhcpIpConflictDetectEnabled, captivePortalApiUrl);
+                captivePortalApiUrl);
     }
 
     private List<DhcpPacket> handleDhcpPackets(final boolean isSuccessLease,
             final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu,
-            final boolean isDhcpIpConflictDetectEnabled, final String captivePortalApiUrl)
-            throws Exception {
+            final String captivePortalApiUrl) throws Exception {
         final List<DhcpPacket> packetList = new ArrayList<>();
         DhcpPacket packet;
         while ((packet = getNextDhcpPacket()) != null) {
@@ -720,15 +721,6 @@
 
             // wait for reply to DHCPOFFER packet if disabling rapid commit option
             if (shouldReplyRapidCommitAck || !(packet instanceof DhcpDiscoverPacket)) {
-                if (!isDhcpIpConflictDetectEnabled && isSuccessLease) {
-                    // verify IPv4-only provisioning success before exiting loop.
-                    // 1. if it's a failure lease, onProvisioningSuccess() won't be called;
-                    // 2. if duplicated IPv4 address detection is enabled, verify TIMEOUT
-                    //    will affect ARP packet capture running in other test cases.
-                    ArgumentCaptor<LinkProperties> captor =
-                            ArgumentCaptor.forClass(LinkProperties.class);
-                    verifyProvisioningSuccess(captor, Collections.singletonList(CLIENT_ADDR));
-                }
                 return packetList;
             }
         }
@@ -790,8 +782,15 @@
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(emptyLp);
     }
 
-    private void verifyProvisioningSuccess(ArgumentCaptor<LinkProperties> captor,
-            final Collection<InetAddress> addresses) throws Exception {
+    // Verify IPv4-only provisioning success. No need to verify IPv4 provisioning when below cases
+    // happen:
+    // 1. if there's a failure lease, onProvisioningSuccess() won't be called;
+    // 2. if duplicated IPv4 address detection is enabled, verify TIMEOUT will affect ARP packets
+    //    capture running in other test cases.
+    // 3. if IPv6 is enabled, e.g. withoutIPv6() isn't called when starting provisioning.
+    private void verifyIPv4OnlyProvisioningSuccess(final Collection<InetAddress> addresses)
+            throws Exception {
+        final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
         LinkProperties lp = captor.getValue();
         assertNotNull(lp);
@@ -809,6 +808,7 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 mtu, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, mtu);
 
         if (shouldChangeMtu) {
@@ -996,9 +996,8 @@
             assertEquals(5, packetList.size());
             assertArpProbe(packetList.get(0));
             assertArpAnnounce(packetList.get(3));
-
-            ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
-            verifyProvisioningSuccess(captor, Collections.singletonList(CLIENT_ADDR));
+        } else {
+            verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
             assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime,
                     TEST_DEFAULT_MTU);
         }
@@ -1037,6 +1036,7 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
@@ -1056,6 +1056,7 @@
         performDhcpHandshake(true /* isSuccessLease */, INFINITE_LEASE,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime, TEST_DEFAULT_MTU);
     }
 
@@ -1065,6 +1066,7 @@
         performDhcpHandshake(true /* isSuccessLease */, null /* no lease time */,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(null, currentTime, TEST_DEFAULT_MTU);
     }
 
@@ -1073,6 +1075,7 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryNeverStoreNetworkAttributes();
     }
 
@@ -1082,6 +1085,7 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, true /* shouldReplyRapidCommitAck */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
@@ -1205,6 +1209,7 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 TEST_MIN_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_MIN_MTU);
 
         // Pretend that ConnectivityService set the MTU.
@@ -1221,6 +1226,7 @@
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 0 /* mtu */, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, 0 /* mtu */);
         assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_DEFAULT_MTU);
     }
@@ -1241,11 +1247,11 @@
         fail("No router solicitation received on interface within timeout");
     }
 
-    private void sendBasicRouterAdvertisement(boolean waitForRs) throws Exception {
+    private void sendRouterAdvertisement(boolean waitForRs, short lifetime) throws Exception {
         final String dnsServer = "2001:4860:4860::64";
         final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
         ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
-        ByteBuffer ra = buildRaPacket(pio, rdnss);
+        ByteBuffer ra = buildRaPacket(lifetime, pio, rdnss);
 
         if (waitForRs) {
             waitForRouterSolicitation();
@@ -1254,6 +1260,14 @@
         mPacketReader.sendResponse(ra);
     }
 
+    private void sendBasicRouterAdvertisement(boolean waitForRs) throws Exception {
+        sendRouterAdvertisement(waitForRs, (short) 1800);
+    }
+
+    private void sendRouterAdvertisementWithZeroLifetime() throws Exception {
+        sendRouterAdvertisement(false /* waitForRs */, (short) 0);
+    }
+
     // TODO: move this and the following method to a common location and use them in ApfTest.
     private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
             throws Exception {
@@ -1315,7 +1329,8 @@
         return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
     }
 
-    private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+    private static ByteBuffer buildRaPacket(short lifetime, ByteBuffer... options)
+            throws Exception {
         final MacAddress srcMac = MacAddress.fromString("33:33:00:00:00:01");
         final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
         final byte[] routerLinkLocal = InetAddresses.parseNumericAddress("fe80::1").getAddress();
@@ -1343,7 +1358,7 @@
         packet.putShort((short) 0);                      // Checksum, TBD
         packet.put((byte) 0);                            // Hop limit, unspecified
         packet.put((byte) 0);                            // M=0, O=0
-        packet.putShort((short) 1800);                   // Router lifetime
+        packet.putShort(lifetime);                       // Router lifetime
         packet.putInt(0);                                // Reachable time, unspecified
         packet.putInt(100);                              // Retrans time 100ms.
 
@@ -1363,6 +1378,10 @@
         return packet;
     }
 
+    private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+        return buildRaPacket((short) 1800, options);
+    }
+
     private void disableIpv6ProvisioningDelays() throws Exception {
         // Speed up the test by disabling DAD and removing router_solicitation_delay.
         // We don't need to restore the default value because the interface is removed in tearDown.
@@ -1640,12 +1659,12 @@
         HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
     }
 
-    @Test
-    public void testIpClientClearingIpAddressState() throws Exception {
+    private void doIPv4OnlyProvisioningAndExitWithLeftAddress() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
 
         // Stop IpClient and expect a final LinkProperties callback with an empty LP.
@@ -1665,6 +1684,11 @@
         // TODO: once IpClient gets IP addresses directly from netlink instead of from netd, it
         // may be sufficient to call waitForIdle to see if IpClient has seen the address.
         addIpAddressAndWaitForIt(mIfaceName);
+    }
+
+    @Test
+    public void testIpClientClearingIpAddressState() throws Exception {
+        doIPv4OnlyProvisioningAndExitWithLeftAddress();
 
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
@@ -1684,6 +1708,22 @@
     }
 
     @Test
+    public void testIpClientClearingIpAddressState_enablePreconnection() throws Exception {
+        doIPv4OnlyProvisioningAndExitWithLeftAddress();
+
+        // Enter ClearingIpAddressesState to clear the remaining IPv4 addresses and transition to
+        // PreconnectionState instead of RunningState.
+        startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
+                false /* shouldReplyRapidCommitAck */, true /* isDhcpPreConnectionEnabled */,
+                false /* isDhcpIpConflictDetectEnabled */);
+        assertDiscoverPacketOnPreconnectionStart();
+
+        // Force to enter RunningState.
+        mIpc.notifyPreconnectionComplete(false /* abort */);
+        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+    }
+
+    @Test
     public void testDhcpClientPreconnection_success() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 false /* shouldAbortPreconnection */, false /* shouldFirePreconnectionTimeout */,
@@ -1858,6 +1898,7 @@
                 true /* isHostnameConfigurationEnabled */, TEST_HOST_NAME /* hostname */,
                 null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
         assertEquals(2, sentPackets.size());
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
@@ -1872,6 +1913,7 @@
                 false /* isHostnameConfigurationEnabled */, TEST_HOST_NAME,
                 null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
         assertEquals(2, sentPackets.size());
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
@@ -1886,6 +1928,7 @@
                 true /* isHostnameConfigurationEnabled */, null /* hostname */,
                 null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
         assertEquals(2, sentPackets.size());
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */,
                 sentPackets);
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
@@ -1906,8 +1949,7 @@
                 (short) TEST_DEFAULT_MTU, serverSentUrl));
         final int testMtu = 1345;
         handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
-                false /* isDhcpRapidCommitEnabled */, testMtu,
-                false /* isDhcpIpConflictDetectEnabled */, serverSentUrl);
+                false /* shouldReplyRapidCommitAck */, testMtu, serverSentUrl);
 
         final Uri expectedUrl = featureEnabled && serverSendsOption
                 ? Uri.parse(TEST_CAPTIVE_PORTAL_URL) : null;
@@ -1976,6 +2018,7 @@
                 false /* isHostnameConfigurationEnabled */, null /* hostname */,
                 null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */);
         assertEquals(2, sentPackets.size());
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
 
         ArgumentCaptor<DhcpResultsParcelable> captor =
                 ArgumentCaptor.forClass(DhcpResultsParcelable.class);
@@ -2070,6 +2113,7 @@
                 TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */,
                 true /* isHostnameConfigurationEnabled */, null /* hostname */,
                 null /* captivePortalApiUrl */, displayName, scanResultInfo);
+        verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
 
         // simulate the roaming by updating bssid.
@@ -2144,4 +2188,73 @@
         doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                 TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
     }
+
+    private void doDualStackProvisioning() throws Exception {
+        when(mCm.shouldAvoidBadWifi()).thenReturn(true);
+
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .build();
+        // Accelerate DHCP handshake to shorten test duration, not strictly necessary.
+        mDependencies.setDhcpRapidCommitEnabled(true);
+        mIpc.startProvisioning(config);
+
+        final InOrder inOrder = inOrder(mCb);
+        final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+        final String dnsServer = "2001:4860:4860::64";
+        final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
+        final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+        final ByteBuffer ra = buildRaPacket(pio, rdnss);
+
+        doIpv6OnlyProvisioning(inOrder, ra);
+
+        // Start IPv4 provisioning and wait until entire provisioning completes.
+        handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+                true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */);
+        verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(argThat(x -> {
+            if (!x.isIpv4Provisioned() || !x.isIpv6Provisioned()) return false;
+            lpFuture.complete(x);
+            return true;
+        }));
+
+        final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertNotNull(lp);
+        assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer)));
+        assertTrue(lp.getDnsServers().contains(SERVER_ADDR));
+
+        reset(mCb);
+    }
+
+    @Test
+    public void testIgnoreIpv6ProvisioningLoss() throws Exception {
+        doDualStackProvisioning();
+
+        final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
+        // Send RA with 0-lifetime and wait until all IPv6-related default route and DNS servers
+        // have been removed, then verify if there is IPv4-only info left in the LinkProperties.
+        sendRouterAdvertisementWithZeroLifetime();
+        verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
+                argThat(x -> {
+                    final boolean isOnlyIPv4Provisioned = (x.getLinkAddresses().size() == 1
+                            && x.getDnsServers().size() == 1
+                            && x.getAddresses().get(0) instanceof Inet4Address
+                            && x.getDnsServers().get(0) instanceof Inet4Address);
+
+                    if (!isOnlyIPv4Provisioned) return false;
+                    lpFuture.complete(x);
+                    return true;
+                }));
+        final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertNotNull(lp);
+        assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
+        assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+    }
+
+    @Test
+    public void testDualStackProvisioning() throws Exception {
+        doDualStackProvisioning();
+
+        verify(mCb, never()).onProvisioningFailure(any());
+    }
 }
diff --git a/tests/lib/multivariant/com/android/testutils/PacketFilter.kt b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
new file mode 100644
index 0000000..cd8d6a5
--- /dev/null
+++ b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package com.android.testutils
+
+import java.util.function.Predicate
+
+const val ETHER_TYPE_OFFSET = 12
+const val ETHER_HEADER_LENGTH = 14
+const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
+const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
+const val IPV4_HEADER_LENGTH = 20
+const val IPV4_UDP_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
+const val BOOTP_OFFSET = IPV4_UDP_OFFSET + 8
+const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
+const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
+const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
+
+/**
+ * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
+ * [offset].
+ */
+class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> {
+    override fun test(packet: ByteArray) =
+            bytes.withIndex().all { it.value == packet[offset + it.index] }
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
+ */
+class IPv4UdpFilter : Predicate<ByteArray> {
+    private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
+            OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */))
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
+ */
+class DhcpClientPacketFilter : Predicate<ByteArray> {
+    private val impl = IPv4UdpFilter()
+            .and(OffsetFilter(IPV4_UDP_OFFSET /* source port */, 0x00, 0x44 /* 68 */))
+            .and(OffsetFilter(IPV4_UDP_OFFSET + 2 /* dest port */, 0x00, 0x43 /* 67 */))
+    override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that
+ * contains the specified option with the specified [bytes] as value.
+ */
+class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> {
+    override fun test(packet: ByteArray): Boolean {
+        val option = findDhcpOption(packet, option) ?: return false
+        return option.contentEquals(bytes)
+    }
+}
+
+/**
+ * Find a DHCP option in a packet and return its value, if found.
+ */
+fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? =
+        findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let {
+            val optionLen = packet[it + 1]
+            return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen)
+        }
+
+private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? {
+    if (packet.size <= searchOffset + 2 /* type, length bytes */) return null
+
+    return if (packet[searchOffset] == option) searchOffset else {
+        val optionLen = packet[searchOffset + 1]
+        findOptionOffset(packet, option, searchOffset + 2 + optionLen)
+    }
+}
diff --git a/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt b/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt
new file mode 100644
index 0000000..69ed048
--- /dev/null
+++ b/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package com.android.testutils
+
+/**
+ * Skip the test in presubmit runs for the reason specified in [reason].
+ *
+ * This annotation is typically used to document hardware or test bench limitations.
+ */
+annotation class SkipPresubmit(val reason: String)
\ No newline at end of file
diff --git a/tests/lib/src/com/android/testutils/TapPacketReader.java b/tests/lib/src/com/android/testutils/TapPacketReader.java
index c68e5a5..e55ed44 100644
--- a/tests/lib/src/com/android/testutils/TapPacketReader.java
+++ b/tests/lib/src/com/android/testutils/TapPacketReader.java
@@ -19,6 +19,7 @@
 import android.net.util.PacketReader;
 import android.os.Handler;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.io.FileDescriptor;
@@ -26,12 +27,16 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+import kotlin.Lazy;
+import kotlin.LazyKt;
 
 public class TapPacketReader extends PacketReader {
     private final FileDescriptor mTapFd;
-    private final LinkedBlockingQueue<byte[]> mReceivedPackets = new LinkedBlockingQueue<byte[]>();
+    private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>();
+    private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead =
+            LazyKt.lazy(mReceivedPackets::newReadHead);
 
     public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) {
         super(h, maxPacketSize);
@@ -46,23 +51,25 @@
     @Override
     protected void handlePacket(byte[] recvbuf, int length) {
         final byte[] newPacket = Arrays.copyOf(recvbuf, length);
-        if (!mReceivedPackets.offer(newPacket)) {
+        if (!mReceivedPackets.add(newPacket)) {
             throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!");
         }
     }
 
     /**
      * Get the next packet that was received on the interface.
-     *
      */
     @Nullable
     public byte[] popPacket(long timeoutMs) {
-        try {
-            return mReceivedPackets.poll(timeoutMs, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // Fall through
-        }
-        return null;
+        return mReadHead.getValue().poll(timeoutMs, packet -> true);
+    }
+
+    /**
+     * Get the next packet that was received on the interface and matches the specified filter.
+     */
+    @Nullable
+    public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+        return mReadHead.getValue().poll(timeoutMs, filter::test);
     }
 
     public void sendResponse(final ByteBuffer packet) throws IOException {
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index d85d059..6e969ec 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -51,7 +51,6 @@
 import android.net.ip.IpClient.IpClientCallbacksWrapper;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
-import android.net.shared.Inet4AddressUtils;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
 import android.os.ConditionVariable;
@@ -67,6 +66,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
+import com.android.net.module.util.Inet4AddressUtils;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.server.networkstack.tests.R;
 import com.android.server.util.NetworkStackConstants;
diff --git a/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
index 3a6a890..81685ef 100644
--- a/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -20,8 +20,8 @@
 import static android.net.dhcp.DhcpLease.HOSTNAME_NONE;
 import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
 import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 
 import static org.junit.Assert.assertEquals;
diff --git a/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt b/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt
index 2971f65..743418c 100644
--- a/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt
+++ b/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt
@@ -18,7 +18,7 @@
 
 import android.net.InetAddresses.parseNumericAddress
 import android.net.MacAddress
-import android.net.shared.Inet4AddressUtils.intToInet4AddressHTH
+import com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.testutils.assertFieldCountEquals
diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
index c565238..9d2a630 100644
--- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
@@ -34,8 +34,9 @@
 import static android.net.dhcp.DhcpPacket.INADDR_ANY;
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.dhcp.DhcpPacket.ParseException;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
similarity index 91%
rename from tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java
rename to tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
index 532cb64..7d899e0 100644
--- a/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package android.net.shared;
+package android.net.dhcp;
 
 import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
-import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
 
 import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
 
@@ -26,6 +26,7 @@
 
 import android.net.DhcpResults;
 import android.net.LinkAddress;
+import android.net.shared.IpConfigurationParcelableUtil;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -41,7 +42,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class IpConfigurationParcelableUtilTest {
+public class DhcpResultsParcelableUtilTest {
     private DhcpResults mDhcpResults;
 
     @Before
diff --git a/tests/unit/src/android/net/dhcp/DhcpServerTest.java b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
index 2e8a3c2..d313b94 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServerTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
@@ -23,9 +23,10 @@
 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
 import static android.net.dhcp.DhcpServer.CMD_RECEIVE_PACKET;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
 
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -51,12 +52,12 @@
 import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
 import android.net.dhcp.DhcpServer.Clock;
 import android.net.dhcp.DhcpServer.Dependencies;
-import android.net.shared.Inet4AddressUtils;
 import android.net.util.SharedLog;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.net.module.util.Inet4AddressUtils;
 import com.android.testutils.HandlerUtilsKt;
 
 import org.junit.After;
diff --git a/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java b/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
index 1ce2f82..6506eba 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -18,7 +18,8 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -28,11 +29,11 @@
 import android.annotation.Nullable;
 import android.net.LinkAddress;
 import android.net.dhcp.DhcpServingParams.InvalidParameterException;
-import android.net.shared.Inet4AddressUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.Inet4AddressUtils;
 import com.android.testutils.MiscAssertsKt;
 
 import org.junit.Before;
diff --git a/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java b/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java
deleted file mode 100644
index 35f8c79..0000000
--- a/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.shared;
-
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getImplicitNetmask;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL;
-import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.fail;
-
-import android.net.InetAddresses;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.Inet4Address;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class Inet4AddressUtilsTest {
-
-    @Test
-    public void testInet4AddressToIntHTL() {
-        assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0")));
-        assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0")));
-        assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0")));
-        assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0")));
-        assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254")));
-        assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255")));
-    }
-
-    @Test
-    public void testIntToInet4AddressHTL() {
-        assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0));
-        assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff));
-        assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a));
-        assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a));
-        assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0));
-        assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0));
-    }
-
-    @Test
-    public void testInet4AddressToIntHTH() {
-        assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0")));
-        assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0")));
-        assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0")));
-        assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0")));
-        assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254")));
-        assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255")));
-    }
-
-    @Test
-    public void testIntToInet4AddressHTH() {
-        assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0));
-        assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000));
-        assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000));
-        assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00));
-        assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe));
-        assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff));
-    }
-
-
-    @Test
-    public void testPrefixLengthToV4NetmaskIntHTL() {
-        assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
-        assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9));
-        assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17));
-        assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23));
-        assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31));
-        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32));
-    }
-
-    @Test
-    public void testPrefixLengthToV4NetmaskIntHTH() {
-        assertEquals(0, prefixLengthToV4NetmaskIntHTH(0));
-        assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9));
-        assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17));
-        assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23));
-        assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31));
-        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32));
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() {
-        prefixLengthToV4NetmaskIntHTH(-1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() {
-        prefixLengthToV4NetmaskIntHTH(33);
-    }
-
-    private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) {
-        final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength);
-        final int addrInt = inet4AddressToIntHTH(ipv4Address(addr));
-        assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt));
-    }
-
-    @Test
-    public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() {
-        checkAddressMasking("192.168.0.0", "192.168.128.1", 16);
-        checkAddressMasking("255.240.0.0", "255.255.255.255", 12);
-        checkAddressMasking("255.255.255.255", "255.255.255.255", 32);
-        checkAddressMasking("0.0.0.0", "255.255.255.255", 0);
-    }
-
-    @Test
-    public void testGetImplicitNetmask() {
-        assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2")));
-        assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7")));
-        assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105")));
-        assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145")));
-        assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1")));
-        assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1")));
-        assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1")));
-        assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8")));
-    }
-
-    private void assertInvalidNetworkMask(Inet4Address addr) {
-        try {
-            netmaskToPrefixLength(addr);
-            fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testNetmaskToPrefixLength() {
-        assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0")));
-        assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0")));
-        assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0")));
-        assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0")));
-        assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254")));
-        assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255")));
-
-        assertInvalidNetworkMask(ipv4Address("0.0.0.1"));
-        assertInvalidNetworkMask(ipv4Address("255.255.255.253"));
-        assertInvalidNetworkMask(ipv4Address("255.255.0.255"));
-    }
-
-    @Test
-    public void testGetPrefixMaskAsAddress() {
-        assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
-        assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
-        assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
-        assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
-    }
-
-    @Test
-    public void testGetBroadcastAddress() {
-        assertEquals("192.168.15.255",
-                getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress());
-        assertEquals("192.255.255.255",
-                getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress());
-        assertEquals("192.168.0.123",
-                getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress());
-        assertEquals("255.255.255.255",
-                getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetBroadcastAddress_PrefixTooLarge() {
-        getBroadcastAddress(ipv4Address("192.168.0.123"), 33);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetBroadcastAddress_NegativePrefix() {
-        getBroadcastAddress(ipv4Address("192.168.0.123"), -1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
-        getPrefixMaskAsInet4Address(33);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetPrefixMaskAsAddress_NegativePrefix() {
-        getPrefixMaskAsInet4Address(-1);
-    }
-
-    private Inet4Address ipv4Address(String addr) {
-        return (Inet4Address) InetAddresses.parseNumericAddress(addr);
-    }
-}
diff --git a/tests/unit/src/android/net/shared/NetdUtilsTest.java b/tests/unit/src/android/net/shared/NetdUtilsTest.java
new file mode 100644
index 0000000..f3ef53a
--- /dev/null
+++ b/tests/unit/src/android/net/shared/NetdUtilsTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 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.INetd.LOCAL_NET_ID;
+import static android.system.OsConstants.EBUSY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NetdUtilsTest {
+    private static final String IFACE_NAME = "testnet1";
+    private static final IpPrefix TEST_IPPREFIX = new IpPrefix("192.168.42.1/24");
+
+    @Mock private INetd mNetd;
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private void setNetworkAddInterfaceOutcome(final Exception cause, int numLoops)
+            throws Exception {
+        // This cannot be an int because local variables referenced from a lambda expression must
+        // be final or effectively final.
+        final Counter myCounter = new Counter();
+        doAnswer((invocation) -> {
+            myCounter.count();
+            if (myCounter.isCounterReached(numLoops)) {
+                if (cause == null) return null;
+
+                throw cause;
+            }
+
+            throw new ServiceSpecificException(EBUSY);
+        }).when(mNetd).networkAddInterface(LOCAL_NET_ID, IFACE_NAME);
+    }
+
+    class Counter {
+        private int mValue = 0;
+
+        private void count() {
+            mValue++;
+        }
+
+        private boolean isCounterReached(int target) {
+            return mValue >= target;
+        }
+    }
+
+    private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
+        setNetworkAddInterfaceOutcome(null, expectedTries);
+
+        NetdUtils.tetherInterface(mNetd, IFACE_NAME, TEST_IPPREFIX);
+        verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE_NAME);
+        verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE_NAME), any(), any());
+        verifyNoMoreInteractions(mNetd);
+        reset(mNetd);
+    }
+
+    @Test
+    public void testTetherInterfaceSuccessful() throws Exception {
+        // Expect #networkAddInterface successful at first tries.
+        verifyTetherInterfaceSucceeds(1);
+
+        // Expect #networkAddInterface successful after 10 tries.
+        verifyTetherInterfaceSucceeds(10);
+    }
+
+    private void runTetherInterfaceWithServiceSpecificException(int expectedTries,
+            int expectedCode) throws Exception {
+        setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
+
+        try {
+            NetdUtils.tetherInterface(mNetd, IFACE_NAME, TEST_IPPREFIX, 20, 0);
+            fail("Expect throw ServiceSpecificException");
+        } catch (ServiceSpecificException e) {
+            assertEquals(e.errorCode, expectedCode);
+        }
+
+        verifyNetworkAddInterfaceFails(expectedTries);
+        reset(mNetd);
+    }
+
+    private void runTetherInterfaceWithRemoteException(int expectedTries) throws Exception {
+        setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
+
+        try {
+            NetdUtils.tetherInterface(mNetd, IFACE_NAME, TEST_IPPREFIX, 20, 0);
+            fail("Expect throw RemoteException");
+        } catch (RemoteException e) { }
+
+        verifyNetworkAddInterfaceFails(expectedTries);
+        reset(mNetd);
+    }
+
+    private void verifyNetworkAddInterfaceFails(int expectedTries) throws Exception {
+        verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE_NAME);
+        verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+        verifyNoMoreInteractions(mNetd);
+    }
+
+    @Test
+    public void testTetherInterfaceFailOnNetworkAddInterface() throws Exception {
+        // Test throwing ServiceSpecificException with EBUSY failure.
+        runTetherInterfaceWithServiceSpecificException(20, EBUSY);
+
+        // Test throwing ServiceSpecificException with unexpectedError.
+        final int unexpectedError = 999;
+        runTetherInterfaceWithServiceSpecificException(1, unexpectedError);
+
+        // Test throwing ServiceSpecificException with unexpectedError after 7 tries.
+        runTetherInterfaceWithServiceSpecificException(7, unexpectedError);
+
+        // Test throwing RemoteException.
+        runTetherInterfaceWithRemoteException(1);
+
+        // Test throwing RemoteException after 3 tries.
+        runTetherInterfaceWithRemoteException(3);
+    }
+}
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
index 1be6ee7..348392d 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -20,8 +20,9 @@
 import android.app.NotificationChannel
 import android.app.NotificationManager
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_NONE
 import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.app.PendingIntent.FLAG_IMMUTABLE
 import android.content.Context
 import android.content.Intent
 import android.content.res.Resources
@@ -56,6 +57,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.intThat
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.any
@@ -80,6 +82,8 @@
     @Mock
     private lateinit var mNm: NotificationManager
     @Mock
+    private lateinit var mNotificationChannelsNm: NotificationManager
+    @Mock
     private lateinit var mCm: ConnectivityManager
     @Mock
     private lateinit var mResources: Resources
@@ -141,10 +145,12 @@
                 realContext.packageName, 0, UserHandle.ALL)
 
         mAllUserContext.mockService(Context.NOTIFICATION_SERVICE, NotificationManager::class, mNm)
+        mContext.mockService(Context.NOTIFICATION_SERVICE, NotificationManager::class,
+                mNotificationChannelsNm)
         mContext.mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class, mCm)
 
         doReturn(NotificationChannel(CHANNEL_VENUE_INFO, "TestChannel", IMPORTANCE_DEFAULT))
-                .`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO)
+                .`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
 
         doReturn(mPendingIntent).`when`(mDependencies).getActivityPendingIntent(
                 any(), any(), anyInt())
@@ -183,7 +189,8 @@
         assertEquals(CHANNEL_CONNECTED, note.channelId)
         assertEquals(timeout, note.timeoutAfter)
         verify(mDependencies).getActivityPendingIntent(
-                eq(mCurrentUserContext), mIntentCaptor.capture(), eq(FLAG_UPDATE_CURRENT))
+                eq(mCurrentUserContext), mIntentCaptor.capture(),
+                intThat { it or FLAG_IMMUTABLE != 0 })
     }
 
     private fun verifyCanceledNotificationAfterNetworkLost() {
@@ -247,7 +254,8 @@
     fun testConnectedVenueInfoNotification_VenueInfoDisabled() {
         // Venue info (CaptivePortalData) is not available for API <= Q
         assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
-        doReturn(null).`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO)
+        val channel = NotificationChannel(CHANNEL_VENUE_INFO, "test channel", IMPORTANCE_NONE)
+        doReturn(channel).`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
         mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
         onLinkPropertiesChanged(mTestCapportLp)
         onDefaultNetworkAvailable(TEST_NETWORK)
@@ -273,7 +281,8 @@
 
         verify(mNm).notify(eq(TEST_NETWORK_TAG), mNoteIdCaptor.capture(), mNoteCaptor.capture())
         verify(mDependencies).getActivityPendingIntent(
-                eq(mCurrentUserContext), mIntentCaptor.capture(), eq(FLAG_UPDATE_CURRENT))
+                eq(mCurrentUserContext), mIntentCaptor.capture(),
+                intThat { it or FLAG_IMMUTABLE != 0 })
         verifyVenueInfoIntent(mIntentCaptor.value)
         verifyCanceledNotificationAfterDefaultNetworkLost()
     }
diff --git a/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt b/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
index 4c62071..578e6ca 100644
--- a/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
+++ b/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
@@ -16,18 +16,33 @@
 
 package com.android.networkstack.metrics
 
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.captiveportal.CaptivePortalProbeResult
+import android.net.metrics.ValidationProbeEvent
+import android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS
+import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.server.connectivity.nano.CellularData
 import com.android.server.connectivity.nano.DataStallEventProto
+import com.android.server.connectivity.nano.DnsEvent
+import com.google.protobuf.nano.MessageNano
+import java.util.Arrays
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
-import android.net.metrics.ValidationProbeEvent
+import org.mockito.ArgumentMatchers.eq
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class DataStallStatsUtilsTest {
+    private val TEST_ELAPSED_TIME_MS = 123456789L
+    private val TEST_MCCMNC = "123456"
+    private val TEST_SIGNAL_STRENGTH = -100
+    private val RETURN_CODE_DNS_TIMEOUT = 255
+
     @Test
     fun testProbeResultToEnum() {
         assertEquals(DataStallStatsUtils.probeResultToEnum(null), DataStallEventProto.INVALID)
@@ -53,4 +68,61 @@
                 CaptivePortalProbeResult.PORTAL_CODE, ValidationProbeEvent.PROBE_HTTPS)),
                 DataStallEventProto.PORTAL)
     }
+
+    @Test
+    fun testWrite() {
+        val session = mockitoSession().spyStatic(NetworkStackStatsLog::class.java).startMocking()
+        val stats = DataStallDetectionStats.Builder()
+                .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
+                .setNetworkType(TRANSPORT_CELLULAR)
+                .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
+                        true /* roaming */,
+                        TEST_MCCMNC /* networkMccmnc */,
+                        TEST_MCCMNC /* simMccmnc */,
+                        TEST_SIGNAL_STRENGTH /* signalStrength */)
+                .setTcpFailRate(90)
+                .setTcpSentSinceLastRecv(10)
+        generateTimeoutDnsEvent(stats, count = 5)
+        DataStallStatsUtils.write(stats.build(), CaptivePortalProbeResult.PARTIAL)
+
+        verify { NetworkStackStatsLog.write(
+                eq(NetworkStackStatsLog.DATA_STALL_EVENT),
+                eq(DATA_STALL_EVALUATION_TYPE_DNS),
+                eq(DataStallEventProto.PARTIAL),
+                eq(TRANSPORT_CELLULAR),
+                eq(DataStallDetectionStats.emptyWifiInfoIfNull(null)),
+                eq(makeTestCellDataNano()),
+                eq(makeTestDnsTimeoutNano(5)),
+                eq(90) /* tcpFailRate */,
+                eq(10) /* tcpSentSinceLastRecv */) }
+
+        session.finishMocking()
+    }
+
+    private fun makeTestDnsTimeoutNano(timeoutCount: Int): ByteArray? {
+        // Make an expected nano dns message.
+        val event = DnsEvent()
+        event.dnsReturnCode = IntArray(timeoutCount)
+        event.dnsTime = LongArray(timeoutCount)
+        Arrays.fill(event.dnsReturnCode, RETURN_CODE_DNS_TIMEOUT)
+        Arrays.fill(event.dnsTime, TEST_ELAPSED_TIME_MS)
+        return MessageNano.toByteArray(event)
+    }
+
+    private fun makeTestCellDataNano(): ByteArray? {
+        // Make an expected nano cell data message.
+        val data = CellularData()
+        data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_LTE
+        data.networkMccmnc = TEST_MCCMNC
+        data.simMccmnc = TEST_MCCMNC
+        data.isRoaming = true
+        data.signalStrength = TEST_SIGNAL_STRENGTH
+        return MessageNano.toByteArray(data)
+    }
+
+    private fun generateTimeoutDnsEvent(stats: DataStallDetectionStats.Builder, count: Int) {
+        repeat(count) {
+            stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, TEST_ELAPSED_TIME_MS)
+        }
+    }
 }
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
new file mode 100644
index 0000000..01d94e2
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.net.dhcp.DhcpPacket;
+import android.net.metrics.DhcpErrorEvent;
+import android.stats.connectivity.DhcpErrorCode;
+import android.stats.connectivity.DhcpFeature;
+import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.HostnameTransResult;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Tests for IpProvisioningMetrics.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkIpProvisioningMetricsTest {
+    @Test
+    public void testIpProvisioningMetrics_setHostnameTransinfo() throws Exception {
+        NetworkIpProvisioningReported mStats;
+        final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+
+        mMetrics.reset();
+        mMetrics.setHostnameTransinfo(false /* isOptionEnabled */, false /* transSuccess */);
+        mStats = mMetrics.statsWrite();
+        assertEquals(HostnameTransResult.HTR_DISABLE, mStats.getDhcpSession().getHtResult());
+
+        mMetrics.reset();
+        mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, false /* transSuccess */);
+        mStats = mMetrics.statsWrite();
+        assertEquals(HostnameTransResult.HTR_FAILURE, mStats.getDhcpSession().getHtResult());
+
+        mMetrics.reset();
+        mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, true /* transSuccess */);
+        mStats = mMetrics.statsWrite();
+        assertEquals(HostnameTransResult.HTR_SUCCESS, mStats.getDhcpSession().getHtResult());
+    }
+
+    @Test
+    public void testIpProvisioningMetrics_addDhcpErrorCode() throws Exception {
+        final NetworkIpProvisioningReported mStats;
+        final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+        mMetrics.reset();
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.BOOTP_TOO_SHORT);
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.errorCodeWithOption(
+                DhcpErrorEvent.BUFFER_UNDERFLOW, DhcpPacket.DHCP_HOST_NAME));
+        // Write the incorrect input number 50000 as DHCP error number
+        int incorrectErrorCodeNumber = 50000;
+        mMetrics.addDhcpErrorCode(incorrectErrorCodeNumber);
+        for (int i = 0; i < mMetrics.MAX_DHCP_ERROR_COUNT; i++) {
+            mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+        }
+        mStats = mMetrics.statsWrite();
+        assertEquals(DhcpErrorCode.ET_BOOTP_TOO_SHORT, mStats.getDhcpSession().getErrorCode(0));
+        assertEquals(DhcpErrorCode.ET_BUFFER_UNDERFLOW, mStats.getDhcpSession().getErrorCode(1));
+        // When the input is an invalid integer value (such as incorrectErrorCodeNumber),
+        // the DhcpErrorCode will be ET_UNKNOWN
+        assertEquals(DhcpErrorCode.ET_UNKNOWN, mStats.getDhcpSession().getErrorCode(2));
+        // If the same error code appears, it must be recorded
+        assertEquals(DhcpErrorCode.ET_PARSING_ERROR, mStats.getDhcpSession().getErrorCode(3));
+        assertEquals(DhcpErrorCode.ET_PARSING_ERROR, mStats.getDhcpSession().getErrorCode(4));
+        // The maximum number of DHCP error code counts is MAX_DHCP_ERROR_COUNT
+        assertEquals(mMetrics.MAX_DHCP_ERROR_COUNT, mStats.getDhcpSession().getErrorCodeCount());
+    }
+    @Test
+    public void testIpProvisioningMetrics_CollectMetrics() throws Exception {
+        final NetworkIpProvisioningReported mStats;
+        final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+        mMetrics.reset();
+        // Entering 3 DISCOVER_SEND_COUNTs
+        mMetrics.incrementCountForDiscover();
+        mMetrics.incrementCountForDiscover();
+        mMetrics.incrementCountForDiscover();
+
+        // Entering 2 SEND_REQUEST_COUNTs
+        mMetrics.incrementCountForRequest();
+        mMetrics.incrementCountForRequest();
+
+        // Entering 1 IP_CONFLICT_COUNT
+        mMetrics.incrementCountForIpConflict();
+
+        // Entering 4 DhcpFeatures and one is repeated, so it should only count to 3
+        mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
+        mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
+        mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+        mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+
+        // Entering 6 DhcpErrorCodes
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.L3_TOO_SHORT);
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH);
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.RECEIVE_ERROR);
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.RECEIVE_ERROR);
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+        mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+
+        mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, true /* transSuccess */);
+
+        // Only the first IP provisioning disconnect code is recorded.
+        mMetrics.setDisconnectCode(DisconnectCode.DC_PROVISIONING_TIMEOUT);
+        mMetrics.setDisconnectCode(DisconnectCode.DC_ERROR_STARTING_IPV4);
+
+        mMetrics.setIPv4ProvisionedLatencyOnFirstTime(true);
+        mMetrics.setIPv6ProvisionedLatencyOnFirstTime(true);
+
+        // Writing the metrics into statsd
+        mStats = mMetrics.statsWrite();
+
+        // Verifing the result of the metrics.
+        assertEquals(3, mStats.getDhcpSession().getDiscoverCount());
+        assertEquals(2, mStats.getDhcpSession().getRequestCount());
+        assertEquals(1, mStats.getDhcpSession().getConflictCount());
+        assertEquals(3, mStats.getDhcpSession().getUsedFeaturesCount());
+        assertEquals(6, mStats.getDhcpSession().getErrorCodeCount());
+        assertEquals(HostnameTransResult.HTR_SUCCESS, mStats.getDhcpSession().getHtResult());
+        assertEquals(DisconnectCode.DC_PROVISIONING_TIMEOUT, mStats.getDisconnectCode());
+        assertTrue(mStats.getIpv4LatencyMicros() >= 0);
+        assertTrue(mStats.getIpv6LatencyMicros() >= 0);
+        assertTrue(mStats.getProvisioningDurationMicros() >= 0);
+    }
+
+    @Test
+    public void testIpProvisioningMetrics_VerifyConsecutiveMetricsLatency() throws Exception {
+        final IpProvisioningMetrics metrics = new IpProvisioningMetrics();
+        for (int i = 0; i < 2; i++) {
+            metrics.reset();
+            // delay 1 msec.
+            Thread.sleep(1);
+            metrics.setIPv4ProvisionedLatencyOnFirstTime(true);
+            metrics.setIPv6ProvisionedLatencyOnFirstTime(true);
+            NetworkIpProvisioningReported mStats = metrics.statsWrite();
+            // Each timer should be greater than 1000.
+            assertTrue(mStats.getIpv4LatencyMicros() >= 1000);
+            assertTrue(mStats.getIpv6LatencyMicros() >= 1000);
+            assertTrue(mStats.getProvisioningDurationMicros() >= 1000);
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
new file mode 100644
index 0000000..98e7b63
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+package com.android.networkstack.metrics;
+
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.INetworkMonitor;
+import android.net.NetworkCapabilities;
+import android.net.captiveportal.CaptivePortalProbeResult;
+import android.net.metrics.ValidationProbeEvent;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
+import android.stats.connectivity.TransportType;
+import android.stats.connectivity.ValidationResult;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkValidationMetricsTest {
+    private static final String TEST_LOGIN_URL = "https://testportal.example.com/login";
+    private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
+    private static final int TTL_TOLERANCE_SECS = 10;
+
+    private static final NetworkCapabilities WIFI_CAPABILITIES =
+            new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+
+    @Test
+    public void testNetworkValidationMetrics_VerifyProbeTypeToEnum() throws Exception {
+        verifyProbeType(ValidationProbeEvent.PROBE_DNS, ProbeType.PT_DNS);
+        verifyProbeType(ValidationProbeEvent.PROBE_HTTP, ProbeType.PT_HTTP);
+        verifyProbeType(ValidationProbeEvent.PROBE_HTTPS, ProbeType.PT_HTTPS);
+        verifyProbeType(ValidationProbeEvent.PROBE_PAC, ProbeType.PT_PAC);
+        verifyProbeType(ValidationProbeEvent.PROBE_FALLBACK, ProbeType.PT_FALLBACK);
+        verifyProbeType(ValidationProbeEvent.PROBE_PRIVDNS, ProbeType.PT_PRIVDNS);
+    }
+
+    private void verifyProbeType(int inputProbeType, ProbeType expectedEnumType) {
+        assertEquals(expectedEnumType, NetworkValidationMetrics.probeTypeToEnum(inputProbeType));
+    }
+
+    @Test
+    public void testNetworkValidationMetrics_VerifyHttpProbeResultToEnum() throws Exception {
+        verifyProbeType(new CaptivePortalProbeResult(CaptivePortalProbeResult.SUCCESS_CODE,
+                ValidationProbeEvent.PROBE_HTTP), ProbeResult.PR_SUCCESS);
+        verifyProbeType(new CaptivePortalProbeResult(CaptivePortalProbeResult.FAILED_CODE,
+                ValidationProbeEvent.PROBE_HTTP), ProbeResult.PR_FAILURE);
+        verifyProbeType(new CaptivePortalProbeResult(CaptivePortalProbeResult.PORTAL_CODE,
+                ValidationProbeEvent.PROBE_HTTP), ProbeResult.PR_PORTAL);
+        verifyProbeType(CaptivePortalProbeResult.PRIVATE_IP, ProbeResult.PR_PRIVATE_IP_DNS);
+    }
+
+    private void verifyProbeType(CaptivePortalProbeResult inputResult,
+            ProbeResult expectedResult) {
+        assertEquals(expectedResult, NetworkValidationMetrics.httpProbeResultToEnum(inputResult));
+    }
+
+    @Test
+    public void testNetworkValidationMetrics_VerifyValidationResultToEnum() throws Exception {
+        verifyProbeType(INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID, null,
+                ValidationResult.VR_SUCCESS);
+        verifyProbeType(0, TEST_LOGIN_URL, ValidationResult.VR_PORTAL);
+        verifyProbeType(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null,
+                ValidationResult.VR_PARTIAL);
+        verifyProbeType(0, null, ValidationResult.VR_FAILURE);
+    }
+
+    private void verifyProbeType(int inputResult, String inputRedirectUrl,
+            ValidationResult expectedResult) {
+        assertEquals(expectedResult, NetworkValidationMetrics.validationResultToEnum(inputResult,
+                inputRedirectUrl));
+    }
+
+    @Test
+    public void testNetworkValidationMetrics_VerifyTransportTypeToEnum() throws Exception {
+        final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.addTransportType(TRANSPORT_WIFI);
+        assertEquals(TransportType.TT_WIFI, metrics.getTransportTypeFromNC(nc));
+        nc.addTransportType(TRANSPORT_VPN);
+        assertEquals(TransportType.TT_WIFI_VPN, metrics.getTransportTypeFromNC(nc));
+        nc.addTransportType(TRANSPORT_CELLULAR);
+        assertEquals(TransportType.TT_WIFI_CELLULAR_VPN, metrics.getTransportTypeFromNC(nc));
+
+        nc = new NetworkCapabilities();
+        nc.addTransportType(TRANSPORT_CELLULAR);
+        assertEquals(TransportType.TT_CELLULAR, metrics.getTransportTypeFromNC(nc));
+        nc.addTransportType(TRANSPORT_VPN);
+        assertEquals(TransportType.TT_CELLULAR_VPN, metrics.getTransportTypeFromNC(nc));
+
+        nc = new NetworkCapabilities();
+        nc.addTransportType(TRANSPORT_BLUETOOTH);
+        assertEquals(TransportType.TT_BLUETOOTH, metrics.getTransportTypeFromNC(nc));
+        nc.addTransportType(TRANSPORT_VPN);
+        assertEquals(TransportType.TT_BLUETOOTH_VPN, metrics.getTransportTypeFromNC(nc));
+
+        nc = new NetworkCapabilities();
+        nc.addTransportType(TRANSPORT_ETHERNET);
+        assertEquals(TransportType.TT_ETHERNET, metrics.getTransportTypeFromNC(nc));
+        nc.addTransportType(TRANSPORT_VPN);
+        assertEquals(TransportType.TT_ETHERNET_VPN, metrics.getTransportTypeFromNC(nc));
+    }
+
+    @Test
+    public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception {
+        final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+        metrics.startCollection(WIFI_CAPABILITIES);
+        // 1. PT_DNS probe
+        metrics.addProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null);
+        // 2. Consecutive PT_HTTP probe failure
+        for (int i = 0; i < 30; i++) {
+            metrics.addProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null);
+        }
+
+        // Write metric into statsd
+        final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend();
+
+        // The maximum number of probe records should be the same as MAX_PROBE_EVENTS_COUNT
+        final ProbeEvents probeEvents = stats.getProbeEvents();
+        assertEquals(NetworkValidationMetrics.MAX_PROBE_EVENTS_COUNT,
+                probeEvents.getProbeEventCount());
+    }
+
+    @Test
+    public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception {
+        final long bytesRemaining = 12_345L;
+        final long secondsRemaining = 3000L;
+        String apiContent = "{'captive': true,"
+                + "'user-portal-url': '" + TEST_LOGIN_URL + "',"
+                + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
+                + "'bytes-remaining': " + bytesRemaining + ","
+                + "'seconds-remaining': " + secondsRemaining + "}";
+        final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+        final int validationIndex = 1;
+        final long longlatency = Integer.MAX_VALUE + 12344567L;
+        metrics.startCollection(WIFI_CAPABILITIES);
+
+        final JSONObject info = new JSONObject(apiContent);
+        final CaptivePortalDataShim captivePortalData = CaptivePortalDataShimImpl.isSupported()
+                ? CaptivePortalDataShimImpl.fromJson(info) : null;
+
+        // 1. PT_CAPPORT_API probe w CapportApiData info
+        metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS,
+                captivePortalData);
+        // 2. PT_CAPPORT_API probe w/o CapportApiData info
+        metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null);
+
+        // 3. PT_DNS probe
+        metrics.addProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null);
+
+        // 4. PT_HTTP probe
+        metrics.addProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null);
+
+        // add Validation result
+        metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null);
+
+        // Write metric into statsd
+        final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend();
+
+        // Verify: TransportType: WIFI
+        assertEquals(TransportType.TT_WIFI, stats.getTransportType());
+
+        // Verify: validationIndex
+        assertEquals(validationIndex, stats.getValidationIndex());
+
+        //  probe Count: 4 (PT_CAPPORT_API, PT_DNS, PT_HTTP, PT_CAPPORT_API)
+        final ProbeEvents probeEvents = stats.getProbeEvents();
+        assertEquals(4, probeEvents.getProbeEventCount());
+
+        // Verify the 1st probe: ProbeType = PT_CAPPORT_API, Latency_us = 1234,
+        //                       ProbeResult = PR_SUCCESS, CapportApiData = capportData
+        ProbeEvent probeEvent = probeEvents.getProbeEvent(0);
+        assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType());
+        assertEquals(1234, probeEvent.getLatencyMicros());
+        assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult());
+        if (CaptivePortalDataShimImpl.isSupported()) {
+            assertTrue(probeEvent.hasCapportApiData());
+            // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds
+            final CapportApiData capportData = probeEvent.getCapportApiData();
+            assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining);
+            assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining);
+            assertEquals(captivePortalData.getByteLimit() / 1000, capportData.getRemainingBytes());
+        } else {
+            assertFalse(probeEvent.hasCapportApiData());
+        }
+
+        // Verify the 2nd probe: ProbeType = PT_CAPPORT_API, Latency_us = 1234,
+        //                       ProbeResult = PR_SUCCESS, CapportApiData = null
+        probeEvent = probeEvents.getProbeEvent(1);
+        assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType());
+        assertEquals(1234, probeEvent.getLatencyMicros());
+        assertEquals(ProbeResult.PR_FAILURE, probeEvent.getProbeResult());
+        assertEquals(false, probeEvent.hasCapportApiData());
+
+        // Verify the 3rd probe: ProbeType = PT_DNS, Latency_us = 5678,
+        //                       ProbeResult = PR_FAILURE, CapportApiData = null
+        probeEvent = probeEvents.getProbeEvent(2);
+        assertEquals(ProbeType.PT_DNS, probeEvent.getProbeType());
+        assertEquals(5678, probeEvent.getLatencyMicros());
+        assertEquals(ProbeResult.PR_FAILURE, probeEvent.getProbeResult());
+        assertEquals(false, probeEvent.hasCapportApiData());
+
+        // Verify the 4th probe: ProbeType = PT_HTTP, Latency_us = longlatency,
+        //                       ProbeResult = PR_PORTAL, CapportApiData = null
+        probeEvent = probeEvents.getProbeEvent(3);
+        assertEquals(ProbeType.PT_HTTP, probeEvent.getProbeType());
+        // The latency exceeds Integer.MAX_VALUE(2147483647), it is limited to Integer.MAX_VALUE
+        assertEquals(Integer.MAX_VALUE, probeEvent.getLatencyMicros());
+        assertEquals(ProbeResult.PR_PORTAL, probeEvent.getProbeResult());
+        assertEquals(false, probeEvent.hasCapportApiData());
+
+        // Verify the ValidationResult
+        assertEquals(ValidationResult.VR_PARTIAL, stats.getValidationResult());
+    }
+}
diff --git a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
index 9de828f..c054b3a 100644
--- a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
+++ b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
@@ -29,11 +29,11 @@
 import android.net.dhcp.IDhcpServerCallbacks
 import android.net.ip.IIpClientCallbacks
 import android.net.ip.IpClient
-import android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH
 import android.os.Build
 import android.os.IBinder
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH
 import com.android.server.NetworkStackService.Dependencies
 import com.android.server.NetworkStackService.NetworkStackConnector
 import com.android.server.NetworkStackService.PermissionChecker
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index f0a7bfb..a3ef532 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -52,6 +52,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
+import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS;
 import static com.android.server.connectivity.NetworkMonitor.extractCharset;
 
 import static junit.framework.Assert.assertEquals;
@@ -68,6 +69,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
@@ -257,6 +259,7 @@
     private static final String TEST_LOGIN_URL = "https://testportal.example.com/login";
     private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
     private static final String TEST_SPEED_TEST_URL = "https://speedtest.example.com";
+    private static final String TEST_RELATIVE_URL = "/test/relative/gen_204";
     private static final String TEST_MCCMNC = "123456";
     private static final String[] TEST_HTTP_URLS = {TEST_HTTP_OTHER_URL1, TEST_HTTP_OTHER_URL2};
     private static final String[] TEST_HTTPS_URLS = {TEST_HTTPS_OTHER_URL1, TEST_HTTPS_OTHER_URL2};
@@ -864,12 +867,15 @@
         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
                 eq(android.Manifest.permission.ACCESS_FINE_LOCATION),  anyInt(), anyInt());
         doReturn(new ContextWrapper(mContext)).when(mContext).createConfigurationContext(any());
+        doReturn(null).when(mTelephony).getAllCellInfo();
+        assertNull(wnm.getLocationMcc());
         // Prepare CellInfo and check if the vote mechanism is working or not.
         final List<CellInfo> cellList = new ArrayList<CellInfo>();
+        doReturn(cellList).when(mTelephony).getAllCellInfo();
+        assertNull(wnm.getLocationMcc());
         cellList.add(makeTestCellInfoGsm("460"));
         cellList.add(makeTestCellInfoGsm("460"));
         cellList.add(makeTestCellInfoLte("466"));
-        doReturn(cellList).when(mTelephony).getAllCellInfo();
         // The count of 460 is 2 and the count of 466 is 1, so the getLocationMcc() should return
         // 460.
         assertEquals("460", wnm.getLocationMcc());
@@ -1112,10 +1118,13 @@
         verify(mFallbackConnection, times(1)).getResponseCode();
         verify(mOtherFallbackConnection, never()).getResponseCode();
 
-        // Second check uses the URL chosen by Random
-        final CaptivePortalProbeResult result = monitor.isCaptivePortal();
-        assertTrue(result.isPortal());
-        verify(mOtherFallbackConnection, times(1)).getResponseCode();
+        // Second check should be triggered automatically after the reevaluate delay, and uses the
+        // URL chosen by mRandom
+        // This test is appropriate to cover reevaluate behavior as long as the timeout is short
+        assertTrue(INITIAL_REEVALUATE_DELAY_MS < 2000);
+        verify(mOtherFallbackConnection, timeout(INITIAL_REEVALUATE_DELAY_MS + HANDLER_TIMEOUT_MS))
+                .getResponseCode();
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
     }
 
     @Test
@@ -1143,7 +1152,29 @@
     }
 
     @Test
-    public void testIsCaptivePortal_CapportApiIsPortal() throws Exception {
+    public void testIsCaptivePortal_CapportApiIsPortalWithNullPortalUrl() throws Exception {
+        assumeTrue(CaptivePortalDataShimImpl.isSupported());
+        setSslException(mHttpsConnection);
+        final long bytesRemaining = 10_000L;
+        final long secondsRemaining = 500L;
+        // Set content without partal url.
+        setApiContent(mCapportApiConnection, "{'captive': true,"
+                + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
+                + "'bytes-remaining': " + bytesRemaining + ","
+                + "'seconds-remaining': " + secondsRemaining + "}");
+        setPortal302(mHttpConnection);
+
+        runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL,
+                0 /* probesSucceeded*/, TEST_LOGIN_URL);
+
+        verify(mCapportApiConnection).getResponseCode();
+
+        verify(mHttpConnection, times(1)).getResponseCode();
+        verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
+    }
+
+    @Test
+    public void testIsCaptivePortal_CapportApiIsPortalWithValidPortalUrl() throws Exception {
         assumeTrue(CaptivePortalDataShimImpl.isSupported());
         setSslException(mHttpsConnection);
         final long bytesRemaining = 10_000L;
@@ -1198,6 +1229,32 @@
     }
 
     @Test
+    public void testIsCaptivePortal_NoRevalidationBeforeNetworkConnected() throws Exception {
+        assumeTrue(CaptivePortalDataShimImpl.isSupported());
+
+        final NetworkMonitor nm = makeCellMeteredNetworkMonitor();
+
+        final LinkProperties lp = makeCapportLPs();
+
+        // LinkProperties changed, but NM should not revalidate before notifyNetworkConnected
+        nm.notifyLinkPropertiesChanged(lp);
+        verify(mHttpConnection, after(100).never()).getResponseCode();
+        verify(mHttpsConnection, never()).getResponseCode();
+        verify(mCapportApiConnection, never()).getResponseCode();
+
+        setValidProbes();
+        setApiContent(mCapportApiConnection, "{'captive': true, "
+                + "'user-portal-url': '" + TEST_LOGIN_URL + "'}");
+
+        // After notifyNetworkConnected, validation uses the capport API contents
+        nm.notifyNetworkConnected(lp, CELL_METERED_CAPABILITIES);
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+
+        verify(mHttpConnection, never()).getResponseCode();
+        verify(mCapportApiConnection).getResponseCode();
+    }
+
+    @Test
     public void testIsCaptivePortal_CapportApiNotPortalNotValidated() throws Exception {
         assumeTrue(CaptivePortalDataShimImpl.isSupported());
         setSslException(mHttpsConnection);
@@ -1253,7 +1310,7 @@
     @Test
     public void testIsCaptivePortal_CapportApiInvalidContent() throws Exception {
         assumeTrue(CaptivePortalDataShimImpl.isSupported());
-        setStatus(mHttpsConnection, 204);
+        setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
         setApiContent(mCapportApiConnection, "{SomeInvalidText");
         runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
@@ -1264,6 +1321,36 @@
         verify(mHttpConnection).getResponseCode();
     }
 
+    private void runCapportApiInvalidUrlTest(String url) throws Exception {
+        assumeTrue(CaptivePortalDataShimImpl.isSupported());
+        setSslException(mHttpsConnection);
+        setPortal302(mHttpConnection);
+        final LinkProperties lp = new LinkProperties(TEST_LINK_PROPERTIES);
+        lp.setCaptivePortalApiUrl(Uri.parse(url));
+        runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
+                VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
+                TEST_LOGIN_URL);
+
+        verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
+        verify(mCapportApiConnection, never()).getInputStream();
+        verify(mHttpConnection).getResponseCode();
+    }
+
+    @Test
+    public void testIsCaptivePortal_HttpIsInvalidCapportApiScheme() throws Exception {
+        runCapportApiInvalidUrlTest("http://capport.example.com");
+    }
+
+    @Test
+    public void testIsCaptivePortal_FileIsInvalidCapportApiScheme() throws Exception {
+        runCapportApiInvalidUrlTest("file://localhost/myfile");
+    }
+
+    @Test
+    public void testIsCaptivePortal_InvalidUrlFormat() throws Exception {
+        runCapportApiInvalidUrlTest("ThisIsNotAValidUrl");
+    }
+
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testIsCaptivePortal_CapportApiNotSupported() throws Exception {
         // Test that on a R+ device, if NetworkStack was compiled without CaptivePortalData support
@@ -1805,10 +1892,11 @@
     private void testDataStall_StallTcpSuspectedAndSendMetrics(int transport,
             NetworkCapabilities nc) throws Exception {
         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+        setupTcpDataStall();
         // NM suspects data stall from TCP signal and sends data stall metrics.
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
         final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
-        setupTcpDataStall();
+
         // Trigger a tcp event immediately.
         setTcpPollingInterval(0);
         nm.sendTcpPollingEvent();
@@ -2150,20 +2238,35 @@
     @Test
     public void testDismissPortalInValidatedNetworkEnabledOsSupported() throws Exception {
         assumeTrue(ShimUtils.isAtLeastR());
-        testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL);
+        testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL, TEST_LOGIN_URL);
+    }
+
+    @Test
+    public void testDismissPortalInValidatedNetworkEnabledOsSupported_NullLocationUrl()
+            throws Exception {
+        assumeTrue(ShimUtils.isAtLeastR());
+        testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, null /* locationUrl */);
+    }
+
+    @Test
+    public void testDismissPortalInValidatedNetworkEnabledOsSupported_InvalidLocationUrl()
+            throws Exception {
+        assumeTrue(ShimUtils.isAtLeastR());
+        testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_RELATIVE_URL);
     }
 
     @Test
     public void testDismissPortalInValidatedNetworkEnabledOsNotSupported() throws Exception {
         assumeFalse(ShimUtils.isAtLeastR());
-        testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL);
+        testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_LOGIN_URL);
     }
 
-    private void testDismissPortalInValidatedNetworkEnabled(String portalUrl) throws Exception {
+    private void testDismissPortalInValidatedNetworkEnabled(String expectedUrl, String locationUrl)
+            throws Exception {
         setDismissPortalInValidatedNetwork(true);
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
-        when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL);
+        when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(locationUrl);
         final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
         notifyNetworkConnected(nm, CELL_METERED_CAPABILITIES);
 
@@ -2188,7 +2291,7 @@
         assertEquals(TEST_NETID, networkCaptor.getValue().netId);
         // Portal URL should be redirect URL.
         final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
-        assertEquals(portalUrl, redirectUrl);
+        assertEquals(expectedUrl, redirectUrl);
     }
 
     @Test
diff --git a/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index 533bbc3..c0bdc4c 100644
--- a/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -33,6 +34,7 @@
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -44,6 +46,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -69,10 +72,13 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /** Unit tests for {@link IpMemoryStoreService}. */
@@ -198,6 +204,32 @@
         };
     }
 
+    /** Helper method to make a vanilla IOnStatusAndCountListener */
+    private IOnStatusAndCountListener onDeleteStatus(BiConsumer<Status, Integer> functor) {
+        return new IOnStatusAndCountListener() {
+            @Override
+            public void onComplete(final StatusParcelable statusParcelable, final int deletedCount)
+                    throws RemoteException {
+                functor.accept(new Status(statusParcelable), deletedCount);
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+
+            @Override
+            public int getInterfaceVersion() {
+                return this.VERSION;
+            }
+
+            @Override
+            public String getInterfaceHash() {
+                return this.HASH;
+            }
+        };
+    }
+
     /** Helper method to make an IOnBlobRetrievedListener */
     private interface OnBlobRetrievedListener {
         void onBlobRetrieved(Status status, String l2Key, String name, byte[] data);
@@ -339,17 +371,18 @@
         }
     }
 
-    // Helper method to store network attributes to database
-    private void storeAttributes(final String l2Key, final NetworkAttributes na) {
-        storeAttributes("Did not complete storing attributes", l2Key, na);
+    // Helper method to store network attributes to database. Returns the stored attributes.
+    private NetworkAttributes storeAttributes(final String l2Key, final NetworkAttributes na) {
+        return storeAttributes("Did not complete storing attributes", l2Key, na);
     }
-    private void storeAttributes(final String timeoutMessage, final String l2Key,
+    private NetworkAttributes storeAttributes(final String timeoutMessage, final String l2Key,
             final NetworkAttributes na) {
         doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
                 onStatus(status -> {
                     assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
                     latch.countDown();
                 })));
+        return na;
     }
 
     // Helper method to store blob data to database
@@ -546,28 +579,37 @@
                         })));
     }
 
-    @Test
-    public void testFindL2Key() throws UnknownHostException {
+    private List<NetworkAttributes> storeFixture() throws Exception {
+        final ArrayList<NetworkAttributes> stored = new ArrayList<>();
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
         na.setCluster("cluster0");
-        storeAttributes(FAKE_KEYS[0], na.build());
+        stored.add(storeAttributes(FAKE_KEYS[0], na.build()));
 
         na.setDnsAddresses(Arrays.asList(
                 new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
-        na.setMtu(219);
-        storeAttributes(FAKE_KEYS[1], na.build());
+        na.setMtu(208);
+        stored.add(storeAttributes(FAKE_KEYS[1], na.build()));
         na.setMtu(null);
         na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setDnsAddresses(Arrays.asList(
                 new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
         na.setCluster("cluster1");
-        storeAttributes(FAKE_KEYS[2], na.build());
+        stored.add(storeAttributes(FAKE_KEYS[2], na.build()));
         na.setMtu(219);
-        storeAttributes(FAKE_KEYS[3], na.build());
+        stored.add(storeAttributes(FAKE_KEYS[3], na.build()));
+        na.setCluster(null);
         na.setMtu(240);
-        storeAttributes(FAKE_KEYS[4], na.build());
+        stored.add(storeAttributes(FAKE_KEYS[4], na.build()));
         na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
-        storeAttributes(FAKE_KEYS[5], na.build());
+        stored.add(storeAttributes(FAKE_KEYS[5], na.build()));
+        return stored;
+    }
+
+    @Test
+    public void testFindL2Key() throws Exception {
+        final List<NetworkAttributes> stored = storeFixture();
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder(
+                stored.get(stored.size() - 1));
 
         // Matches key 5 exactly
         doLatched("Did not finish finding L2Key", latch ->
@@ -589,6 +631,7 @@
                 })));
 
         // Closest to key 3 (indeed, identical)
+        na.setCluster("cluster1");
         na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setMtu(219);
         doLatched("Did not finish finding L2Key", latch ->
@@ -620,13 +663,13 @@
                     latch.countDown();
                 })));
 
-        // But changing the MTU makes this closer to key 4
-        na.setMtu(240);
+        // But changing the MTU makes this closer to key 2
+        na.setMtu(208);
         doLatched("Did not finish finding L2Key", latch ->
                 mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
-                    assertEquals(FAKE_KEYS[4], key);
+                    assertEquals(FAKE_KEYS[2], key);
                     latch.countDown();
                 })));
 
@@ -691,6 +734,63 @@
                 })));
     }
 
+    private NetworkAttributes fetchAttributes(@NonNull final String l2Key) throws Exception {
+        final CompletableFuture<NetworkAttributes> f = new CompletableFuture<>();
+        mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    f.complete(attr);
+                }));
+        return f.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+
+    private void delete(@NonNull final String l2Key) {
+        doLatched("Did not finish deleting", latch ->
+                mService.delete(l2Key, false /* needWipe */, onDeleteStatus((status, deleted) -> {
+                    assertTrue("Deleting failed :" + status.resultCode, status.isSuccess());
+                    assertEquals("Deleting count != 1 :" + deleted, 1, deleted.intValue());
+                    latch.countDown();
+                })), LONG_TIMEOUT_MS);
+    }
+
+    @Test
+    public void testDelete() throws Exception {
+        storeFixture();
+
+        delete(FAKE_KEYS[0]);
+        delete(FAKE_KEYS[3]);
+
+        assertNull(fetchAttributes(FAKE_KEYS[0]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[1]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[2]));
+        assertNull(fetchAttributes(FAKE_KEYS[3]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[4]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[5]));
+    }
+
+    @Test
+    public void testDeleteCluster() throws Exception {
+        storeFixture();
+
+        doLatched("Did not finish deleting", latch ->
+                mService.deleteCluster("cluster1", false /* needWipe */,
+                        onDeleteStatus((status, deletedCount) -> {
+                            assertTrue("Delete failed : " + status.resultCode, status.isSuccess());
+                            // The fixture stores 2 keys under "cluster1"
+                            assertEquals("Unexpected deleted count : " + deletedCount,
+                                    2, deletedCount.intValue());
+                            latch.countDown();
+                        })), LONG_TIMEOUT_MS);
+
+        assertNotNull(fetchAttributes(FAKE_KEYS[0]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[1]));
+        assertNull(fetchAttributes(FAKE_KEYS[2]));
+        assertNull(fetchAttributes(FAKE_KEYS[3]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[4]));
+        assertNotNull(fetchAttributes(FAKE_KEYS[5]));
+    }
+
     @Test
     public void testFullMaintenance() throws Exception {
         copyTestData(mDbFile);