Snap for 6877830 from 07a95325e41ce0a2c86be509bf6966511102c7cc to sdk-release

Change-Id: I12782859f0f6358d55a7ff9bcc05f0767be521e2
diff --git a/Android.bp b/Android.bp
index ae25f17..314def9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -48,8 +48,7 @@
 
 java_defaults {
     name: "NetworkStackReleaseApiLevel",
-    // TODO: use system_30 once a prebuilt SDK is available for R
-    sdk_version: "system_current",
+    sdk_version: "system_30",
     min_sdk_version: "29",
     target_sdk_version: "30",
 }
@@ -92,9 +91,11 @@
         "netlink-client",
         "networkstack-client",
         "net-utils-framework-common",
+        // See note on statsprotos when adding/updating proto build rules
         "datastallprotosnano",
         "statsprotos",
         "captiveportal-lib",
+        "net-utils-device-common",
     ],
     plugins: ["java_api_finder"],
 }
@@ -128,7 +129,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: [
@@ -254,15 +255,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" +
@@ -294,6 +286,8 @@
     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: {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6f785c4..b61c89f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,13 +19,13 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.android.networkstack"
   android:sharedUserId="android.uid.networkstack"
-  android:versionCode="299900000"
-  android:versionName="2019-09"
+  android:versionCode="300900700"
+  android:versionName="r_aml_300900700"
 >
     <!-- Permissions must be defined here, and not in the base manifest, as the network stack
          running in the system server process does not need any permission, and having privileged
          permissions added would cause crashes on startup unless they are also added to the
-         privileged permissions whitelist for that package. -->
+         privileged permissions allowlist for that package. -->
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
@@ -50,6 +50,17 @@
                 <action android:name="android.net.INetworkStackConnector"/>
             </intent-filter>
         </service>
+        <!-- Test instrumentation service, only usable on debuggable builds.
+             The service is protected by NETWORK_SETTINGS permissions as there is no better
+             networking-related permission that exists on Q, is sufficiently protected (signature),
+             and can be obtained via shell permissions. -->
+        <service android:name="com.android.server.TestNetworkStackService"
+                 android:permission="android.permission.NETWORK_SETTINGS"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.net.INetworkStackConnector.Test"/>
+            </intent-filter>
+        </service>
         <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
diff --git a/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java b/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java
index d78cd0c..648751b 100644
--- a/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java
+++ b/apishim/common/com/android/networkstack/apishim/common/ShimUtils.java
@@ -49,4 +49,11 @@
     public static boolean isAtLeastR() {
         return isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q);
     }
+
+    /**
+     * Check whether the device supports in-development or final S networking APIs.
+     */
+    public static boolean isAtLeastS() {
+        return isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R);
+    }
 }
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 9dfec42..0e78212 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -56,22 +56,8 @@
         "src/android/net/netlink/*.java",
         "src/android/net/shared/NetdUtils.java",
         "src/android/net/shared/RouteUtils.java",
-        "src/android/net/util/FdEventsReader.java",
         "src/android/net/util/InterfaceParams.java",
-        "src/android/net/util/PacketReader.java",
-        "src/android/net/util/SharedLog.java"
+        "src/android/net/util/SharedLog.java",
     ],
     visibility: ["//frameworks/base/packages/Tethering"],
 }
-
-// Utility sources used by test libraries.
-// This is its own group to limit indiscriminate dependency of test code on production code.
-// TODO: move these classes and NetworkStack/tests/lib to frameworks/libs/net, and remove this.
-filegroup {
-    name: "net-module-utils-srcs-for-tests",
-    visibility: ["//packages/modules/NetworkStack/tests/lib"],
-    srcs: [
-        "src/android/net/util/FdEventsReader.java",
-        "src/android/net/util/PacketReader.java",
-    ],
-}
diff --git a/common/moduleutils/src/android/net/util/FdEventsReader.java b/common/moduleutils/src/android/net/util/FdEventsReader.java
deleted file mode 100644
index 9045c01..0000000
--- a/common/moduleutils/src/android/net/util/FdEventsReader.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * 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.util;
-
-import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
-import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.MessageQueue;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-
-/**
- * This class encapsulates the mechanics of registering a file descriptor
- * with a thread's Looper and handling read events (and errors).
- *
- * Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override
- * onStop() and onStart().
- *
- * Subclasses can expect a call life-cycle like the following:
- *
- *     [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all
- *         goes well. Implementations may override onStart() for additional initialization.
- *
- *     [2] yield, waiting for read event or error notification:
- *
- *             [a] readPacket() && handlePacket()
- *
- *             [b] if (no error):
- *                     goto 2
- *                 else:
- *                     goto 3
- *
- *     [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never
- *         started). Implementations may override onStop() for additional cleanup.
- *
- * The packet receive buffer is recycled on every read call, so subclasses
- * should make any copies they would like inside their handlePacket()
- * implementation.
- *
- * All public methods MUST only be called from the same thread with which
- * the Handler constructor argument is associated.
- *
- * @param <BufferType> the type of the buffer used to read data.
- */
-public abstract class FdEventsReader<BufferType> {
-    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
-    private static final int UNREGISTER_THIS_FD = 0;
-
-    @NonNull
-    private final Handler mHandler;
-    @NonNull
-    private final MessageQueue mQueue;
-    @NonNull
-    private final BufferType mBuffer;
-    @Nullable
-    private FileDescriptor mFd;
-    private long mPacketsReceived;
-
-    protected static void closeFd(FileDescriptor fd) {
-        try {
-            SocketUtils.closeSocket(fd);
-        } catch (IOException ignored) {
-        }
-    }
-
-    protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) {
-        mHandler = h;
-        mQueue = mHandler.getLooper().getQueue();
-        mBuffer = buffer;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    protected MessageQueue getMessageQueue() {
-        return mQueue;
-    }
-
-    /** Start this FdEventsReader. */
-    public boolean start() {
-        if (!onCorrectThread()) {
-            throw new IllegalStateException("start() called from off-thread");
-        }
-
-        return createAndRegisterFd();
-    }
-
-    /** Stop this FdEventsReader and destroy the file descriptor. */
-    public void stop() {
-        if (!onCorrectThread()) {
-            throw new IllegalStateException("stop() called from off-thread");
-        }
-
-        unregisterAndDestroyFd();
-    }
-
-    @NonNull
-    public Handler getHandler() {
-        return mHandler;
-    }
-
-    protected abstract int recvBufSize(@NonNull BufferType buffer);
-
-    /** Returns the size of the receive buffer. */
-    public int recvBufSize() {
-        return recvBufSize(mBuffer);
-    }
-
-    /**
-     * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}.
-     *
-     * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0.
-     */
-    public final long numPacketsReceived() {
-        return mPacketsReceived;
-    }
-
-    /**
-     * Subclasses MUST create the listening socket here, including setting all desired socket
-     * options, interface or address/port binding, etc. The socket MUST be created nonblocking.
-     */
-    @Nullable
-    protected abstract FileDescriptor createFd();
-
-    /**
-     * Implementations MUST return the bytes read or throw an Exception.
-     *
-     * <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or
-     * {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer
-     * contents and respectively wait for further input or retry the read immediately. For all other
-     * exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this
-     * method.
-     */
-    protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer)
-            throws Exception;
-
-    /**
-     * Called by the main loop for every packet.  Any desired copies of
-     * |recvbuf| should be made in here, as the underlying byte array is
-     * reused across all reads.
-     */
-    protected void handlePacket(@NonNull BufferType recvbuf, int length) {}
-
-    /**
-     * Called by the main loop to log errors.  In some cases |e| may be null.
-     */
-    protected void logError(@NonNull String msg, @Nullable Exception e) {}
-
-    /**
-     * Called by start(), if successful, just prior to returning.
-     */
-    protected void onStart() {}
-
-    /**
-     * Called by stop() just prior to returning.
-     */
-    protected void onStop() {}
-
-    private boolean createAndRegisterFd() {
-        if (mFd != null) return true;
-
-        try {
-            mFd = createFd();
-        } catch (Exception e) {
-            logError("Failed to create socket: ", e);
-            closeFd(mFd);
-            mFd = null;
-        }
-
-        if (mFd == null) return false;
-
-        getMessageQueue().addOnFileDescriptorEventListener(
-                mFd,
-                FD_EVENTS,
-                (fd, events) -> {
-                    // Always call handleInput() so read/recvfrom are given
-                    // a proper chance to encounter a meaningful errno and
-                    // perhaps log a useful error message.
-                    if (!isRunning() || !handleInput()) {
-                        unregisterAndDestroyFd();
-                        return UNREGISTER_THIS_FD;
-                    }
-                    return FD_EVENTS;
-                });
-        onStart();
-        return true;
-    }
-
-    private boolean isRunning() {
-        return (mFd != null) && mFd.valid();
-    }
-
-    // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
-    private boolean handleInput() {
-        while (isRunning()) {
-            final int bytesRead;
-
-            try {
-                bytesRead = readPacket(mFd, mBuffer);
-                if (bytesRead < 1) {
-                    if (isRunning()) logError("Socket closed, exiting", null);
-                    break;
-                }
-                mPacketsReceived++;
-            } catch (ErrnoException e) {
-                if (e.errno == OsConstants.EAGAIN) {
-                    // We've read everything there is to read this time around.
-                    return true;
-                } else if (e.errno == OsConstants.EINTR) {
-                    continue;
-                } else {
-                    if (isRunning()) logError("readPacket error: ", e);
-                    break;
-                }
-            } catch (Exception e) {
-                if (isRunning()) logError("readPacket error: ", e);
-                break;
-            }
-
-            try {
-                handlePacket(mBuffer, bytesRead);
-            } catch (Exception e) {
-                logError("handlePacket error: ", e);
-                Log.wtf(FdEventsReader.class.getSimpleName(), "Error handling packet", e);
-            }
-        }
-
-        return false;
-    }
-
-    private void unregisterAndDestroyFd() {
-        if (mFd == null) return;
-
-        getMessageQueue().removeOnFileDescriptorEventListener(mFd);
-        closeFd(mFd);
-        mFd = null;
-        onStop();
-    }
-
-    private boolean onCorrectThread() {
-        return (mHandler.getLooper() == Looper.myLooper());
-    }
-}
diff --git a/common/moduleutils/src/android/net/util/PacketReader.java b/common/moduleutils/src/android/net/util/PacketReader.java
deleted file mode 100644
index 0be7187..0000000
--- a/common/moduleutils/src/android/net/util/PacketReader.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import static java.lang.Math.max;
-
-import android.os.Handler;
-import android.system.Os;
-
-import java.io.FileDescriptor;
-
-/**
- * Specialization of {@link FdEventsReader} that reads packets into a byte array.
- *
- * TODO: rename this class to something more correctly descriptive (something
- * like [or less horrible than] FdReadEventsHandler?).
- */
-public abstract class PacketReader extends FdEventsReader<byte[]> {
-
-    public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
-
-    protected PacketReader(Handler h) {
-        this(h, DEFAULT_RECV_BUF_SIZE);
-    }
-
-    protected PacketReader(Handler h, int recvBufSize) {
-        super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]);
-    }
-
-    @Override
-    protected final int recvBufSize(byte[] buffer) {
-        return buffer.length;
-    }
-
-    /**
-     * Subclasses MAY override this to change the default read() implementation
-     * in favour of, say, recvfrom().
-     *
-     * Implementations MUST return the bytes read or throw an Exception.
-     */
-    @Override
-    protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
-        return Os.read(fd, packetBuffer, 0, packetBuffer.length);
-    }
-}
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index c7e4f04..a86f7fd 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -27,7 +27,9 @@
         java: {
             apex_available: [
                 "//apex_available:platform",
+                "com.android.wifi",
                 "com.android.bluetooth.updatable",
+                "com.android.tethering",
             ],
             // this is part of updatable modules(NetworkStack) which targets 29(Q)
             min_sdk_version: "29",
@@ -47,6 +49,7 @@
         "5",
         "6",
         "7",
+        "8",
     ],
     visibility: [
         "//system/tools/aidl/build",
@@ -85,12 +88,15 @@
         "src/android/net/dhcp/IDhcpServerCallbacks.aidl",
         "src/android/net/ip/IIpClient.aidl",
         "src/android/net/ip/IIpClientCallbacks.aidl",
+        // New AIDL classes should go into android.net.networkstack.aidl so they can be clearly
+        // identified
     ],
     backend: {
         java: {
             apex_available: [
                 "//apex_available:platform",
                 "com.android.bluetooth.updatable",
+                "com.android.wifi",
                 "com.android.tethering",
             ],
             // this is part of updatable modules(NetworkStack) which targets 29(Q)
@@ -112,6 +118,7 @@
         "5",
         "6",
         "7",
+        "8",
     ],
     // TODO: have tethering depend on networkstack-client and set visibility to private
     visibility: [
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/.hash b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/.hash
new file mode 100644
index 0000000..346969a
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/.hash
@@ -0,0 +1 @@
+70cbb9e5b4009a86a81aa2a9bdf25e21442224be
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..bf7a26d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..2024391
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..8a1b57e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..e711272
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..4abecb9
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..05c48b3
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..0bc8c5e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..cf30fa1
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..e71de47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..92a570d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..eca0987
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/8/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..7554608
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/8/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/networkstack-aidl-interfaces/8/.hash b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/.hash
new file mode 100644
index 0000000..c7ab51a
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/.hash
@@ -0,0 +1 @@
+f6e2137b5033902774f78726d429399db3b18cab
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/DataStallReportParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/DataStallReportParcelable.aidl
new file mode 100644
index 0000000..69ff31f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/DataStallReportParcelable.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+parcelable DataStallReportParcelable {
+  long timestampMillis = 0;
+  int detectionMethod = 1;
+  int tcpPacketFailRate = 2;
+  int tcpMetricsCollectionPeriodMillis = 3;
+  int dnsConsecutiveTimeouts = 4;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/DhcpResultsParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..7bb5c41
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  @nullable String serverHostName;
+  @nullable String captivePortalApiUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkMonitor.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..5945819
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkMonitor.aidl
@@ -0,0 +1,42 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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 INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 1;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_PROBE_DNS = 4;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkMonitorCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..b7ddad9
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkMonitorCallbacks.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 INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor) = 0;
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl) = 1;
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config) = 2;
+  oneway void showProvisioningNotification(String action, String packageName) = 3;
+  oneway void hideProvisioningNotification() = 4;
+  oneway void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) = 5;
+  oneway void notifyNetworkTestedWithExtras(in android.net.NetworkTestResultParcelable result) = 6;
+  oneway void notifyDataStallSuspected(in android.net.DataStallReportParcelable report) = 7;
+  oneway void notifyCaptivePortalDataChanged(in android.net.CaptivePortalData data) = 8;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..0864886
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkStackConnector.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;
+/* @hide */
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+  oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkStackStatusCallback.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..ec16def
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/INetworkStackStatusCallback.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 INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/InformationElementParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/InformationElementParcelable.aidl
new file mode 100644
index 0000000..c882bf4
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/InformationElementParcelable.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;
+parcelable InformationElementParcelable {
+  int id;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/InitialConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..c91d7a2
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/InitialConfigurationParcelable.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;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/Layer2InformationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/Layer2InformationParcelable.aidl
new file mode 100644
index 0000000..dca5138
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/Layer2InformationParcelable.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+parcelable Layer2InformationParcelable {
+  String l2Key;
+  String cluster;
+  android.net.MacAddress bssid;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..2e0955f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/Layer2PacketParcelable.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;
+parcelable Layer2PacketParcelable {
+  android.net.MacAddress dstMacAddress;
+  byte[] payload;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/NattKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..aa09c3d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/NattKeepalivePacketDataParcelable.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;
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/NetworkTestResultParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/NetworkTestResultParcelable.aidl
new file mode 100644
index 0000000..f31a669
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/NetworkTestResultParcelable.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+parcelable NetworkTestResultParcelable {
+  long timestampMillis;
+  int result;
+  int probesSucceeded;
+  int probesAttempted;
+  String redirectUrl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/PrivateDnsConfigParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..cada4d3
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/PrivateDnsConfigParcel.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;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..b8dfb91
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,35 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+parcelable ProvisioningConfigurationParcelable {
+  boolean enableIPv4;
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+  boolean enablePreconnection;
+  @nullable android.net.ScanResultInfoParcelable scanResultInfo;
+  @nullable android.net.Layer2InformationParcelable layer2Info;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ScanResultInfoParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ScanResultInfoParcelable.aidl
new file mode 100644
index 0000000..f7ac167
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ScanResultInfoParcelable.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+parcelable ScanResultInfoParcelable {
+  String ssid;
+  String bssid;
+  android.net.InformationElementParcelable[] informationElements;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/TcpKeepalivePacketDataParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..c50f541
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/TcpKeepalivePacketDataParcelable.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;
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/DhcpLeaseParcelable.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/DhcpLeaseParcelable.aidl
new file mode 100644
index 0000000..adbd57d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/DhcpLeaseParcelable.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.dhcp;
+parcelable DhcpLeaseParcelable {
+  byte[] clientId;
+  byte[] hwAddr;
+  int netAddr;
+  int prefixLength;
+  long expTime;
+  String hostname;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/DhcpServingParamsParcel.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..d66ca9d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/DhcpServingParamsParcel.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.dhcp;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+  int singleClientAddr = 0;
+  boolean changePrefixOnDecline = false;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpEventCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpEventCallbacks.aidl
new file mode 100644
index 0000000..dfcaf98
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpEventCallbacks.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.dhcp;
+interface IDhcpEventCallbacks {
+  oneway void onLeasesChanged(in List<android.net.dhcp.DhcpLeaseParcelable> newLeases);
+  oneway void onNewPrefixRequest(in android.net.IpPrefix currentPrefix);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpServer.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..ef936cc
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,29 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.dhcp;
+/* @hide */
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb) = 0;
+  oneway void startWithCallbacks(in android.net.INetworkStackStatusCallback statusCb, in android.net.dhcp.IDhcpEventCallbacks eventCb) = 3;
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb) = 1;
+  oneway void stop(in android.net.INetworkStackStatusCallback cb) = 2;
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpServerCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..63b89ad
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/dhcp/IDhcpServerCallbacks.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.dhcp;
+/* @hide */
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ip/IIpClient.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..9245954
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ip/IIpClient.aidl
@@ -0,0 +1,36 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.ip;
+/* @hide */
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String cluster);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+  oneway void notifyPreconnectionComplete(boolean success);
+  oneway void updateLayer2Information(in android.net.Layer2InformationParcelable info);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..9aabb1f
--- /dev/null
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/8/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,35 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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.ip;
+/* @hide */
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+  oneway void onPreconnectionStart(in List<android.net.Layer2PacketParcelable> packets);
+}
diff --git a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
index 17a65cf..0864886 100644
--- a/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
+++ b/common/networkstackclient/aidl_api/networkstack-aidl-interfaces/current/android/net/INetworkStackConnector.aidl
@@ -22,4 +22,5 @@
   oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
   oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
   oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+  oneway void allowTestUid(int uid, in android.net.INetworkStackStatusCallback cb);
 }
diff --git a/common/networkstackclient/src/android/net/INetworkStackConnector.aidl b/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
index 3751c36..fa7abf5 100644
--- a/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
+++ b/common/networkstackclient/src/android/net/INetworkStackConnector.aidl
@@ -17,6 +17,7 @@
 
 import android.net.IIpMemoryStoreCallbacks;
 import android.net.INetworkMonitorCallbacks;
+import android.net.INetworkStackStatusCallback;
 import android.net.Network;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServerCallbacks;
@@ -29,4 +30,21 @@
     void makeNetworkMonitor(in Network network, String name, in INetworkMonitorCallbacks cb);
     void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
     void fetchIpMemoryStore(in IIpMemoryStoreCallbacks cb);
+    /**
+     * Mark a UID as test UID, allowing it to use the TestNetworkStackService.
+     *
+     * TestNetworkStackService is a binder service identical to NetworkStackService, but only
+     * available on userdebug builds, and only usable by the test UID. It does not require the
+     * MAINLINE_NETWORK_STACK signature permission like NetworkStackService does, so it allows the
+     * test UID to use other methods in this interface even though it would otherwise not have
+     * permission to.
+     *
+     * This method must be called as root and can only be used on debuggable builds. It only affects
+     * the NetworkStack until it is restarted.
+     * Callers should pass in -1 to reset after use.
+     *
+     * @param cb Callback that will be called with a status of 0 if the call succeeds. Calls without
+     *           sufficient permissions may be dropped without generating a callback.
+     */
+    oneway void allowTestUid(int uid, in INetworkStackStatusCallback cb);
 }
diff --git a/jarjar-rules-shared.txt b/jarjar-rules-shared.txt
index 24dc4fb..e766b5f 100644
--- a/jarjar-rules-shared.txt
+++ b/jarjar-rules-shared.txt
@@ -2,6 +2,7 @@
 rule com.android.net.module.util.** com.android.networkstack.util.@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
diff --git a/jni/network_stack_utils_jni.cpp b/jni/network_stack_utils_jni.cpp
index 60eb897..22d573e 100644
--- a/jni/network_stack_utils_jni.cpp
+++ b/jni/network_stack_utils_jni.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "NetworkStackUtils-JNI"
 
+#include <dlfcn.h>
 #include <errno.h>
 #include <jni.h>
 #include <linux/filter.h>
@@ -27,11 +28,11 @@
 #include <netinet/ip6.h>
 #include <netinet/udp.h>
 #include <stdlib.h>
+#include <sys/system_properties.h>
 
 #include <string>
 
 #include <nativehelper/JNIHelp.h>
-#include <nativehelper/JNIHelpCompat.h>
 
 #include <android/log.h>
 
@@ -57,6 +58,76 @@
     return true;
 }
 
+static bool isAtLeastS() {
+    static bool atLeastS = false;
+    static bool doProbe = true;
+
+    if (!doProbe) {
+        return atLeastS;
+    }
+
+    atLeastS = (android_get_device_api_level() > __ANDROID_API_R__);
+    if (!atLeastS) {
+        // Check if this is a device with Android S dogfood build.
+        static constexpr const char* kCodenameProperty = "ro.build.version.codename";
+        char codename[PROP_VALUE_MAX] = { 0 };
+        // SDK may be 30 (R) with codename T if S SDK was finalized but not yet merged in the
+        // branch, and T development started.
+        atLeastS = (__system_property_get(kCodenameProperty, codename) > 0 &&
+                    (strncmp(codename, "S", 2) == 0 || strncmp(codename, "T", 2) == 0));
+    }
+    doProbe = false;
+
+    return atLeastS;
+}
+
+static int getNativeFileDescriptorWithoutNdk(JNIEnv* env, jobject javaFd) {
+    // Prior to Android S, we need to find the descriptor field in the FileDescriptor class. The
+    // symbol name has been stable in libcore, but is a private implementation detail.
+    // Older libnativehelper_compat_c++ versions had a jniGetFdFromFileDescriptor method, but this
+    // was removed in S to replace it with the NDK API in libnativehelper.
+    // The code is copied here instead. This code can be removed once R is not supported anymore.
+    static jfieldID descriptorFieldID = nullptr;
+    if (descriptorFieldID == nullptr) {
+        jclass fileDescriptorClass = env->FindClass("java/io/FileDescriptor");
+        descriptorFieldID = env->GetFieldID(fileDescriptorClass, "descriptor", "I");
+        env->DeleteLocalRef(fileDescriptorClass);
+        if (descriptorFieldID == nullptr) {
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to get descriptor field.");
+            return -1;
+        }
+    }
+    return javaFd != nullptr ? env->GetIntField(javaFd, descriptorFieldID) : -1;
+}
+
+static int getNativeFileDescriptorWithNdk(JNIEnv* env, jobject javaFd) {
+    // Since Android S, there is an NDK API to get a file descriptor present in libnativehelper.so.
+    // libnativehelper is loaded into all processes by the zygote since the zygote uses it
+    // to load the Android Runtime and is also a public library (because of the NDK API).
+    static int (*ndkGetFD)(JNIEnv*, jobject) = nullptr;
+    if (ndkGetFD == nullptr) {
+        void* handle = dlopen("libnativehelper.so", RTLD_NOLOAD | RTLD_NODELETE);
+        ndkGetFD = reinterpret_cast<decltype(ndkGetFD)>(dlsym(handle, "AFileDescriptor_getFD"));
+        if (ndkGetFD == nullptr) {
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                                "Failed to dlsym(AFileDescriptor_getFD): %s", dlerror());
+            dlclose(handle);
+            return -1;
+        }
+        dlclose(handle);
+    }
+    return javaFd != nullptr ? ndkGetFD(env, javaFd) : -1;
+}
+
+static int getNativeFileDescriptor(JNIEnv* env, jobject javaFd) {
+    // Check if we should use the NDK File Descriptor API introduced in S.
+    if (isAtLeastS()) {
+        return getNativeFileDescriptorWithNdk(env, javaFd);
+    } else {
+        return getNativeFileDescriptorWithoutNdk(env, javaFd);
+    }
+}
+
 static void network_stack_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
         jbyteArray ipv4Addr, jstring ifname, jobject javaFd) {
     arpreq req = {};
@@ -84,7 +155,7 @@
     env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
 
     req.arp_flags = ATF_COM;  // Completed entry (ha valid)
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = getNativeFileDescriptor(env, javaFd);
     if (fd < 0) {
         jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
         return;
@@ -122,7 +193,7 @@
         filter_code,
     };
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = getNativeFileDescriptor(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
@@ -155,7 +226,7 @@
         filter_code,
     };
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = getNativeFileDescriptor(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
@@ -230,7 +301,7 @@
         filter_code,
     };
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = getNativeFileDescriptor(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
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..215b872
--- /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..13700a6
--- /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/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 165e37b..173b5ef 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -778,7 +778,7 @@
             mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
             mLastSeen = currentTimeSeconds();
 
-            // Sanity check packet in case a packet arrives before we attach RA filter
+            // Check packet in case a packet arrives before we attach RA filter
             // to our packet socket. b/29586253
             if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
                     getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
diff --git a/src/android/net/dhcp/DhcpAckPacket.java b/src/android/net/dhcp/DhcpAckPacket.java
index 9e981ef..225e447 100644
--- a/src/android/net/dhcp/DhcpAckPacket.java
+++ b/src/android/net/dhcp/DhcpAckPacket.java
@@ -46,10 +46,11 @@
             dnsServers += dnsServer.toString() + " ";
         }
 
-        return s + " ACK: your new IP " + mYourIp +
-                ", netmask " + mSubnetMask +
-                ", gateways " + mGateways + dnsServers +
-                ", lease time " + mLeaseTime;
+        return s + " ACK: your new IP " + mYourIp
+                + ", netmask " + mSubnetMask
+                + ", gateways " + mGateways + dnsServers
+                + ", lease time " + mLeaseTime
+                + (mIpv6OnlyWaitTime != null ? ", V6ONLY_WAIT " + mIpv6OnlyWaitTime : "");
     }
 
     /**
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 4fedf30..4b78433 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -20,6 +20,7 @@
 import static android.net.dhcp.DhcpPacket.DHCP_CAPTIVE_PORTAL;
 import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
 import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
+import static android.net.dhcp.DhcpPacket.DHCP_IPV6_ONLY_PREFERRED;
 import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
 import static android.net.dhcp.DhcpPacket.DHCP_MTU;
 import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
@@ -31,6 +32,7 @@
 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.util.NetworkStackUtils.DHCP_INIT_REBOOT_VERSION;
+import static android.net.util.NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION;
 import static android.net.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION;
 import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
 import static android.net.util.NetworkStackUtils.closeSocketQuietly;
@@ -104,6 +106,7 @@
 import com.android.networkstack.arp.ArpPacket;
 import com.android.networkstack.metrics.IpProvisioningMetrics;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Inet4Address;
@@ -244,6 +247,7 @@
     /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
     public static final int DHCP_SUCCESS = 1;
     public static final int DHCP_FAILURE = 2;
+    public static final int DHCP_IPV6_ONLY = 3;
 
     // Internal messages.
     private static final int PRIVATE_BASE         = IpClient.DHCPCLIENT_CMD_BASE + 100;
@@ -287,13 +291,18 @@
 
     @NonNull
     private byte[] getRequestedParams() {
+        // Set an initial size large enough for all optional parameters that we might request.
+        final int numOptionalParams = 2;
+        final ByteArrayOutputStream params =
+                new ByteArrayOutputStream(DEFAULT_REQUESTED_PARAMS.length + numOptionalParams);
+        params.write(DEFAULT_REQUESTED_PARAMS, 0, DEFAULT_REQUESTED_PARAMS.length);
         if (isCapportApiEnabled()) {
-            final byte[] params = Arrays.copyOf(DEFAULT_REQUESTED_PARAMS,
-                    DEFAULT_REQUESTED_PARAMS.length + 1);
-            params[params.length - 1] = DHCP_CAPTIVE_PORTAL;
-            return params;
+            params.write(DHCP_CAPTIVE_PORTAL);
         }
-        return DEFAULT_REQUESTED_PARAMS;
+        if (isIPv6OnlyPreferredModeEnabled()) {
+            params.write(DHCP_IPV6_ONLY_PREFERRED);
+        }
+        return params.toByteArray();
     }
 
     private static boolean isCapportApiEnabled() {
@@ -338,10 +347,10 @@
     private int mConflictCount;
     private long mLastAssignedIpv4AddressExpiry;
     private Dependencies mDependencies;
-    @NonNull
-    private final NetworkStackIpMemoryStore mIpMemoryStore;
     @Nullable
     private DhcpPacketHandler mDhcpPacketHandler;
+    @NonNull
+    private final NetworkStackIpMemoryStore mIpMemoryStore;
     @Nullable
     private final String mHostname;
 
@@ -349,6 +358,10 @@
     private long mLastInitEnterTime;
     private long mLastBoundExitTime;
 
+    // 32-bit unsigned integer used to indicate the number of milliseconds the DHCP client should
+    // disable DHCPv4.
+    private long mIpv6OnlyWaitTimeMs;
+
     // States.
     private State mStoppedState = new StoppedState();
     private State mDhcpState = new DhcpState();
@@ -370,6 +383,7 @@
             new WaitBeforeObtainingConfigurationState(mObtainingConfigurationState);
     private State mIpAddressConflictDetectingState = new IpAddressConflictDetectingState();
     private State mDhcpDecliningState = new DhcpDecliningState();
+    private State mIpv6OnlyWaitState = new Ipv6OnlyWaitState();
 
     private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
         cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
@@ -470,6 +484,7 @@
             addState(mDhcpSelectingState, mDhcpState);
             addState(mDhcpRequestingState, mDhcpState);
             addState(mIpAddressConflictDetectingState, mDhcpState);
+            addState(mIpv6OnlyWaitState, mDhcpState);
             addState(mDhcpHaveLeaseState, mDhcpState);
                 addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
                 addState(mDhcpBoundState, mDhcpHaveLeaseState);
@@ -544,6 +559,19 @@
                 false /* defaultEnabled */);
     }
 
