Merge changes I98979758,I1c49711d into rvc-dev

* changes:
  Create base class that sets up test network
  Create TestNetworkUtils for IKE and IPsec CTS
diff --git a/tests/cts/net/ipsec/src/android/net/eap/cts/EapSessionConfigTest.java b/tests/cts/net/ipsec/src/android/net/eap/cts/EapSessionConfigTest.java
new file mode 100644
index 0000000..c24379d
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/eap/cts/EapSessionConfigTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.eap.cts;
+
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.eap.EapSessionConfig;
+import android.net.eap.EapSessionConfig.EapAkaConfig;
+import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
+import android.net.eap.EapSessionConfig.EapMsChapV2Config;
+import android.net.eap.EapSessionConfig.EapSimConfig;
+import android.net.eap.EapSessionConfig.EapUiccConfig;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EapSessionConfigTest {
+    // These constants are IANA-defined values and are copies of hidden constants in
+    // frameworks/opt/net/ike/src/java/com/android/internal/net/eap/message/EapData.java.
+    private static final int EAP_TYPE_SIM = 18;
+    private static final int EAP_TYPE_AKA = 23;
+    private static final int EAP_TYPE_MSCHAP_V2 = 26;
+    private static final int EAP_TYPE_AKA_PRIME = 50;
+
+    private static final int SUB_ID = 1;
+    private static final byte[] EAP_IDENTITY = "test@android.net".getBytes();
+    private static final String NETWORK_NAME = "android.net";
+    private static final String EAP_MSCHAPV2_USERNAME = "username";
+    private static final String EAP_MSCHAPV2_PASSWORD = "password";
+
+    @Test
+    public void testBuildWithAllEapMethods() {
+        EapSessionConfig result =
+                new EapSessionConfig.Builder()
+                        .setEapIdentity(EAP_IDENTITY)
+                        .setEapSimConfig(SUB_ID, APPTYPE_USIM)
+                        .setEapAkaConfig(SUB_ID, APPTYPE_USIM)
+                        .setEapAkaPrimeConfig(
+                                SUB_ID,
+                                APPTYPE_USIM,
+                                NETWORK_NAME,
+                                true /* allowMismatchedNetworkNames */)
+                        .setEapMsChapV2Config(EAP_MSCHAPV2_USERNAME, EAP_MSCHAPV2_PASSWORD)
+                        .build();
+
+        assertArrayEquals(EAP_IDENTITY, result.getEapIdentity());
+
+        EapSimConfig eapSimConfig = result.getEapSimConfig();
+        assertNotNull(eapSimConfig);
+        assertEquals(EAP_TYPE_SIM, eapSimConfig.getMethodType());
+        verifyEapUiccConfigCommon(eapSimConfig);
+
+        EapAkaConfig eapAkaConfig = result.getEapAkaConfig();
+        assertNotNull(eapAkaConfig);
+        assertEquals(EAP_TYPE_AKA, eapAkaConfig.getMethodType());
+        verifyEapUiccConfigCommon(eapAkaConfig);
+
+        EapAkaPrimeConfig eapAkaPrimeConfig = result.getEapAkaPrimeConfig();
+        assertNotNull(eapAkaPrimeConfig);
+        assertEquals(EAP_TYPE_AKA_PRIME, eapAkaPrimeConfig.getMethodType());
+        assertEquals(NETWORK_NAME, eapAkaPrimeConfig.getNetworkName());
+        assertTrue(NETWORK_NAME, eapAkaPrimeConfig.allowsMismatchedNetworkNames());
+        verifyEapUiccConfigCommon(eapAkaPrimeConfig);
+
+        EapMsChapV2Config eapMsChapV2Config = result.getEapMsChapV2onfig();
+        assertNotNull(eapMsChapV2Config);
+        assertEquals(EAP_TYPE_MSCHAP_V2, eapMsChapV2Config.getMethodType());
+        assertEquals(EAP_MSCHAPV2_USERNAME, eapMsChapV2Config.getUsername());
+        assertEquals(EAP_MSCHAPV2_PASSWORD, eapMsChapV2Config.getPassword());
+    }
+
+    private void verifyEapUiccConfigCommon(EapUiccConfig config) {
+        assertEquals(SUB_ID, config.getSubId());
+        assertEquals(APPTYPE_USIM, config.getAppType());
+    }
+}
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
index baed48d..3953aeb 100644
--- a/tests/cts/net/jni/Android.bp
+++ b/tests/cts/net/jni/Android.bp
@@ -16,6 +16,7 @@
     name: "libnativedns_jni",
 
     srcs: ["NativeDnsJni.c"],
+    sdk_version: "current",
 
     shared_libs: [
         "libnativehelper_compat_libc++",
@@ -35,6 +36,7 @@
     name: "libnativemultinetwork_jni",
 
     srcs: ["NativeMultinetworkJni.cpp"],
+    sdk_version: "current",
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/tests/cts/net/jni/NativeDnsJni.c b/tests/cts/net/jni/NativeDnsJni.c
index 6d3d1c3..4ec800e 100644
--- a/tests/cts/net/jni/NativeDnsJni.c
+++ b/tests/cts/net/jni/NativeDnsJni.c
@@ -19,7 +19,12 @@
 #include <netdb.h>
 #include <stdio.h>
 #include <string.h>
-#include <utils/Log.h>
+
+#include <android/log.h>
+
+#define LOG_TAG "NativeDns-JNI"
+#define LOGD(fmt, ...) \
+        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
 
 const char *GoogleDNSIpV4Address="8.8.8.8";
 const char *GoogleDNSIpV4Address2="8.8.4.4";
@@ -33,7 +38,7 @@
     struct addrinfo *answer;
 
     int res = getaddrinfo(node, service, NULL, &answer);
-    ALOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
+    LOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
     if (res != 0) return JNI_FALSE;
 
     // check for v4 & v6
@@ -47,12 +52,12 @@
                 inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
                         buf, sizeof(buf));
                 foundv4 = 1;
-                ALOGD("  %s", buf);
+                LOGD("  %s", buf);
             } else if (current->ai_addr->sa_family == AF_INET6) {
                 inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
                         buf, sizeof(buf));
                 foundv6 = 1;
-                ALOGD("  %s", buf);
+                LOGD("  %s", buf);
             }
             current = current->ai_next;
         }
@@ -60,14 +65,14 @@
         freeaddrinfo(answer);
         answer = NULL;
         if (foundv4 != 1 && foundv6 != 1) {
-            ALOGD("getaddrinfo(www.google.com) didn't find either v4 or v6 address");
+            LOGD("getaddrinfo(www.google.com) didn't find either v4 or v6 address");
             return JNI_FALSE;
         }
     }
 
     node = "ipv6.google.com";
     res = getaddrinfo(node, service, NULL, &answer);
