Snap for 5626642 from 7fe2754b244a1f5186e8e2a80122408dd9d512c3 to qt-c2f2-release

Change-Id: I343609a79a1915c54c33c1626347095a85605130
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index c3ae793..8c9bf6e 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -63,11 +63,13 @@
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -847,30 +849,6 @@
                 keepalivesPerTransport, nc);
     }
 
-    private boolean isKeepaliveSupported() throws Exception {
-        final Network network = ensureWifiConnected();
-        final Executor executor = mContext.getMainExecutor();
-        final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
-        try (Socket s = getConnectedSocket(network, TEST_HOST,
-                HTTP_PORT, KEEPALIVE_SOCKET_TIMEOUT_MS, AF_INET);
-                SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
-            sk.start(MIN_KEEPALIVE_INTERVAL);
-            final TestSocketKeepaliveCallback.CallbackValue result = callback.pollCallback();
-            switch (result.callbackType) {
-                case ON_STARTED:
-                    sk.stop();
-                    callback.expectStopped();
-                    return true;
-                case ON_ERROR:
-                    if (result.error == SocketKeepalive.ERROR_UNSUPPORTED) return false;
-                    // else fallthrough.
-                default:
-                    fail("Got unexpected callback: " + result);
-                    return false;
-            }
-        }
-    }
-
     private void adoptShellPermissionIdentity() {
         mUiAutomation.adoptShellPermissionIdentity();
         mShellPermissionIdentityAdopted = true;
@@ -883,11 +861,85 @@
         }
     }
 
+    private static boolean isTcpKeepaliveSupportedByKernel() {
+        final String kVersionString = VintfRuntimeInfo.getKernelVersion();
+        return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
+    }
+
+    private static Pair<Integer, Integer> getVersionFromString(String version) {
+        // Only gets major and minor number of the version string.
+        final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
+        final Matcher m = versionPattern.matcher(version);
+        if (m.matches()) {
+            final int major = Integer.parseInt(m.group(1));
+            final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
+            return new Pair<>(major, minor);
+        } else {
+            return new Pair<>(0, 0);
+        }
+    }
+
+    // TODO: Move to util class.
+    private static int compareMajorMinorVersion(final String s1, final String s2) {
+        final Pair<Integer, Integer> v1 = getVersionFromString(s1);
+        final Pair<Integer, Integer> v2 = getVersionFromString(s2);
+
+        if (v1.first == v2.first) {
+            return Integer.compare(v1.second, v2.second);
+        } else {
+            return Integer.compare(v1.first, v2.first);
+        }
+    }
+
+    /**
+     * Verifies that version string compare logic returns expected result for various cases.
+     * Note that only major and minor number are compared.
+     */
+    public void testMajorMinorVersionCompare() {
+        assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
+        assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
+        assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
+        assertEquals(1, compareMajorMinorVersion("5", "4.8"));
+        assertEquals(0, compareMajorMinorVersion("5", "5.0"));
+        assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
+        assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
+        assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
+        assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
+        assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
+        assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
+    }
+
+    /**
+     * Verifies that the keepalive API cannot create any keepalive when the maximum number of
+     * keepalives is set to 0.
+     */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    public void testKeepaliveUnsupported() throws Exception {
+        if (getSupportedKeepalivesFromRes() != 0) return;
+
+        adoptShellPermissionIdentity();
+
+        assertEquals(0, createConcurrentSocketKeepalives(1, 0));
+        assertEquals(0, createConcurrentSocketKeepalives(0, 1));
+
+        dropShellPermissionIdentity();
+    }
+
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testCreateTcpKeepalive() throws Exception {
         adoptShellPermissionIdentity();
 
-        if (!isKeepaliveSupported()) return;
+        if (getSupportedKeepalivesFromRes() == 0) return;
+        // If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
+        // NAT-T keepalive. If keepalive limits from resource overlay is not zero, TCP keepalive
+        // needs to be supported except if the kernel doesn't support it.
+        if (!isTcpKeepaliveSupportedByKernel()) {
+            // Sanity check to ensure the callback result is expected.
+            assertEquals(0, createConcurrentSocketKeepalives(0, 1));
+            Log.i(TAG, "testCreateTcpKeepalive is skipped for kernel "
+                    + VintfRuntimeInfo.getKernelVersion());
+            return;
+        }
 
         final Network network = ensureWifiConnected();
         final byte[] requestBytes = CtsNetUtils.HTTP_REQUEST.getBytes("UTF-8");
@@ -952,14 +1004,16 @@
                 sk.start(MIN_KEEPALIVE_INTERVAL);
                 callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
             }