+    /**
+     * check whether or not to support IPv6-only preferred option.
+     *
+     * IPv6-only preferred option is supported on Android S by default if there is no experiment
+     * flag set to disable this feature explicitly.
+     */
+    public boolean isIPv6OnlyPreferredModeEnabled() {
+        final boolean defaultEnabled =
+                ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R);
+        return mDependencies.isFeatureEnabled(mContext, DHCP_IPV6_ONLY_PREFERRED_VERSION,
+                defaultEnabled);
+    }
+
     private void recordMetricEnabledFeatures() {
         if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
         if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
@@ -605,6 +633,13 @@
         }
     }
 
+    private byte[] getOptionsToSkip() {
+        final ByteArrayOutputStream optionsToSkip = new ByteArrayOutputStream(2);
+        if (!isCapportApiEnabled()) optionsToSkip.write(DHCP_CAPTIVE_PORTAL);
+        if (!isIPv6OnlyPreferredModeEnabled()) optionsToSkip.write(DHCP_IPV6_ONLY_PREFERRED);
+        return optionsToSkip.toByteArray();
+    }
+
     private class DhcpPacketHandler extends PacketReader {
         private FileDescriptor mPacketSock;
 
@@ -615,10 +650,8 @@
         @Override
         protected void handlePacket(byte[] recvbuf, int length) {
             try {
-                final byte[] optionsToSkip =
-                        isCapportApiEnabled() ? new byte[0] : new byte[] { DHCP_CAPTIVE_PORTAL };
                 final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf, length,
-                        DhcpPacket.ENCAP_L2, optionsToSkip);
+                        DhcpPacket.ENCAP_L2, getOptionsToSkip());
                 if (DBG) Log.d(TAG, "Received packet: " + packet);
                 sendMessage(CMD_RECEIVED_PACKET, packet);
             } catch (DhcpPacket.ParseException e) {
@@ -944,10 +977,11 @@
         // This is part of the initial configuration because it is passed in on startup and
         // never updated.
         // TODO: decide what to do about L2 key changes while the client is connected.
+        @Nullable
         public final String l2Key;
         public final boolean isPreconnectionEnabled;
 
-        public Configuration(String l2Key, boolean isPreconnectionEnabled) {
+        public Configuration(@Nullable final String l2Key, final boolean isPreconnectionEnabled) {
             this.l2Key = l2Key;
             this.isPreconnectionEnabled = isPreconnectionEnabled;
         }
@@ -1052,7 +1086,7 @@
     }
 
     abstract class TimeoutState extends LoggingState {
-        protected int mTimeout = 0;
+        protected long mTimeout = 0;
 
         @Override
         public void enter() {
@@ -1223,6 +1257,15 @@
         }
     }
 
+    private boolean maybeTransitionToIpv6OnlyWaitState(@NonNull final DhcpPacket packet) {
+        if (!isIPv6OnlyPreferredModeEnabled()) return false;
+        if (packet.getIpv6OnlyWaitTimeMillis() == DhcpPacket.V6ONLY_PREFERRED_ABSENCE) return false;
+
+        mIpv6OnlyWaitTimeMs = packet.getIpv6OnlyWaitTimeMillis();
+        transitionTo(mIpv6OnlyWaitState);
+        return true;
+    }
+
     private void receiveOfferOrAckPacket(final DhcpPacket packet, final boolean acceptRapidCommit) {
         if (!isValidPacket(packet)) return;
 
@@ -1230,6 +1273,9 @@
         // 2. received the DHCPACK packet from DHCP Servers that support Rapid
         //    Commit option, process it by following RFC4039.
         if (packet instanceof DhcpOfferPacket) {
+            if (maybeTransitionToIpv6OnlyWaitState(packet)) {
+                return;
+            }
             mOffer = packet.toDhcpResults();
             if (mOffer != null) {
                 Log.d(TAG, "Got pending lease: " + mOffer);
@@ -1362,7 +1408,10 @@
         protected void receivePacket(DhcpPacket packet) {
             if (!isValidPacket(packet)) return;
             if ((packet instanceof DhcpAckPacket)) {
-                DhcpResults results = packet.toDhcpResults();
+                if (maybeTransitionToIpv6OnlyWaitState(packet)) {
+                    return;
+                }
+                final DhcpResults results = packet.toDhcpResults();
                 if (results != null) {
                     confirmDhcpLease(packet, results);
                     transitionTo(isDhcpIpConflictDetectEnabled()
@@ -1762,6 +1811,9 @@
         protected void receivePacket(DhcpPacket packet) {
             if (!isValidPacket(packet)) return;
             if ((packet instanceof DhcpAckPacket)) {
+                if (maybeTransitionToIpv6OnlyWaitState(packet)) {
+                    return;
+                }
                 final DhcpResults results = packet.toDhcpResults();
                 if (results != null) {
                     if (!mDhcpLease.ipAddress.equals(results.ipAddress)) {
@@ -1905,6 +1957,32 @@
         }
     }
 
+    // This state is used for IPv6-only preferred mode defined in the draft-ietf-dhc-v6only.
+    // For IPv6-only capable host, it will forgo obtaining an IPv4 address for V6ONLY_WAIT
+    // period if the network indicates that it can provide IPv6 connectivity by replying
+    // with a valid IPv6-only preferred option in the DHCPOFFER or DHCPACK.
+    class Ipv6OnlyWaitState extends TimeoutState {
+        @Override
+        public void enter() {
+            mTimeout = mIpv6OnlyWaitTimeMs;
+            super.enter();
+
+            // Restore power save and suspend optimization if it was disabled before.
+            if (mRegisteredForPreDhcpNotification) {
+                mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_IPV6_ONLY, 0, null);
+            }
+        }
+
+        @Override
+        public void exit() {
+            mIpv6OnlyWaitTimeMs = 0;
+        }
+
+        protected void timeout() {
+            startInitRebootOrInit();
+        }
+    }
+
     private void logState(String name, int durationMs) {
         final DhcpClientEvent event = new DhcpClientEvent.Builder()
                 .setMsg(name)
diff --git a/src/android/net/dhcp/DhcpOfferPacket.java b/src/android/net/dhcp/DhcpOfferPacket.java
index aae08a7..e3e5d0f 100644
--- a/src/android/net/dhcp/DhcpOfferPacket.java
+++ b/src/android/net/dhcp/DhcpOfferPacket.java
@@ -47,9 +47,12 @@
             }
         }
 
-        return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask +
-                dnsServers + ", gateways " + mGateways +
-                " lease time " + mLeaseTime + ", domain " + mDomainName;
+        return s + " OFFER, ip " + mYourIp
+                + ", mask " + mSubnetMask + dnsServers
+                + ", gateways " + mGateways
+                + ", lease time " + mLeaseTime
+                + ", domain " + mDomainName
+                + (mIpv6OnlyWaitTime != null ? ", V6ONLY_WAIT " + mIpv6OnlyWaitTime : "");
     }
 
     /**
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
index 3915740..b61146c 100644
--- a/src/android/net/dhcp/DhcpPacket.java
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -88,6 +88,10 @@
     public static final int HWADDR_LEN = 16;
     public static final int MAX_OPTION_LEN = 255;
 
+    // The lower boundary for V6ONLY_WAIT.
+    public static final long MIN_V6ONLY_WAIT_MS = 300_000;
+    public static final long V6ONLY_PREFERRED_ABSENCE = -1L;
+
     /**
      * The minimum and maximum MTU that we are prepared to use. We set the minimum to the minimum
      * IPv6 MTU because the IPv6 stack enters unusual codepaths when the link MTU drops below 1280,
@@ -307,6 +311,16 @@
     public static final byte DHCP_RAPID_COMMIT = 80;
     protected boolean mRapidCommit;
 
+    /**
+     * DHCP IPv6-Only Preferred Option(draft-ietf-dhc-v6only).
+     * Indicate that a host supports an IPv6-only mode and willing to forgo obtaining an IPv4
+     * address for V6ONLY_WAIT period if the network provides IPv6 connectivity. V6ONLY_WAIT
+     * is 32-bit unsigned integer, so the Integer value cannot be used as-is.
+     */
+    public static final byte DHCP_IPV6_ONLY_PREFERRED = (byte) 108;
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    public Integer mIpv6OnlyWaitTime;
+
     public static final byte DHCP_CAPTIVE_PORTAL = (byte) 114;
     protected String mCaptivePortalUrl;
 
@@ -789,6 +803,9 @@
         if (mMtu != null && Short.toUnsignedInt(mMtu) >= IPV4_MIN_MTU) {
             addTlv(buf, DHCP_MTU, mMtu);
         }
+        if (mIpv6OnlyWaitTime != null) {
+            addTlv(buf, DHCP_IPV6_ONLY_PREFERRED, (int) Integer.toUnsignedLong(mIpv6OnlyWaitTime));
+        }
         addTlv(buf, DHCP_CAPTIVE_PORTAL, mCaptivePortalUrl);
     }
 
@@ -942,6 +959,7 @@
         Integer leaseTime = null;
         Integer T1 = null;
         Integer T2 = null;
+        Integer ipv6OnlyWaitTime = null;
 
         // dhcp options
         byte dhcpType = (byte) 0xFF;
@@ -1204,6 +1222,10 @@
                             expectedLen = optionLen;
                             captivePortalUrl = readAsciiString(packet, optionLen, true);
                             break;
+                        case DHCP_IPV6_ONLY_PREFERRED:
+                            expectedLen = 4;
+                            ipv6OnlyWaitTime = Integer.valueOf(packet.getInt());
+                            break;
                         default:
                             expectedLen = skipOption(packet, optionLen);
                     }
@@ -1292,6 +1314,7 @@
         newPacket.mVendorId = vendorId;
         newPacket.mVendorInfo = vendorInfo;
         newPacket.mCaptivePortalUrl = captivePortalUrl;
+        newPacket.mIpv6OnlyWaitTime = ipv6OnlyWaitTime;
         if ((optionOverload & OPTION_OVERLOAD_SNAME) == 0) {
             newPacket.mServerHostName = serverHostName;
         } else {
@@ -1385,6 +1408,14 @@
     }
 
     /**
+     * Returns the IPv6-only wait time, in milliseconds, or -1 if the option is not present.
+     */
+    public long getIpv6OnlyWaitTimeMillis() {
+        if (mIpv6OnlyWaitTime == null) return V6ONLY_PREFERRED_ABSENCE;
+        return Math.max(MIN_V6ONLY_WAIT_MS, Integer.toUnsignedLong(mIpv6OnlyWaitTime) * 1000);
+    }
+
+    /**
      * Builds a DHCP-DISCOVER packet from the required specified
      * parameters.
      */
@@ -1399,15 +1430,14 @@
     }
 
     /**
-     * Builds a DHCP-OFFER packet from the required specified
-     * parameters.
+     * Builds a DHCP-OFFER packet from the required specified parameters.
      */
     public static ByteBuffer buildOfferPacket(int encap, int transactionId,
             boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
             Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
             Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
             Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
-            short mtu, String captivePortalUrl) {
+            short mtu, String captivePortalUrl, Integer ipv6OnlyWaitTime) {
         DhcpPacket pkt = new DhcpOfferPacket(
                 transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
                 INADDR_ANY /* clientIp */, yourIp, mac);
@@ -1424,10 +1454,27 @@
         if (metered) {
             pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
         }
+        if (ipv6OnlyWaitTime != null) {
+            pkt.mIpv6OnlyWaitTime = ipv6OnlyWaitTime;
+        }
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
     /**
+     * Builds a DHCP-OFFER packet from the required specified parameters.
+     */
+    public static ByteBuffer buildOfferPacket(int encap, int transactionId,
+            boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
+            Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
+            Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+            Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+            short mtu, String captivePortalUrl) {
+        return buildOfferPacket(encap, transactionId, broadcast, serverIpAddr, relayIp, yourIp,
+                mac, timeout, netMask, bcAddr, gateways, dnsServers, dhcpServerIdentifier,
+                domainName, hostname, metered, mtu, captivePortalUrl, null /* V6ONLY_WAIT */);
+    }
+
+    /**
      * Builds a DHCP-ACK packet from the required specified parameters.
      */
     public static ByteBuffer buildAckPacket(int encap, int transactionId,
@@ -1435,7 +1482,7 @@
             Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
             Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
             Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
-            short mtu, boolean rapidCommit, String captivePortalUrl) {
+            short mtu, boolean rapidCommit, String captivePortalUrl, Integer ipv6OnlyWaitTime) {
         DhcpPacket pkt = new DhcpAckPacket(
                 transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp,
                 mac, rapidCommit);
@@ -1452,10 +1499,28 @@
         if (metered) {
             pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
         }
+        if (ipv6OnlyWaitTime != null) {
+            pkt.mIpv6OnlyWaitTime = ipv6OnlyWaitTime;
+        }
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
     /**
+     * Builds a DHCP-ACK packet from the required specified parameters.
+     */
+    public static ByteBuffer buildAckPacket(int encap, int transactionId,
+            boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
+            Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
+            Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+            Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+            short mtu, boolean rapidCommit, String captivePortalUrl) {
+        return buildAckPacket(encap, transactionId, broadcast, serverIpAddr, relayIp, yourIp,
+                requestClientIp, mac, timeout, netMask, bcAddr, gateways, dnsServers,
+                dhcpServerIdentifier, domainName, hostname, metered, mtu, rapidCommit,
+                captivePortalUrl, null /* V6ONLY_WAIT */);
+    }
+
+    /**
      * Builds a DHCP-NAK packet from the required specified parameters.
      */
     public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr,
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index 6c95b5a..60c7323 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -99,6 +99,7 @@
     private static final int CMD_UPDATE_PARAMS = 3;
     @VisibleForTesting
     protected static final int CMD_RECEIVE_PACKET = 4;
+    private static final int CMD_TERMINATE_AFTER_STOP = 5;
 
     @NonNull
     private final Context mContext;
@@ -362,10 +363,12 @@
      * Stop listening for packets.
      *
      * <p>As the server is stopped asynchronously, some packets may still be processed shortly after
-     * calling this method.
+     * calling this method. The server will also be cleaned up and can't be started again, even if
+     * it was already stopped.
      */
     void stop(@Nullable INetworkStackStatusCallback cb) {
         sendMessage(CMD_STOP_DHCP_SERVER, cb);
+        sendMessage(CMD_TERMINATE_AFTER_STOP);
     }
 
     private void maybeNotifyStatus(@Nullable INetworkStackStatusCallback cb, int statusCode) {
@@ -407,6 +410,9 @@
                     mEventCallbacks = obj.second;
                     transitionTo(mRunningState);
                     return HANDLED;
+                case CMD_TERMINATE_AFTER_STOP:
+                    quit();
+                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index c4f46ae..cc4a26c 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -777,6 +777,7 @@
 
     private void stopStateMachineUpdaters() {
         mObserverRegistry.unregisterObserver(mLinkObserver);
+        mLinkObserver.shutdown();
     }
 
     @Override
@@ -1582,7 +1583,11 @@
             return;
         }
 
-        if (params.index != mInterfaceParams.index) {
+        // Check whether "mInterfaceParams" is null or not to prevent the potential NPE
+        // introduced if the interface was initially not found, but came back before this
+        // method was called. See b/162808916 for more details. TODO: query the new interface
+        // parameters by the interface index instead and check that the index has not changed.
+        if (mInterfaceParams == null || params.index != mInterfaceParams.index) {
             Log.w(mTag, "interface: " + mInterfaceName + " has a different index: " + params.index);
             return;
         }
@@ -1601,8 +1606,16 @@
         mL2Key = info.l2Key;
         mCluster = info.cluster;
 
+        // Sometimes the wifi code passes in a null BSSID. Don't use Log.wtf in R because
+        // it's a known bug that will not be fixed in R.
         if (info.bssid == null || mCurrentBssid == null) {
-            Log.wtf(mTag, "bssid in the parcelable or current tracked bssid should be non-null");
+            final String msg = "bssid in the parcelable: " + info.bssid + " or "
+                    + "current tracked bssid: " + mCurrentBssid + " is null";
+            if (ShimUtils.isAtLeastS()) {
+                Log.wtf(mTag, msg);
+            } else {
+                Log.w(mTag, msg);
+            }
             return;
         }
 
@@ -2156,7 +2169,8 @@
                 //     a) initial address acquisition succeeds,
                 //     b) renew succeeds or is NAK'd,
                 //     c) rebind succeeds or is NAK'd, or
-                //     c) the lease expires,
+                //     d) the lease expires, or
+                //     e) the IPv6-only preferred option is enabled and entering Ipv6OnlyWaitState.
                 //
                 // but never when initial address acquisition fails. The latter
                 // condition is now governed by the provisioning timeout.
@@ -2170,6 +2184,8 @@
                         case DhcpClient.DHCP_FAILURE:
                             handleIPv4Failure();
                             break;
+                        case DhcpClient.DHCP_IPV6_ONLY:
+                            break;
                         default:
                             logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
                     }
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index 46b3844..5fd45a3 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -114,6 +114,7 @@
     private DnsServerRepository mDnsServerRepository;
     private final AlarmManager mAlarmManager;
     private final Configuration mConfig;
+    private final Handler mHandler;
 
     private final MyNetlinkMonitor mNetlinkMonitor;
 
@@ -127,11 +128,16 @@
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mInterfaceName);
         mConfig = config;
+        mHandler = h;
         mInterfaceLinkState = true; // Assume up by default
         mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
-        h.post(mNetlinkMonitor::start);
+        mHandler.post(mNetlinkMonitor::start);
+    }
+
+    public void shutdown() {
+        mHandler.post(mNetlinkMonitor::stop);
     }
 
     private void maybeLog(String operation, String iface, LinkAddress address) {
@@ -155,8 +161,12 @@
             // 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(getInterfaceLinkState());
+            final boolean linkState;
+            synchronized (this) {
+                clearLinkProperties();
+                linkState = getInterfaceLinkStateLocked();
+            }
+            mCallback.update(linkState);
         }
     }
 
@@ -164,7 +174,9 @@
     public void onInterfaceLinkStateChanged(String iface, boolean state) {
         if (mInterfaceName.equals(iface)) {
             maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
-            setInterfaceLinkState(state);
+            synchronized (this) {
+                setInterfaceLinkStateLocked(state);
+            }
         }
     }
 
@@ -172,12 +184,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(getInterfaceLinkState());
+                mCallback.update(linkState);
             }
         }
     }
@@ -186,12 +200,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(getInterfaceLinkState());
+                mCallback.update(linkState);
             }
         }
     }
@@ -200,12 +216,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(getInterfaceLinkState());
+                mCallback.update(linkState);
             }
         }
     }
@@ -214,12 +232,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(getInterfaceLinkState());
+                mCallback.update(linkState);
             }
         }
     }
@@ -228,12 +248,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(getInterfaceLinkState());
+                mCallback.update(linkState);
             }
         }
     }
@@ -258,11 +280,11 @@
         mLinkProperties.setInterfaceName(mInterfaceName);
     }
 
-    private synchronized boolean getInterfaceLinkState() {
+    private boolean getInterfaceLinkStateLocked() {
         return mInterfaceLinkState;
     }
 
-    private synchronized void setInterfaceLinkState(boolean state) {
+    private void setInterfaceLinkStateLocked(boolean state) {
         mInterfaceLinkState = state;
     }
 
@@ -378,7 +400,7 @@
                 cancelPref64Alarm();
             }
 
-            mCallback.update(getInterfaceLinkState());
+            mCallback.update(getInterfaceLinkStateLocked());
         }
 
         private void processPref64Option(StructNdOptPref64 opt, final long now) {
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 1aea3ed..de94ee3 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -212,6 +212,12 @@
     public static final String DHCP_IP_CONFLICT_DETECT_VERSION = "dhcp_ip_conflict_detect_version";
 
     /**
+     * Minimum module version at which to enable the IPv6-Only preferred option.
+     */
+    public static final String DHCP_IPV6_ONLY_PREFERRED_VERSION =
+            "dhcp_ipv6_only_preferred_version";
+
+    /**
      * Minimum module version at which to enable dismissal CaptivePortalLogin app in validated
      * network feature. CaptivePortalLogin app will also use validation facilities in
      * {@link NetworkMonitor} to perform portal validation if feature is enabled.
@@ -228,6 +234,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");
     }
@@ -350,6 +365,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.
@@ -365,6 +383,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.
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index 872834a..dbb62b1 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -239,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) {
@@ -252,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 {
diff --git a/src/com/android/networkstack/metrics/DataStallDetectionStats.java b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
index 47f5805..131fb79 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/NetworkValidationMetrics.java b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
index 3789c5c..f27a939 100644
--- a/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
+++ b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
@@ -60,9 +60,11 @@
     public static final int MAX_PROBE_EVENTS_COUNT = 20;
 
     /**
-     *  Reset this NetworkValidationMetrics.
+     * Reset this NetworkValidationMetrics and start collecting timing and metrics.
+     *
+     * <p>This must be called when validation starts.
      */
-    public void reset(@Nullable NetworkCapabilities nc) {
+    public void startCollection(@Nullable NetworkCapabilities nc) {
         mStatsBuilder.clear();
         mProbeEventsBuilder.clear();
         mCapportApiDataBuilder.clear();
@@ -72,16 +74,23 @@
     }
 
     /**
-     * Returns the enum TransportType
+     * Returns the enum TransportType.
      *
-     * @param NetworkCapabilities
+     * <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) {
+    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);
@@ -90,13 +99,29 @@
         boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE);
         boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN);
 
-        if (hasCellular && hasWifi && hasVpn) return TransportType.TT_WIFI_CELLULAR_VPN;
-        if (hasWifi) return hasVpn ? TransportType.TT_WIFI_VPN : TransportType.TT_WIFI;
-        if (hasCellular) return hasVpn ? TransportType.TT_CELLULAR_VPN : TransportType.TT_CELLULAR;
-        if (hasBT) return hasVpn ? TransportType.TT_BLUETOOTH_VPN : TransportType.TT_BLUETOOTH;
-        if (hasEthernet) return hasVpn ? TransportType.TT_ETHERNET_VPN : TransportType.TT_ETHERNET;
-        if (hasWifiAware) return TransportType.TT_WIFI_AWARE;
-        if (hasLopan) return TransportType.TT_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;
     }
 
@@ -146,6 +171,8 @@
      */
     @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) {
@@ -158,12 +185,14 @@
     }
 
     /**
-     * Write each network probe event to mProbeEventsBuilder.
+     * Add a network probe event to the metrics builder.
      */
-    public void setProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result,
+    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);
@@ -178,7 +207,9 @@
                     (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000;
             mCapportApiDataBuilder
                 .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining))
-                .setRemainingBytes(NetworkStackUtils.saturatedCast(capportData.getByteLimit()))
+                // 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);
@@ -196,25 +227,28 @@
 
     /**
      * 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 sendValidationStats() {
+    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 mStats = mStatsBuilder.build();
-        final byte[] probeEvents = mStats.getProbeEvents().toByteArray();
+        final NetworkValidationReported stats = mStatsBuilder.build();
+        final byte[] probeEvents = stats.getProbeEvents().toByteArray();
 
         NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED,
-                mStats.getTransportType().getNumber(),
+                stats.getTransportType().getNumber(),
                 probeEvents,
-                mStats.getValidationResult().getNumber(),
-                mStats.getLatencyMicros(),
-                mStats.getValidationIndex(),
-                mStats.getRandomNumber());
+                stats.getValidationResult().getNumber(),
+                stats.getLatencyMicros(),
+                stats.getValidationIndex(),
+                stats.getRandomNumber());
         mWatch.reset();
-        return mStats;
+        return stats;
     }
 }
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java
index 4c9d2bb..512f221 100644
--- a/src/com/android/server/NetworkStackService.java
+++ b/src/com/android/server/NetworkStackService.java
@@ -32,6 +32,7 @@
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkStackConnector;
+import android.net.INetworkStackStatusCallback;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -411,6 +412,15 @@
         }
 
         @Override
+        public void allowTestUid(int uid, @Nullable INetworkStackStatusCallback cb)
+                throws RemoteException {
+            // setTestUid does its own permission checks
+            PermissionUtil.setTestUid(mContext, uid);
+            mLog.i("Allowing test uid " + uid);
+            if (cb != null) cb.onStatusAvailable(0);
+        }
+
+        @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
             checkDumpPermission();
diff --git a/src/com/android/server/TestNetworkStackService.java b/src/com/android/server/TestNetworkStackService.java
new file mode 100644
index 0000000..23981e5
--- /dev/null
+++ b/src/com/android/server/TestNetworkStackService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.server.util.PermissionUtil.isDebuggableBuild;
+
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A {@link NetworkStackService} that can only be bound to on debuggable builds.
+ */
+public class TestNetworkStackService extends NetworkStackService {
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (!isDebuggableBuild()) {
+            throw new SecurityException(
+                    "TestNetworkStackService is only available on debuggable builds");
+        }
+        return super.onBind(intent);
+    }
+}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 1595435..9aff0ce 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -451,7 +451,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.
@@ -506,6 +513,14 @@
 
     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();
@@ -574,6 +589,8 @@
 
         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
         mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
+        mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
+                NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */);
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
         mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
@@ -786,28 +803,48 @@
         }
     }
 