-    ALOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
+    LOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
     if (res != 0) return JNI_FALSE;
 
     {
@@ -79,12 +84,12 @@
             if (current->ai_addr->sa_family == AF_INET) {
                 inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
                         buf, sizeof(buf));
-                ALOGD("  %s", buf);
+                LOGD("  %s", buf);
                 foundv4 = 1;
             } else if (current->ai_addr->sa_family == AF_INET6) {
                 inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
                         buf, sizeof(buf));
-                ALOGD("  %s", buf);
+                LOGD("  %s", buf);
                 foundv6 = 1;
             }
             current = current->ai_next;
@@ -93,7 +98,7 @@
         freeaddrinfo(answer);
         answer = NULL;
         if (foundv4 == 1 || foundv6 != 1) {
-            ALOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
+            LOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
             return JNI_FALSE;
         }
     }
@@ -116,12 +121,12 @@
 
     res = getnameinfo((const struct sockaddr*)&sa4, sizeof(sa4), buf, sizeof(buf), NULL, 0, flags);
     if (res != 0) {
-        ALOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV4Address, res,
+        LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV4Address, res,
             gai_strerror(res));
         return JNI_FALSE;
     }
     if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
-        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+        LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
             GoogleDNSIpV4Address, buf);
         return JNI_FALSE;
     }
@@ -129,12 +134,12 @@
     memset(buf, 0, sizeof(buf));
     res = getnameinfo((const struct sockaddr*)&sa6, sizeof(sa6), buf, sizeof(buf), NULL, 0, flags);
     if (res != 0) {
-        ALOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV6Address2,
+        LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV6Address2,
             res, gai_strerror(res));
         return JNI_FALSE;
     }
     if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
-        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+        LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
             GoogleDNSIpV6Address2, buf);
         return JNI_FALSE;
     }
@@ -142,11 +147,11 @@
     // gethostbyname
     struct hostent *my_hostent = gethostbyname("www.youtube.com");
     if (my_hostent == NULL) {
-        ALOGD("gethostbyname(www.youtube.com) gave null response");
+        LOGD("gethostbyname(www.youtube.com) gave null response");
         return JNI_FALSE;
     }
     if ((my_hostent->h_addr_list == NULL) || (*my_hostent->h_addr_list == NULL)) {
-        ALOGD("gethostbyname(www.youtube.com) gave 0 addresses");
+        LOGD("gethostbyname(www.youtube.com) gave 0 addresses");
         return JNI_FALSE;
     }
     {
@@ -154,7 +159,7 @@
         while (*current != NULL) {
             char buf[256];
             inet_ntop(my_hostent->h_addrtype, *current, buf, sizeof(buf));
-            ALOGD("gethostbyname(www.youtube.com) gave %s", buf);
+            LOGD("gethostbyname(www.youtube.com) gave %s", buf);
             current++;
         }
     }
@@ -164,11 +169,11 @@
     inet_pton(AF_INET6, GoogleDNSIpV6Address, addr6);
     my_hostent = gethostbyaddr(addr6, sizeof(addr6), AF_INET6);
     if (my_hostent == NULL) {
-        ALOGD("gethostbyaddr(%s (GoogleDNS) ) gave null response", GoogleDNSIpV6Address);
+        LOGD("gethostbyaddr(%s (GoogleDNS) ) gave null response", GoogleDNSIpV6Address);
         return JNI_FALSE;
     }
 
-    ALOGD("gethostbyaddr(%s (GoogleDNS) ) gave %s for name", GoogleDNSIpV6Address,
+    LOGD("gethostbyaddr(%s (GoogleDNS) ) gave %s for name", GoogleDNSIpV6Address,
         my_hostent->h_name ? my_hostent->h_name : "null");
 
     if (my_hostent->h_name == NULL) return JNI_FALSE;
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
index 2832c3d..cd94709 100644
--- a/tests/cts/net/jni/NativeMultinetworkJni.cpp
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -16,7 +16,6 @@
 
 
 #define LOG_TAG "MultinetworkApiTest"
-#include <utils/Log.h>
 
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
@@ -34,9 +33,13 @@
 
 #include <string>
 
+#include <android/log.h>
 #include <android/multinetwork.h>
 #include <nativehelper/JNIHelp.h>
 
+#define LOGD(fmt, ...) \
+        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
+
 #define EXPECT_GE(env, actual, expected, msg)                        \
     do {                                                             \
         if (actual < expected) {                                     \
@@ -138,7 +141,7 @@
     uint8_t buf[MAXPACKET] = {};
     int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
     if (res != expectedErrno) {
-        ALOGD("res:%d, expectedErrno = %d", res, expectedErrno);
+        LOGD("res:%d, expectedErrno = %d", res, expectedErrno);
         return (res > 0) ? -EREMOTEIO : res;
     }
     return 0;
@@ -326,7 +329,7 @@
     const int saved_errno = errno;
     freeaddrinfo(res);
 
-    ALOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
+    LOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
           handle, kHostname, rval, saved_errno);
     return rval == 0 ? 0 : -saved_errno;
 }
@@ -339,7 +342,7 @@
     errno = 0;
     int rval = android_setprocnetwork(handle);
     const int saved_errno = errno;
-    ALOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
+    LOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
           handle, rval, saved_errno);
     return rval == 0 ? 0 : -saved_errno;
 }
@@ -352,14 +355,14 @@
     errno = 0;
     int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
     if (fd < 0) {
-        ALOGD("socket() failed, errno=%d", errno);
+        LOGD("socket() failed, errno=%d", errno);
         return -errno;
     }
 
     errno = 0;
     int rval = android_setsocknetwork(handle, fd);
     const int saved_errno = errno;
-    ALOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
+    LOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
           handle, fd, rval, saved_errno);
     close(fd);
     return rval == 0 ? 0 : -saved_errno;
@@ -404,7 +407,7 @@
     static const char kPort[] = "443";
     int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
     if (rval != 0) {
-        ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
+        LOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
               handle, kHostname, rval, errno);
         freeaddrinfo(res);
         return -errno;
