Merge changes Ic19b3b64,I26ca370f

* changes:
  Add cts test cases for NetworkCallback.onBlockedStatusChanged
  Export API of listening for network change events in app2
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 6fbe586..f6cc768 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -17,9 +17,11 @@
 package android.net.cts;
 
 import static android.net.DnsResolver.CLASS_IN;
+import static android.net.DnsResolver.FLAG_EMPTY;
 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 android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +32,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.ParseException;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
 import android.system.ErrnoException;
@@ -50,6 +53,7 @@
             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
     };
     static final int TIMEOUT_MS = 12_000;
+    static final int CANCEL_RETRY_TIMES = 5;
 
     private ConnectivityManager mCM;
     private Executor mExecutor;
@@ -118,7 +122,7 @@
                 }
             };
             mDns.query(network, dname, CLASS_IN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+                    mExecutor, null, callback);
             try {
                 assertTrue(msg + " but no valid answer after " + TIMEOUT_MS + "ms.",
                         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -174,12 +178,19 @@
     class RawAnswerCallbackImpl extends DnsResolver.RawAnswerCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final String mMsg;
-        RawAnswerCallbackImpl(String msg) {
+        private final int mTimeout;
+
+        RawAnswerCallbackImpl(@NonNull String msg, int timeout) {
             this.mMsg = msg;
+            this.mTimeout = timeout;
+        }
+
+        RawAnswerCallbackImpl(@NonNull String msg) {
+            this(msg, TIMEOUT_MS);
         }
 
         public boolean waitForAnswer() throws InterruptedException {
-            return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return mLatch.await(mTimeout, TimeUnit.MILLISECONDS);
         }
 
         @Override
@@ -210,7 +221,7 @@
         for (Network network : getTestableNetworks()) {
             final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
             mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+                    mExecutor, null, callback);
             try {
                 assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
                         callback.waitForAnswer());
@@ -238,7 +249,7 @@
         final String msg = "Query with RawAnswerCallback " + byteArrayToHexString(blob);
         for (Network network : getTestableNetworks()) {
             final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
-            mDns.query(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, callback);
+            mDns.query(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
             try {
                 assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
                         callback.waitForAnswer());
@@ -276,7 +287,7 @@
                 }
             };
             mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+                    mExecutor, null, callback);
             try {
                 assertTrue(msg + "but no answer after " + TIMEOUT_MS + "ms.",
                         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -313,7 +324,7 @@
                 }
             };
             mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+                    mExecutor, null, callback);
             try {
                 assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
                         latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -322,4 +333,141 @@
             }
         }
     }
+
+    /**
+     * A query callback that ensures that the query is cancelled and that onAnswer is never
+     * called. If the query succeeds before it is cancelled, needRetry will return true so the
+     * test can retry.
+     */
+    class VerifyCancelCallback extends DnsResolver.RawAnswerCallback {
+        private static final int CANCEL_TIMEOUT = 3_000;
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final String mMsg;
+        private final CancellationSignal mCancelSignal;
+
+        VerifyCancelCallback(@NonNull String msg, @NonNull CancellationSignal cancelSignal) {
+            this.mMsg = msg;
+            this.mCancelSignal = cancelSignal;
+        }
+
+        public boolean needRetry() throws InterruptedException {
+            return mLatch.await(CANCEL_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void onAnswer(@NonNull byte[] answer) {
+            if (mCancelSignal.isCanceled()) {
+                fail(mMsg + " should not have returned any answers");
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onParseException(@NonNull ParseException e) {
+            fail(mMsg + e.getMessage());
+        }
+
+        @Override
+        public void onQueryException(@NonNull ErrnoException e) {
+            if (mCancelSignal.isCanceled() && e.errno == EBADF) return;
+            fail(mMsg + e.getMessage());
+        }
+    }
+
+    public void testQueryCancel() throws ErrnoException {
+        final String dname = "www.google.com";
+        final String msg = "Test cancel query " + dname;
+        // 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 until it is.
+        for (Network network : getTestableNetworks()) {
+            boolean retry = false;
+            int round = 0;
+            do {
+                if (++round > CANCEL_RETRY_TIMES) {
+                    fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+                }
+                final CountDownLatch latch = new CountDownLatch(1);
+                final CancellationSignal cancelSignal = new CancellationSignal();
+                final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
+                mDns.query(network, dname, 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");
+                }
+            } while (retry);
+        }
+    }
+
+    public void testQueryBlobCancel() 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 raw Query " + byteArrayToHexString(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 until it is.
+        for (Network network : getTestableNetworks()) {
+            boolean retry = false;
+            int round = 0;
+            do {
+                if (++round > CANCEL_RETRY_TIMES) {
+                    fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+                }
+                final CountDownLatch latch = new CountDownLatch(1);
+                final CancellationSignal cancelSignal = new CancellationSignal();
+                final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
+                mDns.query(network, 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");
+                }
+            } while (retry);
+        }
+    }
+
+    public void testCancelBeforeQuery() throws ErrnoException {
+        final String dname = "www.google.com";
+        final String msg = "Test cancelled query " + dname;
+        for (Network network : getTestableNetworks()) {
+            final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg, 3_000);
+            final CancellationSignal cancelSignal = new CancellationSignal();
+            cancelSignal.cancel();
+            mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+                    mExecutor, cancelSignal, callback);
+            try {
+                assertTrue(msg + " should not return any answers",
+                        !callback.waitForAnswer());
+            } catch (InterruptedException e) {
+                fail(msg + " Waiting for DNS lookup was interrupted");
+            }
+        }
+    }
 }