-    private void recordMetricsReset(@Nullable NetworkCapabilities nc) {
-        synchronized (mNetworkValidationMetrics) {
-            mNetworkValidationMetrics.reset(nc);
+    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 recordMetricsProbeEvent(ProbeType type, long latencyMicros, ProbeResult result,
+    private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result,
             CaptivePortalDataShim capportData) {
-        synchronized (mNetworkValidationMetrics) {
-            mNetworkValidationMetrics.setProbeEvent(type, latencyMicros, result, 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 recordMetricsValidationResult(int result, String redirectUrl) {
-        synchronized (mNetworkValidationMetrics) {
-            mNetworkValidationMetrics.setValidationResult(result, redirectUrl);
+    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 recordMetricsValidationStats() {
-        synchronized (mNetworkValidationMetrics) {
-            mNetworkValidationMetrics.sendValidationStats();
+    private void maybeStopCollectionAndSendMetrics() {
+        if (!mMetricsEnabled) return;
+        try {
+            synchronized (mNetworkValidationMetrics) {
+                mNetworkValidationMetrics.maybeStopCollectionAndSend();
+            }
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error sending validation stats", e);
         }
     }
 
@@ -823,7 +860,7 @@
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_NETWORK_DISCONNECTED:
-                    recordMetricsValidationStats();
+                    maybeStopCollectionAndSendMetrics();
                     logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
                     quit();
                     return HANDLED;
@@ -975,7 +1012,7 @@
             initSocketTrackingIfRequired();
             // start periodical polling.
             sendTcpPollingEvent();
-            recordMetricsValidationStats();
+            maybeStopCollectionAndSendMetrics();
         }
 
         private void initSocketTrackingIfRequired() {
@@ -1326,7 +1363,7 @@
             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
             mValidations++;
-            recordMetricsValidationStats();
+            maybeStopCollectionAndSendMetrics();
         }
 
         @Override
@@ -1360,7 +1397,9 @@
                                 handlePrivateDnsEvaluationFailure();
                                 // The private DNS probe fails-fast if the server hostname cannot
                                 // be resolved. Record it as a failure with zero latency.
-                                recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, 0 /* 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;
                             }
@@ -1471,7 +1510,7 @@
                 validationLog(PROBE_PRIVDNS, host,
                         String.format("%dus - Error: %s", time, uhe.getMessage()));
             }
-            recordMetricsProbeEvent(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS :
+            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;
@@ -1483,8 +1522,14 @@
 
         @Override
         public void enter() {
-            recordMetricsValidationStats();
-            recordMetricsReset(mNetworkCapabilities);
+            // 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();
@@ -1566,7 +1611,9 @@
     private class WaitingForNextProbeState extends State {
         @Override
         public void enter() {
-            recordMetricsValidationStats();
+            // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be
+            // restarted when the next probe starts.
+            maybeStopCollectionAndSendMetrics();
             scheduleNextProbe();
         }
 
@@ -1857,7 +1904,7 @@
     }
 
     @Nullable
-    private String getTestUrl(@NonNull String key) {
+    private URL getTestUrl(@NonNull String key) {
         final String strExpiration = mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
                 TEST_URL_EXPIRATION_TIME, null);
         if (strExpiration == null) return null;
@@ -1873,13 +1920,13 @@
         final long now = System.currentTimeMillis();
         if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) return null;
 
-        return mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
+        final String strUrl = mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY,
                 key, null /* defaultValue */);
+        if (!isValidTestUrl(strUrl)) return null;
+        return makeURL(strUrl);
     }
 
     private String getCaptivePortalServerHttpsUrl() {
-        final String testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL);
-        if (isValidTestUrl(testUrl)) return testUrl;
         return getSettingFromResource(mCustomizedContext,
                 R.string.config_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL,
                 mCustomizedContext.getResources().getString(
@@ -1960,8 +2007,6 @@
      * on one URL that can be used, while NetworkMonitor may implement more complex logic.
      */
     public String getCaptivePortalServerHttpUrl() {
-        final String testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL);
-        if (isValidTestUrl(testUrl)) return testUrl;
         return getSettingFromResource(mCustomizedContext,
                 R.string.config_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL,
                 mCustomizedContext.getResources().getString(
@@ -2039,6 +2084,9 @@
     }
 
     private URL[] makeCaptivePortalHttpsUrls() {
+        final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL);
+        if (testUrl != null) return new URL[] { testUrl };
+
         final String firstUrl = getCaptivePortalServerHttpsUrl();
         try {
             final URL[] settingProviderUrls =
@@ -2057,6 +2105,9 @@
     }
 
     private URL[] makeCaptivePortalHttpUrls() {
+        final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL);
+        if (testUrl != null) return new URL[] { testUrl };
+
         final String firstUrl = getCaptivePortalServerHttpUrl();
         try {
             final URL[] settingProviderUrls =
@@ -2312,7 +2363,7 @@
         // network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe.
         if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP
                 && (proxy == null) && hasPrivateIpAddress(resolvedAddr)) {
-            recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType),
+            recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
                     0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */);
             return CaptivePortalProbeResult.PRIVATE_IP;
         }
@@ -2345,7 +2396,7 @@
             result = ValidationProbeEvent.DNS_FAILURE;
         }
         final long latency = watch.stop();
-        recordMetricsProbeEvent(ProbeType.PT_DNS, latency,
+        recordProbeEventMetrics(ProbeType.PT_DNS, latency,
                 (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS :
                 ProbeResult.PR_FAILURE, null /* capportData */);
         logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
@@ -2472,7 +2523,7 @@
         } else {
             probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
         }
-        recordMetricsProbeEvent(NetworkValidationMetrics.probeTypeToEnum(probeType),
+        recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
                 probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult),
                 null /* capportData */);
         return probeResult;
@@ -2630,6 +2681,8 @@
         }
 
         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;
@@ -2690,7 +2743,7 @@
             if (mCaptivePortalApiUrl == null) return null;
             final Stopwatch capportApiWatch = new Stopwatch().start();
             final CaptivePortalDataShim capportData = sendCapportApiProbe();
-            recordMetricsProbeEvent(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(),
+            recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(),
                     capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS,
                     capportData);
             return capportData;
@@ -3421,7 +3474,7 @@
             p.redirectUrl = redirectUrl;
             p.timestampMillis = SystemClock.elapsedRealtime();
             notifyNetworkTested(p);
-            recordMetricsValidationResult(result, redirectUrl);
+            recordValidationResult(result, redirectUrl);
         }
 
         @VisibleForTesting
diff --git a/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java b/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java
index 70688ad..c8559c8 100644
--- a/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java
+++ b/src/com/android/server/connectivity/ipmemorystore/RegularMaintenanceJobService.java
@@ -29,7 +29,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -65,7 +65,8 @@
         }
     }
 
-    private static final ArrayList<InterruptMaintenance> sInterruptList = new ArrayList<>();
+    private static final CopyOnWriteArrayList<InterruptMaintenance> sInterruptList =
+            new CopyOnWriteArrayList<>();
     private static IpMemoryStoreService sIpMemoryStoreService;
 
     @Override
diff --git a/src/com/android/server/util/PermissionUtil.java b/src/com/android/server/util/PermissionUtil.java
index 3dff715..fbd13d7 100644
--- a/src/com/android/server/util/PermissionUtil.java
+++ b/src/com/android/server/util/PermissionUtil.java
@@ -16,12 +16,18 @@
 
 package com.android.server.util;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Binder.getCallingPid;
 import static android.os.Binder.getCallingUid;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 
+import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -30,6 +36,8 @@
 public final class PermissionUtil {
     private static final AtomicInteger sSystemPid = new AtomicInteger(-1);
 
+    private static volatile int sTestUid = Process.INVALID_UID;
+
     /**
      * Check that the caller is allowed to communicate with the network stack.
      * @throws SecurityException The caller is not allowed to communicate with the network stack.
@@ -41,8 +49,9 @@
             return;
         }
 
-        if (caller != Process.myUid() && // apps with NETWORK_STACK_UID
-                UserHandle.getAppId(caller) != Process.BLUETOOTH_UID) {
+        if (caller != Process.myUid() // apps with NETWORK_STACK_UID
+                && UserHandle.getAppId(caller) != Process.BLUETOOTH_UID
+                && !isTestUid(caller)) {
             throw new SecurityException("Invalid caller: " + caller);
         }
     }
@@ -67,6 +76,42 @@
         }
     }
 
+    private static boolean isTestUid(int uid) {
+        return uid == sTestUid;
+    }
+
+    /**
+     * Set a test uid that is allowed to call the NetworkStack. Pass in -1 to reset.
+     *
+     * <p>The UID must have a package with NETWORK_SETTINGS permissions when it is allowed.
+     */
+    public static void setTestUid(Context context, int uid) {
+        if (!isDebuggableBuild()) {
+            throw new SecurityException("Cannot set test UID on non-debuggable builds");
+        }
+        if (getCallingUid() != Process.ROOT_UID) {
+            throw new SecurityException("Only root can set the test UID");
+        }
+
+        if (uid == Process.INVALID_UID) {
+            sTestUid = uid;
+            return;
+        }
+
+        final PackageManager pm = context.getPackageManager();
+        final String[] packages = pm.getPackagesForUid(uid);
+        if (packages == null) {
+            throw new SecurityException("No package in uid " + uid);
+        }
+        final boolean hasPermission = Arrays.stream(packages).anyMatch(
+                p -> pm.checkPermission(NETWORK_SETTINGS, p) == PERMISSION_GRANTED);
+        if (!hasPermission) {
+            throw new SecurityException(
+                    "The uid must have a package with NETWORK_SETTINGS permissions");
+        }
+        sTestUid = uid;
+    }
+
     /**
      * Check that the caller is allowed to dump the network stack, e.g. dumpsys.
      * @throws SecurityException The caller is not allowed to dump the network stack.
@@ -79,6 +124,14 @@
         }
     }
 
+    /**
+     * @see android.os.Build.IS_DEBUGGABLE
+     */
+    public static boolean isDebuggableBuild() {
+        // TODO: consider adding Build.IS_DEBUGGABLE to @SystemApi
+        return SystemProperties.getInt("ro.debuggable", 0) == 1;
+    }
+
     private PermissionUtil() {
         throw new UnsupportedOperationException("This class is not to be instantiated");
     }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 1a985a9..833b7d9 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -30,7 +30,10 @@
 
 java_defaults {
     name: "NetworkStackIntegrationTestsDefaults",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.rules",
@@ -82,6 +85,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 {
@@ -95,7 +110,7 @@
     static_libs: [
         "NetworkStackTestsLib",
         "NetworkStackIntegrationTestsLib",
-        "NetworkStaticLibTestsLib",
+        "NetworkStackStaticLibTestsLib",
     ],
     compile_multilib: "both",
     manifest: "AndroidManifest_coverage.xml",
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
new file mode 100644
index 0000000..5a847a0
--- /dev/null
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip
+
+/**
+ * Tests for IpClient, run with signature permissions.
+ */
+class IpClientIntegrationTest : IpClientIntegrationTestCommon() {
+    override fun makeIIpClient(ifaceName: String, cb: IIpClientCallbacks): IIpClient {
+        return mIpc.makeConnector()
+    }
+
+    override fun useNetworkStackSignature() = true
+
+    override fun setDhcpFeatures(
+        isDhcpLeaseCacheEnabled: Boolean,
+        isRapidCommitEnabled: Boolean,
+        isDhcpIpConflictDetectEnabled: Boolean
+    ) {
+        mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled)
+        mDependencies.setDhcpRapidCommitEnabled(isRapidCommitEnabled)
+        mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled)
+    }
+}
\ No newline at end of file
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
similarity index 77%
rename from tests/integration/src/android/net/ip/IpClientIntegrationTest.java
rename to tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 8f73eba..fda0aeb 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -19,11 +19,13 @@
 import static android.net.dhcp.DhcpClient.EXPIRED_LEASE;
 import static android.net.dhcp.DhcpPacket.DHCP_BOOTREQUEST;
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
+import static android.net.dhcp.DhcpPacket.DHCP_IPV6_ONLY_PREFERRED;
 import static android.net.dhcp.DhcpPacket.DHCP_MAGIC_COOKIE;
 import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
 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.DhcpPacket.MIN_V6ONLY_WAIT_MS;
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
 import static android.net.ipmemorystore.Status.SUCCESS;
 import static android.system.OsConstants.ETH_P_IPV6;
@@ -46,6 +48,7 @@
 import static com.android.server.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.server.util.NetworkStackConstants.IPV6_LEN_OFFSET;
 import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
@@ -66,10 +69,12 @@
 import static org.mockito.ArgumentMatchers.longThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -110,6 +115,7 @@
 import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
 import android.net.ipmemorystore.Status;
 import android.net.netlink.StructNdOptPref64;
+import android.net.networkstack.TestNetworkStackServiceClient;
 import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
@@ -120,6 +126,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -127,11 +134,13 @@
 import android.system.ErrnoException;
 import android.system.Os;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.ArrayTrackRecord;
 import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.ShimUtils;
@@ -144,14 +153,14 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
@@ -159,8 +168,15 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
+import java.io.BufferedReader;
 import java.io.FileDescriptor;
+import java.io.FileReader;
 import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
@@ -179,29 +195,61 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
 
+import kotlin.Lazy;
+import kotlin.LazyKt;
+
 /**
- * Tests for IpClient.
+ * Base class for IpClient tests.
+ *
+ * Tests in this class can either be run with signature permissions, or with root access.
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class IpClientIntegrationTest {
+public abstract class IpClientIntegrationTestCommon {
     private static final int DATA_BUFFER_LEN = 4096;
     private static final int PACKET_TIMEOUT_MS = 5_000;
-    private static final int TEST_TIMEOUT_MS = 400;
     private static final String TEST_L2KEY = "some l2key";
     private static final String TEST_CLUSTER = "some cluster";
     private static final int TEST_LEASE_DURATION_S = 3_600; // 1 hour
+    private static final int TEST_IPV6_ONLY_WAIT_S = 1_800; // 30 min
+    private static final int TEST_LOWER_IPV6_ONLY_WAIT_S = (int) (MIN_V6ONLY_WAIT_MS / 1000 - 1);
+    private static final int TEST_ZERO_IPV6_ONLY_WAIT_S = 0;
+    private static final long TEST_MAX_IPV6_ONLY_WAIT_S = 0xffffffffL;
 
     // TODO: move to NetlinkConstants, NetworkStackConstants, or OsConstants.
     private static final int IFA_F_STABLE_PRIVACY = 0x800;
 
+    protected static final long TEST_TIMEOUT_MS = 2_000L;
+
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+    @Rule
+    public final TestName mTestNameRule = new TestName();
+
+    /**
+     * Indicates that a test requires signature permissions to run.
+     *
+     * Such tests can only be run on devices that use known signing keys, so this annotation must be
+     * avoided as much as possible. Consider whether the test can be written to use shell and root
+     * shell permissions, and run against the NetworkStack AIDL interface (IIpClient) instead.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    private @interface SignatureRequiredTest {
+        String reason();
+    }
+
+    /**** BEGIN signature required test members ****/
+    // Do not use unless the test *really* cannot be written to exercise IIpClient without mocks.
+    // Tests using the below members must be annotated with @SignatureRequiredTest (otherwise the
+    // members will be null), and can only be run on devices that use known signing keys.
+    // The members could technically be moved to the IpClientIntegrationTest subclass together with
+    // the tests requiring signature permissions, but this would make it harder to follow tests in
+    // multiple classes, and harder to migrate tests between signature required and not required.
 
     @Mock private Context mContext;
     @Mock private ConnectivityManager mCm;
     @Mock private Resources mResources;
-    @Mock private IIpClientCallbacks mCb;
     @Mock private AlarmManager mAlarm;
     @Mock private ContentResolver mContentResolver;
     @Mock private NetworkStackServiceManager mNetworkStackServiceManager;
@@ -210,17 +258,33 @@
     @Mock private PowerManager.WakeLock mTimeoutWakeLock;
 
     @Spy private INetd mNetd;
-
-    private String mIfaceName;
     private NetworkObserverRegistry mNetworkObserverRegistry;
+
+    protected IpClient mIpc;
+    protected Dependencies mDependencies;
+
+    /***** END signature required test members *****/
+
+    private IIpClientCallbacks mCb;
+    private IIpClient mIIpClient;
+    private String mIfaceName;
     private HandlerThread mPacketReaderThread;
     private Handler mHandler;
     private TapPacketReader mPacketReader;
     private FileDescriptor mTapFd;
-    private IpClient mIpc;
-    private Dependencies mDependencies;
     private byte[] mClientMac;
 
+    private boolean mIsSignatureRequiredTest;
+
+    // ReadHeads for various packet streams. Cannot be initialized in @Before because ReadHead is
+    // single-thread-only, and AndroidJUnitRunner runs @Before and @Test on different threads.
+    // While it looks like these are created only once per test, they are actually created once per
+    // test method because JUnit recreates a fresh test class instance before every test method.
+    private Lazy<ArrayTrackRecord<byte[]>.ReadHead> mDhcpPacketReadHead =
+            LazyKt.lazy(() -> mPacketReader.getReceivedPackets().newReadHead());
+    private Lazy<ArrayTrackRecord<byte[]>.ReadHead> mArpPacketReadHead =
+            LazyKt.lazy(() -> mPacketReader.getReceivedPackets().newReadHead());
+
     // Ethernet header
     private static final int ETH_HEADER_LEN = 14;
 
@@ -273,7 +337,7 @@
     private static final String TEST_DHCP_ROAM_CLUSTER = "roaming_cluster";
     private static final byte[] TEST_AP_OUI = new byte[] { 0x00, 0x1A, 0x11 };
 
-    private class Dependencies extends IpClient.Dependencies {
+    protected class Dependencies extends IpClient.Dependencies {
         private boolean mIsDhcpLeaseCacheEnabled;
         private boolean mIsDhcpRapidCommitEnabled;
         private boolean mIsDhcpIpConflictDetectEnabled;
@@ -282,6 +346,8 @@
         private DhcpClient mDhcpClient;
         private boolean mIsHostnameConfigurationEnabled;
         private String mHostname;
+        private boolean mIsInterfaceRecovered;
+        private boolean mIsIPv6OnlyPreferredEnabled;
 
         public void setDhcpLeaseCacheEnabled(final boolean enable) {
             mIsDhcpLeaseCacheEnabled = enable;
@@ -295,11 +361,32 @@
             mIsDhcpIpConflictDetectEnabled = enable;
         }
 
+        public void setIPv6OnlyPreferredEnabled(final boolean enable) {
+            mIsIPv6OnlyPreferredEnabled = enable;
+        }
+
         public void setHostnameConfiguration(final boolean enable, final String hostname) {
             mIsHostnameConfigurationEnabled = enable;
             mHostname = hostname;
         }
 
+        // Enable this flag to simulate the interface has been added back after removing
+        // on the provisioning start. However, the actual tap interface has been removed,
+        // interface parameters query will get null when attempting to restore Interface
+        // MTU. Create a new InterfaceParams instance and return instead just for interface
+        // toggling test case.
+        public void simulateInterfaceRecover() {
+            mIsInterfaceRecovered = true;
+        }
+
+        @Override
+        public InterfaceParams getInterfaceParams(String ifname) {
+            return mIsInterfaceRecovered
+                    ? new InterfaceParams(ifname, 1 /* index */,
+                            MacAddress.fromString("00:11:22:33:44:55"))
+                    : super.getInterfaceParams(ifname);
+        }
+
         @Override
         public INetd getNetd(Context context) {
             return mNetd;
@@ -332,6 +419,8 @@
                             return mIsDhcpLeaseCacheEnabled;
                         case NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION:
                             return mIsDhcpIpConflictDetectEnabled;
+                        case NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION:
+                            return mIsIPv6OnlyPreferredEnabled;
                         default:
                             fail("Invalid experiment flag: " + name);
                             return false;
@@ -375,8 +464,41 @@
         }
     }
 
+    @NonNull
+    protected abstract IIpClient makeIIpClient(
+            @NonNull String ifaceName, @NonNull IIpClientCallbacks cb);
+
+    protected abstract void setDhcpFeatures(boolean isDhcpLeaseCacheEnabled,
+            boolean isRapidCommitEnabled, boolean isDhcpIpConflictDetectEnabled);
+
+    protected abstract boolean useNetworkStackSignature();
+
+    protected final boolean testSkipped() {
+        // TODO: split out a test suite for root tests, and fail hard instead of skipping the test
+        // if it is run on devices where TestNetworkStackServiceClient is not supported
+        return !useNetworkStackSignature()
+                && (mIsSignatureRequiredTest || !TestNetworkStackServiceClient.isSupported());
+    }
+
     @Before
     public void setUp() throws Exception {
+        final Method testMethod = IpClientIntegrationTestCommon.class.getMethod(
+                mTestNameRule.getMethodName());
+        mIsSignatureRequiredTest = testMethod.getAnnotation(SignatureRequiredTest.class) != null;
+        assumeFalse(testSkipped());
+
+        setUpTapInterface();
+        mCb = mock(IIpClientCallbacks.class);
+
+        if (useNetworkStackSignature()) {
+            setUpMocks();
+            setUpIpClient();
+        }
+
+        mIIpClient = makeIIpClient(mIfaceName, mCb);
+    }
+
+    protected void setUpMocks() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mDependencies = new Dependencies();
@@ -394,9 +516,6 @@
         mDependencies.setDeviceConfigProperty(DhcpClient.ARP_PROBE_MAX_MS, 20);
         mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_ANNOUNCE_DELAY_MS, 10);
         mDependencies.setDeviceConfigProperty(DhcpClient.ARP_ANNOUNCE_INTERVAL_MS, 10);
-
-        setUpTapInterface();
-        setUpIpClient();
     }
 
     private void awaitIpClientShutdown() throws Exception {
@@ -405,6 +524,7 @@
 
     @After
     public void tearDown() throws Exception {
+        if (testSkipped()) return;
         if (mPacketReader != null) {
             mHandler.post(() -> mPacketReader.stop()); // Also closes the socket
             mTapFd = null;
@@ -412,11 +532,11 @@
         if (mPacketReaderThread != null) {
             mPacketReaderThread.quitSafely();
         }
-        mIpc.shutdown();
+        mIIpClient.shutdown();
         awaitIpClientShutdown();
     }
 
-    private void setUpTapInterface() {
+    private void setUpTapInterface() throws Exception {
         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
         // Adopt the shell permission identity to create a test TAP interface.
         inst.getUiAutomation().adoptShellPermissionIdentity();
@@ -432,8 +552,9 @@
             inst.getUiAutomation().dropShellPermissionIdentity();
         }
         mIfaceName = iface.getInterfaceName();
-        mClientMac = InterfaceParams.getByName(mIfaceName).macAddr.toByteArray();
-        mPacketReaderThread = new HandlerThread(IpClientIntegrationTest.class.getSimpleName());
+        mClientMac = getIfaceMacAddr(mIfaceName).toByteArray();
+        mPacketReaderThread = new HandlerThread(
+                IpClientIntegrationTestCommon.class.getSimpleName());
         mPacketReaderThread.start();
         mHandler = mPacketReaderThread.getThreadHandler();
 
@@ -448,6 +569,32 @@
         mHandler.post(() -> mPacketReader.start());
     }
 
+    private MacAddress getIfaceMacAddr(String ifaceName) throws IOException {
+        // InterfaceParams.getByName requires CAP_NET_ADMIN: read the mac address with the shell
+        final String strMacAddr = getOneLineCommandOutput(
+                "su root cat /sys/class/net/" + ifaceName + "/address");
+        return MacAddress.fromString(strMacAddr);
+    }
+
+    private String getOneLineCommandOutput(String cmd) throws IOException {
+        try (ParcelFileDescriptor fd = InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().executeShellCommand(cmd);
+             BufferedReader reader = new BufferedReader(new FileReader(fd.getFileDescriptor()))) {
+            return reader.readLine();
+        }
+    }
+
+    private IpClient makeIpClient() throws Exception {
+        IpClient ipc = new IpClient(mContext, mIfaceName, mCb, mNetworkObserverRegistry,
+                mNetworkStackServiceManager, mDependencies);
+        // Wait for IpClient to enter its initial state. Otherwise, additional setup steps or tests
+        // that mock IpClient's dependencies might interact with those mocks while IpClient is
+        // starting. This would cause UnfinishedStubbingExceptions as mocks cannot be interacted
+        // with while they are being stubbed.
+        HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        return ipc;
+    }
+
     private void setUpIpClient() throws Exception {
         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
         final IBinder netdIBinder =
@@ -458,13 +605,7 @@
 
         mNetworkObserverRegistry = new NetworkObserverRegistry();
         mNetworkObserverRegistry.register(mNetd);
-        mIpc = new IpClient(mContext, mIfaceName, mCb, mNetworkObserverRegistry,
-                mNetworkStackServiceManager, mDependencies);
-        // Wait for IpClient to enter its initial state. Otherwise, additional setup steps or tests
-        // that mock IpClient's dependencies might interact with those mocks while IpClient is
-        // starting. This would cause UnfinishedStubbingExceptions as mocks cannot be interacted
-        // with while they are being stubbed.
-        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+        mIpc = makeIpClient();
 
         // Tell the IpMemoryStore immediately to answer any question about network attributes with a
         // null response. Otherwise, the DHCP client will wait for two seconds before starting,
@@ -494,7 +635,8 @@
         inOrder.verify(mAlarm, timeout(TEST_TIMEOUT_MS)).cancel(eq(listener));
     }
 
-    private OnAlarmListener expectAlarmSet(InOrder inOrder, String tagMatch, int afterSeconds) {
+    private OnAlarmListener expectAlarmSet(InOrder inOrder, String tagMatch, long afterSeconds,
+            Handler handler) {
         // Allow +/- 3 seconds to prevent flaky tests.
         final long when = SystemClock.elapsedRealtime() + afterSeconds * 1000;
         final long min = when - 3 * 1000;
@@ -502,10 +644,14 @@
         ArgumentCaptor<OnAlarmListener> captor = ArgumentCaptor.forClass(OnAlarmListener.class);
         verifyWithTimeout(inOrder, mAlarm).setExact(
                 anyInt(), longThat(x -> x >= min && x <= max),
-                contains(tagMatch), captor.capture(), eq(mIpc.getHandler()));
+                contains(tagMatch), captor.capture(), eq(handler));
         return captor.getValue();
     }
 
+    private OnAlarmListener expectAlarmSet(InOrder inOrder, String tagMatch, int afterSeconds) {
+        return expectAlarmSet(inOrder, tagMatch, (long) afterSeconds, mIpc.getHandler());
+    }
+
     private boolean packetContainsExpectedField(final byte[] packet, final int offset,
             final byte[] expected) {
         if (packet.length < offset + expected.length) return false;
@@ -549,20 +695,29 @@
     }
 
     private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
-            final Integer leaseTimeSec, final short mtu, final String captivePortalUrl) {
+            final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
+            final String captivePortalUrl, final Integer ipv6OnlyWaitTime) {
         return DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
                 false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */,
-                CLIENT_ADDR /* yourIp */, packet.getClientMac(), leaseTimeSec,
+                clientAddress /* yourIp */, packet.getClientMac(), leaseTimeSec,
                 NETMASK /* netMask */, BROADCAST_ADDR /* bcAddr */,
                 Collections.singletonList(SERVER_ADDR) /* gateways */,
                 Collections.singletonList(SERVER_ADDR) /* dnsServers */,
                 SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME,
-                false /* metered */, mtu, captivePortalUrl);
+                false /* metered */, mtu, captivePortalUrl, ipv6OnlyWaitTime);
+    }
+
+    private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
+            final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
+            final String captivePortalUrl) {
+        return buildDhcpOfferPacket(packet, clientAddress, leaseTimeSec, mtu, captivePortalUrl,
+                null /* ipv6OnlyWaitTime */);
     }
 
     private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet,
             final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
-            final boolean rapidCommit, final String captivePortalApiUrl) {
+            final boolean rapidCommit, final String captivePortalApiUrl,
+            final Integer ipv6OnlyWaitTime) {
         return DhcpPacket.buildAckPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
                 false /* broadcast */, SERVER_ADDR, INADDR_ANY /* relayIp */,
                 clientAddress /* yourIp */, CLIENT_ADDR /* requestIp */, packet.getClientMac(),
@@ -570,7 +725,14 @@
                 Collections.singletonList(SERVER_ADDR) /* gateways */,
                 Collections.singletonList(SERVER_ADDR) /* dnsServers */,
                 SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, HOSTNAME,
-                false /* metered */, mtu, rapidCommit, captivePortalApiUrl);
+                false /* metered */, mtu, rapidCommit, captivePortalApiUrl, ipv6OnlyWaitTime);
+    }
+
+    private static ByteBuffer buildDhcpAckPacket(final DhcpPacket packet,
+            final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
+            final boolean rapidCommit, final String captivePortalApiUrl) {
+        return buildDhcpAckPacket(packet, clientAddress, leaseTimeSec, mtu, rapidCommit,
+                captivePortalApiUrl, null /* ipv6OnlyWaitTime */);
     }
 
     private static ByteBuffer buildDhcpNakPacket(final DhcpPacket packet) {
@@ -595,12 +757,14 @@
         mPacketReader.sendResponse(packet);
     }
 
+    private void startIpClientProvisioning(final ProvisioningConfiguration cfg) throws Exception {
+        mIIpClient.startProvisioning(cfg.toStableParcelable());
+    }
+
     private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
             final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled,
             final boolean isDhcpIpConflictDetectEnabled,