@@ -413,14 +416,14 @@
     // Rely upon getaddrinfo sorting the best destination to the front.
     int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
     if (fd < 0) {
-        ALOGD("socket(%d, %d, %d) failed, errno=%d",
+        LOGD("socket(%d, %d, %d) failed, errno=%d",
               res->ai_family, res->ai_socktype, res->ai_protocol, errno);
         freeaddrinfo(res);
         return -errno;
     }
 
     rval = android_setsocknetwork(handle, fd);
-    ALOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
+    LOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
           handle, fd, rval, errno);
     if (rval != 0) {
         close(fd);
@@ -430,7 +433,7 @@
 
     char addrstr[kSockaddrStrLen+1];
     sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
-    ALOGD("Attempting connect() to %s ...", addrstr);
+    LOGD("Attempting connect() to %s ...", addrstr);
 
     rval = connect(fd, res->ai_addr, res->ai_addrlen);
     if (rval != 0) {
@@ -447,7 +450,7 @@
         return -errno;
     }
     sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
-    ALOGD("... from %s", addrstr);
+    LOGD("... from %s", addrstr);
 
     // Don't let reads or writes block indefinitely.
     const struct timeval timeo = { 2, 0 };  // 2 seconds
@@ -479,7 +482,7 @@
         sent = send(fd, quic_packet, sizeof(quic_packet), 0);
         if (sent < (ssize_t)sizeof(quic_packet)) {
             errnum = errno;
-            ALOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
+            LOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
             close(fd);
             return -errnum;
         }
@@ -489,14 +492,14 @@
             break;
         } else {
             errnum = errno;
-            ALOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
+            LOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
                   i + 1, MAX_RETRIES, rcvd, errnum);
         }
     }
     if (rcvd < 9) {
-        ALOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
+        LOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
         if (rcvd <= 0) {
-            ALOGD("Does this network block UDP port %s?", kPort);
+            LOGD("Does this network block UDP port %s?", kPort);
         }
         close(fd);
         return -EPROTO;
@@ -504,7 +507,7 @@
 
     int conn_id_cmp = memcmp(quic_packet + 1, response + 1, 8);
     if (conn_id_cmp != 0) {
-        ALOGD("sent and received connection IDs do not match");
+        LOGD("sent and received connection IDs do not match");
         close(fd);
         return -EPROTO;
     }
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 0a80047..9d35705 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -18,15 +18,17 @@
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
 
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
 import android.content.Context;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.NetworkRequest;
+import android.os.Build;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -34,7 +36,8 @@
 
 import java.util.concurrent.Executor;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
 public class ConnectivityDiagnosticsManagerTest {
     private static final Executor INLINE_EXECUTOR = x -> x.run();
     private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index fa7e138..1ee08ff 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -639,11 +639,14 @@
         }
     }
 
-    private void waitForActiveNetworkMetered(boolean requestedMeteredness) throws Exception {
+    private void waitForActiveNetworkMetered(int targetTransportType, boolean requestedMeteredness)
+            throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final NetworkCallback networkCallback = new NetworkCallback() {
             @Override
             public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+                if (!nc.hasTransport(targetTransportType)) return;
+
                 final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
                 if (metered == requestedMeteredness) {
                     latch.countDown();
@@ -709,7 +712,7 @@
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testGetMultipathPreference() throws Exception {
         final ContentResolver resolver = mContext.getContentResolver();
-        final Network network = ensureWifiConnected();
+        ensureWifiConnected();
         final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
         final String oldMeteredSetting = getWifiMeteredStatus(ssid);
         final String oldMeteredMultipathPreference = Settings.Global.getString(
@@ -720,7 +723,11 @@
             Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
                     Integer.toString(newMeteredPreference));
             setWifiMeteredStatus(ssid, "true");
-            waitForActiveNetworkMetered(true);
+            waitForActiveNetworkMetered(TRANSPORT_WIFI, true);
+            // Wifi meterness changes from unmetered to metered will disconnect and reconnect since
+            // R.
+            final Network network = ensureWifiConnected();
+            assertEquals(ssid, unquoteSSID(mWifiManager.getConnectionInfo().getSSID()));
             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
                     NET_CAPABILITY_NOT_METERED), false);
             assertMultipathPreferenceIsEventually(network, initialMeteredPreference,
@@ -736,7 +743,8 @@
                     oldMeteredPreference, newMeteredPreference);
 
             setWifiMeteredStatus(ssid, "false");
-            waitForActiveNetworkMetered(false);
+            // No disconnect from unmetered to metered.
+            waitForActiveNetworkMetered(TRANSPORT_WIFI, false);
             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
                     NET_CAPABILITY_NOT_METERED), true);
             assertMultipathPreferenceIsEventually(network, newMeteredPreference,
diff --git a/tests/cts/net/src/android/net/cts/DhcpInfoTest.java b/tests/cts/net/src/android/net/cts/DhcpInfoTest.java
deleted file mode 100644
index b8d2392..0000000
--- a/tests/cts/net/src/android/net/cts/DhcpInfoTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open 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.cts;
-
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
-
-import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
-import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.annotation.Nullable;
-import android.net.DhcpInfo;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-
-@RunWith(AndroidJUnit4.class)
-public class DhcpInfoTest {
-    private static final String STR_ADDR1 = "255.255.255.255";
-    private static final String STR_ADDR2 = "127.0.0.1";
-    private static final String STR_ADDR3 = "192.168.1.1";
-    private static final String STR_ADDR4 = "192.168.1.0";
-    private static final int LEASE_TIME = 9999;
-
-    private int ipToInteger(String ipString) throws Exception {
-        return inet4AddressToIntHTL((Inet4Address) InetAddress.getByName(ipString));
-    }
-
-    private DhcpInfo createDhcpInfoObject() throws Exception {
-        final DhcpInfo dhcpInfo = new DhcpInfo();
-        dhcpInfo.ipAddress = ipToInteger(STR_ADDR1);
-        dhcpInfo.gateway = ipToInteger(STR_ADDR2);
-        dhcpInfo.netmask = ipToInteger(STR_ADDR3);
-        dhcpInfo.dns1 = ipToInteger(STR_ADDR4);
-        dhcpInfo.dns2 = ipToInteger(STR_ADDR4);
-        dhcpInfo.serverAddress = ipToInteger(STR_ADDR2);
-        dhcpInfo.leaseDuration = LEASE_TIME;
-        return dhcpInfo;
-    }
-
-    @Test
-    public void testConstructor() {
-        new DhcpInfo();
-    }
-
-    @Test
-    public void testToString() throws Exception {
-        final String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 "
-                + "dns1 0.0.0.0 dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds";
-
-        DhcpInfo dhcpInfo = new DhcpInfo();
-
-        // Test default string.
-        assertEquals(expectedDefault, dhcpInfo.toString());
-
-        dhcpInfo = createDhcpInfoObject();
-
-        final String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask "
-                + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server "
-                + STR_ADDR2 + " lease " + LEASE_TIME + " seconds";
-        // Test with new values
-        assertEquals(expected, dhcpInfo.toString());
-    }
-
-    private boolean dhcpInfoEquals(@Nullable DhcpInfo left, @Nullable DhcpInfo right) {
-        if (left == null && right == null) return true;
-
-        if (left == null || right == null) return false;
-
-        return left.ipAddress == right.ipAddress
-                && left.gateway == right.gateway
-                && left.netmask == right.netmask
-                && left.dns1 == right.dns1
-                && left.dns2 == right.dns2
-                && left.serverAddress == right.serverAddress
-                && left.leaseDuration == right.leaseDuration;
-    }
-
-    @Test
-    public void testParcelDhcpInfo() throws Exception {
-        // Cannot use assertParcelSane() here because this requires .equals() to work as
-        // defined, but DhcpInfo has a different legacy behavior that we cannot change.
-        final DhcpInfo dhcpInfo = createDhcpInfoObject();
-        assertFieldCountEquals(7, DhcpInfo.class);
-
-        final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo);
-        assertTrue(dhcpInfoEquals(null, null));
-        assertFalse(dhcpInfoEquals(null, dhcpInfoRoundTrip));
-        assertFalse(dhcpInfoEquals(dhcpInfo, null));
-        assertTrue(dhcpInfoEquals(dhcpInfo, dhcpInfoRoundTrip));
-    }
-}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 89d3dff..03b961b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -180,7 +180,7 @@
     }
 
     private open class TestableNetworkAgent(
-        val looper: Looper,
+        looper: Looper,
         val nc: NetworkCapabilities,
         val lp: LinkProperties,
         conf: NetworkAgentConfig
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 8b97c8c..5e92b41 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -16,8 +16,11 @@
 
 package android.net.cts;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static org.junit.Assert.assertEquals;
@@ -26,13 +29,15 @@
 import static org.junit.Assert.assertTrue;
 
 import android.net.MacAddress;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
-import android.net.wifi.WifiConfiguration;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.os.Build;
+import android.os.Process;
 import android.os.PatternMatcher;
-import android.util.Pair;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -49,6 +54,7 @@
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
     private static final String TEST_SSID = "TestSSID";
+    private static final String OTHER_SSID = "OtherSSID";
     private static final int TEST_UID = 2097;
     private static final String TEST_PACKAGE_NAME = "test.package.name";
     private static final MacAddress ARBITRARY_ADDRESS = MacAddress.fromString("3:5:8:12:9:2");
@@ -59,6 +65,18 @@
                 .hasCapability(NET_CAPABILITY_MMS));
         assertFalse(new NetworkRequest.Builder().removeCapability(NET_CAPABILITY_MMS).build()
                 .hasCapability(NET_CAPABILITY_MMS));