-
         }
     }
 
+    /**
+     * Creates concurrent keepalives until the specified counts of each type of keepalives are
+     * reached or the expected error callbacks are received for each type of keepalives.
+     *
+     * @return the total number of keepalives created.
+     */
     private int createConcurrentSocketKeepalives(int nattCount, int tcpCount) throws Exception {
-        // Use customization value in resource to prevent the need of privilege.
-        if (getSupportedKeepalivesFromRes() == 0) return 0;
-
         final Network network = ensureWifiConnected();
 
         final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
@@ -978,10 +1032,14 @@
                 ka.start(MIN_KEEPALIVE_INTERVAL);
                 TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
                 assertNotNull(cv);
-                if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR
-                        && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
-                    // Limit reached.
-                    break;
+                if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
+                    if (i == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
+                        // Unsupported.
+                        break;
+                    } else if (i != 0 && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+                        // Limit reached.
+                        break;
+                    }
                 }
                 if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
                     kalist.add(ka);
@@ -1007,10 +1065,14 @@
             ka.start(MIN_KEEPALIVE_INTERVAL);
             TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
             assertNotNull(cv);
-            if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR
-                    && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
-                // Limit reached.
-                break;
+            if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
+                if (i == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
+                    // Unsupported.
+                    break;
+                } else if (i != 0 && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+                    // Limit reached.
+                    break;
+                }
             }
             if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
                 kalist.add(ka);