-            final boolean isHostnameConfigurationEnabled, final String hostname,
-            final String displayName, final ScanResultInfo scanResultInfo)
-            throws RemoteException {
+            final String displayName, final ScanResultInfo scanResultInfo) throws Exception {
         ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
                 .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -610,11 +774,10 @@
         if (displayName != null) prov.withDisplayName(displayName);
         if (scanResultInfo != null) prov.withScanResultInfo(scanResultInfo);
 
-        mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled);
-        mDependencies.setDhcpRapidCommitEnabled(shouldReplyRapidCommitAck);
-        mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled);
-        mDependencies.setHostnameConfiguration(isHostnameConfigurationEnabled, hostname);
-        mIpc.startProvisioning(prov.build());
+        setDhcpFeatures(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
+                isDhcpIpConflictDetectEnabled);
+
+        startIpClientProvisioning(prov.build());
         if (!isPreconnectionEnabled) {
             verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
         }
@@ -623,11 +786,9 @@
 
     private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
             final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled,
-            final boolean isDhcpIpConflictDetectEnabled)
-            throws RemoteException {
+            final boolean isDhcpIpConflictDetectEnabled)  throws Exception {
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
                 isPreconnectionEnabled, isDhcpIpConflictDetectEnabled,
-                false /* isHostnameConfigurationEnabled */, null /* hostname */,
                 null /* displayName */, null /* ScanResultInfo */);
     }
 
@@ -688,9 +849,10 @@
             final boolean isHostnameConfigurationEnabled, final String hostname,
             final String captivePortalApiUrl, final String displayName,
             final ScanResultInfo scanResultInfo) throws Exception {
+        mDependencies.setHostnameConfiguration(isHostnameConfigurationEnabled, hostname);
         startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
                 false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
-                isHostnameConfigurationEnabled, hostname, displayName, scanResultInfo);
+                displayName, scanResultInfo);
         return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
                 captivePortalApiUrl);
     }
@@ -707,8 +869,8 @@
                     mPacketReader.sendResponse(buildDhcpAckPacket(packet, CLIENT_ADDR, leaseTimeSec,
                               (short) mtu, true /* rapidCommit */, captivePortalApiUrl));
                 } else {
-                    mPacketReader.sendResponse(buildDhcpOfferPacket(packet, leaseTimeSec,
-                            (short) mtu, captivePortalApiUrl));
+                    mPacketReader.sendResponse(buildDhcpOfferPacket(packet, CLIENT_ADDR,
+                            leaseTimeSec, (short) mtu, captivePortalApiUrl));
                 }
             } else if (packet instanceof DhcpRequestPacket) {
                 final ByteBuffer byteBuffer = isSuccessLease
@@ -746,13 +908,9 @@
     }
 
     private DhcpPacket getNextDhcpPacket() throws ParseException {
-        byte[] packet;
-        while ((packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS)) != null) {
-            if (!isDhcpPacket(packet)) continue;
-            return DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L2);
-        }
-        fail("No expected DHCP packet received on interface within timeout");
-        return null;
+        byte[] packet = mDhcpPacketReadHead.getValue().poll(PACKET_TIMEOUT_MS, this::isDhcpPacket);
+        assertNotNull("No expected DHCP packet received on interface within timeout", packet);
+        return DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L2);
     }
 
     private DhcpPacket getReplyFromDhcpLease(final NetworkAttributes na, boolean timeout)
@@ -874,7 +1032,7 @@
             }
 
             mIpc.notifyPreconnectionComplete(false /* abort */);