+
+        final NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+        // Verify request has no capabilities
+        verifyNoCapabilities(nr);
+    }
+
+    private void verifyNoCapabilities(NetworkRequest nr) {
+        // NetworkCapabilities.mNetworkCapabilities is defined as type long
+        final int MAX_POSSIBLE_CAPABILITY = Long.SIZE;
+        for(int bit = 0; bit < MAX_POSSIBLE_CAPABILITY; bit++) {
+            assertFalse(nr.hasCapability(bit));
+        }
     }
 
     @Test
@@ -83,5 +101,96 @@
                 .build()
                 .getNetworkSpecifier();
         assertEquals(obtainedSpecifier, specifier);
+
+        assertNull(new NetworkRequest.Builder()
+                .clearCapabilities()
+                .build()
+                .getNetworkSpecifier());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testRequestorPackageName() {
+        assertNull(new NetworkRequest.Builder().build().getRequestorPackageName());
+        final String pkgName = "android.net.test";
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .setRequestorPackageName(pkgName)
+                .build();
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .setCapabilities(nc)
+                .build();
+        assertEquals(pkgName, nr.getRequestorPackageName());
+        assertNull(new NetworkRequest.Builder()
+                .clearCapabilities()
+                .build()
+                .getRequestorPackageName());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testCanBeSatisfiedBy() {
+        final TelephonyNetworkSpecifier specifier1 = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(1234 /* subId */)
+                .build();
+        final TelephonyNetworkSpecifier specifier2 = new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(5678 /* subId */)
+                .build();
+        final NetworkCapabilities cap = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_MMS)
+                .addCapability(NET_CAPABILITY_INTERNET);
+        final NetworkCapabilities capDualTransport = new NetworkCapabilities(cap)
+                .addTransportType(TRANSPORT_VPN);
+        final NetworkCapabilities capWithSpecifier1 =
+                new NetworkCapabilities(cap).setNetworkSpecifier(specifier1);
+        final NetworkCapabilities capDiffTransportWithSpecifier1 = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addTransportType(TRANSPORT_VPN)
+                .setNetworkSpecifier(specifier1);
+
+        final NetworkRequest requestWithSpecifier1 = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .setNetworkSpecifier(specifier1)
+                .build();
+        assertFalse(requestWithSpecifier1.canBeSatisfiedBy(null));
+        assertFalse(requestWithSpecifier1.canBeSatisfiedBy(new NetworkCapabilities()));
+        assertTrue(requestWithSpecifier1.canBeSatisfiedBy(new NetworkCapabilities(cap)
+                .setNetworkSpecifier(new MatchAllNetworkSpecifier())));
+        assertTrue(requestWithSpecifier1.canBeSatisfiedBy(cap));
+        assertTrue(requestWithSpecifier1.canBeSatisfiedBy(capWithSpecifier1));
+        assertTrue(requestWithSpecifier1.canBeSatisfiedBy(capDualTransport));
+        assertFalse(requestWithSpecifier1.canBeSatisfiedBy(
+                new NetworkCapabilities(cap).setNetworkSpecifier(specifier2)));
+
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        assertTrue(request.canBeSatisfiedBy(cap));
+        assertTrue(request.canBeSatisfiedBy(capWithSpecifier1));
+        assertTrue(request.canBeSatisfiedBy(
+                new NetworkCapabilities(cap).setNetworkSpecifier(specifier2)));
+        assertFalse(request.canBeSatisfiedBy(capDiffTransportWithSpecifier1));
+        assertTrue(request.canBeSatisfiedBy(capDualTransport));
+
+        assertEquals(requestWithSpecifier1.canBeSatisfiedBy(capWithSpecifier1),
+                new NetworkCapabilities(capWithSpecifier1)
+                    .satisfiedByNetworkCapabilities(capWithSpecifier1));
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testRequestorUid() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // Verify default value is INVALID_UID
+        assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
+                 .setCapabilities(nc).build().getRequestorUid());
+
+        nc.setRequestorUid(1314);
+        final NetworkRequest nr = new NetworkRequest.Builder().setCapabilities(nc).build();
+        assertEquals(1314, nr.getRequestorUid());
+
+        assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
+                .clearCapabilities().build().getRequestorUid());
     }
 }
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index 577e24a..37bdd44 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,11 +16,9 @@
 
 package android.net.cts;
 
-import android.content.pm.PackageManager;
 import android.net.NetworkStats;
 import android.net.TrafficStats;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -267,28 +265,6 @@
         assertTrue("ifrxp: " + ifaceRxPacketsBefore + " -> " + ifaceRxPacketsAfter,
                 totalRxPacketsAfter >= totalRxPacketsBefore + ifaceRxDeltaPackets);
 