@@ -1038,32 +1100,35 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testSocketKeepaliveLimit() throws Exception {
-        adoptShellPermissionIdentity();
-
         final int supported = getSupportedKeepalivesFromRes();
-
-        if (!isKeepaliveSupported()) {
-            // Sanity check.
-            assertEquals(0, supported);
+        if (supported == 0) {
             return;
         }
 
+        adoptShellPermissionIdentity();
+
         // Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
         assertGreaterOrEqual(supported, KeepaliveUtils.MIN_SUPPORTED_KEEPALIVE_COUNT);
 
-        // Verifies that different types of keepalives can be established.
+        // Verifies that Nat-T keepalives can be established.
         assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
-        assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
-
-        // Verifies that different types can be established at the same time.
-        assertEquals(supported, createConcurrentSocketKeepalives(
-                supported / 2, supported - supported / 2));
-
         // Verifies that keepalives don't get leaked in second round.
         assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
-        assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
-        assertEquals(supported, createConcurrentSocketKeepalives(
-                supported / 2, supported - supported / 2));
+
+        // If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
+        // NAT-T keepalive. Test below cases only if TCP keepalive is supported by kernel.
+        if (isTcpKeepaliveSupportedByKernel()) {
+            assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
+
+            // Verifies that different types can be established at the same time.
+            assertEquals(supported, createConcurrentSocketKeepalives(
+                    supported / 2, supported - supported / 2));
+
+            // Verifies that keepalives don't get leaked in second round.
+            assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
+            assertEquals(supported, createConcurrentSocketKeepalives(
+                    supported / 2, supported - supported / 2));
+        }
 
         dropShellPermissionIdentity();
     }
@@ -1074,14 +1139,9 @@
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testSocketKeepaliveUnprivileged() throws Exception {
         final int supported = getSupportedKeepalivesFromRes();
-
-        adoptShellPermissionIdentity();
-        if (!isKeepaliveSupported()) {
-            // Sanity check.
-            assertEquals(0, supported);
+        if (supported == 0) {
             return;
         }
-        dropShellPermissionIdentity();
 
         final int allowedUnprivilegedPerUid = mContext.getResources().getInteger(
                 R.integer.config_allowedUnprivilegedKeepalivePerUid);
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 945f51d..e16fce0 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -21,20 +21,25 @@
 import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
 import static android.net.DnsResolver.TYPE_A;
 import static android.net.DnsResolver.TYPE_AAAA;
-import static android.system.OsConstants.EBADF;
+import static android.system.OsConstants.ETIMEDOUT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.ContentResolver;
 import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.DnsPacket;
 import android.net.DnsResolver;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.ParseException;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.Settings;
 import android.system.ErrnoException;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -53,20 +58,63 @@
     private static final char[] HEX_CHARS = {
             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
     };
+
+    static final String TEST_DOMAIN = "www.google.com";
+    static final String INVALID_PRIVATE_DNS_SERVER = "invalid.google";
+    static final byte[] TEST_BLOB = new byte[]{
+            /* Header */
+            0x55, 0x66, /* Transaction ID */
+            0x01, 0x00, /* Flags */
+            0x00, 0x01, /* Questions */
+            0x00, 0x00, /* Answer RRs */
+            0x00, 0x00, /* Authority RRs */
+            0x00, 0x00, /* Additional RRs */
+            /* Queries */
+            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+            0x00, 0x01, /* Type */
+            0x00, 0x01  /* Class */
+    };
     static final int TIMEOUT_MS = 12_000;
     static final int CANCEL_TIMEOUT_MS = 3_000;
     static final int CANCEL_RETRY_TIMES = 5;
     static final int NXDOMAIN = 3;
+    static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 2_000;
 
+    private ContentResolver mCR;
     private ConnectivityManager mCM;
     private Executor mExecutor;
     private DnsResolver mDns;
 
+    private String mOldMode;
+    private String mOldDnsSpecifier;
+
+    @Override
     protected void setUp() throws Exception {
         super.setUp();
         mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         mDns = DnsResolver.getInstance();
         mExecutor = new Handler(Looper.getMainLooper())::post;
+        mCR = getContext().getContentResolver();
+        storePrivateDnsSetting();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        restorePrivateDnsSetting();
+        super.tearDown();
+    }
+
+    private void storePrivateDnsSetting() {
+        // Store private DNS setting
+        mOldMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+        mOldDnsSpecifier = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER);
+    }
+
+    private void restorePrivateDnsSetting() {
+        // restore private DNS setting
+        Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldMode);
+        Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, mOldDnsSpecifier);
     }
 
     private static String byteArrayToHexString(byte[] bytes) {
@@ -94,6 +142,9 @@
                 "This test requires that at least one network be connected. " +
                         "Please ensure that the device is connected to a network.",
                 testableNetworks.size() >= 1);
+        // In order to test query with null network, add null as an element.
+        // Test cases which query with null network will go on default network.
+        testableNetworks.add(null);
         return testableNetworks.toArray(new Network[0]);
     }
 
@@ -105,15 +156,12 @@
         public DnsParseException(String msg) {
             super(msg);
         }