-            HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+            HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
 
             if (shouldFirePreconnectionTimeout && !timeoutBeforePreconnectionComplete) {
                 mDependencies.mDhcpClient.sendMessage(DhcpClient.CMD_TIMEOUT);
@@ -897,8 +1055,8 @@
 
         final short mtu = (short) TEST_DEFAULT_MTU;
         if (!shouldReplyRapidCommitAck) {
-            mPacketReader.sendResponse(buildDhcpOfferPacket(packet, TEST_LEASE_DURATION_S, mtu,
-                    null /* captivePortalUrl */));
+            mPacketReader.sendResponse(buildDhcpOfferPacket(packet, CLIENT_ADDR,
+                    TEST_LEASE_DURATION_S, mtu, null /* captivePortalUrl */));
             packet = getNextDhcpPacket();
             assertTrue(packet instanceof DhcpRequestPacket);
         }
@@ -907,7 +1065,7 @@
 
         if (!shouldAbortPreconnection) {
             mIpc.notifyPreconnectionComplete(true /* success */);
-            HandlerUtilsKt.waitForIdle(mDependencies.mDhcpClient.getHandler(), TEST_TIMEOUT_MS);
+            HandlerUtils.waitForIdle(mDependencies.mDhcpClient.getHandler(), TEST_TIMEOUT_MS);
 
             // If timeout fires after successful preconnection, right now DhcpClient will have
             // already entered BOUND state, the delayed CMD_TIMEOUT command would be ignored. So
@@ -927,9 +1085,9 @@
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
-    private ArpPacket getNextArpPacket(final int timeout) throws Exception {
+    private ArpPacket getNextArpPacket(final long timeout) throws Exception {
         byte[] packet;
-        while ((packet = mPacketReader.popPacket(timeout)) != null) {
+        while ((packet = mArpPacketReadHead.getValue().poll(timeout, p -> true)) != null) {
             final ArpPacket arpPacket = parseArpPacketOrNull(packet);
             if (arpPacket != null) return arpPacket;
         }
@@ -1004,7 +1162,7 @@
         }
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN")
     public void testInterfaceParams() throws Exception {
         InterfaceParams params = InterfaceParams.getByName(mIfaceName);
         assertNotNull(params);
@@ -1013,7 +1171,7 @@
         assertNotNull(params.macAddr);
         assertTrue(params.hasMacAddress);
 
-        // Sanity check.
+        //  Check interface "lo".
         params = InterfaceParams.getByName("lo");
         assertNotNull(params);
         assertEquals("lo", params.name);
@@ -1031,7 +1189,7 @@
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHandleSuccessDhcpLease() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
@@ -1041,7 +1199,7 @@
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHandleFailureDhcpLease() throws Exception {
         performDhcpHandshake(false /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
@@ -1051,7 +1209,7 @@
         assertIpMemoryNeverStoreNetworkAttributes();
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHandleInfiniteLease() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, INFINITE_LEASE,
@@ -1061,7 +1219,7 @@
         assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime, TEST_DEFAULT_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHandleNoLease() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, null /* no lease time */,
@@ -1072,6 +1230,7 @@
     }
 
     @Test @IgnoreAfter(Build.VERSION_CODES.Q) // INIT-REBOOT is enabled on R.
+    @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHandleDisableInitRebootState() throws Exception {
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
@@ -1080,7 +1239,7 @@
         assertIpMemoryNeverStoreNetworkAttributes();
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHandleRapidCommitOption() throws Exception {
         final long currentTime = System.currentTimeMillis();
         performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
@@ -1090,7 +1249,7 @@
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientStartWithCachedInfiniteLease() throws Exception {
         final DhcpPacket packet = getReplyFromDhcpLease(
                 new NetworkAttributes.Builder()
@@ -1103,7 +1262,7 @@
         assertTrue(packet instanceof DhcpRequestPacket);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientStartWithCachedExpiredLease() throws Exception {
         final DhcpPacket packet = getReplyFromDhcpLease(
                  new NetworkAttributes.Builder()
@@ -1116,13 +1275,13 @@
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientStartWithNullRetrieveNetworkAttributes() throws Exception {
         final DhcpPacket packet = getReplyFromDhcpLease(null /* na */, false /* timeout */);
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientStartWithTimeoutRetrieveNetworkAttributes() throws Exception {
         final DhcpPacket packet = getReplyFromDhcpLease(
                 new NetworkAttributes.Builder()
@@ -1135,7 +1294,7 @@
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientStartWithCachedLeaseWithoutIPAddress() throws Exception {
         final DhcpPacket packet = getReplyFromDhcpLease(
                 new NetworkAttributes.Builder()
@@ -1146,7 +1305,7 @@
         assertTrue(packet instanceof DhcpDiscoverPacket);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientRapidCommitEnabled() throws Exception {
         startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
                 true /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */,
@@ -1156,6 +1315,7 @@
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpServerInLinkProperties() throws Exception {
         assumeTrue(ConstantsShim.VERSION > Build.VERSION_CODES.Q);
 
@@ -1165,17 +1325,17 @@
         assertEquals(SERVER_ADDR, captor.getValue().getDhcpServerAddress());
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu() throws Exception {
         doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_WithoutMtuChange() throws Exception {
         doRestoreInitialMtuTest(false /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_WithException() throws Exception {
         doThrow(new RemoteException("NetdNativeService::interfaceSetMtu")).when(mNetd)
                 .interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
@@ -1184,12 +1344,12 @@
         assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_MIN_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStopping() throws Exception {
         doRestoreInitialMtuTest(true /* shouldChangeMtu */, true /* shouldRemoveTapInterface */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_NotFoundInterfaceWhenStartingProvisioning()
             throws Exception {
         removeTapInterface(mTapFd);
@@ -1203,7 +1363,7 @@
         verify(mCb, never()).setNeighborDiscoveryOffload(true);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRestoreInitialInterfaceMtu_stopIpClientAndRestart() throws Exception {
         long currentTime = System.currentTimeMillis();
 
@@ -1232,6 +1392,36 @@
         assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_DEFAULT_MTU);
     }
 
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testRestoreInitialInterfaceMtu_removeInterfaceAndAddback() throws Exception {
+        doAnswer(invocation -> {
+            final LinkProperties lp = invocation.getArgument(0);
+            assertEquals(lp.getInterfaceName(), mIfaceName);
+            assertEquals(0, lp.getLinkAddresses().size());
+            assertEquals(0, lp.getDnsServers().size());
+
+            mDependencies.simulateInterfaceRecover();
+            return null;
+        }).when(mCb).onProvisioningFailure(any());
+
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withoutIPv6()
+                .build();
+
+        // Intend to remove the tap interface and force IpClient throw provisioning failure
+        // due to that interface is not found.
+        removeTapInterface(mTapFd);
+        assertNull(InterfaceParams.getByName(mIfaceName));
+
+        mIpc.startProvisioning(config);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(any());
+
+        // Make sure everything queued by this test was processed (e.g. transition to StoppingState
+        // from ClearingIpAddressState) and tearDown will check if IpClient exits normally or crash.
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+    }
+
     private boolean isRouterSolicitation(final byte[] packetBytes) {
         ByteBuffer packet = ByteBuffer.wrap(packetBytes);
         return packet.getShort(ETHER_TYPE_OFFSET) == (short) ETH_P_IPV6
@@ -1241,11 +1431,8 @@
     }
 
     private void waitForRouterSolicitation() throws ParseException {
-        byte[] packet;
-        while ((packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS)) != null) {
-            if (isRouterSolicitation(packet)) return;
-        }
-        fail("No router solicitation received on interface within timeout");
+        assertNotNull("No router solicitation received on interface within timeout",
+                mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation));
     }
 
     private void sendRouterAdvertisement(boolean waitForRs, short lifetime) throws Exception {
@@ -1447,7 +1634,7 @@
         return lp;
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testRaRdnss() throws Exception {
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withoutIpReachabilityMonitor()
@@ -1509,8 +1696,8 @@
 
     }
 
-    @Ignore  // AOSP kernels don't support the PREF64 option yet.
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testPref64Option() throws Exception {
         assumeTrue(ConstantsShim.VERSION > Build.VERSION_CODES.Q);
 
@@ -1616,14 +1803,14 @@
 
         // Check that the alarm is cancelled when IpClient is stopped.
         mIpc.stop();
-        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
         expectAlarmCancelled(inOrder, clearAlarm);
         expectNat64PrefixUpdate(inOrder, null);
 
         // Check that even if the alarm was already in the message queue while it was cancelled, it
         // is safely ignored.
         mIpc.getHandler().post(() -> clearAlarm.onAlarm());
-        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
     }
 
     private void addIpAddressAndWaitForIt(final String iface) throws Exception {
@@ -1658,7 +1845,7 @@
         }
 
         // Wait for IpClient to process the addition of the address.
-        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
     }
 
     private void doIPv4OnlyProvisioningAndExitWithLeftAddress() throws Exception {
@@ -1688,7 +1875,7 @@
         addIpAddressAndWaitForIt(mIfaceName);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testIpClientClearingIpAddressState() throws Exception {
         doIPv4OnlyProvisioningAndExitWithLeftAddress();
 
@@ -1709,7 +1896,7 @@
         assertEquals("0.0.0.0", cfg.ipv4Addr);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testIpClientClearingIpAddressState_enablePreconnection() throws Exception {
         doIPv4OnlyProvisioningAndExitWithLeftAddress();
 
@@ -1722,45 +1909,45 @@
 
         // Force to enter RunningState.
         mIpc.notifyPreconnectionComplete(false /* abort */);
-        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_success() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 false /* shouldAbortPreconnection */, false /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_SuccessWithoutRapidCommit() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(false /* shouldReplyRapidCommitAck */,
                 false /* shouldAbortPreconnection */, false /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_Abort() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 true /* shouldAbortPreconnection */, false /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_AbortWithoutRapiCommit() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(false /* shouldReplyRapidCommitAck */,
                 true /* shouldAbortPreconnection */, false /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutBeforeAbort() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 true /* shouldAbortPreconnection */, true /* shouldFirePreconnectionTimeout */,
                 true /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutBeforeAbortWithoutRapidCommit()
             throws Exception {
         doIpClientProvisioningWithPreconnectionTest(false /* shouldReplyRapidCommitAck */,
@@ -1768,28 +1955,28 @@
                 true /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutafterAbort() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 true /* shouldAbortPreconnection */, true /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutAfterAbortWithoutRapidCommit() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(false /* shouldReplyRapidCommitAck */,
                 true /* shouldAbortPreconnection */, true /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutBeforeSuccess() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 false /* shouldAbortPreconnection */, true /* shouldFirePreconnectionTimeout */,
                 true /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutBeforeSuccessWithoutRapidCommit()
             throws Exception {
         doIpClientProvisioningWithPreconnectionTest(false /* shouldReplyRapidCommitAck */,
@@ -1797,14 +1984,14 @@
                 true /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutAfterSuccess() throws Exception {
         doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
                 false /* shouldAbortPreconnection */, true /* shouldFirePreconnectionTimeout */,
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_TimeoutAfterSuccessWithoutRapidCommit()
             throws Exception {
         doIpClientProvisioningWithPreconnectionTest(false /* shouldReplyRapidCommitAck */,
@@ -1812,7 +1999,7 @@
                 false /* timeoutBeforePreconnectionComplete */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientPreconnection_WithoutLayer2InfoWhenStartingProv() throws Exception {
         // For FILS connection, current bssid (also l2key and cluster) is still null when
         // starting provisioning since the L2 link hasn't been established yet. Ensure that
@@ -1830,67 +2017,67 @@
 
         // Force IpClient transition to RunningState from PreconnectionState.
         mIpc.notifyPreconnectionComplete(false /* success */);
-        HandlerUtilsKt.waitForIdle(mDependencies.mDhcpClient.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mDependencies.mDhcpClient.getHandler(), TEST_TIMEOUT_MS);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_conflictByArpReply() throws Exception {
         doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
                 false /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
                 true /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_conflictByArpProbe() throws Exception {
         doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
                 false /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
                 false /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_EnableFlagWithoutIpConflict() throws Exception {
         doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
                 false /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
                 false /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_WithoutIpConflict() throws Exception {
         doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
                 false /* shouldReplyRapidCommitAck */, false /* isDhcpIpConflictDetectEnabled */,
                 false /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_WithRapidCommitWithoutIpConflict() throws Exception {
         doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
                 true /* shouldReplyRapidCommitAck */, false /* isDhcpIpConflictDetectEnabled */,
                 false /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_WithRapidCommitConflictByArpReply() throws Exception {
         doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
                 true /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
                 true /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_WithRapidCommitConflictByArpProbe() throws Exception {
         doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
                 true /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
                 false /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpDecline_EnableFlagWithRapidCommitWithoutIpConflict() throws Exception {
         doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
                 true /* shouldReplyRapidCommitAck */, true /* isDhcpIpConflictDetectEnabled */,
                 false /* shouldResponseArpReply */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHostname_enableConfig() throws Exception {
         final long currentTime = System.currentTimeMillis();
         final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
@@ -1905,7 +2092,7 @@
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHostname_disableConfig() throws Exception {
         final long currentTime = System.currentTimeMillis();
         final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
@@ -1920,7 +2107,7 @@
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testHostname_enableConfigWithNullHostname() throws Exception {
         final long currentTime = System.currentTimeMillis();
         final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
@@ -1947,42 +2134,43 @@
 
         // Send Offer and handle Request -> Ack
         final String serverSentUrl = serverSendsOption ? TEST_CAPTIVE_PORTAL_URL : null;
-        mPacketReader.sendResponse(buildDhcpOfferPacket(discover, TEST_LEASE_DURATION_S,
-                (short) TEST_DEFAULT_MTU, serverSentUrl));
+        mPacketReader.sendResponse(buildDhcpOfferPacket(discover, CLIENT_ADDR,
+                TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, serverSentUrl));
         final int testMtu = 1345;
         handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
                 false /* shouldReplyRapidCommitAck */, testMtu, serverSentUrl);
 
         final Uri expectedUrl = featureEnabled && serverSendsOption
                 ? Uri.parse(TEST_CAPTIVE_PORTAL_URL) : null;
-        // Wait for LinkProperties containing DHCP-obtained info, such as MTU
+        // LinkProperties will be updated multiple times. Wait for it to contain DHCP-obtained info,
+        // such as MTU.
         final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
-        verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(
+        verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
                 argThat(lp -> lp.getMtu() == testMtu));
 
         // Ensure that the URL was set as expected in the callbacks.
         // Can't verify the URL up to Q as there is no such attribute in LinkProperties.
         if (!ShimUtils.isAtLeastR()) return;
-        verify(mCb).onLinkPropertiesChange(captor.capture());
+        verify(mCb, atLeastOnce()).onLinkPropertiesChange(captor.capture());
         assertTrue(captor.getAllValues().stream().anyMatch(
                 lp -> Objects.equals(expectedUrl, lp.getCaptivePortalApiUrl())));
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientCaptivePortalApiEnabled() throws Exception {
         // Only run the test on platforms / builds where the API is enabled
         assumeTrue(CaptivePortalDataShimImpl.isSupported());
         runDhcpClientCaptivePortalApiTest(true /* featureEnabled */, true /* serverSendsOption */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientCaptivePortalApiEnabled_NoUrl() throws Exception {
         // Only run the test on platforms / builds where the API is enabled
         assumeTrue(CaptivePortalDataShimImpl.isSupported());
         runDhcpClientCaptivePortalApiTest(true /* featureEnabled */, false /* serverSendsOption */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpClientCaptivePortalApiDisabled() throws Exception {
         // Only run the test on platforms / builds where the API is disabled
         assumeFalse(CaptivePortalDataShimImpl.isSupported());
@@ -2043,7 +2231,7 @@
         assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testUpstreamHotspotDetection() throws Exception {
         byte[] data = new byte[10];
         new Random().nextBytes(data);
@@ -2052,7 +2240,7 @@
                 true /* expectMetered */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testUpstreamHotspotDetection_incorrectIeId() throws Exception {
         byte[] data = new byte[10];
         new Random().nextBytes(data);
@@ -2061,7 +2249,7 @@
                 false /* expectMetered */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testUpstreamHotspotDetection_incorrectOUI() throws Exception {
         byte[] data = new byte[10];
         new Random().nextBytes(data);
@@ -2070,7 +2258,7 @@
                 false /* expectMetered */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testUpstreamHotspotDetection_incorrectSsid() throws Exception {
         byte[] data = new byte[10];
         new Random().nextBytes(data);
@@ -2079,7 +2267,7 @@
                 false /* expectMetered */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testUpstreamHotspotDetection_incorrectType() throws Exception {
         byte[] data = new byte[10];
         new Random().nextBytes(data);
@@ -2088,7 +2276,7 @@
                 false /* expectMetered */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testUpstreamHotspotDetection_zeroLengthData() throws Exception {
         byte[] data = new byte[0];
         doUpstreamHotspotDetectionTest(0xdd, "\"ssid\"", "ssid",
@@ -2099,7 +2287,8 @@
     private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName,
             final String ssid, final String bssid, final boolean expectRoaming) throws Exception {
         long currentTime = System.currentTimeMillis();
-        final ScanResultInfo scanResultInfo = makeScanResultInfo(ssid, bssid);
+        final ScanResultInfo scanResultInfo = (ssid == null || bssid == null)
+                ? null : makeScanResultInfo(ssid, bssid);
 
         doAnswer(invocation -> {
             // we don't rely on the Init-Reboot state to renew previous cached IP lease.
@@ -2143,7 +2332,7 @@
         mPacketReader.sendResponse(buildDhcpAckPacket(packet,
                 hasMismatchedIpAddress ? CLIENT_ADDR_NEW : CLIENT_ADDR, TEST_LEASE_DURATION_S,
                 (short) TEST_DEFAULT_MTU, false /* rapidcommit */, null /* captivePortalUrl */));
-        HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
         if (hasMismatchedIpAddress) {
             // notifyFailure
             ArgumentCaptor<DhcpResultsParcelable> captor =
@@ -2161,31 +2350,37 @@
         }
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                 TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidBssid() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                 TEST_DHCP_ROAM_SSID, TEST_DHCP_ROAM_BSSID, false /* expectRoaming */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDhcpRoaming_nullScanResultInfo() throws Exception {
+        doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
+                null /* SSID */, null /* BSSID */, false /* expectRoaming */);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidSsid() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                 TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_invalidDisplayName() throws Exception {
         doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"test-ssid\"" /* display name */,
                 TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDhcpRoaming_mismatchedLeasedIpAddress() throws Exception {
         doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
                 TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
@@ -2227,7 +2422,7 @@
         reset(mCb);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testIgnoreIpv6ProvisioningLoss() throws Exception {
         doDualStackProvisioning();
 
@@ -2253,10 +2448,210 @@
         assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
     }
 
-    @Test
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
     public void testDualStackProvisioning() throws Exception {
         doDualStackProvisioning();
 
         verify(mCb, never()).onProvisioningFailure(any());
     }
+
+    private DhcpPacket verifyDhcpPacketRequestsIPv6OnlyPreferredOption(
+            Class<? extends DhcpPacket> packetType) throws Exception {
+        final DhcpPacket packet = getNextDhcpPacket();
+        assertTrue(packetType.isInstance(packet));
+        assertTrue(packet.hasRequestedParam(DHCP_IPV6_ONLY_PREFERRED));
+        return packet;
+    }
+
+    private void doIPv6OnlyPreferredOptionTest(final Integer ipv6OnlyWaitTime,
+            final Inet4Address clientAddress) throws Exception {
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .build();
+        mDependencies.setIPv6OnlyPreferredEnabled(true);
+        mIpc.startProvisioning(config);
+
+        final DhcpPacket packet =
+                verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpDiscoverPacket.class);
+
+        // Respond DHCPOFFER with IPv6-Only preferred option and offered address.
+        mPacketReader.sendResponse(buildDhcpOfferPacket(packet, clientAddress,
+                TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, null /* captivePortalUrl */,
+                ipv6OnlyWaitTime));
+    }
+
+    private void doDiscoverIPv6OnlyPreferredOptionTest(final int optionSecs,
+            final long expectedWaitSecs) throws Exception {
+        doIPv6OnlyPreferredOptionTest(optionSecs, CLIENT_ADDR);
+        final OnAlarmListener alarm = expectAlarmSet(null /* inOrder */, "TIMEOUT",
+                expectedWaitSecs, mDependencies.mDhcpClient.getHandler());
+        mDependencies.mDhcpClient.getHandler().post(() -> alarm.onAlarm());
+        // Implicitly check that the client never sent a DHCPREQUEST to request the offered address.
+        verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpDiscoverPacket.class);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption() throws Exception {
+        doDiscoverIPv6OnlyPreferredOptionTest(TEST_IPV6_ONLY_WAIT_S, TEST_IPV6_ONLY_WAIT_S);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption_LowerIPv6OnlyWait() throws Exception {
+        doDiscoverIPv6OnlyPreferredOptionTest(TEST_LOWER_IPV6_ONLY_WAIT_S,
+                TEST_LOWER_IPV6_ONLY_WAIT_S);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption_ZeroIPv6OnlyWait() throws Exception {
+        doDiscoverIPv6OnlyPreferredOptionTest(TEST_ZERO_IPV6_ONLY_WAIT_S,
+                TEST_LOWER_IPV6_ONLY_WAIT_S);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption_MaxIPv6OnlyWait() throws Exception {
+        doDiscoverIPv6OnlyPreferredOptionTest((int) TEST_MAX_IPV6_ONLY_WAIT_S, 0xffffffffL);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption_ZeroIPv6OnlyWaitWithOfferedAnyAddress()
+            throws Exception {
+        doIPv6OnlyPreferredOptionTest(TEST_ZERO_IPV6_ONLY_WAIT_S, IPV4_ADDR_ANY);
+
+        final OnAlarmListener alarm = expectAlarmSet(null /* inOrder */, "TIMEOUT", 300,
+                mDependencies.mDhcpClient.getHandler());
+        mDependencies.mDhcpClient.getHandler().post(() -> alarm.onAlarm());
+
+        verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpDiscoverPacket.class);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption_enabledPreconnection() throws Exception {
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withPreconnection()
+                .build();
+
+        mDependencies.setDhcpRapidCommitEnabled(true);
+        mDependencies.setIPv6OnlyPreferredEnabled(true);
+        mIpc.startProvisioning(config);
+
+        final DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart();
+        verify(mCb).setNeighborDiscoveryOffload(true);
+
+        // Force IpClient transition to RunningState from PreconnectionState.
+        mIpc.notifyPreconnectionComplete(true /* success */);
+        HandlerUtils.waitForIdle(mDependencies.mDhcpClient.getHandler(), TEST_TIMEOUT_MS);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
+
+        // DHCP server SHOULD NOT honor the Rapid-Commit option if the response would
+        // contain the IPv6-only Preferred option to the client, instead respond with
+        // a DHCPOFFER.
+        mPacketReader.sendResponse(buildDhcpOfferPacket(packet, CLIENT_ADDR, TEST_LEASE_DURATION_S,
+                (short) TEST_DEFAULT_MTU, null /* captivePortalUrl */, TEST_IPV6_ONLY_WAIT_S));
+
+        final OnAlarmListener alarm = expectAlarmSet(null /* inOrder */, "TIMEOUT", 1800,
+                mDependencies.mDhcpClient.getHandler());
+        mDependencies.mDhcpClient.getHandler().post(() -> alarm.onAlarm());
+
+        verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpDiscoverPacket.class);
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testDiscoverIPv6OnlyPreferredOption_NoIPv6OnlyPreferredOption() throws Exception {
+        doIPv6OnlyPreferredOptionTest(null /* ipv6OnlyWaitTime */, CLIENT_ADDR);
+
+        // The IPv6-only Preferred option SHOULD be included in the Parameter Request List option
+        // in DHCPREQUEST messages after receiving a DHCPOFFER without this option.
+        verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpRequestPacket.class);
+    }
+
+    private void startFromInitRebootStateWithIPv6OnlyPreferredOption(final Integer ipv6OnlyWaitTime,
+            final long expectedWaitSecs) throws Exception {
+        doAnswer(invocation -> {
+            ((OnNetworkAttributesRetrievedListener) invocation.getArgument(1))
+                    .onNetworkAttributesRetrieved(new Status(SUCCESS), TEST_L2KEY,
+                            new NetworkAttributes.Builder()
+                                .setAssignedV4Address(CLIENT_ADDR)
+                                .setAssignedV4AddressExpiry(Long.MAX_VALUE) // lease is always valid
+                                .setMtu(new Integer(TEST_DEFAULT_MTU))
+                                .setCluster(TEST_CLUSTER)
+                                .setDnsAddresses(Collections.singletonList(SERVER_ADDR))
+                                .build());
+            return null;
+        }).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any());
+
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIpReachabilityMonitor()
+                .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+                          MacAddress.fromString(TEST_DEFAULT_BSSID)))
+                .build();
+
+        mDependencies.setDhcpLeaseCacheEnabled(true);
+        mDependencies.setIPv6OnlyPreferredEnabled(true);
+        mIpc.startProvisioning(config);
+
+        final DhcpPacket packet =
+                verifyDhcpPacketRequestsIPv6OnlyPreferredOption(DhcpRequestPacket.class);
+
+        // Respond DHCPACK with IPv6-Only preferred option.
+        mPacketReader.sendResponse(buildDhcpAckPacket(packet, CLIENT_ADDR,
+                TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, false /* rapidcommit */,
+                null /* captivePortalUrl */, ipv6OnlyWaitTime));
+
+        if (ipv6OnlyWaitTime != null) {
+            expectAlarmSet(null /* inOrder */, "TIMEOUT", expectedWaitSecs,
+                    mDependencies.mDhcpClient.getHandler());
+        }
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testRequestIPv6OnlyPreferredOption() throws Exception {
+        startFromInitRebootStateWithIPv6OnlyPreferredOption(TEST_IPV6_ONLY_WAIT_S,
+                TEST_IPV6_ONLY_WAIT_S);
+
+        // Client transits to IPv6OnlyPreferredState from INIT-REBOOT state when receiving valid
+        // IPv6-Only preferred option(default value) in the DHCPACK packet.
+        assertIpMemoryNeverStoreNetworkAttributes();
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testRequestIPv6OnlyPreferredOption_LowerIPv6OnlyWait() throws Exception {
+        startFromInitRebootStateWithIPv6OnlyPreferredOption(TEST_LOWER_IPV6_ONLY_WAIT_S,
+                TEST_LOWER_IPV6_ONLY_WAIT_S);
+
+        // Client transits to IPv6OnlyPreferredState from INIT-REBOOT state when receiving valid
+        // IPv6-Only preferred option(less than MIN_V6ONLY_WAIT_MS) in the DHCPACK packet.
+        assertIpMemoryNeverStoreNetworkAttributes();
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testRequestIPv6OnlyPreferredOption_ZeroIPv6OnlyWait() throws Exception {
+        startFromInitRebootStateWithIPv6OnlyPreferredOption(TEST_ZERO_IPV6_ONLY_WAIT_S,
+                TEST_LOWER_IPV6_ONLY_WAIT_S);
+
+        // Client transits to IPv6OnlyPreferredState from INIT-REBOOT state when receiving valid
+        // IPv6-Only preferred option(0) in the DHCPACK packet.
+        assertIpMemoryNeverStoreNetworkAttributes();
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testRequestIPv6OnlyPreferredOption_MaxIPv6OnlyWait() throws Exception {
+        startFromInitRebootStateWithIPv6OnlyPreferredOption((int) TEST_MAX_IPV6_ONLY_WAIT_S,
+                0xffffffffL);
+
+        // Client transits to IPv6OnlyPreferredState from INIT-REBOOT state when receiving valid
+        // IPv6-Only preferred option(MAX_UNSIGNED_INTEGER: 0xFFFFFFFF) in the DHCPACK packet.
+        assertIpMemoryNeverStoreNetworkAttributes();
+    }
+
+    @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
+    public void testRequestIPv6OnlyPreferredOption_NoIPv6OnlyPreferredOption() throws Exception {
+        final long currentTime = System.currentTimeMillis();
+        startFromInitRebootStateWithIPv6OnlyPreferredOption(null /* ipv6OnlyWaitTime */,
+                0 /* expectedWaitSecs */);
+
+        // Client processes DHCPACK packet normally and transits to the ConfiguringInterfaceState
+        // due to the null V6ONLY_WAIT.
+        assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
+    }
 }
diff --git a/tests/integration/src/android/net/ip/IpClientRootTest.kt b/tests/integration/src/android/net/ip/IpClientRootTest.kt
new file mode 100644
index 0000000..77b612d
--- /dev/null
+++ b/tests/integration/src/android/net/ip/IpClientRootTest.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.ip
+
+import android.Manifest
+import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
+import android.net.IIpMemoryStore
+import android.net.IIpMemoryStoreCallbacks
+import android.net.networkstack.TestNetworkStackServiceClient
+import android.net.util.NetworkStackUtils
+import android.os.Process
+import android.provider.DeviceConfig
+import android.util.ArrayMap
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.BeforeClass
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import java.lang.System.currentTimeMillis
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+// Stable AIDL method 5 in INetworkStackConnector is allowTestUid
+private const val ALLOW_TEST_UID_INDEX = 5
+
+/**
+ * Tests for IpClient, run with root access but no signature permissions.
+ *
+ * Tests run from this class interact with the real network stack process and can affect system
+ * state, e.g. by changing flags.
+ * State should be restored at the end of the test, but is not guaranteed if the test process is
+ * terminated during the run.
+ */
+class IpClientRootTest : IpClientIntegrationTestCommon() {
+    companion object {
+        private val TAG = IpClientRootTest::class.java.simpleName
+        private val automation by lazy { InstrumentationRegistry.getInstrumentation().uiAutomation }
+        private lateinit var nsClient: TestNetworkStackServiceClient
+
+        private class IpMemoryStoreCallbacks(
+            private val fetchedFuture: CompletableFuture<IIpMemoryStore>
+        ) : IIpMemoryStoreCallbacks.Stub() {
+            override fun onIpMemoryStoreFetched(ipMemoryStore: IIpMemoryStore) {
+                fetchedFuture.complete(ipMemoryStore)
+            }
+            override fun getInterfaceVersion() = IIpMemoryStoreCallbacks.VERSION
+            override fun getInterfaceHash() = IIpMemoryStoreCallbacks.HASH
+        }
+
+        @JvmStatic @BeforeClass
+        fun setUpClass() {
+            // Connect to the NetworkStack only once, as it is relatively expensive (~50ms plus any
+            // polling time waiting for the test UID to be allowed), and there should be minimal
+            // side-effects between tests compared to reconnecting every time.
+            automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS)
+            try {
+                automation.executeShellCommand("su root service call network_stack " +
+                        "$ALLOW_TEST_UID_INDEX i32 " + Process.myUid())
+                // Connecting to the test service does not require allowing the test UID (binding is
+                // always allowed with NETWORK_SETTINGS permissions on debuggable builds), but
+                // allowing the test UID is required to call any method on the service.
+                nsClient = TestNetworkStackServiceClient.connect()
+                // Wait for oneway call to be processed: unfortunately there is no easy way to wait
+                // for a success callback via the service shell command.
+                // TODO: build a small native util that also waits for the success callback, bundle
+                // it in the test APK, and run it as shell command as root instead.
+                waitUntilTestUidAllowed()
+            } finally {
+                automation.dropShellPermissionIdentity()
+            }
+        }
+
+        private fun waitUntilTestUidAllowed() {
+            // Until the test UID is allowed, oneway binder calls will not receive any reply.
+            // Call fetchIpMemoryStore (which has limited side-effects) repeatedly until any call
+            // gets a callback.
+            val limit = currentTimeMillis() + TEST_TIMEOUT_MS
+            val fetchedFuture = CompletableFuture<IIpMemoryStore>()
+            Log.i(TAG, "Starting multiple attempts to fetch IpMemoryStore; failures are expected")
+            while (currentTimeMillis() < limit) {
+                try {
+                    nsClient.fetchIpMemoryStore(IpMemoryStoreCallbacks(fetchedFuture))
+                    // The future may be completed by any previous call to fetchIpMemoryStore.
+                    fetchedFuture.get(20, TimeUnit.MILLISECONDS)
+                    Log.i(TAG, "Obtained IpMemoryStore")
+                    return
+                } catch (e: TimeoutException) {
+                    // Fall through
+                }
+            }
+        }
+
+        @JvmStatic @AfterClass
+        fun tearDownClass() {
+            nsClient.disconnect()
+            automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS)
+            try {
+                // Reset the test UID as -1.
+                // This may not be called if the test process is terminated before completing,
+                // however this is fine as the test UID is only usable on userdebug builds, and
+                // the system does not reuse UIDs across apps until reboot.
+                automation.executeShellCommand("su root service call network_stack " +
+                        "$ALLOW_TEST_UID_INDEX i32 -1")
+            } finally {
+                automation.dropShellPermissionIdentity()
+            }
+        }
+    }
+
+    private val originalFlagValues = ArrayMap<String, String>()
+
+    /**
+     * Wrapper class for IIpClientCallbacks.
+     *
+     * Used to delegate method calls to mock interfaces used to verify the calls, while using
+     * real implementations of the binder stub (such as [asBinder]) to properly receive the calls.
+     */
+    private class BinderCbWrapper(base: IIpClientCallbacks) :
+            IIpClientCallbacks.Stub(), IIpClientCallbacks by base {
+        // asBinder is implemented by both base class and delegate: specify explicitly
+        override fun asBinder() = super.asBinder()
+    }
+
+    @After
+    fun tearDownFlags() {
+        if (testSkipped()) return
+        automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+        try {
+            for ((key, value) in originalFlagValues.entries) {
+                if (key == null) continue
+                DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key,
+                        value, false /* makeDefault */)
+            }
+        } finally {
+            automation.dropShellPermissionIdentity()
+        }
+    }
+
+    override fun useNetworkStackSignature() = false
+
+    override fun makeIIpClient(ifaceName: String, cbMock: IIpClientCallbacks): IIpClient {
+        val ipClientCaptor = ArgumentCaptor.forClass(IIpClient::class.java)
+        // Older versions of NetworkStack do not clear the calling identity when creating IpClient,
+        // so READ_DEVICE_CONFIG is required to initialize it (b/168577898).
+        automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG)
+        try {
+            nsClient.makeIpClient(ifaceName, BinderCbWrapper(cbMock))
+            verify(cbMock, timeout(TEST_TIMEOUT_MS)).onIpClientCreated(ipClientCaptor.capture())
+        } finally {
+            automation.dropShellPermissionIdentity()
+        }
+        return ipClientCaptor.value
+    }
+
+    override fun setDhcpFeatures(
+        isDhcpLeaseCacheEnabled: Boolean,
+        isRapidCommitEnabled: Boolean,
+        isDhcpIpConflictDetectEnabled: Boolean
+    ) {
+        automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+        try {
+            setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled)
+            setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled)
+            setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION,
+                    isDhcpIpConflictDetectEnabled)
+        } finally {
+            automation.dropShellPermissionIdentity()
+        }
+    }
+
+    private fun setFeatureEnabled(feature: String, enabled: Boolean) {
+        // Do not use computeIfAbsent as it would overwrite null values (flag originally unset)
+        if (!originalFlagValues.containsKey(feature)) {
+            originalFlagValues[feature] =
+                    DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature)
+        }
+        // The feature is enabled if the flag is lower than the package version.
+        // Package versions follow a standard format with 9 digits.
+        // TODO: consider resetting flag values on reboot when set to special values like "1" or
+        // "999999999"
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature,
+                if (enabled) "1" else "999999999", false)
+    }
+}
\ No newline at end of file
diff --git a/tests/integration/src/android/net/networkstack/TestNetworkStackServiceClient.kt b/tests/integration/src/android/net/networkstack/TestNetworkStackServiceClient.kt
new file mode 100644
index 0000000..bc3e5e0
--- /dev/null
+++ b/tests/integration/src/android/net/networkstack/TestNetworkStackServiceClient.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.networkstack
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.PackageManager.MATCH_SYSTEM_ONLY
+import android.net.INetworkStackConnector
+import android.os.IBinder
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.test.fail
+
+/**
+ * A [NetworkStackClientBase] that binds to [com.android.server.TestNetworkStackService]
+ */
+class TestNetworkStackServiceClient private constructor() : NetworkStackClientBase() {
+    companion object {
+        private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+        private val networkStackVersion by lazy {
+            val component = getNetworkStackComponent()
+            val info = context.packageManager.getPackageInfo(component.packageName, 0 /* flags */)
+            info.longVersionCode
+        }
+
+        /**
+         * Create a [TestNetworkStackServiceClient] and connect it to the NetworkStack.
+         */
+        @JvmStatic
+        fun connect(): TestNetworkStackServiceClient {
+            return TestNetworkStackServiceClient().apply { init() }
+        }
+
+        @JvmStatic
+        fun isSupported(): Boolean {
+            // TestNetworkStackService was introduced in NetworkStack version 301100000.
+            // It is also available at HEAD in development branches, where the version code is
+            // 300000000.
+            return networkStackVersion == 300000000L || networkStackVersion >= 301100000L
+        }
+
+        private fun getNetworkStackComponent(): ComponentName {
+            val connectorIntent = Intent(INetworkStackConnector::class.java.name)
+            return connectorIntent.resolveSystemService(context.packageManager, MATCH_SYSTEM_ONLY)
+                    ?: fail("TestNetworkStackService not found")
+        }
+    }
+
+    private val serviceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(service))
+        }
+
+        override fun onServiceDisconnected(name: ComponentName) = Unit
+    }
+
+    private fun init() {
+        val bindIntent = Intent(INetworkStackConnector::class.java.name + ".Test")
+        bindIntent.component = getNetworkStackComponent()
+        context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+    }
+
+    fun disconnect() {
+        InstrumentationRegistry.getInstrumentation().context.unbindService(serviceConnection)
+    }
+}
\ No newline at end of file
diff --git a/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt
new file mode 100644
index 0000000..7a13b1f
--- /dev/null
+++ b/tests/integration/src/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.util
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.content.Context
+import android.net.InetAddresses.parseNumericAddress
+import android.net.MacAddress
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.dhcp.DhcpPacket
+import android.os.HandlerThread
+import android.system.Os
+import android.system.OsConstants.AF_INET
+import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.OsConstants.SOCK_NONBLOCK
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN
+import com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY
+import com.android.testutils.ArpRequestFilter
+import com.android.testutils.ETHER_HEADER_LENGTH
+import com.android.testutils.IPV4_HEADER_LENGTH
+import com.android.testutils.IPv4UdpFilter
+import com.android.testutils.TapPacketReader
+import com.android.testutils.UDP_HEADER_LENGTH
+import org.junit.After
+import org.junit.Assert.assertArrayEquals
+import org.junit.Before
+import org.junit.Test
+import java.net.Inet4Address
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+class NetworkStackUtilsIntegrationTest {
+    private val inst by lazy { InstrumentationRegistry.getInstrumentation() }
+    private val context by lazy { inst.context }
+
+    private val TEST_TIMEOUT_MS = 10_000L
+    private val TEST_TARGET_IPV4_ADDR = parseNumericAddress("192.0.2.42") as Inet4Address
+    private val TEST_TARGET_MAC = MacAddress.fromString("01:23:45:67:89:0A")
+
+    private val readerHandler = HandlerThread(
+            NetworkStackUtilsIntegrationTest::class.java.simpleName)
+    private lateinit var iface: TestNetworkInterface
+    private lateinit var reader: TapPacketReader
+
+    @Before
+    fun setUp() {
+        inst.uiAutomation.adoptShellPermissionIdentity(MANAGE_TEST_NETWORKS)
+        try {
+            val tnm = context.assertHasService(TestNetworkManager::class)
+            iface = tnm.createTapInterface()
+        } finally {
+            inst.uiAutomation.dropShellPermissionIdentity()
+        }
+        readerHandler.start()
+        reader = TapPacketReader(readerHandler.threadHandler, iface.fileDescriptor.fileDescriptor,
+                1500 /* maxPacketSize */)
+        readerHandler.threadHandler.post { reader.start() }
+    }
+
+    @After
+    fun tearDown() {
+        readerHandler.quitSafely()
+        if (this::iface.isInitialized) iface.fileDescriptor.close()
+    }
+
+    @Test
+    fun testAddArpEntry() {
+        val socket = Os.socket(AF_INET, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_UDP)
+        SocketUtils.bindSocketToInterface(socket, iface.interfaceName)
+
+        NetworkStackUtils.addArpEntry(TEST_TARGET_IPV4_ADDR, TEST_TARGET_MAC, iface.interfaceName,
+                socket)
+
+        // Fake DHCP packet: would not be usable as a DHCP offer (most IPv4 addresses are all-zero,
+        // no gateway or DNS servers, etc).
+        // Using a DHCP packet to replicate actual usage of the API: it is used in DhcpServer to
+        // send packets to clients before their IP address has been assigned.
+        val buffer = DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_BOOTP, 123 /* transactionId */,
+                false /* broadcast */, IPV4_ADDR_ANY /* serverIpAddr */,
+                IPV4_ADDR_ANY /* relayIp */, IPV4_ADDR_ANY /* yourIp */,
+                TEST_TARGET_MAC.toByteArray(), 3600 /* timeout */, IPV4_ADDR_ANY /* netMask */,
+                IPV4_ADDR_ANY /* bcAddr */, emptyList<Inet4Address>() /* gateways */,
+                emptyList<Inet4Address>() /* dnsServers */,
+                IPV4_ADDR_ANY /* dhcpServerIdentifier */, null /* domainName */,
+                null /* hostname */, false /* metered */, 1500 /* mtu */,
+                null /* captivePortalUrl */)
+        // Not using .array as per errorprone "ByteBufferBackingArray" recommendation
+        val originalPacket = ByteArray(buffer.limit())
+        buffer.get(originalPacket)
+
+        Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size /* bytesCount */,
+                0 /* flags */, TEST_TARGET_IPV4_ADDR, DhcpPacket.DHCP_CLIENT.toInt() /* port */)
+
+        // Verify the packet was sent to the mac address specified in the ARP entry
+        // Also accept ARP requests, but expect that none is sent before the UDP packet
+        // IPv6 NS may be sent on the interface but will be filtered out
+        val sentPacket = reader.popPacket(TEST_TIMEOUT_MS, IPv4UdpFilter().or(ArpRequestFilter()))
+                ?: fail("Packet was not sent on the interface")
+
+        val sentTargetAddr = MacAddress.fromBytes(sentPacket.copyOfRange(0, ETHER_ADDR_LEN))
+        assertEquals(TEST_TARGET_MAC, sentTargetAddr, "Destination ethernet address does not match")
+
+        val sentDhcpPacket = sentPacket.copyOfRange(
+                ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH, sentPacket.size)
+
+        assertArrayEquals("Sent packet != original packet", originalPacket, sentDhcpPacket)
+    }
+}
+
+private fun <T : Any> Context.assertHasService(manager: KClass<T>) = getSystemService(manager.java)
+        ?: fail("Could not find service $manager")
\ No newline at end of file
diff --git a/tests/lib/Android.bp b/tests/lib/Android.bp
deleted file mode 100644
index 47b950d..0000000
--- a/tests/lib/Android.bp
+++ /dev/null
@@ -1,56 +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.
-//
-
-java_library {
-    name: "net-tests-utils-multivariant",
-    srcs: [
-        "multivariant/**/*.java",
-        "multivariant/**/*.kt",
-    ],
-    host_supported: true,
-    static_libs: [
-        "kotlin-test",
-        "junit",
-    ],
-}
-
-java_library {
-    name: "net-tests-utils",
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
-        ":net-module-utils-srcs-for-tests",
-    ],
-    defaults: ["lib_mockito_extended"],
-    libs: [
-        "androidx.annotation_annotation",
-    ],
-    static_libs: [
-        "androidx.test.ext.junit",
-        "net-tests-utils-multivariant",
-    ],
-}
-
-java_defaults {
-    name: "lib_mockito_extended",
-    static_libs: [
-        "mockito-target-extended-minus-junit4"
-    ],
-    jni_libs: [
-         "libdexmakerjvmtiagent",
-         "libstaticjvmtiagent",
-    ],
-}
diff --git a/tests/lib/multivariant/com/android/testutils/ConcurrentUtils.kt b/tests/lib/multivariant/com/android/testutils/ConcurrentUtils.kt
deleted file mode 100644
index a365af5..0000000
--- a/tests/lib/multivariant/com/android/testutils/ConcurrentUtils.kt
+++ /dev/null
@@ -1,26 +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 com.android.testutils
-
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import kotlin.system.measureTimeMillis
-
-// For Java usage
-fun durationOf(fn: Runnable) = measureTimeMillis { fn.run() }
-
-fun CountDownLatch.await(timeoutMs: Long): Boolean = await(timeoutMs, TimeUnit.MILLISECONDS)
diff --git a/tests/lib/multivariant/com/android/testutils/ExceptionUtils.java b/tests/lib/multivariant/com/android/testutils/ExceptionUtils.java
deleted file mode 100644
index e7dbed5..0000000
--- a/tests/lib/multivariant/com/android/testutils/ExceptionUtils.java
+++ /dev/null
@@ -1,67 +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 com.android.testutils;
-
-import java.util.function.Supplier;
-
-public class ExceptionUtils {
-    /**
-     * Like a Consumer, but declared to throw an exception.
-     * @param <T>
-     */
-    @FunctionalInterface
-    public interface ThrowingConsumer<T> {
-        void accept(T t) throws Exception;
-    }
-
-    /**
-     * Like a Supplier, but declared to throw an exception.
-     * @param <T>
-     */
-    @FunctionalInterface
-    public interface ThrowingSupplier<T> {
-        T get() throws Exception;
-    }
-
-    /**
-     * Like a Runnable, but declared to throw an exception.
-     */
-    @FunctionalInterface
-    public interface ThrowingRunnable {
-        void run() throws Exception;
-    }
-
-
-    public static <T> Supplier<T> ignoreExceptions(ThrowingSupplier<T> func) {
-        return () -> {
-            try {
-                return func.get();
-            } catch (Exception e) {
-                return null;
-            }
-        };
-    }
-
-    public static Runnable ignoreExceptions(ThrowingRunnable r) {
-        return () -> {
-            try {
-                r.run();
-            } catch (Exception e) {
-            }
-        };
-    }
-}
diff --git a/tests/lib/multivariant/com/android/testutils/FileUtils.kt b/tests/lib/multivariant/com/android/testutils/FileUtils.kt
deleted file mode 100644
index 678f977..0000000
--- a/tests/lib/multivariant/com/android/testutils/FileUtils.kt
+++ /dev/null
@@ -1,27 +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 com.android.testutils
-
-// This function is private because the 2 is hardcoded here, and is not correct if not called
-// directly from __LINE__ or __FILE__.
-private fun callerStackTrace(): StackTraceElement = try {
-    throw RuntimeException()
-} catch (e: RuntimeException) {
-    e.stackTrace[2] // 0 is here, 1 is get() in __FILE__ or __LINE__
-}
-val __FILE__: String get() = callerStackTrace().fileName
-val __LINE__: Int get() = callerStackTrace().lineNumber
diff --git a/tests/lib/multivariant/com/android/testutils/MiscAsserts.kt b/tests/lib/multivariant/com/android/testutils/MiscAsserts.kt
deleted file mode 100644
index 32c22c2..0000000
--- a/tests/lib/multivariant/com/android/testutils/MiscAsserts.kt
+++ /dev/null
@@ -1,103 +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 com.android.testutils
-
-import com.android.testutils.ExceptionUtils.ThrowingRunnable
-import java.lang.reflect.Modifier
-import kotlin.system.measureTimeMillis
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-
-private const val TAG = "Connectivity unit test"
-
-fun <T> assertEmpty(ts: Array<T>) = ts.size.let { len ->
-    assertEquals(0, len, "Expected empty array, but length was $len")
-}
-
-fun <T> assertLength(expected: Int, got: Array<T>) = got.size.let { len ->
-    assertEquals(expected, len, "Expected array of length $expected, but was $len for $got")
-}
-
-// Bridge method to help write this in Java. If you're writing Kotlin, consider using native
-// kotlin.test.assertFailsWith instead, as that method is reified and inlined.
-fun <T : Exception> assertThrows(expected: Class<T>, block: ThrowingRunnable): T {
-    return assertFailsWith(expected.kotlin) { block.run() }
-}
-
-fun <T : Exception> assertThrows(msg: String, expected: Class<T>, block: ThrowingRunnable): T {
-    return assertFailsWith(expected.kotlin, msg) { block.run() }
-}
-
-fun <T> assertEqualBothWays(o1: T, o2: T) {
-    assertTrue(o1 == o2)
-    assertTrue(o2 == o1)
-}
-
-fun <T> assertNotEqualEitherWay(o1: T, o2: T) {
-    assertFalse(o1 == o2)
-    assertFalse(o2 == o1)
-}
-
-fun assertStringContains(got: String, want: String) {
-    assertTrue(got.contains(want), "$got did not contain \"${want}\"")
-}
-
-fun assertContainsExactly(actual: IntArray, vararg expected: Int) {
-    // IntArray#sorted() returns a list, so it's fine to test with equals()
-    assertEquals(actual.sorted(), expected.sorted(),
-            "$actual does not contain exactly $expected")
-}
-
-fun assertContainsStringsExactly(actual: Array<String>, vararg expected: String) {
-    assertEquals(actual.sorted(), expected.sorted(),
-            "$actual does not contain exactly $expected")
-}
-
-fun <T> assertContainsAll(list: Collection<T>, vararg elems: T) {
-    assertContainsAll(list, elems.asList())
-}
-
-fun <T> assertContainsAll(list: Collection<T>, elems: Collection<T>) {
-    elems.forEach { assertTrue(list.contains(it), "$it not in list") }
-}
-
-fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: Runnable) {
-    assertRunsInAtMost(descr, timeLimit) { fn.run() }
-}
-
-fun assertRunsInAtMost(descr: String, timeLimit: Long, fn: () -> Unit) {
-    val timeTaken = measureTimeMillis(fn)
-    val msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit)
-    assertTrue(timeTaken <= timeLimit, msg)
-}
-
-/**
- * Verifies that the number of nonstatic fields in a java class equals a given count.
- * Note: this is essentially not useful for Kotlin code where fields are not really a thing.
- *
- * This assertion serves as a reminder to update test code around it if fields are added
- * after the test is written.
- * @param count Expected number of nonstatic fields in the class.
- * @param clazz Class to test.
- */
-fun <T> assertFieldCountEquals(count: Int, clazz: Class<T>) {
-    assertEquals(count, clazz.declaredFields.filter {
-        !Modifier.isStatic(it.modifiers) && !Modifier.isTransient(it.modifiers)
-    }.size)
-}
diff --git a/tests/lib/multivariant/com/android/testutils/PacketFilter.kt b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
deleted file mode 100644
index cd8d6a5..0000000
--- a/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 69ed048..0000000
--- a/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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/multivariant/com/android/testutils/TrackRecord.kt b/tests/lib/multivariant/com/android/testutils/TrackRecord.kt
deleted file mode 100644
index 3cdea12..0000000
--- a/tests/lib/multivariant/com/android/testutils/TrackRecord.kt
+++ /dev/null
@@ -1,247 +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 com.android.testutils
-
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.locks.Condition
-import java.util.concurrent.locks.ReentrantLock
-import kotlin.concurrent.withLock
-
-/**
- * A List that additionally offers the ability to append via the add() method, and to retrieve
- * an element by its index optionally waiting for it to become available.
- */
-interface TrackRecord<E> : List<E> {
-    /**
-     * Adds an element to this queue, waking up threads waiting for one. Returns true, as
-     * per the contract for List.
-     */
-    fun add(e: E): Boolean
-
-    /**
-     * Returns the first element after {@param pos}, possibly blocking until one is available, or
-     * null if no such element can be found within the timeout.
-     * If a predicate is given, only elements matching the predicate are returned.
-     *
-     * @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation).
-     * @param pos the position at which to start polling.
-     * @param predicate an optional predicate to filter elements to be returned.
-     * @return an element matching the predicate, or null if timeout.
-     */
-    fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean = { true }): E?
-}
-
-/**
- * A thread-safe implementation of TrackRecord that is backed by an ArrayList.
- *
- * This class also supports the creation of a read-head for easier single-thread access.
- * Refer to the documentation of {@link ArrayTrackRecord.ReadHead}.
- */
-class ArrayTrackRecord<E> : TrackRecord<E> {
-    private val lock = ReentrantLock()
-    private val condition = lock.newCondition()
-    // Backing store. This stores the elements in this ArrayTrackRecord.
-    private val elements = ArrayList<E>()
-
-    // The list iterator for RecordingQueue iterates over a snapshot of the collection at the
-    // time the operator is created. Because TrackRecord is only ever mutated by appending,
-    // that makes this iterator thread-safe as it sees an effectively immutable List.
-    class ArrayTrackRecordIterator<E>(
-        private val list: ArrayList<E>,
-        start: Int,
-        private val end: Int
-    ) : ListIterator<E> {
-        var index = start
-        override fun hasNext() = index < end
-        override fun next() = list[index++]
-        override fun hasPrevious() = index > 0
-        override fun nextIndex() = index + 1
-        override fun previous() = list[--index]
-        override fun previousIndex() = index - 1
-    }
-
-    // List<E> implementation
-    override val size get() = lock.withLock { elements.size }
-    override fun contains(element: E) = lock.withLock { elements.contains(element) }
-    override fun containsAll(elements: Collection<E>) = lock.withLock {
-        this.elements.containsAll(elements)
-    }
-    override operator fun get(index: Int) = lock.withLock { elements[index] }
-    override fun indexOf(element: E): Int = lock.withLock { elements.indexOf(element) }
-    override fun lastIndexOf(element: E): Int = lock.withLock { elements.lastIndexOf(element) }
-    override fun isEmpty() = lock.withLock { elements.isEmpty() }
-    override fun listIterator(index: Int) = ArrayTrackRecordIterator(elements, index, size)
-    override fun listIterator() = listIterator(0)
-    override fun iterator() = listIterator()
-    override fun subList(fromIndex: Int, toIndex: Int): List<E> = lock.withLock {
-        elements.subList(fromIndex, toIndex)
-    }
-
-    // TrackRecord<E> implementation
-    override fun add(e: E): Boolean {
-        lock.withLock {
-            elements.add(e)
-            condition.signalAll()
-        }
-        return true
-    }
-    override fun poll(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean) = lock.withLock {
-        elements.getOrNull(pollForIndexReadLocked(timeoutMs, pos, predicate))
-    }
-
-    // For convenience
-    fun getOrNull(pos: Int, predicate: (E) -> Boolean) = lock.withLock {
-        if (pos < 0 || pos > size) null else elements.subList(pos, size).find(predicate)
-    }
-
-    // Returns the index of the next element whose position is >= pos matching the predicate, if
-    // necessary waiting until such a time that such an element is available, with a timeout.
-    // If no such element is found within the timeout -1 is returned.
-    private fun pollForIndexReadLocked(timeoutMs: Long, pos: Int, predicate: (E) -> Boolean): Int {
-        val deadline = System.currentTimeMillis() + timeoutMs
-        var index = pos
-        do {
-            while (index < elements.size) {
-                if (predicate(elements[index])) return index
-                ++index
-            }
-        } while (condition.await(deadline - System.currentTimeMillis()))
-        return -1
-    }
-
-    /**
-     * Returns a ReadHead over this ArrayTrackRecord. The returned ReadHead is tied to the
-     * current thread.
-     */
-    fun newReadHead() = ReadHead()
-
-    /**
-     * ReadHead is an object that helps users of ArrayTrackRecord keep track of how far
-     * it has read this far in the ArrayTrackRecord. A ReadHead is always associated with
-     * a single instance of ArrayTrackRecord. Multiple ReadHeads can be created and used
-     * on the same instance of ArrayTrackRecord concurrently, and the ArrayTrackRecord
-     * instance can also be used concurrently. ReadHead maintains the current index that is
-     * the next to be read, and calls this the "mark".
-     *
-     * A ReadHead delegates all TrackRecord methods to its associated ArrayTrackRecord, and
-     * inherits its thread-safe properties. However, the additional methods that ReadHead
-     * offers on top of TrackRecord do not share these properties and can only be used by
-     * the thread that created the ReadHead. This is because by construction it does not
-     * make sense to use a ReadHead on multiple threads concurrently (see below for details).
-     *
-     * In a ReadHead, {@link poll(Long, (E) -> Boolean)} works similarly to a LinkedBlockingQueue.
-     * It can be called repeatedly and will return the elements as they arrive.
-     *
-     * Intended usage looks something like this :
-     * val TrackRecord<MyObject> record = ArrayTrackRecord().newReadHead()
-     * Thread().start {
-     *   // do stuff
-     *   record.add(something)
-     *   // do stuff
-     * }
-     *
-     * val obj1 = record.poll(timeout)
-     * // do something with obj1
-     * val obj2 = record.poll(timeout)
-     * // do something with obj2
-     *
-     * The point is that the caller does not have to track the mark like it would have to if
-     * it was using ArrayTrackRecord directly.
-     *
-     * Note that if multiple threads were using poll() concurrently on the same ReadHead, what
-     * happens to the mark and the return values could be well defined, but it could not
-     * be useful because there is no way to provide either a guarantee not to skip objects nor
-     * a guarantee about the mark position at the exit of poll(). This is even more true in the
-     * presence of a predicate to filter returned elements, because one thread might be
-     * filtering out the events the other is interested in.
-     * Instead, this use case is supported by creating multiple ReadHeads on the same instance
-     * of ArrayTrackRecord. Each ReadHead is then guaranteed to see all events always and
-     * guarantees are made on the value of the mark upon return. {@see poll(Long, (E) -> Boolean)}
-     * for details. Be careful to create each ReadHead on the thread it is meant to be used on.
-     *
-     * Users of a ReadHead can ask for the current position of the mark at any time. This mark
-     * can be used later to replay the history of events either on this ReadHead, on the associated
-     * ArrayTrackRecord or on another ReadHead associated with the same ArrayTrackRecord. It
-     * might look like this in the reader thread :
-     *
-     * val markAtStart = record.mark
-     * // Start processing interesting events
-     * while (val element = record.poll(timeout) { it.isInteresting() }) {
-     *   // Do something with element
-     * }
-     * // Look for stuff that happened while searching for interesting events
-     * val firstElementReceived = record.getOrNull(markAtStart)
-     * val firstSpecialElement = record.getOrNull(markAtStart) { it.isSpecial() }
-     * // Get the first special element since markAtStart, possibly blocking until one is available
-     * val specialElement = record.poll(timeout, markAtStart) { it.isSpecial() }
-     */
-    inner class ReadHead : TrackRecord<E> by this@ArrayTrackRecord {
-        private val owningThread = Thread.currentThread()
-        private var readHead = 0
-
-        /**
-         * @return the current value of the mark.
-         */
-        var mark
-            get() = readHead.also { checkThread() }
-            set(v: Int) = rewind(v)
-        fun rewind(v: Int) {
-            checkThread()
-            readHead = v
-        }
-
-        private fun checkThread() = check(Thread.currentThread() == owningThread) {
-            "Must be called by the thread that created this object"
-        }
-
-        /**
-         * Returns the first element after the mark, optionally blocking until one is available, or
-         * null if no such element can be found within the timeout.
-         * If a predicate is given, only elements matching the predicate are returned.
-         *
-         * Upon return the mark will be set to immediately after the returned element, or after
-         * the last element in the queue if null is returned. This means this method will always
-         * skip elements that do not match the predicate, even if it returns null.
-         *
-         * This method can only be used by the thread that created this ManagedRecordingQueue.
-         * If used on another thread, this throws IllegalStateException.
-         *
-         * @param timeoutMs how long, in milliseconds, to wait at most (best effort approximation).
-         * @param predicate an optional predicate to filter elements to be returned.
-         * @return an element matching the predicate, or null if timeout.
-         */
-        fun poll(timeoutMs: Long, predicate: (E) -> Boolean = { true }): E? {
-            checkThread()
-            lock.withLock {
-                val index = pollForIndexReadLocked(timeoutMs, readHead, predicate)
-                readHead = if (index < 0) size else index + 1
-                return getOrNull(index)
-            }
-        }
-
-        /**
-         * Returns the first element after the mark or null. This never blocks.
-         *
-         * This method can only be used by the thread that created this ManagedRecordingQueue.
-         * If used on another thread, this throws IllegalStateException.
-         */
-        fun peek(): E? = getOrNull(readHead).also { checkThread() }
-    }
-}
-
-// Private helper
-private fun Condition.await(timeoutMs: Long) = this.await(timeoutMs, TimeUnit.MILLISECONDS)
diff --git a/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt b/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt
deleted file mode 100644
index 25b1e0f..0000000
--- a/tests/lib/src/com/android/testutils/ConcurrentIntepreter.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-package com.android.testutils
-
-import android.os.SystemClock
-import java.util.concurrent.CyclicBarrier
-import kotlin.system.measureTimeMillis
-import kotlin.test.assertEquals
-import kotlin.test.assertFails
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-
-// The table contains pairs associating a regexp with the code to run. The statement is matched
-// against each matcher in sequence and when a match is found the associated code is run, passing
-// it the TrackRecord under test and the result of the regexp match.
-typealias InterpretMatcher<T> = Pair<Regex, (ConcurrentIntepreter<T>, T, MatchResult) -> Any?>
-
-// The default unit of time for interpreted tests
-val INTERPRET_TIME_UNIT = 40L // ms
-
-/**
- * A small interpreter for testing parallel code. The interpreter will read a list of lines
- * consisting of "|"-separated statements. Each column runs in a different concurrent thread
- * and all threads wait for each other in between lines. Each statement is split on ";" then
- * matched with regular expressions in the instructionTable constant, which contains the
- * code associated with each statement. The interpreter supports an object being passed to
- * the interpretTestSpec() method to be passed in each lambda (think about the object under
- * test), and an optional transform function to be executed on the object at the start of
- * every thread.
- *
- * The time unit is defined in milliseconds by the interpretTimeUnit member, which has a default
- * value but can be passed to the constructor. Whitespace is ignored.
- *
- * The interpretation table has to be passed as an argument. It's a table associating a regexp
- * with the code that should execute, as a function taking three arguments : the interpreter,
- * the regexp match, and the object. See the individual tests for the DSL of that test.
- * Implementors for new interpreting languages are encouraged to look at the defaultInterpretTable
- * constant below for an example of how to write an interpreting table.
- * Some expressions already exist by default and can be used by all interpreters. They include :
- * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
- * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
- *   string "null" or an int. Returns Unit.
- * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
- *   y time units.
- * EXPR // any text : comments are ignored.
- */
-open class ConcurrentIntepreter<T>(
-    localInterpretTable: List<InterpretMatcher<T>>,
-    val interpretTimeUnit: Long = INTERPRET_TIME_UNIT
-) {
-    private val interpretTable: List<InterpretMatcher<T>> =
-            localInterpretTable + getDefaultInstructions()
-
-    // Split the line into multiple statements separated by ";" and execute them. Return whatever
-    // the last statement returned.
-    fun interpretMultiple(instr: String, r: T): Any? {
-        return instr.split(";").map { interpret(it.trim(), r) }.last()
-    }
-
-    // Match the statement to a regex and interpret it.
-    fun interpret(instr: String, r: T): Any? {
-        val (matcher, code) =
-                interpretTable.find { instr matches it.first } ?: throw SyntaxException(instr)
-        val match = matcher.matchEntire(instr) ?: throw SyntaxException(instr)
-        return code(this, r, match)
-    }
-
-    // Spins as many threads as needed by the test spec and interpret each program concurrently,
-    // having all threads waiting on a CyclicBarrier after each line.
-    // |lineShift| says how many lines after the call the spec starts. This is used for error
-    // reporting. Unfortunately AFAICT there is no way to get the line of an argument rather
-    // than the line at which the expression starts.
-    fun interpretTestSpec(
-        spec: String,
-        initial: T,
-        lineShift: Int = 0,
-        threadTransform: (T) -> T = { it }
-    ) {
-        // For nice stack traces
-        val callSite = getCallingMethod()
-        val lines = spec.trim().trim('\n').split("\n").map { it.split("|") }
-        // |threads| contains arrays of strings that make up the statements of a thread : in other
-        // words, it's an array that contains a list of statements for each column in the spec.
-        val threadCount = lines[0].size
-        assertTrue(lines.all { it.size == threadCount })
-        val threadInstructions = (0 until threadCount).map { i -> lines.map { it[i].trim() } }
-        val barrier = CyclicBarrier(threadCount)
-        var crash: InterpretException? = null
-        threadInstructions.mapIndexed { threadIndex, instructions ->
-            Thread {
-                val threadLocal = threadTransform(initial)
-                barrier.await()
-                var lineNum = 0
-                instructions.forEach {
-                    if (null != crash) return@Thread
-                    lineNum += 1
-                    try {
-                        interpretMultiple(it, threadLocal)
-                    } catch (e: Throwable) {
-                        // If fail() or some exception was called, the thread will come here ; if
-                        // the exception isn't caught the process will crash, which is not nice for
-                        // testing. Instead, catch the exception, cancel other threads, and report
-                        // nicely. Catch throwable because fail() is AssertionError, which inherits
-                        // from Error.
-                        crash = InterpretException(threadIndex, it,
-                                callSite.lineNumber + lineNum + lineShift,
-                                callSite.className, callSite.methodName, callSite.fileName, e)
-                    }
-                    barrier.await()
-                }
-            }.also { it.start() }
-        }.forEach { it.join() }
-        // If the test failed, crash with line number
-        crash?.let { throw it }
-    }
-
-    // Helper to get the stack trace for a calling method
-    private fun getCallingStackTrace(): Array<StackTraceElement> {
-        try {
-            throw RuntimeException()
-        } catch (e: RuntimeException) {
-            return e.stackTrace
-        }
-    }
-
-    // Find the calling method. This is the first method in the stack trace that is annotated
-    // with @Test.
-    fun getCallingMethod(): StackTraceElement {
-        val stackTrace = getCallingStackTrace()
-        return stackTrace.find { element ->
-            val clazz = Class.forName(element.className)
-            // Because the stack trace doesn't list the formal arguments, find all methods with
-            // this name and return this name if any of them is annotated with @Test.
-            clazz.declaredMethods
-                    .filter { method -> method.name == element.methodName }
-                    .any { method -> method.getAnnotation(org.junit.Test::class.java) != null }
-        } ?: stackTrace[3]
-        // If no method is annotated return the 4th one, because that's what it usually is :
-        // 0 is getCallingStackTrace, 1 is this method, 2 is ConcurrentInterpreter#interpretTestSpec
-    }
-}
-
-private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>(
-    // Interpret an empty line as doing nothing.
-    Regex("") to { _, _, _ -> null },
-    // Ignore comments.
-    Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) },
-    // Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y
-    Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r ->
-        val time = measureTimeMillis { i.interpret(r.strArg(1), t) }
-        assertTrue(time in r.timeArg(2)..r.timeArg(3), "$time not in ${r.timeArg(2)..r.timeArg(3)}")
-    },
-    // Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported
-    Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r ->
-        i.interpret(r.strArg(1), t).also {
-            if ("null" == r.strArg(2)) assertNull(it) else assertEquals(r.intArg(2), it)
-        }
-    },
-    // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units.
-    Regex("""sleep(\((\d+)\))?""") to { i, t, r ->
-        SystemClock.sleep(if (r.strArg(2).isEmpty()) i.interpretTimeUnit else r.timeArg(2))
-    },
-    Regex("""(.*)\s*fails""") to { i, t, r ->
-        assertFails { i.interpret(r.strArg(1), t) }
-    }
-)
-
-class SyntaxException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause)
-class InterpretException(
-    threadIndex: Int,
-    instr: String,
-    lineNum: Int,
-    className: String,
-    methodName: String,
-    fileName: String,
-    cause: Throwable
-) : RuntimeException("Failure: $instr", cause) {
-    init {
-        stackTrace = arrayOf(StackTraceElement(
-                className,
-                "$methodName:thread$threadIndex",
-                fileName,
-                lineNum)) + super.getStackTrace()
-    }
-}
-
-// Some small helpers to avoid to say the large ".groupValues[index].trim()" every time
-fun MatchResult.strArg(index: Int) = this.groupValues[index].trim()
-fun MatchResult.intArg(index: Int) = strArg(index).toInt()
-fun MatchResult.timeArg(index: Int) = INTERPRET_TIME_UNIT * intArg(index)
diff --git a/tests/lib/src/com/android/testutils/DevSdkIgnoreRule.kt b/tests/lib/src/com/android/testutils/DevSdkIgnoreRule.kt
deleted file mode 100644
index 4a83f6f..0000000
--- a/tests/lib/src/com/android/testutils/DevSdkIgnoreRule.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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 android.os.Build
-import org.junit.Assume.assumeTrue
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Returns true if the development SDK version of the device is in the provided range.
- *
- * If the device is not using a release SDK, the development SDK is considered to be higher than
- * [Build.VERSION.SDK_INT].
- */
-fun isDevSdkInRange(minExclusive: Int?, maxInclusive: Int?): Boolean {
-    // In-development API n+1 will have SDK_INT == n and CODENAME != REL.
-    // Stable API n has SDK_INT == n and CODENAME == REL.
-    val release = "REL" == Build.VERSION.CODENAME
-    val sdkInt = Build.VERSION.SDK_INT
-    val devApiLevel = sdkInt + if (release) 0 else 1
-
-    return (minExclusive == null || devApiLevel > minExclusive) &&
-            (maxInclusive == null || devApiLevel <= maxInclusive)
-}
-
-/**
- * A test rule to ignore tests based on the development SDK level.
- *
- * If the device is not using a release SDK, the development SDK is considered to be higher than
- * [Build.VERSION.SDK_INT].
- *
- * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value.
- * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value.
- */
-class DevSdkIgnoreRule @JvmOverloads constructor(
-    private val ignoreClassUpTo: Int? = null,
-    private val ignoreClassAfter: Int? = null
-) : TestRule {
-    override fun apply(base: Statement, description: Description): Statement {
-        return IgnoreBySdkStatement(base, description)
-    }
-
-    /**
-     * Ignore the test for any development SDK that is strictly after [value].
-     *
-     * If the device is not using a release SDK, the development SDK is considered to be higher
-     * than [Build.VERSION.SDK_INT].
-     */
-    annotation class IgnoreAfter(val value: Int)
-
-    /**
-     * Ignore the test for any development SDK that lower than or equal to [value].
-     *
-     * If the device is not using a release SDK, the development SDK is considered to be higher
-     * than [Build.VERSION.SDK_INT].
-     */
-    annotation class IgnoreUpTo(val value: Int)
-
-    private inner class IgnoreBySdkStatement(
-        private val base: Statement,
-        private val description: Description
-    ) : Statement() {
-        override fun evaluate() {
-            val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java)
-            val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java)
-
-            val message = "Skipping test for build ${Build.VERSION.CODENAME} " +
-                    "with SDK ${Build.VERSION.SDK_INT}"
-            assumeTrue(message, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
-            assumeTrue(message, isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value))
-            base.evaluate()
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/lib/src/com/android/testutils/DevSdkIgnoreRunner.kt b/tests/lib/src/com/android/testutils/DevSdkIgnoreRunner.kt
deleted file mode 100644
index 73b2843..0000000
--- a/tests/lib/src/com/android/testutils/DevSdkIgnoreRunner.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import org.junit.runner.Description
-import org.junit.runner.Runner
-import org.junit.runner.notification.RunNotifier
-
-/**
- * A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule].
- *
- * Generally [DevSdkIgnoreRule] should be used for that purpose (using rules is preferable over
- * replacing the test runner), however JUnit runners inspect all methods in the test class before
- * processing test rules. This may cause issues if the test methods are referencing classes that do
- * not exist on the SDK of the device the test is run on.
- *
- * This runner inspects [IgnoreAfter] and [IgnoreUpTo] annotations on the test class, and will skip
- * the whole class if they do not match the development SDK as defined in [DevSdkIgnoreRule].
- * Otherwise, it will delegate to [AndroidJUnit4] to run the test as usual.
- *
- * Example usage:
- *
- *     @RunWith(DevSdkIgnoreRunner::class)
- *     @IgnoreUpTo(Build.VERSION_CODES.Q)
- *     class MyTestClass { ... }
- */
-class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner() {
-    private val baseRunner = klass.let {
-        val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java)
-        val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java)
-
-        if (isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value)) AndroidJUnit4(klass) else null
-    }
-
-    override fun run(notifier: RunNotifier) {
-        if (baseRunner != null) {
-            baseRunner.run(notifier)
-            return
-        }
-
-        // Report a single, skipped placeholder test for this class, so that the class is still
-        // visible as skipped in test results.
-        notifier.fireTestIgnored(
-                Description.createTestDescription(klass, "skippedClassForDevSdkMismatch"))
-    }
-
-    override fun getDescription(): Description {
-        return baseRunner?.description ?: Description.createSuiteDescription(klass)
-    }
-
-    override fun testCount(): Int {
-        // When ignoring the tests, a skipped placeholder test is reported, so test count is 1.
-        return baseRunner?.testCount() ?: 1
-    }
-}
\ No newline at end of file
diff --git a/tests/lib/src/com/android/testutils/FakeDns.kt b/tests/lib/src/com/android/testutils/FakeDns.kt
deleted file mode 100644
index 825d748..0000000
--- a/tests/lib/src/com/android/testutils/FakeDns.kt
+++ /dev/null
@@ -1,94 +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 com.android.testutils
-
-import android.net.DnsResolver
-import android.net.InetAddresses
-import android.os.Looper
-import android.os.Handler
-import com.android.internal.annotations.GuardedBy
-import java.net.InetAddress
-import java.util.concurrent.Executor
-import org.mockito.invocation.InvocationOnMock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doAnswer
-
-const val TYPE_UNSPECIFIED = -1
-// TODO: Integrate with NetworkMonitorTest.
-class FakeDns(val mockResolver: DnsResolver) {
-    class DnsEntry(val hostname: String, val type: Int, val addresses: List<InetAddress>) {
-        fun match(host: String, type: Int) = hostname.equals(host) && type == type
-    }
-
-    @GuardedBy("answers")
-    val answers = ArrayList<DnsEntry>()
-
-    fun getAnswer(hostname: String, type: Int): DnsEntry? = synchronized(answers) {
-        return answers.firstOrNull { it.match(hostname, type) }
-    }
-
-    fun setAnswer(hostname: String, answer: Array<String>, type: Int) = synchronized(answers) {
-        val ans = DnsEntry(hostname, type, generateAnswer(answer))
-        // Replace or remove the existing one.
-        when (val index = answers.indexOfFirst { it.match(hostname, type) }) {
-            -1 -> answers.add(ans)
-            else -> answers[index] = ans
-        }
-    }
-
-    private fun generateAnswer(answer: Array<String>) =
-            answer.filterNotNull().map { InetAddresses.parseNumericAddress(it) }
-
-    fun startMocking() {
-        // Mock DnsResolver.query() w/o type
-        doAnswer {
-            mockAnswer(it, 1, -1, 3, 5)
-        }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* flags */,
-                any() /* executor */, any() /* cancellationSignal */, any() /*callback*/)
-        // Mock DnsResolver.query() w/ type
-        doAnswer {
-            mockAnswer(it, 1, 2, 4, 6)
-        }.`when`(mockResolver).query(any() /* network */, any() /* domain */, anyInt() /* nsType */,
-                anyInt() /* flags */, any() /* executor */, any() /* cancellationSignal */,
-        any() /*callback*/)
-    }
-
-    private fun mockAnswer(
-        it: InvocationOnMock,
-        posHos: Int,
-        posType: Int,
-        posExecutor: Int,
-        posCallback: Int
-    ) {
-        val hostname = it.arguments[posHos] as String
-        val executor = it.arguments[posExecutor] as Executor
-        val callback = it.arguments[posCallback] as DnsResolver.Callback<List<InetAddress>>
-        var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED
-        val answer = getAnswer(hostname, type)
-
-        if (!answer?.addresses.isNullOrEmpty()) {
-            Handler(Looper.getMainLooper()).post({ executor.execute({
-                    callback.onAnswer(answer?.addresses, 0); }) })
-        }
-    }
-
-    /** Clears all entries. */
-    fun clearAll() = synchronized(answers) {
-        answers.clear()
-    }
-}
diff --git a/tests/lib/src/com/android/testutils/HandlerUtils.kt b/tests/lib/src/com/android/testutils/HandlerUtils.kt
deleted file mode 100644
index fa36209..0000000
--- a/tests/lib/src/com/android/testutils/HandlerUtils.kt
+++ /dev/null
@@ -1,49 +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 com.android.testutils
-
-import android.os.ConditionVariable
-import android.os.Handler
-import android.os.HandlerThread
-import java.util.concurrent.Executor
-import kotlin.system.measureTimeMillis
-import kotlin.test.fail
-
-/**
- * Block until the specified Handler or HandlerThread becomes idle, or until timeoutMs has passed.
- */
-fun HandlerThread.waitForIdle(timeoutMs: Int) = threadHandler.waitForIdle(timeoutMs.toLong())
-fun HandlerThread.waitForIdle(timeoutMs: Long) = threadHandler.waitForIdle(timeoutMs)
-fun Handler.waitForIdle(timeoutMs: Int) = waitForIdle(timeoutMs.toLong())
-fun Handler.waitForIdle(timeoutMs: Long) {
-    val cv = ConditionVariable(false)
-    post(cv::open)
-    if (!cv.block(timeoutMs)) {
-        fail("Handler did not become idle after ${timeoutMs}ms")
-    }
-}
-
-/**
- * Block until the given Serial Executor becomes idle, or until timeoutMs has passed.
- */
-fun waitForIdleSerialExecutor(executor: Executor, timeoutMs: Long) {
-    val cv = ConditionVariable()
-    executor.execute(cv::open)
-    if (!cv.block(timeoutMs)) {
-        fail("Executor did not become idle after ${timeoutMs}ms")
-    }
-}
diff --git a/tests/lib/src/com/android/testutils/NetworkStatsUtils.kt b/tests/lib/src/com/android/testutils/NetworkStatsUtils.kt
deleted file mode 100644
index 8324b25..0000000
--- a/tests/lib/src/com/android/testutils/NetworkStatsUtils.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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 android.net.NetworkStats
-import kotlin.test.assertTrue
-
-@JvmOverloads
-fun orderInsensitiveEquals(
-    leftStats: NetworkStats,
-    rightStats: NetworkStats,
-    compareTime: Boolean = false
-): Boolean {
-    if (leftStats == rightStats) return true
-    if (compareTime && leftStats.getElapsedRealtime() != rightStats.getElapsedRealtime()) {
-        return false
-    }
-
-    // While operations such as add/subtract will preserve empty entries. This will make
-    // the result be hard to verify during test. Remove them before comparing since they
-    // are not really affect correctness.
-    // TODO (b/152827872): Remove empty entries after addition/subtraction.
-    val leftTrimmedEmpty = leftStats.removeEmptyEntries()
-    val rightTrimmedEmpty = rightStats.removeEmptyEntries()
-
-    if (leftTrimmedEmpty.size() != rightTrimmedEmpty.size()) return false
-    val left = NetworkStats.Entry()
-    val right = NetworkStats.Entry()
-    // Order insensitive compare.
-    for (i in 0 until leftTrimmedEmpty.size()) {
-        leftTrimmedEmpty.getValues(i, left)
-        val j: Int = rightTrimmedEmpty.findIndexHinted(left.iface, left.uid, left.set, left.tag,
-                left.metered, left.roaming, left.defaultNetwork, i)
-        if (j == -1) return false
-        rightTrimmedEmpty.getValues(j, right)
-        if (left != right) return false
-    }
-    return true
-}
-
-/**
- * Assert that two {@link NetworkStats} are equals, assuming the order of the records are not
- * necessarily the same.
- *
- * @note {@code elapsedRealtime} is not compared by default, given that in test cases that is not
- *       usually used.
- */
-@JvmOverloads
-fun assertNetworkStatsEquals(
-    expected: NetworkStats,
-    actual: NetworkStats,
-    compareTime: Boolean = false
-) {
-    assertTrue(orderInsensitiveEquals(expected, actual, compareTime),
-            "expected: " + expected + " but was: " + actual)
-}
-
-/**
- * Assert that after being parceled then unparceled, {@link NetworkStats} is equal to the original
- * object.
- */
-fun assertParcelingIsLossless(stats: NetworkStats) {
-    assertParcelingIsLossless(stats, { a, b -> orderInsensitiveEquals(a, b) })
-}
diff --git a/tests/lib/src/com/android/testutils/ParcelUtils.kt b/tests/lib/src/com/android/testutils/ParcelUtils.kt
deleted file mode 100644
index 5784f7c..0000000
--- a/tests/lib/src/com/android/testutils/ParcelUtils.kt
+++ /dev/null
@@ -1,68 +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 com.android.testutils
-
-import android.os.Parcel
-import android.os.Parcelable
-import kotlin.test.assertTrue
-import kotlin.test.fail
-
-/**
- * Return a new instance of `T` after being parceled then unparceled.
- */
-fun <T : Parcelable> parcelingRoundTrip(source: T): T {
-    val creator: Parcelable.Creator<T>
-    try {
-        creator = source.javaClass.getField("CREATOR").get(null) as Parcelable.Creator<T>
-    } catch (e: IllegalAccessException) {
-        fail("Missing CREATOR field: " + e.message)
-    } catch (e: NoSuchFieldException) {
-        fail("Missing CREATOR field: " + e.message)
-    }
-
-    var p = Parcel.obtain()
-    source.writeToParcel(p, /* flags */ 0)
-    p.setDataPosition(0)
-    val marshalled = p.marshall()
-    p = Parcel.obtain()
-    p.unmarshall(marshalled, 0, marshalled.size)
-    p.setDataPosition(0)
-    return creator.createFromParcel(p)
-}
-
-/**
- * Assert that after being parceled then unparceled, `source` is equal to the original
- * object. If a customized equals function is provided, uses the provided one.
- */
-@JvmOverloads
-fun <T : Parcelable> assertParcelingIsLossless(
-    source: T,
-    equals: (T, T) -> Boolean = { a, b -> a == b }
-) {
-    val actual = parcelingRoundTrip(source)
-    assertTrue(equals(source, actual), "Expected $source, but was $actual")
-}
-
-@JvmOverloads
-fun <T : Parcelable> assertParcelSane(
-    obj: T,
-    fieldCount: Int,
-    equals: (T, T) -> Boolean = { a, b -> a == b }
-) {
-    assertFieldCountEquals(fieldCount, obj::class.java)
-    assertParcelingIsLossless(obj, equals)
-}
diff --git a/tests/lib/src/com/android/testutils/TapPacketReader.java b/tests/lib/src/com/android/testutils/TapPacketReader.java
deleted file mode 100644
index e55ed44..0000000
--- a/tests/lib/src/com/android/testutils/TapPacketReader.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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 android.net.util.PacketReader;
-import android.os.Handler;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.function.Predicate;
-
-import kotlin.Lazy;
-import kotlin.LazyKt;
-
-public class TapPacketReader extends PacketReader {
-    private final FileDescriptor mTapFd;
-    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);
-        mTapFd = tapFd;
-    }
-
-    @Override
-    protected FileDescriptor createFd() {
-        return mTapFd;
-    }
-
-    @Override
-    protected void handlePacket(byte[] recvbuf, int length) {
-        final byte[] newPacket = Arrays.copyOf(recvbuf, length);
-        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) {
-        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 {
-        try (FileOutputStream out = new FileOutputStream(mTapFd)) {
-            byte[] packetBytes = new byte[packet.limit()];
-            packet.get(packetBytes);
-            packet.flip();  // So we can reuse it in the future.
-            out.write(packetBytes);
-        }
-    }
-}
diff --git a/tests/lib/src/com/android/testutils/TestNetworkTracker.kt b/tests/lib/src/com/android/testutils/TestNetworkTracker.kt
deleted file mode 100644
index 4bd9ae8..0000000
--- a/tests/lib/src/com/android/testutils/TestNetworkTracker.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 android.content.Context
-import android.net.ConnectivityManager
-import android.net.ConnectivityManager.NetworkCallback
-import android.net.LinkAddress
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkRequest
-import android.net.StringNetworkSpecifier
-import android.net.TestNetworkInterface
-import android.net.TestNetworkManager
-import android.os.Binder
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.TimeUnit
-
-/**
- * Create a test network based on a TUN interface.
- *
- * This method will block until the test network is available. Requires
- * [android.Manifest.permission.CHANGE_NETWORK_STATE] and
- * [android.Manifest.permission.MANAGE_TEST_NETWORKS].
- */
-fun initTestNetwork(context: Context, interfaceAddr: LinkAddress, setupTimeoutMs: Long = 10_000L):
-        TestNetworkTracker {
-    val tnm = context.getSystemService(TestNetworkManager::class.java)
-    val iface = tnm.createTunInterface(arrayOf(interfaceAddr))
-    return TestNetworkTracker(context, iface, tnm, setupTimeoutMs)
-}
-
-/**
- * Utility class to create and track test networks.
- *
- * This class is not thread-safe.
- */
-class TestNetworkTracker internal constructor(
-    val context: Context,
-    val iface: TestNetworkInterface,
-    tnm: TestNetworkManager,
-    setupTimeoutMs: Long
-) {
-    private val cm = context.getSystemService(ConnectivityManager::class.java)
-    private val binder = Binder()
-
-    private val networkCallback: NetworkCallback
-    val network: Network
-
-    init {
-        val networkFuture = CompletableFuture<Network>()
-        val networkRequest = NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
-                // Test networks do not have NOT_VPN or TRUSTED capabilities by default
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
-                .setNetworkSpecifier(StringNetworkSpecifier(iface.interfaceName))
-                .build()
-        networkCallback = object : NetworkCallback() {
-            override fun onAvailable(network: Network) {
-                networkFuture.complete(network)
-            }
-        }
-        cm.requestNetwork(networkRequest, networkCallback)
-
-        try {
-            tnm.setupTestNetwork(iface.interfaceName, binder)
-            network = networkFuture.get(setupTimeoutMs, TimeUnit.MILLISECONDS)
-        } catch (e: Throwable) {
-            teardown()
-            throw e
-        }
-    }
-
-    fun teardown() {
-        cm.unregisterNetworkCallback(networkCallback)
-    }
-}
\ No newline at end of file
diff --git a/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt b/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt
deleted file mode 100644
index b63e137..0000000
--- a/tests/lib/src/com/android/testutils/TestableNetworkCallback.kt
+++ /dev/null
@@ -1,392 +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 com.android.testutils
-
-import android.net.ConnectivityManager.NetworkCallback
-import android.net.LinkProperties
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import com.android.testutils.RecorderCallback.CallbackEntry.Available
-import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
-import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
-import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
-import com.android.testutils.RecorderCallback.CallbackEntry.Losing
-import com.android.testutils.RecorderCallback.CallbackEntry.Lost
-import com.android.testutils.RecorderCallback.CallbackEntry.Resumed
-import com.android.testutils.RecorderCallback.CallbackEntry.Suspended
-import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
-import kotlin.reflect.KClass
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
-
-object NULL_NETWORK : Network(-1)
-object ANY_NETWORK : Network(-2)
-
-private val Int.capabilityName get() = NetworkCapabilities.capabilityNameOf(this)
-
-open class RecorderCallback private constructor(
-    private val backingRecord: ArrayTrackRecord<CallbackEntry>
-) : NetworkCallback() {
-    public constructor() : this(ArrayTrackRecord())
-    protected constructor(src: RecorderCallback?): this(src?.backingRecord ?: ArrayTrackRecord())
-
-    sealed class CallbackEntry {
-        // To get equals(), hashcode(), componentN() etc for free, the child classes of
-        // this class are data classes. But while data classes can inherit from other classes,
-        // they may only have visible members in the constructors, so they couldn't declare
-        // a constructor with a non-val arg to pass to CallbackEntry. Instead, force all
-        // subclasses to implement a `network' property, which can be done in a data class
-        // constructor by specifying override.
-        abstract val network: Network
-
-        data class Available(override val network: Network) : CallbackEntry()
-        data class CapabilitiesChanged(
-            override val network: Network,
-            val caps: NetworkCapabilities
-        ) : CallbackEntry()
-        data class LinkPropertiesChanged(
-            override val network: Network,
-            val lp: LinkProperties
-        ) : CallbackEntry()
-        data class Suspended(override val network: Network) : CallbackEntry()
-        data class Resumed(override val network: Network) : CallbackEntry()
-        data class Losing(override val network: Network, val maxMsToLive: Int) : CallbackEntry()
-        data class Lost(override val network: Network) : CallbackEntry()
-        data class Unavailable private constructor(
-            override val network: Network
-        ) : CallbackEntry() {
-            constructor() : this(NULL_NETWORK)
-        }
-        data class BlockedStatus(
-            override val network: Network,
-            val blocked: Boolean
-        ) : CallbackEntry()
-
-        // Convenience constants for expecting a type
-        companion object {
-            @JvmField
-            val AVAILABLE = Available::class
-            @JvmField
-            val NETWORK_CAPS_UPDATED = CapabilitiesChanged::class
-            @JvmField
-            val LINK_PROPERTIES_CHANGED = LinkPropertiesChanged::class
-            @JvmField
-            val SUSPENDED = Suspended::class
-            @JvmField
-            val RESUMED = Resumed::class
-            @JvmField
-            val LOSING = Losing::class
-            @JvmField
-            val LOST = Lost::class
-            @JvmField
-            val UNAVAILABLE = Unavailable::class
-            @JvmField
-            val BLOCKED_STATUS = BlockedStatus::class
-        }
-    }
-
-    val history = backingRecord.newReadHead()
-    val mark get() = history.mark
-
-    override fun onAvailable(network: Network) {
-        history.add(Available(network))
-    }
-
-    // PreCheck is not used in the tests today. For backward compatibility with existing tests that
-    // expect the callbacks not to record this, do not listen to PreCheck here.
-
-    override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
-        history.add(CapabilitiesChanged(network, caps))
-    }
-
-    override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
-        history.add(LinkPropertiesChanged(network, lp))
-    }
-
-    override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
-        history.add(BlockedStatus(network, blocked))
-    }
-
-    override fun onNetworkSuspended(network: Network) {
-        history.add(Suspended(network))
-    }
-
-    override fun onNetworkResumed(network: Network) {
-        history.add(Resumed(network))
-    }
-
-    override fun onLosing(network: Network, maxMsToLive: Int) {
-        history.add(Losing(network, maxMsToLive))
-    }
-
-    override fun onLost(network: Network) {
-        history.add(Lost(network))
-    }
-
-    override fun onUnavailable() {
-        history.add(Unavailable())
-    }
-}
-
-private const val DEFAULT_TIMEOUT = 200L // ms
-
-open class TestableNetworkCallback private constructor(
-    src: TestableNetworkCallback?,
-    val defaultTimeoutMs: Long = DEFAULT_TIMEOUT
-) : RecorderCallback(src) {
-    @JvmOverloads
-    constructor(timeoutMs: Long = DEFAULT_TIMEOUT): this(null, timeoutMs)
-
-    fun createLinkedCopy() = TestableNetworkCallback(this, defaultTimeoutMs)
-
-    // The last available network, or null if any network was lost since the last call to
-    // onAvailable. TODO : fix this by fixing the tests that rely on this behavior
-    val lastAvailableNetwork: Network?
-        get() = when (val it = history.lastOrNull { it is Available || it is Lost }) {
-            is Available -> it.network
-            else -> null
-        }
-
-    fun pollForNextCallback(timeoutMs: Long = defaultTimeoutMs): CallbackEntry {
-        return history.poll(timeoutMs) ?: fail("Did not receive callback after ${timeoutMs}ms")
-    }
-
-    // Make open for use in ConnectivityServiceTest which is the only one knowing its handlers.
-    @JvmOverloads
-    open fun assertNoCallback(timeoutMs: Long = defaultTimeoutMs) {
-        val cb = history.poll(timeoutMs)
-        if (null != cb) fail("Expected no callback but got $cb")
-    }
-
-    // Expects a callback of the specified type on the specified network within the timeout.
-    // If no callback arrives, or a different callback arrives, fail. Returns the callback.
-    inline fun <reified T : CallbackEntry> expectCallback(
-        network: Network = ANY_NETWORK,
-        timeoutMs: Long = defaultTimeoutMs
-    ): T = pollForNextCallback(timeoutMs).let {
-        if (it !is T || (ANY_NETWORK !== network && it.network != network)) {
-            fail("Unexpected callback : $it, expected ${T::class} with Network[$network]")
-        } else {
-            it
-        }
-    }
-
-    // Expects a callback of the specified type matching the predicate within the timeout.
-    // Any callback that doesn't match the predicate will be skipped. Fails only if
-    // no matching callback is received within the timeout.
-    inline fun <reified T : CallbackEntry> eventuallyExpect(
-        timeoutMs: Long = defaultTimeoutMs,
-        from: Int = mark,
-        crossinline predicate: (T) -> Boolean = { true }
-    ): T = eventuallyExpectOrNull(timeoutMs, from, predicate).also {
-        assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms")
-    } as T
-
-    // TODO (b/157405399) straighten and unify the method names
-    inline fun <reified T : CallbackEntry> eventuallyExpectOrNull(
-        timeoutMs: Long = defaultTimeoutMs,
-        from: Int = mark,
-        crossinline predicate: (T) -> Boolean = { true }
-    ) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T?
-
-    fun expectCallbackThat(
-        timeoutMs: Long = defaultTimeoutMs,
-        valid: (CallbackEntry) -> Boolean
-    ) = pollForNextCallback(timeoutMs).also { assertTrue(valid(it), "Unexpected callback : $it") }
-
-    fun expectCapabilitiesThat(
-        net: Network,
-        tmt: Long = defaultTimeoutMs,
-        valid: (NetworkCapabilities) -> Boolean
-    ): CapabilitiesChanged {
-        return expectCallback<CapabilitiesChanged>(net, tmt).also {
-            assertTrue(valid(it.caps), "Capabilities don't match expectations ${it.caps}")
-        }
-    }
-
-    fun expectLinkPropertiesThat(
-        net: Network,
-        tmt: Long = defaultTimeoutMs,
-        valid: (LinkProperties) -> Boolean
-    ): LinkPropertiesChanged {
-        return expectCallback<LinkPropertiesChanged>(net, tmt).also {
-            assertTrue(valid(it.lp), "LinkProperties don't match expectations ${it.lp}")
-        }
-    }
-
-    // Expects onAvailable and the callbacks that follow it. These are:
-    // - onSuspended, iff the network was suspended when the callbacks fire.
-    // - onCapabilitiesChanged.
-    // - onLinkPropertiesChanged.
-    // - onBlockedStatusChanged.
-    //
-    // @param network the network to expect the callbacks on.
-    // @param suspended whether to expect a SUSPENDED callback.
-    // @param validated the expected value of the VALIDATED capability in the
-    //        onCapabilitiesChanged callback.
-    // @param tmt how long to wait for the callbacks.
-    fun expectAvailableCallbacks(
-        net: Network,
-        suspended: Boolean = false,
-        validated: Boolean = true,
-        blocked: Boolean = false,
-        tmt: Long = defaultTimeoutMs
-    ) {
-        expectCallback<Available>(net, tmt)
-        if (suspended) {
-            expectCallback<Suspended>(net, tmt)
-        }
-        expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) }
-        expectCallback<LinkPropertiesChanged>(net, tmt)
-        expectBlockedStatusCallback(blocked, net)
-    }
-
-    // Backward compatibility for existing Java code. Use named arguments instead and remove all
-    // these when there is no user left.
-    fun expectAvailableAndSuspendedCallbacks(
-        net: Network,
-        validated: Boolean,
-        tmt: Long = defaultTimeoutMs
-    ) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt)
-
-    fun expectBlockedStatusCallback(blocked: Boolean, net: Network, tmt: Long = defaultTimeoutMs) {
-        expectCallback<BlockedStatus>(net, tmt).also {
-            assertEquals(it.blocked, blocked, "Unexpected blocked status ${it.blocked}")
-        }
-    }
-
-    // Expects the available callbacks (where the onCapabilitiesChanged must contain the
-    // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
-    // one we just sent.
-    // TODO: this is likely a bug. Fix it and remove this method.
-    fun expectAvailableDoubleValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) {
-        val mark = history.mark
-        expectAvailableCallbacks(net, tmt = tmt)
-        val firstCaps = history.poll(tmt, mark) { it is CapabilitiesChanged }
-        assertEquals(firstCaps, expectCallback<CapabilitiesChanged>(net, tmt))
-    }
-
-    // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
-    // then expects another onCapabilitiesChanged that has the validated bit set. This is used
-    // when a network connects and satisfies a callback, and then immediately validates.
-    fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) {
-        expectAvailableCallbacks(net, validated = false, tmt = tmt)
-        expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
-    }
-
-    // Temporary Java compat measure : have MockNetworkAgent implement this so that all existing
-    // calls with networkAgent can be routed through here without moving MockNetworkAgent.
-    // TODO: clean this up, remove this method.
-    interface HasNetwork {
-        val network: Network
-    }
-
-    @JvmOverloads
-    open fun <T : CallbackEntry> expectCallback(
-        type: KClass<T>,
-        n: Network?,
-        timeoutMs: Long = defaultTimeoutMs
-    ) = pollForNextCallback(timeoutMs).also {
-        val network = n ?: NULL_NETWORK
-        // TODO : remove this .java access if the tests ever use kotlin-reflect. At the time of
-        // this writing this would be the only use of this library in the tests.
-        assertTrue(type.java.isInstance(it) && it.network == network,
-                "Unexpected callback : $it, expected ${type.java} with Network[$network]")
-    } as T
-
-    @JvmOverloads
-    open fun <T : CallbackEntry> expectCallback(
-        type: KClass<T>,
-        n: HasNetwork?,
-        timeoutMs: Long = defaultTimeoutMs
-    ) = expectCallback(type, n?.network, timeoutMs)
-
-    fun expectAvailableCallbacks(
-        n: HasNetwork,
-        suspended: Boolean,
-        validated: Boolean,
-        blocked: Boolean,
-        timeoutMs: Long
-    ) = expectAvailableCallbacks(n.network, suspended, validated, blocked, timeoutMs)
-
-    fun expectAvailableAndSuspendedCallbacks(n: HasNetwork, expectValidated: Boolean) {
-        expectAvailableAndSuspendedCallbacks(n.network, expectValidated)
-    }
-
-    fun expectAvailableCallbacksValidated(n: HasNetwork) {
-        expectAvailableCallbacks(n.network)
-    }
-
-    fun expectAvailableCallbacksValidatedAndBlocked(n: HasNetwork) {
-        expectAvailableCallbacks(n.network, blocked = true)
-    }
-
-    fun expectAvailableCallbacksUnvalidated(n: HasNetwork) {
-        expectAvailableCallbacks(n.network, validated = false)
-    }
-
-    fun expectAvailableCallbacksUnvalidatedAndBlocked(n: HasNetwork) {
-        expectAvailableCallbacks(n.network, validated = false, blocked = true)
-    }
-
-    fun expectAvailableDoubleValidatedCallbacks(n: HasNetwork) {
-        expectAvailableDoubleValidatedCallbacks(n.network, defaultTimeoutMs)
-    }
-
-    fun expectAvailableThenValidatedCallbacks(n: HasNetwork) {
-        expectAvailableThenValidatedCallbacks(n.network, defaultTimeoutMs)
-    }
-
-    @JvmOverloads
-    fun expectLinkPropertiesThat(
-        n: HasNetwork,
-        tmt: Long = defaultTimeoutMs,
-        valid: (LinkProperties) -> Boolean
-    ) = expectLinkPropertiesThat(n.network, tmt, valid)
-
-    @JvmOverloads
-    fun expectCapabilitiesThat(
-        n: HasNetwork,
-        tmt: Long = defaultTimeoutMs,
-        valid: (NetworkCapabilities) -> Boolean
-    ) = expectCapabilitiesThat(n.network, tmt, valid)
-
-    @JvmOverloads
-    fun expectCapabilitiesWith(
-        capability: Int,
-        n: HasNetwork,
-        timeoutMs: Long = defaultTimeoutMs
-    ): NetworkCapabilities {
-        return expectCapabilitiesThat(n.network, timeoutMs) { it.hasCapability(capability) }.caps
-    }
-
-    @JvmOverloads
-    fun expectCapabilitiesWithout(
-        capability: Int,
-        n: HasNetwork,
-        timeoutMs: Long = defaultTimeoutMs
-    ): NetworkCapabilities {
-        return expectCapabilitiesThat(n.network, timeoutMs) { !it.hasCapability(capability) }.caps
-    }
-
-    fun expectBlockedStatusCallback(expectBlocked: Boolean, n: HasNetwork) {
-        expectBlockedStatusCallback(expectBlocked, n.network, defaultTimeoutMs)
-    }
-}
diff --git a/tests/lib/src/com/android/testutils/TestableNetworkStatsProvider.kt b/tests/lib/src/com/android/testutils/TestableNetworkStatsProvider.kt
deleted file mode 100644
index a4ef770..0000000
--- a/tests/lib/src/com/android/testutils/TestableNetworkStatsProvider.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 android.net.netstats.provider.NetworkStatsProvider
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import kotlin.test.fail
-
-private const val DEFAULT_TIMEOUT_MS = 200L
-
-open class TestableNetworkStatsProvider(
-    val defaultTimeoutMs: Long = DEFAULT_TIMEOUT_MS
-) : NetworkStatsProvider() {
-    sealed class CallbackType {
-        data class OnRequestStatsUpdate(val token: Int) : CallbackType()
-        data class OnSetLimit(val iface: String?, val quotaBytes: Long) : CallbackType()
-        data class OnSetAlert(val quotaBytes: Long) : CallbackType()
-    }
-
-    val history = ArrayTrackRecord<CallbackType>().newReadHead()
-    // See ReadHead#mark
-    val mark get() = history.mark
-
-    override fun onRequestStatsUpdate(token: Int) {
-        history.add(CallbackType.OnRequestStatsUpdate(token))
-    }
-
-    override fun onSetLimit(iface: String, quotaBytes: Long) {
-        history.add(CallbackType.OnSetLimit(iface, quotaBytes))
-    }
-
-    override fun onSetAlert(quotaBytes: Long) {
-        history.add(CallbackType.OnSetAlert(quotaBytes))
-    }
-
-    fun expectOnRequestStatsUpdate(token: Int, timeout: Long = defaultTimeoutMs) {
-        assertEquals(CallbackType.OnRequestStatsUpdate(token), history.poll(timeout))
-    }
-
-    fun expectOnSetLimit(iface: String?, quotaBytes: Long, timeout: Long = defaultTimeoutMs) {
-        assertEquals(CallbackType.OnSetLimit(iface, quotaBytes), history.poll(timeout))
-    }
-
-    fun expectOnSetAlert(quotaBytes: Long, timeout: Long = defaultTimeoutMs) {
-        assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(timeout))
-    }
-
-    fun pollForNextCallback(timeout: Long = defaultTimeoutMs) =
-        history.poll(timeout) ?: fail("Did not receive callback after ${timeout}ms")
-
-    inline fun <reified T : CallbackType> expectCallback(
-        timeout: Long = defaultTimeoutMs,
-        predicate: (T) -> Boolean = { true }
-    ): T {
-        return pollForNextCallback(timeout).also { assertTrue(it is T && predicate(it)) } as T
-    }
-
-    // Expects a callback of the specified type matching the predicate within the timeout.
-    // Any callback that doesn't match the predicate will be skipped. Fails only if
-    // no matching callback is received within the timeout.
-    // TODO : factorize the code for this with the identical call in TestableNetworkCallback.
-    // There should be a common superclass doing this generically.
-    // TODO : have a better error message to have this fail. Right now the failure when no
-    // matching callback arrives comes from the casting to a non-nullable T.
-    // TODO : in fact, completely removing this method and have clients use
-    // history.poll(timeout, index, predicate) directly might be simpler.
-    inline fun <reified T : CallbackType> eventuallyExpect(
-        timeoutMs: Long = defaultTimeoutMs,
-        from: Int = mark,
-        crossinline predicate: (T) -> Boolean = { true }
-    ) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T
-
-    fun drainCallbacks() {
-        history.mark = history.size
-    }
-
-    @JvmOverloads
-    fun assertNoCallback(timeout: Long = defaultTimeoutMs) {
-        val cb = history.poll(timeout)
-        cb?.let { fail("Expected no callback but got $cb") }
-    }
-}
diff --git a/tests/lib/src/com/android/testutils/TestableNetworkStatsProviderBinder.kt b/tests/lib/src/com/android/testutils/TestableNetworkStatsProviderBinder.kt
deleted file mode 100644
index 4d9f884..0000000
--- a/tests/lib/src/com/android/testutils/TestableNetworkStatsProviderBinder.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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 android.net.netstats.provider.INetworkStatsProvider
-import kotlin.test.assertEquals
-import kotlin.test.fail
-
-private const val DEFAULT_TIMEOUT_MS = 200L
-
-open class TestableNetworkStatsProviderBinder : INetworkStatsProvider.Stub() {
-    sealed class CallbackType {
-        data class OnRequestStatsUpdate(val token: Int) : CallbackType()
-        data class OnSetLimit(val iface: String?, val quotaBytes: Long) : CallbackType()
-        data class OnSetAlert(val quotaBytes: Long) : CallbackType()
-    }
-
-    private val history = ArrayTrackRecord<CallbackType>().ReadHead()
-
-    override fun onRequestStatsUpdate(token: Int) {
-        history.add(CallbackType.OnRequestStatsUpdate(token))
-    }
-
-    override fun onSetLimit(iface: String?, quotaBytes: Long) {
-        history.add(CallbackType.OnSetLimit(iface, quotaBytes))
-    }
-
-    override fun onSetAlert(quotaBytes: Long) {
-        history.add(CallbackType.OnSetAlert(quotaBytes))
-    }
-
-    fun expectOnRequestStatsUpdate(token: Int) {
-        assertEquals(CallbackType.OnRequestStatsUpdate(token), history.poll(DEFAULT_TIMEOUT_MS))
-    }
-
-    fun expectOnSetLimit(iface: String?, quotaBytes: Long) {
-        assertEquals(CallbackType.OnSetLimit(iface, quotaBytes), history.poll(DEFAULT_TIMEOUT_MS))
-    }
-
-    fun expectOnSetAlert(quotaBytes: Long) {
-        assertEquals(CallbackType.OnSetAlert(quotaBytes), history.poll(DEFAULT_TIMEOUT_MS))
-    }
-
-    @JvmOverloads
-    fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) {
-        val cb = history.poll(timeout)
-        cb?.let { fail("Expected no callback but got $cb") }
-    }
-}
diff --git a/tests/lib/src/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt b/tests/lib/src/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
deleted file mode 100644
index abce700..0000000
--- a/tests/lib/src/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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 android.net.NetworkStats
-import android.net.netstats.provider.INetworkStatsProviderCallback
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import kotlin.test.fail
-
-private const val DEFAULT_TIMEOUT_MS = 3000L
-
-open class TestableNetworkStatsProviderCbBinder : INetworkStatsProviderCallback.Stub() {
-    sealed class CallbackType {
-        data class NotifyStatsUpdated(
-            val token: Int,
-            val ifaceStats: NetworkStats,
-            val uidStats: NetworkStats
-        ) : CallbackType()
-        object NotifyLimitReached : CallbackType()
-        object NotifyAlertReached : CallbackType()
-        object Unregister : CallbackType()
-    }
-
-    private val history = ArrayTrackRecord<CallbackType>().ReadHead()
-
-    override fun notifyStatsUpdated(token: Int, ifaceStats: NetworkStats, uidStats: NetworkStats) {
-        history.add(CallbackType.NotifyStatsUpdated(token, ifaceStats, uidStats))
-    }
-
-    override fun notifyLimitReached() {
-        history.add(CallbackType.NotifyLimitReached)
-    }
-
-    override fun notifyAlertReached() {
-        history.add(CallbackType.NotifyAlertReached)
-    }
-
-    override fun unregister() {
-        history.add(CallbackType.Unregister)
-    }
-
-    fun expectNotifyStatsUpdated() {
-        val event = history.poll(DEFAULT_TIMEOUT_MS)
-        assertTrue(event is CallbackType.NotifyStatsUpdated)
-    }
-
-    fun expectNotifyStatsUpdated(ifaceStats: NetworkStats, uidStats: NetworkStats) {
-        val event = history.poll(DEFAULT_TIMEOUT_MS)!!
-        if (event !is CallbackType.NotifyStatsUpdated) {
-            throw Exception("Expected NotifyStatsUpdated callback, but got ${event::class}")
-        }
-        // TODO: verify token.
-        assertNetworkStatsEquals(ifaceStats, event.ifaceStats)
-        assertNetworkStatsEquals(uidStats, event.uidStats)
-    }
-
-    fun expectNotifyLimitReached() =
-            assertEquals(CallbackType.NotifyLimitReached, history.poll(DEFAULT_TIMEOUT_MS))
-
-    fun expectNotifyAlertReached() =
-            assertEquals(CallbackType.NotifyAlertReached, history.poll(DEFAULT_TIMEOUT_MS))
-
-    // Assert there is no callback in current queue.
-    fun assertNoCallback() {
-        val cb = history.poll(0)
-        cb?.let { fail("Expected no callback but got $cb") }
-    }
-}
diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
index 9d2a630..ff03a8d 100644
--- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
@@ -85,6 +85,7 @@
     // doesn't use == instead of equals when comparing addresses.
     private static final Inet4Address ANY = v4Address("0.0.0.0");
     private static final byte[] TEST_EMPTY_OPTIONS_SKIP_LIST = new byte[0];
+    private static final int TEST_IPV6_ONLY_WAIT_S = 1800; // 30 min
 
     private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
@@ -484,6 +485,53 @@
         assertTrue(dhcpResults.captivePortalApiUrl.length() > 0);
     }
 
+    private void runIPv6OnlyPreferredOption(boolean enabled) throws Exception {
+        // CHECKSTYLE:OFF Generated code
+        final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
+                // IP header.
+                "45100158000040004011B5CEC0A80164C0A80102" +
+                // UDP header
+                "004300440144CE63" +
+                // BOOTP header
+                "02010600B8BF41E60000000000000000C0A80102C0A8016400000000" +
+                // MAC address.
+                "22B3614EE01200000000000000000000" +
+                // Server name and padding.
+                "0000000000000000000000000000000000000000000000000000000000000000" +
+                "0000000000000000000000000000000000000000000000000000000000000000" +
+                // File.
+                "0000000000000000000000000000000000000000000000000000000000000000" +
+                "0000000000000000000000000000000000000000000000000000000000000000" +
+                "0000000000000000000000000000000000000000000000000000000000000000" +
+                "0000000000000000000000000000000000000000000000000000000000000000" +
+                // Options
+                "638253633501023604C0A80164330400000E103A04000007083B0400000C4E01" +
+                "04FFFFFF001C04C0A801FF0304C0A801640604C0A801640C0C74657374686F73" +
+                "746E616D651A0205DC" +
+                // Option 108 (0x6c, IPv6-Only preferred option), length 4 (0x04), 1800s
+                "6C0400000708" +
+                // End of options.
+                "FF"));
+        // CHECKSTYLE:ON Generated code
+
+        final DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3,
+                enabled ? TEST_EMPTY_OPTIONS_SKIP_LIST
+                        : new byte[] { DhcpPacket.DHCP_IPV6_ONLY_PREFERRED });
+        assertTrue(offerPacket instanceof DhcpOfferPacket);
+        assertEquals(offerPacket.mIpv6OnlyWaitTime,
+                enabled ? new Integer(TEST_IPV6_ONLY_WAIT_S) : null);
+    }
+
+    @Test
+    public void testIPv6OnlyPreferredOption() throws Exception {
+        runIPv6OnlyPreferredOption(true /* enabled */);
+    }
+
+    @Test
+    public void testIPv6OnlyPreferredOption_Disable() throws Exception {
+        runIPv6OnlyPreferredOption(false /* enabled */);
+    }
+
     @Test
     public void testBadIpPacket() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
diff --git a/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
index 7d899e0..9ec1271 100644
--- a/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
@@ -20,7 +20,7 @@
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
 import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
 
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/tests/unit/src/android/net/dhcp/DhcpServerTest.java b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
index d313b94..58f9a36 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServerTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
@@ -53,12 +53,13 @@
 import android.net.dhcp.DhcpServer.Clock;
 import android.net.dhcp.DhcpServer.Dependencies;
 import android.net.util.SharedLog;
+import android.os.ConditionVariable;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.net.module.util.Inet4AddressUtils;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -130,11 +131,30 @@
     private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
 
     @NonNull
-    private DhcpServer mServer;
+    private MyDhcpServer mServer;
 
     @Nullable
     private String mPrevShareClassloaderProp;
 
+    private class MyDhcpServer extends DhcpServer {
+        private final ConditionVariable mCv = new ConditionVariable(false);
+
+        MyDhcpServer(Context context, String ifName, DhcpServingParams params, SharedLog log,
+                Dependencies deps) {
+            super(context, ifName, params, log, deps);
+        }
+
+        @Override
+        protected void onQuitting() {
+            super.onQuitting();
+            mCv.open();
+        }
+
+        public void waitForShutdown() {
+            assertTrue(mCv.block(TEST_TIMEOUT_MS));
+        }
+    }
+
     private final INetworkStackStatusCallback mAssertSuccessCallback =
             new INetworkStackStatusCallback.Stub() {
         @Override
@@ -167,7 +187,7 @@
 
     private void startServer() throws Exception {
         mServer.start(mAssertSuccessCallback);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
     }
 
     @Before
@@ -183,7 +203,7 @@
         when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
         when(mPacketListener.start()).thenReturn(true);
 
-        mServer = new DhcpServer(mContext, TEST_IFACE, makeServingParams(),
+        mServer = new MyDhcpServer(mContext, TEST_IFACE, makeServingParams(),
                 new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
     }
 
@@ -191,7 +211,7 @@
     public void tearDown() throws Exception {
         verify(mRepository, never()).addLeaseCallbacks(eq(null));
         mServer.stop(mAssertSuccessCallback);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        mServer.waitForShutdown();
         verify(mPacketListener, times(1)).stop();
     }
 
@@ -205,7 +225,7 @@
     @Test
     public void testStartWithCallbacks() throws Exception {
         mServer.start(mAssertSuccessCallback, mEventCallbacks);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
         verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));
     }
 
@@ -222,7 +242,7 @@
                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                 false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         assertResponseSentTo(TEST_CLIENT_ADDR);
         final DhcpOfferPacket packet = assertOffer(getPacket());
@@ -241,7 +261,7 @@
                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                 false /* broadcast */, INADDR_ANY /* srcIp */, true /* rapidCommit */);
         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         assertResponseSentTo(TEST_CLIENT_ADDR);
         final DhcpAckPacket packet = assertAck(getPacket());
@@ -260,7 +280,7 @@
                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                 false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         assertResponseSentTo(INADDR_BROADCAST);
         final DhcpNakPacket packet = assertNak(getPacket());
@@ -289,7 +309,7 @@
         request.mHostName = TEST_HOSTNAME;
         request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
         mServer.sendMessage(CMD_RECEIVE_PACKET, request);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         assertResponseSentTo(TEST_CLIENT_ADDR);
         final DhcpAckPacket packet = assertAck(getPacket());
@@ -307,7 +327,7 @@
 
         final DhcpRequestPacket request = makeRequestSelectingPacket();
         mServer.sendMessage(CMD_RECEIVE_PACKET, request);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         assertResponseSentTo(INADDR_BROADCAST);
         final DhcpNakPacket packet = assertNak(getPacket());
@@ -322,7 +342,7 @@
                 TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
                 INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
         mServer.sendMessage(CMD_RECEIVE_PACKET, release);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         verify(mRepository, times(1))
                 .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
@@ -348,7 +368,7 @@
                 INADDR_ANY /* nextIp */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                 TEST_CLIENT_ADDR /* requestedIp */, TEST_SERVER_ADDR /* serverIdentifier */);
         mServer.sendMessage(CMD_RECEIVE_PACKET, decline);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
 
         verify(mRepository).markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
                 eq(TEST_CLIENT_ADDR));
@@ -373,7 +393,7 @@
         params.changePrefixOnDecline = changePrefixOnDecline;
 
         mServer.updateParams(params, mAssertSuccessCallback);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
     }
 
     @Test
@@ -382,7 +402,7 @@
                 eq(TEST_CLIENT_ADDR))).thenReturn(true);
 
         mServer.start(mAssertSuccessCallback, mEventCallbacks);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
         verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));
 
         // Enable changePrefixOnDecline
@@ -416,7 +436,7 @@
                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
                 false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
-        HandlerUtilsKt.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
         assertResponseSentTo(clientAddr);
         final DhcpOfferPacket packet = assertOffer(getPacket());
         assertMatchesLease(packet, serverAddr, clientAddr, null);
diff --git a/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java b/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
index 6506eba..290fd69 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -34,7 +34,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.net.module.util.Inet4AddressUtils;
-import com.android.testutils.MiscAssertsKt;
+import com.android.testutils.MiscAsserts;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -199,7 +199,7 @@
         assertEquals(params.singleClientAddr, parceled.singleClientAddr);
         assertEquals(params.changePrefixOnDecline, parceled.changePrefixOnDecline);
 
-        MiscAssertsKt.assertFieldCountEquals(10, DhcpServingParamsParcel.class);
+        MiscAsserts.assertFieldCountEquals(10, DhcpServingParamsParcel.class);
     }
 
     @Test(expected = InvalidParameterException.class)
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index e2b8e4f..e991ea7 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -61,7 +61,7 @@
 import com.android.server.NetworkObserverRegistry;
 import com.android.server.NetworkStackService;
 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -262,7 +262,7 @@
                 lp.getDnsServers().stream().map(InetAddress::getHostAddress)
                         .toArray(String[]::new));
 
-        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
         verify(mCb, never()).onProvisioningFailure(any());
         verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any());
 
@@ -301,7 +301,7 @@
 
         reset(mCb);
         doIPv6ProvisioningLoss(lp);
-        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
         verify(mCb).onProvisioningFailure(lp);
         verify(mCb).onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
 
@@ -326,11 +326,11 @@
         final IpClient ipc = doProvisioningWithDefaultConfiguration();
         final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
         addIPv4Provisioning(lp);
-        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
 
         reset(mCb);
         doIPv6ProvisioningLoss(lp);
-        HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
         if (avoidBadWifi) { // Provisioning failure is expected only when avoidBadWifi is true
             verify(mCb).onProvisioningFailure(lp);
             verify(mCb).onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
diff --git a/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt b/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
index 1bc3ab0..3b485eb 100644
--- a/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
+++ b/tests/unit/src/android/net/netlink/NetlinkTestUtils.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:JvmName("NetlinkTestUtils")
+
 package android.net.netlink
 
 import android.net.netlink.NetlinkConstants.RTM_DELNEIGH
diff --git a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
index 34257b8..ca9ae80 100644
--- a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -16,8 +16,8 @@
 
 package android.net.netlink;
 
-import static android.net.netlink.NetlinkTestUtilsKt.makeDelNeighMessage;
-import static android.net.netlink.NetlinkTestUtilsKt.makeNewNeighMessage;
+import static android.net.netlink.NetlinkTestUtils.makeDelNeighMessage;
+import static android.net.netlink.NetlinkTestUtils.makeNewNeighMessage;
 import static android.net.netlink.StructNdMsg.NUD_STALE;
 
 import static org.junit.Assert.assertEquals;
diff --git a/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java b/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java
index a09805e..0f3020d 100644
--- a/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java
+++ b/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java
@@ -20,7 +20,7 @@
 import static android.net.netlink.StructNdOptPref64.plcToPrefixLength;
 import static android.net.netlink.StructNdOptPref64.prefixLengthToPlc;
 
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
diff --git a/tests/unit/src/android/net/shared/InitialConfigurationTest.java b/tests/unit/src/android/net/shared/InitialConfigurationTest.java
index 17f8324..4d5a7df 100644
--- a/tests/unit/src/android/net/shared/InitialConfigurationTest.java
+++ b/tests/unit/src/android/net/shared/InitialConfigurationTest.java
@@ -18,7 +18,7 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
index 49cea7d..28e787b 100644
--- a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
+++ b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
@@ -19,7 +19,7 @@
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
 
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
index 4e4d25a..5b91985 100644
--- a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
+++ b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
@@ -4,7 +4,7 @@
 import android.net.LinkProperties
 import android.net.Network
 import android.net.NetworkCapabilities
-import com.android.testutils.ConcurrentIntepreter
+import com.android.testutils.ConcurrentInterpreter
 import com.android.testutils.InterpretMatcher
 import com.android.testutils.RecorderCallback.CallbackEntry
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
@@ -247,7 +247,7 @@
     }
 }
 
-private object TNCInterpreter : ConcurrentIntepreter<TestableNetworkCallback>(interpretTable)
+private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable)
 
 val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|")
 private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> {
diff --git a/tests/unit/src/android/net/util/PacketReaderTest.java b/tests/unit/src/android/net/util/PacketReaderTest.java
deleted file mode 100644
index 3947d15..0000000
--- a/tests/unit/src/android/net/util/PacketReaderTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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.util;
-
-import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static android.system.OsConstants.SOCK_NONBLOCK;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_SNDTIMEO;
-
-import static com.android.testutils.MiscAssertsKt.assertThrows;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructTimeval;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileDescriptor;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketException;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for PacketReader.
- *
- * @hide
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PacketReaderTest {
-    static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
-    static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
-
-    protected CountDownLatch mLatch;
-    protected FileDescriptor mLocalSocket;
-    protected InetSocketAddress mLocalSockName;
-    protected byte[] mLastRecvBuf;
-    protected boolean mStopped;
-    protected HandlerThread mHandlerThread;
-    protected PacketReader mReceiver;
-
-    class UdpLoopbackReader extends PacketReader {
-        public UdpLoopbackReader(Handler h) {
-            super(h);
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            FileDescriptor s = null;
-            try {
-                s = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
-                Os.bind(s, LOOPBACK6, 0);
-                mLocalSockName = (InetSocketAddress) Os.getsockname(s);
-                Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
-            } catch (ErrnoException|SocketException e) {
-                closeFd(s);
-                fail();
-                return null;
-            }
-
-            mLocalSocket = s;
-            return s;
-        }
-
-        @Override
-        protected void handlePacket(byte[] recvbuf, int length) {
-            mLastRecvBuf = Arrays.copyOf(recvbuf, length);
-            mLatch.countDown();
-        }
-
-        @Override
-        protected void onStart() {
-            mStopped = false;
-            mLatch.countDown();
-        }
-
-        @Override
-        protected void onStop() {
-            mStopped = true;
-            mLatch.countDown();
-        }
-    };
-
-    @Before
-    public void setUp() {
-        resetLatch();
-        mLocalSocket = null;
-        mLocalSockName = null;
-        mLastRecvBuf = null;
-        mStopped = false;
-
-        mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
-        mHandlerThread.start();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mReceiver != null) {
-            mHandlerThread.getThreadHandler().post(() -> { mReceiver.stop(); });
-            waitForActivity();
-        }
-        mReceiver = null;
-        mHandlerThread.quit();
-        mHandlerThread = null;
-    }
-
-    void resetLatch() { mLatch = new CountDownLatch(1); }
-
-    void waitForActivity() throws Exception {
-        try {
-            mLatch.await(1000, TimeUnit.MILLISECONDS);
-        } finally {
-            resetLatch();
-        }
-    }
-
-    void sendPacket(byte[] contents) throws Exception {
-        final DatagramSocket sender = new DatagramSocket();
-        sender.connect(mLocalSockName);
-        sender.send(new DatagramPacket(contents, contents.length));
-        sender.close();
-    }
-
-    @Test
-    public void testBasicWorking() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-        mReceiver = new UdpLoopbackReader(h);
-
-        h.post(() -> { mReceiver.start(); });
-        waitForActivity();
-        assertTrue(mLocalSockName != null);
-        assertEquals(LOOPBACK6, mLocalSockName.getAddress());
-        assertTrue(0 < mLocalSockName.getPort());
-        assertTrue(mLocalSocket != null);
-        assertFalse(mStopped);
-
-        final byte[] one = "one 1".getBytes("UTF-8");
-        sendPacket(one);
-        waitForActivity();
-        assertEquals(1, mReceiver.numPacketsReceived());
-        assertTrue(Arrays.equals(one, mLastRecvBuf));
-        assertFalse(mStopped);
-
-        final byte[] two = "two 2".getBytes("UTF-8");
-        sendPacket(two);
-        waitForActivity();
-        assertEquals(2, mReceiver.numPacketsReceived());
-        assertTrue(Arrays.equals(two, mLastRecvBuf));
-        assertFalse(mStopped);
-
-        h.post(() -> mReceiver.stop());
-        waitForActivity();
-        assertEquals(2, mReceiver.numPacketsReceived());
-        assertTrue(Arrays.equals(two, mLastRecvBuf));
-        assertTrue(mStopped);
-        mReceiver = null;
-    }
-
-    class NullPacketReader extends PacketReader {
-        public NullPacketReader(Handler h, int recvbufsize) {
-            super(h, recvbufsize);
-        }
-
-        @Override
-        public FileDescriptor createFd() { return null; }
-    }
-
-    @Test
-    public void testMinimalRecvBufSize() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-
-        for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
-            final PacketReader b = new NullPacketReader(h, i);
-            assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
-        }
-    }
-
-    @Test
-    public void testStartingFromWrongThread() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-        final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE);
-        assertThrows(IllegalStateException.class, () -> b.start());
-    }
-
-    @Test
-    public void testStoppingFromWrongThread() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-        final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE);
-        assertThrows(IllegalStateException.class, () -> b.stop());
-    }
-
-    @Test
-    public void testSuccessToCreateSocket() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-        final PacketReader b = new UdpLoopbackReader(h);
-        h.post(() -> assertTrue(b.start()));
-    }
-
-    @Test
-    public void testFailToCreateSocket() throws Exception {
-        final Handler h = mHandlerThread.getThreadHandler();
-        final PacketReader b = new NullPacketReader(h, DEFAULT_RECV_BUF_SIZE);
-        h.post(() -> assertFalse(b.start()));
-    }
-}
diff --git a/tests/unit/src/android/net/testutils/TrackRecordTest.kt b/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
similarity index 98%
rename from tests/unit/src/android/net/testutils/TrackRecordTest.kt
rename to tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
index 995d537..c2bdcd4 100644
--- a/tests/unit/src/android/net/testutils/TrackRecordTest.kt
+++ b/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package android.net.testutils
+package com.android.net.module.util
 
-import com.android.testutils.ArrayTrackRecord
-import com.android.testutils.ConcurrentIntepreter
+import com.android.testutils.ConcurrentInterpreter
 import com.android.testutils.InterpretException
 import com.android.testutils.InterpretMatcher
 import com.android.testutils.SyntaxException
-import com.android.testutils.TrackRecord
 import com.android.testutils.__FILE__
 import com.android.testutils.__LINE__
 import com.android.testutils.intArg
@@ -350,7 +348,7 @@
     }
 }
 
-private object TRTInterpreter : ConcurrentIntepreter<TrackRecord<Int>>(interpretTable) {
+private object TRTInterpreter : ConcurrentInterpreter<TrackRecord<Int>>(interpretTable) {
     fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) {
         interpretTestSpec(spec, initial = ArrayTrackRecord(),
                 threadTransform = { (it as ArrayTrackRecord).newReadHead() })
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
index b2607eb..348392d 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -22,7 +22,7 @@
 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
@@ -57,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
@@ -188,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() {
@@ -279,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/arp/ArpPacketTest.java b/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java
index 18f4e04..12dc0fb 100644
--- a/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java
+++ b/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java
@@ -18,7 +18,7 @@
 
 import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
 import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
-import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
index af68cde..98e7b63 100644
--- a/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
@@ -26,7 +26,6 @@
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
 import android.net.INetworkMonitor;
 import android.net.NetworkCapabilities;
@@ -54,7 +53,7 @@
     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_NOT_METERED_CAPABILITIES =
+    private static final NetworkCapabilities WIFI_CAPABILITIES =
             new NetworkCapabilities()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
 
@@ -136,17 +135,17 @@
 
     @Test
     public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception {
-        final NetworkValidationMetrics Metrics = new NetworkValidationMetrics();
-        Metrics.reset(WIFI_NOT_METERED_CAPABILITIES);
+        final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+        metrics.startCollection(WIFI_CAPABILITIES);
         // 1. PT_DNS probe
-        Metrics.setProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null);
+        metrics.addProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null);
         // 2. Consecutive PT_HTTP probe failure
         for (int i = 0; i < 30; i++) {
-            Metrics.setProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null);
+            metrics.addProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null);
         }
 
         // Write metric into statsd
-        final NetworkValidationReported stats = Metrics.sendValidationStats();
+        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();
@@ -156,40 +155,39 @@
 
     @Test
     public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception {
-        assumeTrue(CaptivePortalDataShimImpl.isSupported());
-        final long bytesRemaining = 10L;
+        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 NetworkValidationMetrics metrics = new NetworkValidationMetrics();
         final int validationIndex = 1;
-        final long longlatency = 2147483649L;
-        Metrics.reset(WIFI_NOT_METERED_CAPABILITIES);
+        final long longlatency = Integer.MAX_VALUE + 12344567L;
+        metrics.startCollection(WIFI_CAPABILITIES);
 
         final JSONObject info = new JSONObject(apiContent);
-        final CaptivePortalDataShim captivePortalData =
-                CaptivePortalDataShimImpl.fromJson(info);
+        final CaptivePortalDataShim captivePortalData = CaptivePortalDataShimImpl.isSupported()
+                ? CaptivePortalDataShimImpl.fromJson(info) : null;
 
         // 1. PT_CAPPORT_API probe w CapportApiData info
-        Metrics.setProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS,
+        metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS,
                 captivePortalData);
         // 2. PT_CAPPORT_API probe w/o CapportApiData info
-        Metrics.setProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null);
+        metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null);
 
         // 3. PT_DNS probe