-        // If the adb TCP port is opened, this test may be run by adb over network.
-        // Huge amount of data traffic might go through the network and accounted into total packets
-        // stats. The upper bound check would be meaningless.
-        // TODO: Consider precisely calculate the traffic accounted due to adb over network and
-        //       subtract it when checking upper bound instead of skip checking.
-        final PackageManager pm = mContext.getPackageManager();
-        if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
-                || SystemProperties.getInt("service.adb.tcp.port", -1) > -1
-                || !pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
-            Log.i(LOG_TAG, "adb is running over the network, skip the upper bound check");
-        } else {
-            // Fudge by 132 packets of 1500 bytes not related to the test.
-            assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
-                    totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
-            assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
-                    totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
-            assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
-                    totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
-            assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
-                    totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
-        }
-
         // Localhost traffic should *not* count against mobile stats,
         // There might be some other traffic, but nowhere near 1MB.
         assertInRange("mtxp", mobileTxPacketsAfter, mobileTxPacketsBefore,
diff --git a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
index 82b3b14..5a70928 100644
--- a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
+++ b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
@@ -16,16 +16,38 @@
 
 package android.net.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 import android.net.UrlQuerySanitizer;
 import android.net.UrlQuerySanitizer.IllegalCharacterValueSanitizer;
 import android.net.UrlQuerySanitizer.ParameterValuePair;
 import android.net.UrlQuerySanitizer.ValueSanitizer;
-import android.test.AndroidTestCase;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.List;
 import java.util.Set;
 
-public class UrlQuerySanitizerTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UrlQuerySanitizerTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final int ALL_OK = IllegalCharacterValueSanitizer.ALL_OK;
 
     // URL for test.
@@ -42,6 +64,7 @@
     private static final String AGE = "age";
     private static final String HEIGHT = "height";
 
+    @Test
     public void testUrlQuerySanitizer() {
         MockUrlQuerySanitizer uqs = new MockUrlQuerySanitizer();
         assertFalse(uqs.getAllowUnregisteredParamaters());
@@ -210,12 +233,14 @@
 
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // Only fixed in R
     public void testScriptUrlOk_73822755() {
         ValueSanitizer sanitizer = new UrlQuerySanitizer.IllegalCharacterValueSanitizer(
                 UrlQuerySanitizer.IllegalCharacterValueSanitizer.SCRIPT_URL_OK);
         assertEquals("javascript:alert()", sanitizer.sanitize("javascript:alert()"));
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // Only fixed in R
     public void testScriptUrlBlocked_73822755() {
         ValueSanitizer sanitizer = UrlQuerySanitizer.getUrlAndSpaceLegal();
         assertEquals("", sanitizer.sanitize("javascript:alert()"));
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 0f98125..85bb0e0 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -25,12 +25,22 @@
     ],
 
     static_libs: [
+        "TetheringCommonTests",
+        "TetheringIntegrationTestsLib",
         "compatibility-device-util-axt",
+        "cts-net-utils",
+        "net-tests-utils",
         "ctstestrunner-axt",
         "junit",
         "junit-params",
     ],
 
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
     // Change to system current when TetheringManager move to bootclass path.
     platform_apis: true,
 
@@ -41,4 +51,6 @@
         "mts",
     ],
 
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
 }
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 193a5dc..bbb9403 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -15,31 +15,59 @@
  */
 package android.tethering.test;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.TetheredClient;
 import android.net.TetheringManager;
+import android.net.TetheringManager.OnTetheringEntitlementResultListener;
 import android.net.TetheringManager.TetheringEventCallback;
 import android.net.TetheringManager.TetheringInterfaceRegexps;
 import android.net.TetheringManager.TetheringRequest;
+import android.net.cts.util.CtsNetUtils;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telephony.TelephonyManager;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.ArrayTrackRecord;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -49,29 +77,47 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 public class TetheringManagerTest {
 
     private Context mContext;
 
+    private ConnectivityManager mCm;
     private TetheringManager mTM;
+    private WifiManager mWm;
+    private PackageManager mPm;
 
     private TetherChangeReceiver mTetherChangeReceiver;
-
-    private String[] mTetheredList;
+    private CtsNetUtils mCtsNetUtils;
 
     private static final int DEFAULT_TIMEOUT_MS = 60_000;
 
+    private void adoptShellPermissionIdentity() {
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity();
+    }
+
+    private void dropShellPermissionIdentity() {
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.dropShellPermissionIdentity();
+    }
+
     @Before
     public void setUp() throws Exception {
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity();
+        adoptShellPermissionIdentity();
         mContext = InstrumentationRegistry.getContext();
+        mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mTM = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
+        mWm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mPm = mContext.getPackageManager();
+        mCtsNetUtils = new CtsNetUtils(mContext);
         mTetherChangeReceiver = new TetherChangeReceiver();
         final IntentFilter filter = new IntentFilter(
                 TetheringManager.ACTION_TETHER_STATE_CHANGED);
@@ -81,10 +127,9 @@
 
     @After
     public void tearDown() throws Exception {
+        mTM.stopAllTethering();
         mContext.unregisterReceiver(mTetherChangeReceiver);
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .dropShellPermissionIdentity();
+        dropShellPermissionIdentity();
     }
 
     private class TetherChangeReceiver extends BroadcastReceiver {
@@ -113,28 +158,24 @@
 
         public final LinkedBlockingQueue<TetherState> mResult = new LinkedBlockingQueue<>();
 
-        // This method expects either an event where one of the interfaces is active, or events
-        // where the interfaces are available followed by one event where one of the interfaces is
-        // active. Here is a typical example for wifi tethering:
-        // AVAILABLE(wlan0) -> AVAILABLE(wlan1) -> ACTIVATE(wlan1).
-        public void expectActiveTethering(String[] ifaceRegexs) {
-            TetherState state = null;
+        // Expects that tethering reaches the desired state.
+        // - If active is true, expects that tethering is enabled on at least one interface
+        //   matching ifaceRegexs.
+        // - If active is false, expects that tethering is disabled on all the interfaces matching
+        //   ifaceRegexs.
+        // Fails if any interface matching ifaceRegexs becomes errored.
+        public void expectTethering(final boolean active, final String[] ifaceRegexs) {
             while (true) {
-                state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS);
-                if (state == null) fail("Do not receive active state change broadcast");
+                final TetherState state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS, ifaceRegexs);
+                assertNotNull("Did not receive expected state change, active: " + active, state);
 
-                if (isIfaceActive(ifaceRegexs, state)) return;
-
-                if (!isIfaceAvailable(ifaceRegexs, state)) break;
+                if (isIfaceActive(ifaceRegexs, state) == active) return;
             }
-
-            fail("Tethering is not actived, available ifaces: " + state.mAvailable.toString()
-                    + ", active ifaces: " + state.mActive.toString());
         }
 
-        private TetherState pollAndAssertNoError(final int timeout) {
+        private TetherState pollAndAssertNoError(final int timeout, final String[] ifaceRegexs) {
             final TetherState state = pollTetherState(timeout);
-            assertNoErroredIfaces(state);
+            assertNoErroredIfaces(state, ifaceRegexs);
             return state;
         }
 
@@ -151,54 +192,63 @@
             return isIfaceMatch(ifaceRegexs, state.mActive);
         }
 
-        private boolean isIfaceAvailable(final String[] ifaceRegexs, final TetherState state) {
-            return isIfaceMatch(ifaceRegexs, state.mAvailable);
-        }
-
-        // This method requires a broadcast to have been recorded iff the timeout is non-zero.
-        public void expectNoActiveTethering(final int timeout) {
-            final TetherState state = pollAndAssertNoError(timeout);
-
-            if (state == null) {
-                if (timeout != 0) {
-                    fail("Do not receive tethering state change broadcast");
-                }
-                return;
-            }
-
-            assertNoActiveIfaces(state);
-
-            for (final TetherState ts : mResult) {
-                assertNoErroredIfaces(ts);
-
-                assertNoActiveIfaces(ts);
-            }
-        }
-
-        private void assertNoErroredIfaces(final TetherState state) {
+        private void assertNoErroredIfaces(final TetherState state, final String[] ifaceRegexs) {
             if (state == null || state.mErrored == null) return;
 
-            if (state.mErrored.size() > 0) {
+            if (isIfaceMatch(ifaceRegexs, state.mErrored)) {
                 fail("Found failed tethering interfaces: " + Arrays.toString(state.mErrored.toArray()));
             }
         }
-
-        private void assertNoActiveIfaces(final TetherState state) {
-            if (state.mActive != null && state.mActive.size() > 0) {
-                fail("Found active tethering interface: " + Arrays.toString(state.mActive.toArray()));
-            }
-        }
     }
 
-    private class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
+    private static class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
+        private static int TIMEOUT_MS = 30_000;
+        public static class CallbackValue {
+            public final int error;
+
+            private CallbackValue(final int e) {
+                error = e;
+            }
+
+            public static class OnTetheringStarted extends CallbackValue {
+                OnTetheringStarted() { super(TETHER_ERROR_NO_ERROR); }
+            }
+
+            public static class OnTetheringFailed extends CallbackValue {
+                OnTetheringFailed(final int error) { super(error); }
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s(%d)", getClass().getSimpleName(), error);
+            }
+        }
+
+        private final ArrayTrackRecord<CallbackValue>.ReadHead mHistory =
+                new ArrayTrackRecord<CallbackValue>().newReadHead();
+
         @Override
         public void onTetheringStarted() {
-            // Do nothing, TetherChangeReceiver will wait until it receives the broadcast.
+            mHistory.add(new CallbackValue.OnTetheringStarted());
         }
 
         @Override
         public void onTetheringFailed(final int error) {
-            fail("startTethering fail: " + error);
+            mHistory.add(new CallbackValue.OnTetheringFailed(error));
+        }
+
+        public void verifyTetheringStarted() {
+            final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
+            assertNotNull("No onTetheringStarted after " + TIMEOUT_MS + " ms", cv);
+            assertTrue("Fail start tethering:" + cv,
+                    cv instanceof CallbackValue.OnTetheringStarted);
+        }
+
+        public void expectTetheringFailed(final int expected) throws InterruptedException {
+            final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
+            assertNotNull("No onTetheringFailed after " + TIMEOUT_MS + " ms", cv);
+            assertTrue("Expect fail with error code " + expected + ", but received: " + cv,
+                (cv instanceof CallbackValue.OnTetheringFailed) && (cv.error == expected));
         }
     }
 
@@ -229,15 +279,18 @@
         final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
         if (wifiRegexs.length == 0) return;
 
-        mTetherChangeReceiver.expectNoActiveTethering(0 /** timeout */);
+        final String[] tetheredIfaces = mTM.getTetheredIfaces();
+        assertTrue(tetheredIfaces.length == 0);
 
         final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
-        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(),
-                startTetheringCallback);
-        mTetherChangeReceiver.expectActiveTethering(wifiRegexs);
+        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+                c -> c.run() /* executor */, startTetheringCallback);
+        startTetheringCallback.verifyTetheringStarted();
+
+        mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
 
         mTM.stopTethering(TETHERING_WIFI);
-        mTetherChangeReceiver.expectNoActiveTethering(DEFAULT_TIMEOUT_MS);
+        mTetherChangeReceiver.expectTethering(false /* active */, wifiRegexs);
     }
 
     @Test