-
-        public DnsParseException(String msg, Throwable cause) {
-            super(msg, cause);
-        }
     }
 
     private static class DnsAnswer extends DnsPacket {
         DnsAnswer(@NonNull byte[] data) throws DnsParseException {
             super(data);
+
             // Check QR field.(query (0), or a response (1)).
             if ((mHeader.flags & (1 << 15)) == 0) {
                 throw new DnsParseException("Not an answer packet");
@@ -123,10 +171,12 @@
         int getRcode() {
             return mHeader.rcode;
         }
-        int getANCount(){
+
+        int getANCount() {
             return mHeader.getRecordCount(ANSECTION);
         }
-        int getQDCount(){
+
+        int getQDCount() {
             return mHeader.getRecordCount(QDSECTION);
         }
     }
@@ -173,7 +223,7 @@
             mRcode = rcode;
             try {
                 mDnsAnswer = new DnsAnswer(answer);
-            } catch (DnsParseException e) {
+            } catch (ParseException | DnsParseException e) {
                 fail(mMsg + e.getMessage());
             }
             Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
@@ -222,90 +272,76 @@
         }
     }
 
-    public void testRawQuery() {
-        final String dname = "www.google.com";
-        final String msg = "RawQuery " + dname;
+    public void testRawQuery() throws InterruptedException {
+        final String msg = "RawQuery " + TEST_DOMAIN;
         for (Network network : getTestableNetworks()) {
             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
-            mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+            mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
                     mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                callback.assertHasAnswer();
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            callback.assertHasAnswer();
         }
     }
 
-    public void testRawQueryBlob() {
+    public void testRawQueryBlob() throws InterruptedException {
         final byte[] blob = new byte[]{
-            /* Header */
-            0x55, 0x66, /* Transaction ID */
-            0x01, 0x00, /* Flags */
-            0x00, 0x01, /* Questions */
-            0x00, 0x00, /* Answer RRs */
-            0x00, 0x00, /* Authority RRs */
-            0x00, 0x00, /* Additional RRs */
-            /* Queries */
-            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
-            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
-            0x00, 0x01, /* Type */
-            0x00, 0x01  /* Class */
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                0x01, 0x00, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x00, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01  /* Class */
         };
         final String msg = "RawQuery blob " + byteArrayToHexString(blob);
         for (Network network : getTestableNetworks()) {
             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
             mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                callback.assertHasAnswer();
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            callback.assertHasAnswer();
         }
     }
 
-    public void testRawQueryRoot() {
+    public void testRawQueryRoot() throws InterruptedException {
         final String dname = "";
         final String msg = "RawQuery empty dname(ROOT) ";
         for (Network network : getTestableNetworks()) {
             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
             mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
                     mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                // Except no answer record because the root does not have AAAA records.
-                callback.assertEmptyAnswer();
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            // Except no answer record because the root does not have AAAA records.
+            callback.assertEmptyAnswer();
         }
     }
 
-    public void testRawQueryNXDomain() {
+    public void testRawQueryNXDomain() throws InterruptedException {
         final String dname = "test1-nx.metric.gstatic.com";
         final String msg = "RawQuery " + dname;
         for (Network network : getTestableNetworks()) {
             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
             mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
                     mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                callback.assertNXDomain();
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            callback.assertNXDomain();
         }
     }
 
-    public void testRawQueryCancel() throws ErrnoException {
-        final String dname = "www.google.com";
-        final String msg = "Test cancel RawQuery " + dname;
+    public void testRawQueryCancel() throws InterruptedException {
+        final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
         // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
         // that the query is cancelled before it succeeds. If it is not cancelled before it
         // succeeds, retry the test until it is.
@@ -319,39 +355,22 @@
                 final CountDownLatch latch = new CountDownLatch(1);
                 final CancellationSignal cancelSignal = new CancellationSignal();
                 final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
-                mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+                mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
                         mExecutor, cancelSignal, callback);
                 mExecutor.execute(() -> {
                     cancelSignal.cancel();
                     latch.countDown();
                 });
-                try {
-                    retry = callback.needRetry();
-                    assertTrue(msg + " query was not cancelled",
-                            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-                } catch (InterruptedException e) {
-                    fail(msg + " Waiting for DNS lookup was interrupted");
-                }
+
+                retry = callback.needRetry();
+                assertTrue(msg + " query was not cancelled",
+                        latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
             } while (retry);
         }
     }
 
-    public void testRawQueryBlobCancel() throws ErrnoException {
-        final byte[] blob = new byte[]{
-            /* Header */
-            0x55, 0x66, /* Transaction ID */
-            0x01, 0x00, /* Flags */
-            0x00, 0x01, /* Questions */
-            0x00, 0x00, /* Answer RRs */
-            0x00, 0x00, /* Authority RRs */
-            0x00, 0x00, /* Additional RRs */
-            /* Queries */
-            0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
-            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
-            0x00, 0x01, /* Type */
-            0x00, 0x01  /* Class */
-        };
-        final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(blob);
+    public void testRawQueryBlobCancel() throws InterruptedException {
+        final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
         // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
         // that the query is cancelled before it succeeds. If it is not cancelled before it
         // succeeds, retry the test until it is.
@@ -365,37 +384,30 @@
                 final CountDownLatch latch = new CountDownLatch(1);
                 final CancellationSignal cancelSignal = new CancellationSignal();
                 final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
-                mDns.rawQuery(network, blob, FLAG_EMPTY, mExecutor, cancelSignal, callback);
+                mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
                 mExecutor.execute(() -> {
                     cancelSignal.cancel();
                     latch.countDown();
                 });
-                try {
-                    retry = callback.needRetry();
-                    assertTrue(msg + " cancel is not done",
-                            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-                } catch (InterruptedException e) {
-                    fail(msg + " Waiting for DNS lookup was interrupted");
-                }
+
+                retry = callback.needRetry();
+                assertTrue(msg + " cancel is not done",
+                        latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
             } while (retry);
         }
     }
 
-    public void testCancelBeforeQuery() throws ErrnoException {
-        final String dname = "www.google.com";
-        final String msg = "Test cancelled RawQuery " + dname;
+    public void testCancelBeforeQuery() throws InterruptedException {
+        final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
         for (Network network : getTestableNetworks()) {
             final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
             final CancellationSignal cancelSignal = new CancellationSignal();
             cancelSignal.cancel();
-            mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+            mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
                     mExecutor, cancelSignal, callback);
-            try {
-                assertTrue(msg + " should not return any answers",
-                        !callback.waitForAnswer(CANCEL_TIMEOUT_MS));
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+
+            assertTrue(msg + " should not return any answers",
+                    !callback.waitForAnswer(CANCEL_TIMEOUT_MS));
         }
     }
 
@@ -462,27 +474,21 @@
         }
     }
 
-    public void testQueryForInetAddress() {
-        final String dname = "www.google.com";
-        final String msg = "Test query for InetAddress " + dname;
+    public void testQueryForInetAddress() throws InterruptedException {
+        final String msg = "Test query for InetAddress " + TEST_DOMAIN;
         for (Network network : getTestableNetworks()) {
             final VerifyCancelInetAddressCallback callback =
                     new VerifyCancelInetAddressCallback(msg, null);
-            mDns.query(network, dname, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+            mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
         }
     }
 
-    public void testQueryCancelForInetAddress() throws ErrnoException {
-        final String dname = "www.google.com";
-        final String msg = "Test cancel query for InetAddress " + dname;
+    public void testQueryCancelForInetAddress() throws InterruptedException {
+        final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
         // Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
         // expect that the query is cancelled before it succeeds. If it is not cancelled before it
         // succeeds, retry the test until it is.
@@ -497,57 +503,131 @@
                 final CancellationSignal cancelSignal = new CancellationSignal();
                 final VerifyCancelInetAddressCallback callback =
                         new VerifyCancelInetAddressCallback(msg, cancelSignal);
-                mDns.query(network, dname, FLAG_EMPTY, mExecutor, cancelSignal, callback);
+                mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
                 mExecutor.execute(() -> {
                     cancelSignal.cancel();
                     latch.countDown();
                 });
-                try {
-                    retry = callback.needRetry();
-                    assertTrue(msg + " query was not cancelled",
-                            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-                } catch (InterruptedException e) {
-                    fail(msg + " Waiting for DNS lookup was interrupted");
-                }
+
+                retry = callback.needRetry();
+                assertTrue(msg + " query was not cancelled",
+                        latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
             } while (retry);
         }
     }
 
-    public void testQueryForInetAddressIpv4() {
-        final String dname = "www.google.com";
-        final String msg = "Test query for IPv4 InetAddress " + dname;
+    public void testQueryForInetAddressIpv4() throws InterruptedException {
+        final String msg = "Test query for IPv4 InetAddress " + TEST_DOMAIN;
         for (Network network : getTestableNetworks()) {
             final VerifyCancelInetAddressCallback callback =
                     new VerifyCancelInetAddressCallback(msg, null);
-            mDns.query(network, dname, TYPE_A, FLAG_NO_CACHE_LOOKUP,
+            mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
                     mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
-                assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+            assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
         }
     }
 
-    public void testQueryForInetAddressIpv6() {
-        final String dname = "www.google.com";
-        final String msg = "Test query for IPv6 InetAddress " + dname;
+    public void testQueryForInetAddressIpv6() throws InterruptedException {
+        final String msg = "Test query for IPv6 InetAddress " + TEST_DOMAIN;
         for (Network network : getTestableNetworks()) {
             final VerifyCancelInetAddressCallback callback =
                     new VerifyCancelInetAddressCallback(msg, null);
-            mDns.query(network, dname, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+            mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
                     mExecutor, null, callback);
-            try {
-                assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
-                        callback.waitForAnswer());
-                assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
-                assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
+
+            assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+            assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
+        }
+    }
+
+    private void awaitPrivateDnsSetting(@NonNull String msg,
+            @NonNull Network network, @NonNull String server) throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+        NetworkCallback callback = new NetworkCallback() {
+            @Override
+            public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
+                if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
+                    latch.countDown();
+                }
             }
+        };
+        mCM.registerNetworkCallback(request, callback);
+        assertTrue(msg, latch.await(PRIVATE_DNS_SETTING_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        mCM.unregisterNetworkCallback(callback);
+    }
+
+    public void testPrivateDnsBypass() throws InterruptedException {
+        final Network[] testNetworks = getTestableNetworks();
+
+        // Set an invalid private DNS server
+        Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
+        Settings.Global.putString(mCR,
+                Settings.Global.PRIVATE_DNS_SPECIFIER, INVALID_PRIVATE_DNS_SERVER);
+
+        final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
+        for (Network network : testNetworks) {
+            // This test cannot be ran with null network because we need to explicitly pass a
+            // private DNS bypassable network or bind one.
+            if (network == null) continue;
+
+            // wait for private DNS setting propagating
+            awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
+                    network, INVALID_PRIVATE_DNS_SERVER);
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final DnsResolver.Callback<List<InetAddress>> errorCallback =
+                    new DnsResolver.Callback<List<InetAddress>>() {
+                        @Override
+                        public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+                            fail(msg + " should not get valid answer");
+                        }
+
+                        @Override
+                        public void onError(@NonNull DnsResolver.DnsException error) {
+                            assertEquals(DnsResolver.ERROR_SYSTEM, error.code);
+                            assertEquals(ETIMEDOUT, ((ErrnoException) error.getCause()).errno);
+                            latch.countDown();
+                        }
+                    };
+            // Private DNS strict mode with invalid DNS server is set
+            // Expect no valid answer returned but ErrnoException with ETIMEDOUT
+            mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, errorCallback);
+
+            assertTrue(msg + " invalid server round. No response after " + TIMEOUT_MS + "ms.",
+                    latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            final VerifyCancelInetAddressCallback callback =
+                    new VerifyCancelInetAddressCallback(msg, null);
+            // Bypass privateDns, expect query works fine
+            mDns.query(network.getPrivateDnsBypassingCopy(),
+                    TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
+
+            assertTrue(msg + " bypass private DNS round. No answer after " + TIMEOUT_MS + "ms.",
+                    callback.waitForAnswer());
+            assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+
+            // To ensure private DNS bypass still work even if passing null network.
+            // Bind process network with a private DNS bypassable network.
+            mCM.bindProcessToNetwork(network.getPrivateDnsBypassingCopy());
+            final VerifyCancelInetAddressCallback callbackWithNullNetwork =
+                    new VerifyCancelInetAddressCallback(msg + " with null network ", null);
+            mDns.query(null,
+                    TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callbackWithNullNetwork);
+
+            assertTrue(msg + " with null network bypass private DNS round. No answer after " +
+                    TIMEOUT_MS + "ms.", callbackWithNullNetwork.waitForAnswer());
+            assertTrue(msg + " with null network returned 0 results",
+                    !callbackWithNullNetwork.isAnswerEmpty());
+
+            // Reset process network to default.
+            mCM.bindProcessToNetwork(null);
         }
     }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
index cb0cda8..e4e350c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
@@ -25,6 +25,7 @@
 import android.net.ConnectivityManager;
 import android.platform.test.annotations.AppModeFull;
 import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -39,7 +40,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Formatter;
@@ -51,8 +52,6 @@
     private static final String TEST_WATCHLIST_XML = "assets/network_watchlist_config_for_test.xml";
     private static final String TEST_EMPTY_WATCHLIST_XML =
             "assets/network_watchlist_config_empty_for_test.xml";
-    private static final String SDCARD_CONFIG_PATH =
-            "/sdcard/network_watchlist_config_for_test.xml";
     private static final String TMP_CONFIG_PATH =
             "/data/local/tmp/network_watchlist_config_for_test.xml";
     // Generated from sha256sum network_watchlist_config_for_test.xml
@@ -84,8 +83,7 @@
         }
     }
 
-    private void cleanup() throws Exception {
-        runCommand("rm " + SDCARD_CONFIG_PATH);
+    private void cleanup() throws IOException {
         runCommand("rm " + TMP_CONFIG_PATH);
     }
 
@@ -118,22 +116,43 @@
     }
 
     private void saveResourceToFile(String res, String filePath) throws IOException {
-        InputStream in = getClass().getClassLoader().getResourceAsStream(res);
-        FileUtils.copyToFileOrThrow(in, new File(filePath));
+        // App can't access /data/local/tmp directly, so we pipe resource to file through stdin.
+        ParcelFileDescriptor stdin = pipeFromStdin(filePath);
+        pipeResourceToFileDescriptor(res, stdin);
+    }
+
+    /* Pipe stdin to a file in filePath. Returns PFD for stdin. */
+    private ParcelFileDescriptor pipeFromStdin(String filePath) {
+        // Not all devices have symlink for /dev/stdin, so use /proc/self/fd/0 directly.
+        // /dev/stdin maps to /proc/self/fd/0.
+        return runRwCommand("cp /proc/self/fd/0 " + filePath)[1];
+    }
+
+    private void pipeResourceToFileDescriptor(String res, ParcelFileDescriptor pfd)
+            throws IOException {
+        InputStream resStream = getClass().getClassLoader().getResourceAsStream(res);
+        FileOutputStream fdStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+
+        FileUtils.copy(resStream, fdStream);
+
+        try {
+            fdStream.close();
+        } catch (IOException e) {
+        }
     }
 
     private static String runCommand(String command) throws IOException {
         return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
     }
 
+    private static ParcelFileDescriptor[] runRwCommand(String command) {
+        return InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().executeShellCommandRw(command);
+    }
+
     private void setWatchlistConfig(String watchlistConfigFile) throws Exception {
         cleanup();
-        // Save test watchlist config to sdcard as app can't access /data/local/tmp
-        saveResourceToFile(watchlistConfigFile, SDCARD_CONFIG_PATH);
-        // Copy test watchlist config from sdcard to /data/local/tmp as system service
-        // can't access /sdcard
-        runCommand("cp " + SDCARD_CONFIG_PATH + " " + TMP_CONFIG_PATH);
-        // Set test watchlist config to system
+        saveResourceToFile(watchlistConfigFile, TMP_CONFIG_PATH);
         final String cmdResult = runCommand(
                 "cmd network_watchlist set-test-config " + TMP_CONFIG_PATH).trim();
         assertThat(cmdResult).contains("Success");