-        Metrics.setProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null);
+        metrics.addProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null);
 
         // 4. PT_HTTP probe
-        Metrics.setProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null);
+        metrics.addProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null);
 
         // add Validation result
-        Metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null);
+        metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null);
 
         // Write metric into statsd
-        final NetworkValidationReported stats = Metrics.sendValidationStats();
+        final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend();
 
         // Verify: TransportType: WIFI
         assertEquals(TransportType.TT_WIFI, stats.getTransportType());
@@ -207,13 +205,13 @@
         assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType());
         assertEquals(1234, probeEvent.getLatencyMicros());
         assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult());
-        assertEquals(true, probeEvent.hasCapportApiData());
         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(), capportData.getRemainingBytes());
+            assertEquals(captivePortalData.getByteLimit() / 1000, capportData.getRemainingBytes());
         } else {
             assertFalse(probeEvent.hasCapportApiData());
         }
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
index a884f7e..8fbe0c4 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -207,7 +207,6 @@
     private static final int TEST_NETID2_FWMARK = 0x1A85;
     private static final int NETID_MASK = 0xffff;
     @Mock private TcpSocketTracker.Dependencies mDependencies;
-    @Mock private FileDescriptor mMockFd;
     @Mock private INetd mNetd;
     private final Network mNetwork = new Network(TEST_NETID1);
     private final Network mOtherNetwork = new Network(TEST_NETID2);