@@ -265,6 +318,8 @@
 
     // Must poll the callback before looking at the member.
     private static class TestTetheringEventCallback implements TetheringEventCallback {
+        private static final int TIMEOUT_MS = 30_000;
+
         public enum CallbackType {
             ON_SUPPORTED,
             ON_UPSTREAM,
@@ -273,6 +328,7 @@
             ON_TETHERED_IFACES,
             ON_ERROR,
             ON_CLIENTS,
+            ON_OFFLOAD_STATUS,
         };
 
         public static class CallbackValue {
@@ -286,7 +342,12 @@
                 this.callbackParam2 = param2;
             }
         }
-        private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+        private final ArrayTrackRecord<CallbackValue> mHistory =
+                new ArrayTrackRecord<CallbackValue>();
+
+        private final ArrayTrackRecord<CallbackValue>.ReadHead mCurrent =
+                mHistory.newReadHead();
 
         private TetheringInterfaceRegexps mTetherableRegex;
         private List<String> mTetherableIfaces;
@@ -294,94 +355,135 @@
 
         @Override
         public void onTetheringSupported(boolean supported) {
-            mCallbacks.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, 0));
+            mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, (supported ? 1 : 0)));
         }
 
         @Override
         public void onUpstreamChanged(Network network) {
-            mCallbacks.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0));
+            mHistory.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0));
         }
 
         @Override
         public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) {
             mTetherableRegex = reg;
-            mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0));
+            mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0));
         }
 
         @Override
         public void onTetherableInterfacesChanged(List<String> interfaces) {
             mTetherableIfaces = interfaces;
-            mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
+            mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
         }
 
         @Override
         public void onTetheredInterfacesChanged(List<String> interfaces) {
             mTetheredIfaces = interfaces;
-            mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
+            mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
         }
 
         @Override
         public void onError(String ifName, int error) {
-            mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
+            mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
         }
 
         @Override
         public void onClientsChanged(Collection<TetheredClient> clients) {
-            mCallbacks.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0));
+            mHistory.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0));
         }
 