@@ -226,7 +225,7 @@
                 Log.setWtfHandler((tag, what, system) -> Log.e(tag, what.getMessage(), what));
         when(mDependencies.getNetd()).thenReturn(mNetd);
         when(mDependencies.isTcpInfoParsingSupported()).thenReturn(true);
-        when(mDependencies.connectToKernel()).thenReturn(mMockFd);
+        when(mDependencies.connectToKernel()).thenReturn(new FileDescriptor());
         when(mDependencies.getDeviceConfigPropertyInt(
                 eq(NAMESPACE_CONNECTIVITY),
                 eq(CONFIG_TCP_PACKETS_FAIL_PERCENTAGE),
diff --git a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
index c054b3a..b32a419 100644
--- a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
+++ b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
@@ -31,6 +31,7 @@
 import android.net.ip.IpClient
 import android.os.Build
 import android.os.IBinder
+import android.os.Process
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH
@@ -42,6 +43,8 @@
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.ExceptionUtils
+import com.android.testutils.assertThrows
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -172,6 +175,7 @@
                 mock(IBinder::class.java)))
         doReturn(9990003).`when`(mockNetworkMonitorCb).interfaceVersion
         doReturn("networkmonitor_hash").`when`(mockNetworkMonitorCb).interfaceHash
+        INetworkMonitorCallbacks.Stub.setDefaultImpl(INetworkMonitorCallbacks.Default())
 
         connector.makeNetworkMonitor(Network(123), "test_nm", mockNetworkMonitorCb)
 