-        public CallbackValue pollCallback() {
-            try {
-                return mCallbacks.poll(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                fail("Callback not seen");
-            }
-            return null;
+        @Override
+        public void onOffloadStatusChanged(int status) {
+            mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
         }
 
         public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
-            while (true) {
-                final CallbackValue cv = pollCallback();
-                if (cv == null) fail("No expected tetherable ifaces callback");
-                if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) continue;
-
-                final List<String> interfaces = (List<String>) cv.callbackParam;
-                if (isIfaceMatch(regexs, interfaces)) break;
-            }
+            assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS,
+                (cv) -> {
+                    if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false;
+                    final List<String> interfaces = (List<String>) cv.callbackParam;
+                    return isIfaceMatch(regexs, interfaces);
+                }));
         }
 
         public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
-            while (true) {
-                final CallbackValue cv = pollCallback();
-                if (cv == null) fail("No expected tethered ifaces callback");
-                if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue;
+            assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS,
+                (cv) -> {
+                    if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
 
-                final List<String> interfaces = (List<String>) cv.callbackParam;
+                    final List<String> interfaces = (List<String>) cv.callbackParam;
 
-                // Null regexs means no active tethering.
-                if (regexs == null) {
-                    if (interfaces.size() == 0) break;
-                } else if (isIfaceMatch(regexs, interfaces)) {
-                    break;
-                }
-            }
+                    // Null regexs means no active tethering.
+                    if (regexs == null) return interfaces.isEmpty();
+
+                    return isIfaceMatch(regexs, interfaces);
+                }));
         }
 
         public void expectCallbackStarted() {
+            int receivedBitMap = 0;
             // The each bit represent a type from CallbackType.ON_*.
             // Expect all of callbacks except for ON_ERROR.
-            final int expectedBitMap = 0x7f ^ (1 << CallbackType.ON_ERROR.ordinal());
-            int receivedBitMap = 0;
-            while (receivedBitMap != expectedBitMap) {
-                final CallbackValue cv = pollCallback();
+            final int expectedBitMap = 0xff ^ (1 << CallbackType.ON_ERROR.ordinal());
+            // Receive ON_ERROR on started callback is not matter. It just means tethering is
+            // failed last time, should able to continue the test this time.
+            while ((receivedBitMap & expectedBitMap) != expectedBitMap) {
+                final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true);
                 if (cv == null) {
                     fail("No expected callbacks, " + "expected bitmap: "
                             + expectedBitMap + ", actual: " + receivedBitMap);
                 }
-                receivedBitMap = receivedBitMap | (1 << cv.callbackType.ordinal());
+
+                receivedBitMap |= (1 << cv.callbackType.ordinal());
             }
         }
 
+        public void expectOneOfOffloadStatusChanged(int... offloadStatuses) {
+            assertNotNull("No offload status changed", mCurrent.poll(TIMEOUT_MS, (cv) -> {
+                if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) return false;
+
+                final int status = (int) cv.callbackParam;
+                for (int offloadStatus : offloadStatuses) if (offloadStatus == status) return true;
+
+                return false;
+            }));
+        }
+
+        public void expectErrorOrTethered(final String iface) {
+            assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> {
+                if (cv.callbackType == CallbackType.ON_ERROR
+                        && iface.equals((String) cv.callbackParam)) {
+                    return true;
+                }
+                if (cv.callbackType == CallbackType.ON_TETHERED_IFACES
+                        && ((List<String>) cv.callbackParam).contains(iface)) {
+                    return true;
+                }
+
+                return false;
+            }));
+        }
+
+        public Network getCurrentValidUpstream() {
+            final CallbackValue result = mCurrent.poll(TIMEOUT_MS, (cv) -> {
+                return (cv.callbackType == CallbackType.ON_UPSTREAM)
+                        && cv.callbackParam != null;
+            });
+
+            assertNotNull("No valid upstream", result);
+            return (Network) result.callbackParam;
+        }
+
+        public void assumeTetheringSupported() {
+            final ArrayTrackRecord<CallbackValue>.ReadHead history =
+                    mHistory.newReadHead();
+            assertNotNull("No onSupported callback", history.poll(TIMEOUT_MS, (cv) -> {
+                if (cv.callbackType != CallbackType.ON_SUPPORTED) return false;
+
+                assumeTrue(cv.callbackParam2 == 1 /* supported */);
+                return true;
+            }));
+        }
+
         public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
             return mTetherableRegex;
         }
@@ -395,47 +497,90 @@
         }
     }
 
-    @Test
-    public void testRegisterTetheringEventCallback() throws Exception {
-        if (!mTM.isTetheringSupported()) return;
+    private TestTetheringEventCallback registerTetheringEventCallback() {
+        final TestTetheringEventCallback tetherEventCallback =
+                new TestTetheringEventCallback();
 
-        final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
-
-        mTM.registerTetheringEventCallback(c -> c.run(), tetherEventCallback);
+        mTM.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
         tetherEventCallback.expectCallbackStarted();
 
-        final TetheringInterfaceRegexps tetherableRegexs =
-                tetherEventCallback.getTetheringInterfaceRegexps();
-        final List<String> wifiRegexs = tetherableRegexs.getTetherableWifiRegexs();
-        if (wifiRegexs.size() == 0) return;
+        return tetherEventCallback;
+    }
 
-        final boolean isIfaceAvailWhenNoTethering =
-                isIfaceMatch(wifiRegexs, tetherEventCallback.getTetherableInterfaces());
+    private void unregisterTetheringEventCallback(final TestTetheringEventCallback callback) {
+        mTM.unregisterTetheringEventCallback(callback);
+    }
 
-        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(),
-                new StartTetheringCallback());
+    private List<String> getWifiTetherableInterfaceRegexps(
+            final TestTetheringEventCallback callback) {
+        return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
+    }
 
-        // If interface is already available before starting tethering, the available callback may
-        // not be sent after tethering enabled.
-        if (!isIfaceAvailWhenNoTethering) {
-            tetherEventCallback.expectTetherableInterfacesChanged(wifiRegexs);
+    private boolean isWifiTetheringSupported(final TestTetheringEventCallback callback) {
+        return !getWifiTetherableInterfaceRegexps(callback).isEmpty();
+    }
+
+    private void startWifiTethering(final TestTetheringEventCallback callback)
+            throws InterruptedException {
+        final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
+        assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
+
+        final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+                c -> c.run() /* executor */, startTetheringCallback);
+        startTetheringCallback.verifyTetheringStarted();
+
+        callback.expectTetheredInterfacesChanged(wifiRegexs);
+
+        callback.expectOneOfOffloadStatusChanged(
+                TETHER_HARDWARE_OFFLOAD_STARTED,
+                TETHER_HARDWARE_OFFLOAD_FAILED);
+    }
+
+    private void stopWifiTethering(final TestTetheringEventCallback callback) {
+        mTM.stopTethering(TETHERING_WIFI);
+        callback.expectTetheredInterfacesChanged(null);
+        callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+    }
+
+    @Test
+    public void testRegisterTetheringEventCallback() throws Exception {
+        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        tetherEventCallback.assumeTetheringSupported();
+
+        if (!isWifiTetheringSupported(tetherEventCallback)) {
+            unregisterTetheringEventCallback(tetherEventCallback);
+            return;
         }
 
-        tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs);
+        startWifiTethering(tetherEventCallback);
 