@@ -202,9 +206,15 @@
         connector.makeDhcpServer(TEST_IFACE, testParams, mockDhcpCb)
         verify(mockDhcpCb, times(2)).onDhcpServerCreated(eq(IDhcpServer.STATUS_SUCCESS), any())
 
-        // Verify all methods were covered by the test (4 methods + getVersion + getHash)
-        assertEquals(6, INetworkStackConnector::class.declaredMemberFunctions.count {
+        // allowTestUid does not need to record the caller's version
+        assertThrows(SecurityException::class.java, ExceptionUtils.ThrowingRunnable {
+            // Should throw because the test does not run as root
+            connector.allowTestUid(Process.myUid(), null)
+        })
+
+        // Verify all methods were covered by the test (5 methods + getVersion + getHash)
+        assertEquals(7, INetworkStackConnector::class.declaredMemberFunctions.count {
             it.visibility == KVisibility.PUBLIC
         })
     }
-}
\ No newline at end of file
+}
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index bbba171..0d3a51a 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -152,7 +152,7 @@
 import com.android.server.connectivity.nano.WifiData;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.HandlerUtils;
 
 import com.google.protobuf.nano.MessageNano;
 
@@ -631,7 +631,7 @@
         final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
         nm.start();
         setNetworkCapabilities(nm, nc);
-        HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
         mCreatedNetworkMonitors.add(nm);
         when(mTstDependencies.isTcpInfoParsingSupported()).thenReturn(false);
 
@@ -655,7 +655,7 @@
 
     private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) {
         nm.notifyNetworkCapabilitiesChanged(nc);
-        HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
     }
 
     @Test
@@ -1541,6 +1541,32 @@
     }
 
     @Test
+    public void testIsCaptivePortal_TestUrlsWithUrlOverlays() throws Exception {
+        setupResourceForMultipleProbes();
+        doReturn(TEST_HTTPS_URL).when(mResources)
+                .getString(R.string.config_captive_portal_https_url);
+        doReturn(TEST_HTTP_URL).when(mResources)
+                .getString(R.string.config_captive_portal_http_url);
+
+        setDeviceConfig(TEST_URL_EXPIRATION_TIME,
+                String.valueOf(currentTimeMillis() + TimeUnit.MINUTES.toMillis(9)));
+        setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL, TEST_OVERRIDE_URL);
+        setDeviceConfig(TEST_CAPTIVE_PORTAL_HTTP_URL, TEST_OVERRIDE_URL);
+        setStatus(mTestOverriddenUrlConnection, 204);
+
+        runValidatedNetworkTest();
+        verify(mHttpsConnection, never()).getResponseCode();
+        verify(mHttpConnection, never()).getResponseCode();
+        verify(mOtherHttpsConnection1, never()).getResponseCode();
+        verify(mOtherHttpsConnection2, never()).getResponseCode();
+        verify(mOtherHttpConnection1, never()).getResponseCode();
+        verify(mOtherHttpConnection2, never()).getResponseCode();
+
+        // Used for both HTTP and HTTPS: can be called once (if HTTPS validates first) or twice
+        verify(mTestOverriddenUrlConnection, atLeastOnce()).getResponseCode();
+    }
+
+    @Test
     public void testIsDataStall_EvaluationDisabled() {
         setDataStallEvaluationType(0);
         WrappedNetworkMonitor wrappedMonitor = makeCellMeteredNetworkMonitor();
@@ -1651,7 +1677,7 @@
         // Trigger a tcp event immediately.
         setTcpPollingInterval(0);
         wrappedMonitor.sendTcpPollingEvent();
-        HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
         assertFalse(wrappedMonitor.isDataStall());
 
         when(mTst.getLatestReceivedCount()).thenReturn(0);
@@ -1659,7 +1685,7 @@
         // Trigger a tcp event immediately.
         setTcpPollingInterval(0);
         wrappedMonitor.sendTcpPollingEvent();
-        HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
         assertTrue(wrappedMonitor.isDataStall());
         verify(mCallbacks).notifyDataStallSuspected(matchTcpDataStallParcelable());
     }
@@ -1679,7 +1705,7 @@
         // Trigger a tcp event immediately.
         setTcpPollingInterval(0);
         nm.sendTcpPollingEvent();
-        HandlerUtilsKt.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
         assertFalse(nm.isDataStall());
     }
 
@@ -1690,7 +1716,7 @@
         WrappedNetworkMonitor wrappedMonitor = makeMonitor(CELL_METERED_CAPABILITIES);
         makeDnsSuccessEvent(wrappedMonitor, 1);
         wrappedMonitor.sendTcpPollingEvent();
-        HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
         assertFalse(wrappedMonitor.isDataStall());
         verify(mTst, never()).isDataStallSuspected();
         verify(mTst, never()).pollSocketsInfo();
@@ -1813,7 +1839,7 @@
 
         wnm.forceReevaluation(Process.myUid());
         // ProbeCompleted should be reset to 0
-        HandlerUtilsKt.waitForIdle(wnm.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(wnm.getHandler(), HANDLER_TIMEOUT_MS);
         assertEquals(wnm.getEvaluationState().getProbeCompletedResult(), 0);
         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged(
@@ -1931,12 +1957,11 @@
             throws Exception {
         assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
         setupTcpDataStall();
+        setTcpPollingInterval(0);
         // NM suspects data stall from TCP signal and sends data stall metrics.
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
         final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
-
         // Trigger a tcp event immediately.
-        setTcpPollingInterval(0);
         nm.sendTcpPollingEvent();
         // Allow only one transport type in the context of this test for simplification.
         final int[] transports = nc.getTransportTypes();
@@ -2546,8 +2571,8 @@
     }
 
     private void setTcpPollingInterval(int time) {
-        when(mDependencies.getDeviceConfigPropertyInt(any(),
-                eq(CONFIG_DATA_STALL_TCP_POLLING_INTERVAL), anyInt())).thenReturn(time);
+        doReturn(time).when(mDependencies).getDeviceConfigPropertyInt(any(),
+                eq(CONFIG_DATA_STALL_TCP_POLLING_INTERVAL), anyInt());
     }
 
     private void setFallbackUrl(String url) {
@@ -2627,7 +2652,7 @@
         final NetworkMonitor monitor = makeMonitor(nc);
         monitor.notifyNetworkConnected(lp, nc);
         verifyNetworkTested(testResult, probesSucceeded, redirectUrl);
-        HandlerUtilsKt.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
+        HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
 
         return monitor;
     }