-        mTM.stopTethering(TETHERING_WIFI);
+        final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
+        assertEquals(1, tetheredIfaces.size());
+        final String wifiTetheringIface = tetheredIfaces.get(0);
 
-        tetherEventCallback.expectTetheredInterfacesChanged(null);
-        mTM.unregisterTetheringEventCallback(tetherEventCallback);
+        stopWifiTethering(tetherEventCallback);
+
+        try {
+            final int ret = mTM.tether(wifiTetheringIface);
+
+            // There is no guarantee that the wifi interface will be available after disabling
+            // the hotspot, so don't fail the test if the call to tether() fails.
+            assumeTrue(ret == TETHER_ERROR_NO_ERROR);
+
+            // If calling #tether successful, there is a callback to tell the result of tethering
+            // setup.
+            tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
+        } finally {
+            mTM.untether(wifiTetheringIface);
+            unregisterTetheringEventCallback(tetherEventCallback);
+        }
     }
 
     @Test
     public void testGetTetherableInterfaceRegexps() {
-        if (!mTM.isTetheringSupported()) return;
-
-        final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
-        mTM.registerTetheringEventCallback(c -> c.run(), tetherEventCallback);
-        tetherEventCallback.expectCallbackStarted();
+        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        tetherEventCallback.assumeTetheringSupported();
 
         final TetheringInterfaceRegexps tetherableRegexs =
                 tetherEventCallback.getTetheringInterfaceRegexps();
@@ -447,11 +592,133 @@
         assertEquals(usbRegexs, Arrays.asList(mTM.getTetherableUsbRegexs()));
         assertEquals(btRegexs, Arrays.asList(mTM.getTetherableBluetoothRegexs()));
 
-        //Verify that any of interface name should only contain in one array.
+        //Verify that any regex name should only contain in one array.
         wifiRegexs.forEach(s -> assertFalse(usbRegexs.contains(s)));
         wifiRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
         usbRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
 
-        mTM.unregisterTetheringEventCallback(tetherEventCallback);
+        unregisterTetheringEventCallback(tetherEventCallback);
+    }
+
+    @Test
+    public void testStopAllTethering() throws Exception {
+        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        tetherEventCallback.assumeTetheringSupported();
+
+        try {
+            if (!isWifiTetheringSupported(tetherEventCallback)) return;
+
+            // TODO: start ethernet tethering here when TetheringManagerTest is moved to
+            // TetheringIntegrationTest.
+
+            startWifiTethering(tetherEventCallback);
+
+            mTM.stopAllTethering();
+            tetherEventCallback.expectTetheredInterfacesChanged(null);
+        } finally {
+            unregisterTetheringEventCallback(tetherEventCallback);
+        }
+    }
+
+    @Test
+    public void testEnableTetheringPermission() throws Exception {
+        dropShellPermissionIdentity();
+        final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+                c -> c.run() /* executor */, startTetheringCallback);
+        startTetheringCallback.expectTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+    }
+
+    private class EntitlementResultListener implements OnTetheringEntitlementResultListener {
+        private final CompletableFuture<Integer> future = new CompletableFuture<>();
+
+        @Override
+        public void onTetheringEntitlementResult(int result) {
+            future.complete(result);
+        }
+
+        public int get(long timeout, TimeUnit unit) throws Exception {
+            return future.get(timeout, unit);
+        }
+
+    }
+
+    private void assertEntitlementResult(final Consumer<EntitlementResultListener> functor,
+            final int expect) throws Exception {
+        final EntitlementResultListener listener = new EntitlementResultListener();
+        functor.accept(listener);
+
+        assertEquals(expect, listener.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRequestLatestEntitlementResult() throws Exception {
+        // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+        // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via listener.
+        assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+                TETHERING_WIFI_P2P, false, c -> c.run(), listener),
+                TETHER_ERROR_ENTITLEMENT_UNKNOWN);
+
+        // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+        // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via receiver.
+        assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+                TETHERING_WIFI_P2P,
+                new ResultReceiver(null /* handler */) {
+                    @Override
+                    public void onReceiveResult(int resultCode, Bundle resultData) {
+                        listener.onTetheringEntitlementResult(resultCode);
+                    }
+                }, false),
+                TETHER_ERROR_ENTITLEMENT_UNKNOWN);
+
+        // Verify that null listener will cause IllegalArgumentException.
+        try {
+            mTM.requestLatestTetheringEntitlementResult(
+                    TETHERING_WIFI, false, c -> c.run(), null);
+        } catch (IllegalArgumentException expect) { }
+    }
+
+    @Test
+    public void testTetheringUpstream() throws Exception {
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        tetherEventCallback.assumeTetheringSupported();
+        final boolean previousWifiEnabledState = mWm.isWifiEnabled();
+
+        try {
+            if (!isWifiTetheringSupported(tetherEventCallback)) return;
+
+            if (previousWifiEnabledState) {
+                mCtsNetUtils.disconnectFromWifi(null);
+            }
+
+            final Network activeNetwork = mCm.getActiveNetwork();
+            assertNotNull("No active network. Please ensure the device has working mobile data.",
+                    activeNetwork);
+            final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
+
+            // If active nework is ETHERNET, tethering may not use cell network as upstream.
+            assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
+
+            assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
+
+            startWifiTethering(tetherEventCallback);
+
+            final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            final boolean dunRequired = telephonyManager.isTetheringApnRequired();
+            final int expectedCap = dunRequired ? NET_CAPABILITY_DUN : NET_CAPABILITY_INTERNET;
+            final Network network = tetherEventCallback.getCurrentValidUpstream();
+            final NetworkCapabilities netCap = mCm.getNetworkCapabilities(network);
+            assertTrue(netCap.hasTransport(TRANSPORT_CELLULAR));
+            assertTrue(netCap.hasCapability(expectedCap));
+
+            stopWifiTethering(tetherEventCallback);
+        } finally {
+            unregisterTetheringEventCallback(tetherEventCallback);
+            if (previousWifiEnabledState) {
+                mCtsNetUtils.connectToWifi();
+            }
+        }
     }
 }