Merge "CTS tests related to VPN meteredness."
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 0656cae..c7cab7b 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -16,6 +16,9 @@
 <configuration description="Config for CTS net host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
     <target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/cts/hostside/aidl/Android.mk b/tests/cts/hostside/aidl/Android.mk
index 85f71c3..20dabc1 100644
--- a/tests/cts/hostside/aidl/Android.mk
+++ b/tests/cts/hostside/aidl/Android.mk
@@ -19,6 +19,7 @@
 LOCAL_SDK_VERSION := current
 LOCAL_SRC_FILES := \
         com/android/cts/net/hostside/IMyService.aidl \
+        com/android/cts/net/hostside/INetworkCallback.aidl \
         com/android/cts/net/hostside/INetworkStateObserver.aidl \
         com/android/cts/net/hostside/IRemoteSocketFactory.aidl
 LOCAL_MODULE := CtsHostsideNetworkTestsAidl
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 72d1059..a820ae5 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -16,10 +16,13 @@
 
 package com.android.cts.net.hostside;
 
+import com.android.cts.net.hostside.INetworkCallback;
+
 interface IMyService {
     void registerBroadcastReceiver();
     int getCounters(String receiverName, String action);
     String checkNetworkStatus();
     String getRestrictBackgroundStatus();
     void sendNotification(int notificationId, String notificationType);
+    void registerNetworkCallback(in INetworkCallback cb);
 }
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
new file mode 100644
index 0000000..740ec26
--- /dev/null
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.net.Network;
+
+interface INetworkCallback {
+    void onBlockedStatusChanged(in Network network, boolean blocked);
+    void onAvailable(in Network network);
+    void onLost(in Network network);
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 5232372..bbc0354 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -370,6 +370,23 @@
     }
 
     /**
+     * As per CDD requirements, if the device doesn't support data saver mode then
+     * ConnectivityManager.getRestrictBackgroundStatus() will always return
+     * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
+     * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
+     * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
+     */
+    protected boolean isDataSaverSupported() throws Exception {
+        assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+        try {
+            setRestrictBackground(true);
+            return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+        } finally {
+            setRestrictBackground(false);
+        }
+    }
+
+    /**
      * Returns whether an app state should be considered "background" for restriction purposes.
      */
     protected boolean isBackground(int state) {
@@ -962,6 +979,10 @@
         fail("app2 receiver is not ready");
     }
 
+    protected void registerNetworkCallback(INetworkCallback cb) throws Exception {
+        mServiceClient.registerNetworkCallback(cb);
+    }
+
     /**
      * Registers a {@link NotificationListenerService} implementation that will execute the
      * notification actions right after the notification is sent.
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 599a31c..72563d4 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -73,23 +73,6 @@
         return mIsDataSaverSupported && super.isSupported();
     }
 
-    /**
-     * As per CDD requirements, if the device doesn't support data saver mode then
-     * ConnectivityManager.getRestrictBackgroundStatus() will always return
-     * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
-     * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
-     * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
-     */
-    private boolean isDataSaverSupported() throws Exception {
-        assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
-        try {
-            setRestrictBackground(true);
-            return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
-        } finally {
-            setRestrictBackground(false);
-        }
-    }
-
     public void testGetRestrictBackgroundStatus_disabled() throws Exception {
         if (!isSupported()) return;
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index e2976c2..3ee7b99 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -26,8 +26,6 @@
 
 import com.android.cts.net.hostside.IMyService;
 
-import java.io.FileDescriptor;
-
 public class MyServiceClient {
     private static final int TIMEOUT_MS = 5000;
     private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
@@ -98,4 +96,8 @@
     public void sendNotification(int notificationId, String notificationType) throws RemoteException {
         mService.sendNotification(notificationId, notificationType);
     }
+
+    public void registerNetworkCallback(INetworkCallback cb) throws RemoteException {
+        mService.registerNetworkCallback(cb);
+    }
 }
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
new file mode 100644
index 0000000..24dde9d
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.net.Network;
+
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
+
+    private boolean mIsDataSaverSupported;
+    private Network mNetwork;
+    private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
+
+    enum CallbackState {
+        NONE,
+        AVAILABLE,
+        LOST,
+        BLOCKED_STATUS
+    }
+
+    private static class CallbackInfo {
+        public final CallbackState state;
+        public final Network network;
+        public final Object arg;
+
+        CallbackInfo(CallbackState s, Network n, Object o) {
+            state = s; network = n; arg = o;
+        }
+
+        public String toString() {
+            return String.format("%s (%s) (%s)", state, network, arg);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof CallbackInfo)) return false;
+            // Ignore timeMs, since it's unpredictable.
+            final CallbackInfo other = (CallbackInfo) o;
+            return (state == other.state) && Objects.equals(network, other.network)
+                    && Objects.equals(arg, other.arg);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(state, network, arg);
+        }
+    }
+
+    private class TestNetworkCallback extends INetworkCallback.Stub {
+        private static final int TEST_CALLBACK_TIMEOUT_MS = 200;
+
+        private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
+
+        protected void setLastCallback(CallbackState state, Network network, Object o) {
+            mCallbacks.offer(new CallbackInfo(state, network, o));
+        }
+
+        CallbackInfo nextCallback(int timeoutMs) {
+            CallbackInfo cb = null;
+            try {
+                cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
+            if (cb == null) {
+                fail("Did not receive callback after " + timeoutMs + "ms");
+            }
+            return cb;
+        }
+
+        CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
+            final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
+            final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
+            assertEquals("Unexpected callback:", expected, actual);
+            return actual;
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            setLastCallback(CallbackState.AVAILABLE, network, null);
+        }
+
+        @Override
+        public void onLost(Network network) {
+            setLastCallback(CallbackState.LOST, network, null);
+        }
+
+        @Override
+        public void onBlockedStatusChanged(Network network, boolean blocked) {
+            setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
+        }
+
+        public void expectLostCallback(Network expectedNetwork) {
+            expectCallback(CallbackState.LOST, expectedNetwork, null);
+        }
+
+        public void expectAvailableCallback(Network expectedNetwork) {
+            expectCallback(CallbackState.AVAILABLE, expectedNetwork, null);
+        }
+
+        public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
+            expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork,
+                    expectBlocked);
+        }
+
+        void assertNoCallback() {
+            CallbackInfo cb = null;
+            try {
+                cb = mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // Expected.
+            }
+            if (cb != null) {
+                assertNull("Unexpected callback: " + cb, cb);
+            }
+        }
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mIsDataSaverSupported = isDataSaverSupported();
+
+        mNetwork = mCm.getActiveNetwork();
+
+        // Set initial state.
+        setBatterySaverMode(false);
+        registerBroadcastReceiver();
+
+        if (!mIsDataSaverSupported) return;
+        setRestrictBackground(false);
+        removeRestrictBackgroundWhitelist(mUid);
+        removeRestrictBackgroundBlacklist(mUid);
+        assertRestrictBackgroundChangedReceived(0);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        if (!mIsDataSaverSupported) return;
+
+        try {
+            resetMeteredNetwork();
+        } finally {
+            setRestrictBackground(false);
+        }
+    }
+
+    public void testOnBlockedStatusChanged_data_saver() throws Exception {
+        if (!mIsDataSaverSupported) return;
+
+        // Prepare metered wifi
+        if (!setMeteredNetwork()) return;
+
+        // Register callback
+        registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+        mTestNetworkCallback.expectAvailableCallback(mNetwork);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+        // Enable restrict background
+        setRestrictBackground(true);
+        assertBackgroundNetworkAccess(false);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+        // Add to whitelist
+        addRestrictBackgroundWhitelist(mUid);
+        assertBackgroundNetworkAccess(true);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+        // Remove from whitelist
+        removeRestrictBackgroundWhitelist(mUid);
+        assertBackgroundNetworkAccess(false);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+        // Set to non-metered network
+        setUnmeteredNetwork();
+        assertBackgroundNetworkAccess(true);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+        // Disable restrict background, should not trigger callback
+        setRestrictBackground(false);
+        assertBackgroundNetworkAccess(true);
+        mTestNetworkCallback.assertNoCallback();
+    }
+
+
+    public void testOnBlockedStatusChanged_power_saver() throws Exception {
+        // Prepare metered wifi
+        if (!setMeteredNetwork()) return;
+
+        // Register callback
+        registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+        mTestNetworkCallback.expectAvailableCallback(mNetwork);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+        // Enable Power Saver
+        setBatterySaverMode(true);
+        assertBackgroundNetworkAccess(false);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+        // Disable Power Saver
+        setBatterySaverMode(false);
+        assertBackgroundNetworkAccess(true);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+        // Set to non-metered network
+        setUnmeteredNetwork();
+        mTestNetworkCallback.assertNoCallback();
+
+        // Enable Power Saver
+        setBatterySaverMode(true);
+        assertBackgroundNetworkAccess(false);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+        // Disable Power Saver
+        setBatterySaverMode(false);
+        assertBackgroundNetworkAccess(true);
+        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+    }
+
+    // TODO: 1. test against VPN lockdown.
+    //       2. test against multiple networks.
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
index a4a2956..124c2c3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.net.hostside;
 
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
+import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
+
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -47,8 +52,6 @@
 
     private static final byte ICMP_ECHO = 8;
     private static final byte ICMP_ECHOREPLY = 0;
-    private static final byte ICMPV6_ECHO_REQUEST = (byte) 128;
-    private static final byte ICMPV6_ECHO_REPLY = (byte) 129;
 
     private static String TAG = "PacketReflector";
 
@@ -125,7 +128,7 @@
 
         byte type = buf[hdrLen];
         if (!(version == 4 && type == ICMP_ECHO) &&
-            !(version == 6 && type == ICMPV6_ECHO_REQUEST)) {
+            !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
             return;
         }
 
@@ -145,10 +148,18 @@
             return;
         }
 
+        byte replyType = buf[hdrLen];
+        if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
+                || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
+            Log.i(TAG, "Received unexpected ICMP reply: original " + type
+                    + ", reply " + replyType);
+            return;
+        }
+
         // Compare the response we got with the original packet.
         // The only thing that should have changed are addresses, type and checksum.
         // Overwrite them with the received bytes and see if the packet is otherwise identical.
-        request[hdrLen] = buf[hdrLen];          // Type.
+        request[hdrLen] = buf[hdrLen];          // Type
         request[hdrLen + 2] = buf[hdrLen + 2];  // Checksum byte 1.
         request[hdrLen + 3] = buf[hdrLen + 3];  // Checksum byte 2.
 
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 2496c4a..ec536af 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -16,6 +16,7 @@
 package com.android.cts.net.hostside.app2;
 
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+
 import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
 import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
 import static com.android.cts.net.hostside.app2.Common.TAG;
@@ -26,13 +27,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.IBinder;
-import android.os.Looper;
+import android.os.RemoteException;
 import android.util.Log;
-import android.widget.Toast;
 
 import com.android.cts.net.hostside.IMyService;
+import com.android.cts.net.hostside.INetworkCallback;
 
 /**
  * Service used to dynamically register a broadcast receiver.
@@ -40,7 +44,10 @@
 public class MyService extends Service {
     private static final String NOTIFICATION_CHANNEL_ID = "MyService";
 
+    ConnectivityManager mCm;
+
     private MyBroadcastReceiver mReceiver;
+    private ConnectivityManager.NetworkCallback mNetworkCallback;
 
     // TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
 
@@ -81,8 +88,67 @@
             MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
                     notificationId, notificationType);
         }
+
+        @Override
+        public void registerNetworkCallback(INetworkCallback cb) {
+            if (mNetworkCallback != null) {
+                Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
+                unregisterNetworkCallback();
+            }
+            Log.d(TAG, "registering network callback");
+
+            mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+                @Override
+                public void onBlockedStatusChanged(Network network, boolean blocked) {
+                    try {
+                        cb.onBlockedStatusChanged(network, blocked);
+                    } catch (RemoteException e) {
+                        Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
+                        unregisterNetworkCallback();
+                    }
+                }
+
+                @Override
+                public void onAvailable(Network network) {
+                    try {
+                        cb.onAvailable(network);
+                    } catch (RemoteException e) {
+                        Log.d(TAG, "Cannot send onAvailable: " + e);
+                        unregisterNetworkCallback();
+                    }
+                }
+
+                @Override
+                public void onLost(Network network) {
+                    try {
+                        cb.onLost(network);
+                    } catch (RemoteException e) {
+                        Log.d(TAG, "Cannot send onLost: " + e);
+                        unregisterNetworkCallback();
+                    }
+                }
+            };
+            mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
+            try {
+                cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
+            } catch (RemoteException e) {
+                unregisterNetworkCallback();
+            }
+        }
       };
 
+    private void unregisterNetworkCallback() {
+        Log.d(TAG, "unregistering network callback");
+        mCm.unregisterNetworkCallback(mNetworkCallback);
+        mNetworkCallback = null;
+    }
+
+    private NetworkRequest makeWifiNetworkRequest() {
+        return new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
+    }
+
     @Override
     public IBinder onBind(Intent intent) {
         return mBinder;
@@ -94,6 +160,8 @@
         ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
                 .createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
                         NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
+        mCm = (ConnectivityManager) getApplicationContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
     @Override
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
new file mode 100644
index 0000000..8d6c4ac
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.net;
+public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        uninstallPackage(TEST_APP2_PKG, false);
+        installPackage(TEST_APP2_APK);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        uninstallPackage(TEST_APP2_PKG, true);
+    }
+
+    public void testOnBlockedStatusChanged_data_saver() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_data_saver");
+    }
+
+    public void testOnBlockedStatusChanged_power_saver() throws Exception {
+        runDeviceTests(TEST_PKG,
+                TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_power_saver");
+    }
+}
+
diff --git a/tests/cts/net/Android.mk b/tests/cts/net/Android.mk
index 2084dbc..0497470 100644
--- a/tests/cts/net/Android.mk
+++ b/tests/cts/net/Android.mk
@@ -39,6 +39,7 @@
 LOCAL_PACKAGE_NAME := CtsNetTestCases
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    FrameworksNetCommonTests \
     core-tests-support \
     compatibility-device-util-axt \
     ctstestrunner-axt \
diff --git a/tests/cts/net/jni/NativeDnsJni.c b/tests/cts/net/jni/NativeDnsJni.c
index 352c0c5..6d3d1c3 100644
--- a/tests/cts/net/jni/NativeDnsJni.c
+++ b/tests/cts/net/jni/NativeDnsJni.c
@@ -120,8 +120,8 @@
             gai_strerror(res));
         return JNI_FALSE;
     }
-    if (strstr(buf, "google.com") == NULL) {
-        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com: %s",
+    if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
+        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
             GoogleDNSIpV4Address, buf);
         return JNI_FALSE;
     }
@@ -133,8 +133,9 @@
             res, gai_strerror(res));
         return JNI_FALSE;
     }
-    if (strstr(buf, "google.com") == NULL) {
-        ALOGD("getnameinfo(%s) didn't return google.com: %s", GoogleDNSIpV6Address2, buf);
+    if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
+        ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+            GoogleDNSIpV6Address2, buf);
         return JNI_FALSE;
     }
 
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
index d1a92a4..a6b5e90 100644
--- a/tests/cts/net/jni/NativeMultinetworkJni.cpp
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -245,13 +245,12 @@
     net_handle_t handle = (net_handle_t) nethandle;
 
     int fd = android_res_nquery(handle, kGoogleName, ns_c_in, ns_t_a, 0);
-    int rcode = -1;
-    uint8_t buf[MAXPACKET] = {};
+    errno = 0;
     android_res_cancel(fd);
-    EXPECT_EQ(env, -EBADF, android_res_nresult(fd, &rcode, buf, MAXPACKET), "res_cancel");
-
-    android_res_cancel(fd);
-    EXPECT_EQ(env, -EBADF, android_res_nresult(fd, &rcode, buf, MAXPACKET), "res_cancel");
+    int err = errno;
+    EXPECT_EQ(env, 0, err, "res_cancel");
+    // DO NOT call cancel or result with the same fd more than once,
+    // otherwise it will hit fdsan double-close fd.
     return 0;
 }
 
@@ -288,10 +287,10 @@
     fd = android_res_nsend(handle, largeBuf, sizeof(largeBuf), 0);
     EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend buffer larger than 8KB");
 
-    // 1000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
-    // commands to 1024 bytes. TODO: b/126307309
-    fd = android_res_nsend(handle, largeBuf, 1000, 0);
-    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 1000 bytes filled with 0");
+    // 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+    // commands to 4096 bytes.
+    fd = android_res_nsend(handle, largeBuf, 5000, 0);
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0");
 
     // 500 bytes filled with 0
     fd = android_res_nsend(handle, largeBuf, 500, 0);
@@ -299,16 +298,16 @@
     EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
             "res_nsend 500 bytes filled with 0 check answers");
 
-    // 1000 bytes filled with 0xFF
-    uint8_t ffBuf[1001] = {};
+    // 5000 bytes filled with 0xFF
+    uint8_t ffBuf[5001] = {};
     memset(ffBuf, 0xFF, sizeof(ffBuf));
-    ffBuf[1000] = '\0';
+    ffBuf[5000] = '\0';
     fd = android_res_nsend(handle, ffBuf, sizeof(ffBuf), 0);
-    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 1000 bytes filled with 0xFF");
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0xFF");
 
     // 500 bytes filled with 0xFF
-    ffBuf[501] = '\0';
-    fd = android_res_nsend(handle, ffBuf, 500, 0);
+    ffBuf[500] = '\0';
+    fd = android_res_nsend(handle, ffBuf, 501, 0);
     EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0xFF");
     EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
             "res_nsend 500 bytes filled with 0xFF check answers");
diff --git a/tests/cts/net/native/dns/AndroidTest.xml b/tests/cts/net/native/dns/AndroidTest.xml
index e63c678..fe88cda 100644
--- a/tests/cts/net/native/dns/AndroidTest.xml
+++ b/tests/cts/net/native/dns/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Native Network dns test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsNativeNetDnsTestCases->/data/local/tmp/CtsNativeNetDnsTestCases" />
diff --git a/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
index 2fc9ff8..e501475 100644
--- a/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
+++ b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
@@ -194,13 +194,12 @@
 TEST (NativeDnsAsyncTest, Async_Cancel) {
     int fd = android_res_nquery(
             NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
-    int rcode = -1;
-    uint8_t buf[MAXPACKET] = {};
+    errno = 0;
     android_res_cancel(fd);
-    android_res_cancel(fd);
-
-    int res = android_res_nresult(fd, &rcode, buf, MAXPACKET);
-    EXPECT_EQ(-EBADF, res);
+    int err = errno;
+    EXPECT_EQ(err, 0);
+    // DO NOT call cancel or result with the same fd more than once,
+    // otherwise it will hit fdsan double-close fd.
 }
 
 TEST (NativeDnsAsyncTest, Async_Query_MALFORMED) {
@@ -235,9 +234,9 @@
             NETWORK_UNSPECIFIED, largeBuf.data(), largeBuf.size(), 0);
     EXPECT_EQ(-EMSGSIZE, fd);
 
-    // 1000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
-    // commands to 1024 bytes. TODO: fix this.
-    fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 1000, 0);
+    // 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+    // commands to 4096 bytes.
+    fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 5000, 0);
     EXPECT_EQ(-EMSGSIZE, fd);
 
     // 500 bytes filled with 0
@@ -245,8 +244,8 @@
     EXPECT_GE(fd, 0);
     expectAnswersNotValid(fd, -EINVAL);
 
-    // 1000 bytes filled with 0xFF
-    std::vector<uint8_t> ffBuf(1000, 0xFF);
+    // 5000 bytes filled with 0xFF
+    std::vector<uint8_t> ffBuf(5000, 0xFF);
     fd = android_res_nsend(
             NETWORK_UNSPECIFIED, ffBuf.data(), ffBuf.size(), 0);
     EXPECT_EQ(-EMSGSIZE, fd);
diff --git a/tests/cts/net/native/qtaguid/AndroidTest.xml b/tests/cts/net/native/qtaguid/AndroidTest.xml
index 7591c87..a55afe7 100644
--- a/tests/cts/net/native/qtaguid/AndroidTest.xml
+++ b/tests/cts/net/native/qtaguid/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Native Network xt_qtaguid test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsNativeNetTestCases->/data/local/tmp/CtsNativeNetTestCases" />
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 1b7d290..4180ea4 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -23,12 +23,17 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import android.app.Instrumentation;
 import android.app.PendingIntent;
+import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -46,8 +51,10 @@
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
 import android.net.NetworkRequest;
+import android.net.SocketKeepalive;
 import android.net.wifi.WifiManager;
 import android.os.Looper;
+import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -64,11 +71,13 @@
 
 import libcore.io.Streams;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -79,6 +88,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
@@ -96,7 +106,11 @@
     private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
     private static final String TEST_HOST = "connectivitycheck.gstatic.com";
     private static final int SOCKET_TIMEOUT_MS = 2000;
+    private static final int CONNECT_TIMEOUT_MS = 2000;
+    private static final int KEEPALIVE_CALLBACK_TIMEOUT_MS = 2000;
+    private static final int KEEPALIVE_SOCKET_TIMEOUT_MS = 5000;
     private static final int SEND_BROADCAST_TIMEOUT = 30000;
+    private static final int MIN_KEEPALIVE_INTERVAL = 10;
     private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
     private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
     private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
@@ -126,7 +140,8 @@
             new HashMap<Integer, NetworkConfig>();
     boolean mWifiConnectAttempted;
     private TestNetworkCallback mCellNetworkCallback;
-
+    private UiAutomation mUiAutomation;
+    private boolean mShellPermissionIdentityAdopted;
 
     @Override
     protected void setUp() throws Exception {
@@ -153,6 +168,8 @@
                 mNetworks.put(n.type, n);
             } catch (Exception e) {}
         }
+        mUiAutomation = mInstrumentation.getUiAutomation();
+        mShellPermissionIdentityAdopted = false;
     }
 
     @Override
@@ -164,6 +181,7 @@
         if (cellConnectAttempted()) {
             disconnectFromCell();
         }
+        dropShellPermissionIdentity();
         super.tearDown();
     }
 
@@ -1037,4 +1055,226 @@
             setWifiMeteredStatus(ssid, oldMeteredSetting);
         }
     }
+
+    // TODO: move the following socket keep alive test to dedicated test class.
+    /**
+     * Callback used in tcp keepalive offload that allows caller to wait callback fires.
+     */
+    private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+        public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+        public static class CallbackValue {
+            public final CallbackType callbackType;
+            public final int error;
+
+            private CallbackValue(final CallbackType type, final int error) {
+                this.callbackType = type;
+                this.error = error;
+            }
+
+            public static class OnStartedCallback extends CallbackValue {
+                OnStartedCallback() { super(CallbackType.ON_STARTED, 0); }
+            }
+
+            public static class OnStoppedCallback extends CallbackValue {
+                OnStoppedCallback() { super(CallbackType.ON_STOPPED, 0); }
+            }
+
+            public static class OnErrorCallback extends CallbackValue {
+                OnErrorCallback(final int error) { super(CallbackType.ON_ERROR, error); }
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                return o.getClass() == this.getClass()
+                        && this.callbackType == ((CallbackValue) o).callbackType
+                        && this.error == ((CallbackValue) o).error;
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error);
+            }
+        }
+
+        private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onStarted() {
+            mCallbacks.add(new CallbackValue.OnStartedCallback());
+        }
+
+        @Override
+        public void onStopped() {
+            mCallbacks.add(new CallbackValue.OnStoppedCallback());
+        }
+
+        @Override
+        public void onError(final int error) {
+            mCallbacks.add(new CallbackValue.OnErrorCallback(error));
+        }
+
+        public CallbackValue pollCallback() {
+            try {
+                return mCallbacks.poll(KEEPALIVE_CALLBACK_TIMEOUT_MS,
+                        TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                fail("Callback not seen after " + KEEPALIVE_CALLBACK_TIMEOUT_MS + " ms");
+            }
+            return null;
+        }
+        private void expectCallback(CallbackValue expectedCallback) {
+            final CallbackValue actualCallback = pollCallback();
+            assertEquals(expectedCallback, actualCallback);
+        }
+
+        public void expectStarted() {
+            expectCallback(new CallbackValue.OnStartedCallback());
+        }
+
+        public void expectStopped() {
+            expectCallback(new CallbackValue.OnStoppedCallback());
+        }
+
+        public void expectError(int error) {
+            expectCallback(new CallbackValue.OnErrorCallback(error));
+        }
+    }
+
+    private InetAddress getAddrByName(final String hostname, final int family) throws Exception {
+        final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
+        for (InetAddress addr : allAddrs) {
+            if (family == AF_INET && addr instanceof Inet4Address) return addr;
+
+            if (family == AF_INET6 && addr instanceof Inet6Address) return addr;
+
+            if (family == AF_UNSPEC) return addr;
+        }
+        return null;
+    }
+
+    private Socket getConnectedSocket(final Network network, final String host, final int port,
+            final int socketTimeOut, final int family) throws Exception {
+        final Socket s = network.getSocketFactory().createSocket();
+        try {
+            final InetAddress addr = getAddrByName(host, family);
+            if (addr == null) fail("Fail to get destination address for " + family);
+
+            final InetSocketAddress sockAddr = new InetSocketAddress(addr, port);
+            s.setSoTimeout(socketTimeOut);
+            s.connect(sockAddr, CONNECT_TIMEOUT_MS);
+        } catch (Exception e) {
+            s.close();
+            throw e;
+        }
+        return s;
+    }
+
+    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;
+    }
+
+    private void dropShellPermissionIdentity() {
+        if (mShellPermissionIdentityAdopted) {
+            mUiAutomation.dropShellPermissionIdentity();
+            mShellPermissionIdentityAdopted = false;
+        }
+    }
+
+    public void testCreateTcpKeepalive() throws Exception {
+        adoptShellPermissionIdentity();
+
+        if (!isKeepaliveSupported()) return;
+
+        final Network network = ensureWifiConnected();
+        final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+        // So far only ipv4 tcp keepalive offload is supported.
+        // TODO: add test case for ipv6 tcp keepalive offload when it is supported.
+        try (Socket s = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
+                KEEPALIVE_SOCKET_TIMEOUT_MS, AF_INET)) {
+
+            // Should able to start keep alive offload when socket is idle.
+            final Executor executor = mContext.getMainExecutor();
+            final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+            try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
+                sk.start(MIN_KEEPALIVE_INTERVAL);
+                callback.expectStarted();
+
+                // App should not able to write during keepalive offload.
+                final OutputStream out = s.getOutputStream();
+                try {
+                    out.write(requestBytes);
+                    fail("Should not able to write");
+                } catch (IOException e) { }
+                // App should not able to read during keepalive offload.
+                final InputStream in = s.getInputStream();
+                byte[] responseBytes = new byte[4096];
+                try {
+                    in.read(responseBytes);
+                    fail("Should not able to read");
+                } catch (IOException e) { }
+
+                // Stop.
+                sk.stop();
+                callback.expectStopped();
+            }
+
+            // Ensure socket is still connected.
+            assertTrue(s.isConnected());
+            assertFalse(s.isClosed());
+
+            // Let socket be not idle.
+            try {
+                final OutputStream out = s.getOutputStream();
+                out.write(requestBytes);
+            } catch (IOException e) {
+                fail("Failed to write data " + e);
+            }
+            // Make sure response data arrives.
+            final MessageQueue fdHandlerQueue = Looper.getMainLooper().getQueue();
+            final FileDescriptor fd = s.getFileDescriptor$();
+            final CountDownLatch mOnReceiveLatch = new CountDownLatch(1);
+            fdHandlerQueue.addOnFileDescriptorEventListener(fd, EVENT_INPUT, (readyFd, events) -> {
+                mOnReceiveLatch.countDown();
+                return 0; // Unregister listener.
+            });
+            if (!mOnReceiveLatch.await(2, TimeUnit.SECONDS)) {
+                fdHandlerQueue.removeOnFileDescriptorEventListener(fd);
+                fail("Timeout: no response data");
+            }
+
+            // Should get ERROR_SOCKET_NOT_IDLE because there is still data in the receive queue
+            // that has not been read.
+            try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
+                sk.start(MIN_KEEPALIVE_INTERVAL);
+                callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
+            }
+
+        }
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 6fbe586..945f51d 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,19 +32,21 @@
 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;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
 
 public class DnsResolverTest extends AndroidTestCase {
     private static final String TAG = "DnsResolverTest";
@@ -50,6 +54,9 @@
             '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_TIMEOUT_MS = 3_000;
+    static final int CANCEL_RETRY_TIMES = 5;
+    static final int NXDOMAIN = 3;
 
     private ConnectivityManager mCM;
     private Executor mExecutor;
@@ -90,73 +97,26 @@
         return testableNetworks.toArray(new Network[0]);
     }
 
-    public void testQueryWithInetAddressCallback() {
-        final String dname = "www.google.com";
-        final String msg = "Query with InetAddressAnswerCallback " + dname;
-        for (Network network : getTestableNetworks()) {
-            final CountDownLatch latch = new CountDownLatch(1);
-            final AtomicReference<List<InetAddress>> answers = new AtomicReference<>();
-            final DnsResolver.InetAddressAnswerCallback callback =
-                    new DnsResolver.InetAddressAnswerCallback() {
-                @Override
-                public void onAnswer(@NonNull List<InetAddress> answerList) {
-                    answers.set(answerList);
-                    for (InetAddress addr : answerList) {
-                        Log.d(TAG, "Reported addr: " + addr.toString());
-                    }
-                    latch.countDown();
-                }
-
-                @Override
-                public void onParseException(@NonNull ParseException e) {
-                    fail(msg + e.getMessage());
-                }
-
-                @Override
-                public void onQueryException(@NonNull ErrnoException e) {
-                    fail(msg + e.getMessage());
-                }
-            };
-            mDns.query(network, dname, CLASS_IN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
-            try {
-                assertTrue(msg + " but no valid answer after " + TIMEOUT_MS + "ms.",
-                        latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-                assertGreaterThan(msg + " returned 0 result", answers.get().size(), 0);
-            } catch (InterruptedException e) {
-                fail(msg + " Waiting for DNS lookup was interrupted");
-            }
-        }
-    }
-
     static private void assertGreaterThan(String msg, int first, int second) {
         assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
     }
 
-    static private void assertValidAnswer(String msg, @NonNull DnsAnswer ans) {
-        // Check rcode field.(0, No error condition).
-        assertTrue(msg + " Response error, rcode: " + ans.getRcode(), ans.getRcode() == 0);
-        // Check answer counts.
-        assertGreaterThan(msg + " No answer found", ans.getANCount(), 0);
-        // Check question counts.
-        assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
-    }
+    private static class DnsParseException extends Exception {
+        public DnsParseException(String msg) {
+            super(msg);
+        }
 
-    static private void assertValidEmptyAnswer(String msg, @NonNull DnsAnswer ans) {
-        // Check rcode field.(0, No error condition).
-        assertTrue(msg + " Response error, rcode: " + ans.getRcode(), ans.getRcode() == 0);
-        // Check answer counts. Expect 0 answer.
-        assertTrue(msg + " Not an empty answer", ans.getANCount() == 0);
-        // Check question counts.
-        assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
+        public DnsParseException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
     }
 
     private static class DnsAnswer extends DnsPacket {
-        DnsAnswer(@NonNull byte[] data) throws ParseException {
+        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 ParseException("Not an answer packet");
+                throw new DnsParseException("Not an answer packet");
             }
         }
 
@@ -171,56 +131,115 @@
         }
     }
 
-    class RawAnswerCallbackImpl extends DnsResolver.RawAnswerCallback {
+    /**
+     * 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 implements DnsResolver.Callback<byte[]> {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final String mMsg;
-        RawAnswerCallbackImpl(String msg) {
-            this.mMsg = msg;
+        private final CancellationSignal mCancelSignal;
+        private int mRcode;
+        private DnsAnswer mDnsAnswer;
+
+        VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
+            mMsg = msg;
+            mCancelSignal = cancel;
+        }
+
+        VerifyCancelCallback(@NonNull String msg) {
+            this(msg, null);
+        }
+
+        public boolean waitForAnswer(int timeout) throws InterruptedException {
+            return mLatch.await(timeout, TimeUnit.MILLISECONDS);
         }
 
         public boolean waitForAnswer() throws InterruptedException {
-            return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return waitForAnswer(TIMEOUT_MS);
+        }
+
+        public boolean needRetry() throws InterruptedException {
+            return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
 
         @Override
-        public void onAnswer(@NonNull byte[] answer) {
+        public void onAnswer(@NonNull byte[] answer, int rcode) {
+            if (mCancelSignal != null && mCancelSignal.isCanceled()) {
+                fail(mMsg + " should not have returned any answers");
+            }
+
+            mRcode = rcode;
             try {
-                assertValidAnswer(mMsg, new DnsAnswer(answer));
-                Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
-                mLatch.countDown();
-            } catch (ParseException e) {
+                mDnsAnswer = new DnsAnswer(answer);
+            } catch (DnsParseException e) {
                 fail(mMsg + e.getMessage());
             }
+            Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
+            mLatch.countDown();
         }
 
         @Override
-        public void onParseException(@NonNull ParseException e) {
-            fail(mMsg + e.getMessage());
+        public void onError(@NonNull DnsResolver.DnsException error) {
+            fail(mMsg + error.getMessage());
         }
 
-        @Override
-        public void onQueryException(@NonNull ErrnoException e) {
-            fail(mMsg + e.getMessage());
+        private void assertValidAnswer() {
+            assertNotNull(mMsg + " No valid answer", mDnsAnswer);
+            assertEquals(mMsg + " Unexpected error: reported rcode" + mRcode +
+                    " blob's rcode " + mDnsAnswer.getRcode(), mRcode, mDnsAnswer.getRcode());
+        }
+
+        public void assertHasAnswer() {
+            assertValidAnswer();
+            // Check rcode field.(0, No error condition).
+            assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
+            // Check answer counts.
+            assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
+            // Check question counts.
+            assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
+        }
+
+        public void assertNXDomain() {
+            assertValidAnswer();
+            // Check rcode field.(3, NXDomain).
+            assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
+            // Check answer counts. Expect 0 answer.
+            assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
+            // Check question counts.
+            assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
+        }
+
+        public void assertEmptyAnswer() {
+            assertValidAnswer();
+            // Check rcode field.(0, No error condition).
+            assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
+            // Check answer counts. Expect 0 answer.
+            assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
+            // Check question counts.
+            assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
         }
     }
 
-    public void testQueryWithRawAnswerCallback() {
+    public void testRawQuery() {
         final String dname = "www.google.com";
-        final String msg = "Query with RawAnswerCallback " + dname;
+        final String msg = "RawQuery " + dname;
         for (Network network : getTestableNetworks()) {
-            final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
-            mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+            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.assertHasAnswer();
             } catch (InterruptedException e) {
                 fail(msg + " Waiting for DNS lookup was interrupted");
             }
         }
     }
 
-    public void testQueryBlobWithRawAnswerCallback() {
+    public void testRawQueryBlob() {
         final byte[] blob = new byte[]{
             /* Header */
             0x55, 0x66, /* Transaction ID */
@@ -235,88 +254,297 @@
             0x00, 0x01, /* Type */
             0x00, 0x01  /* Class */
         };
-        final String msg = "Query with RawAnswerCallback " + byteArrayToHexString(blob);
+        final String msg = "RawQuery blob " + byteArrayToHexString(blob);
         for (Network network : getTestableNetworks()) {
-            final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
-            mDns.query(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, callback);
+            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");
             }
         }
     }
 
-    public void testQueryRoot() {
+    public void testRawQueryRoot() {
         final String dname = "";
-        final String msg = "Query with RawAnswerCallback empty dname(ROOT) ";
+        final String msg = "RawQuery empty dname(ROOT) ";
         for (Network network : getTestableNetworks()) {
-            final CountDownLatch latch = new CountDownLatch(1);
-            final DnsResolver.RawAnswerCallback callback = new DnsResolver.RawAnswerCallback() {
-                @Override
-                public void onAnswer(@NonNull byte[] answer) {
-                    try {
-                        // Except no answer record because of querying with empty dname(ROOT)
-                        assertValidEmptyAnswer(msg, new DnsAnswer(answer));
-                        latch.countDown();
-                    } catch (ParseException e) {
-                        fail(msg + e.getMessage());
-                    }
-                }
-
-                @Override
-                public void onParseException(@NonNull ParseException e) {
-                    fail(msg + e.getMessage());
-                }
-
-                @Override
-                public void onQueryException(@NonNull ErrnoException e) {
-                    fail(msg + e.getMessage());
-                }
-            };
-            mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+            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.",
-                        latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                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");
+                fail(msg + " Waiting for DNS lookup was interrupted");
             }
         }
     }
 
-    public void testQueryNXDomain() {
+    public void testRawQueryNXDomain() {
         final String dname = "test1-nx.metric.gstatic.com";
-        final String msg = "Query with InetAddressAnswerCallback " + dname;
+        final String msg = "RawQuery " + dname;
         for (Network network : getTestableNetworks()) {
-            final CountDownLatch latch = new CountDownLatch(1);
-            final DnsResolver.InetAddressAnswerCallback callback =
-                    new DnsResolver.InetAddressAnswerCallback() {
-                @Override
-                public void onAnswer(@NonNull List<InetAddress> answerList) {
-                    if (answerList.size() == 0) {
-                        latch.countDown();
-                        return;
-                    }
-                    fail(msg + " but get valid answers");
-                }
-
-                @Override
-                public void onParseException(@NonNull ParseException e) {
-                    fail(msg + e.getMessage());
-                }
-
-                @Override
-                public void onQueryException(@NonNull ErrnoException e) {
-                    fail(msg + e.getMessage());
-                }
-            };
-            mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
-                    mExecutor, callback);
+            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.",
-                        latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                        callback.waitForAnswer());
+                callback.assertNXDomain();
+            } catch (InterruptedException e) {
+                fail(msg + " Waiting for DNS lookup was interrupted");
+            }
+        }
+    }
+
+    public void testRawQueryCancel() throws ErrnoException {
+        final String dname = "www.google.com";
+        final String msg = "Test cancel RawQuery " + 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 test 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.rawQuery(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 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);
+        // 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.
+        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.rawQuery(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 RawQuery " + dname;
+        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,
+                    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");
+            }
+        }
+    }
+
+    /**
+     * A query callback for InetAddress 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 VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final String mMsg;
+        private final List<InetAddress> mAnswers;
+        private final CancellationSignal mCancelSignal;
+
+        VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
+            this.mMsg = msg;
+            this.mCancelSignal = cancel;
+            mAnswers = new ArrayList<>();
+        }
+
+        public boolean waitForAnswer() throws InterruptedException {
+            return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+
+        public boolean needRetry() throws InterruptedException {
+            return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+
+        public boolean isAnswerEmpty() {
+            return mAnswers.isEmpty();
+        }
+
+        public boolean hasIpv6Answer() {
+            for (InetAddress answer : mAnswers) {
+                if (answer instanceof Inet6Address) return true;
+            }
+            return false;
+        }
+
+        public boolean hasIpv4Answer() {
+            for (InetAddress answer : mAnswers) {
+                if (answer instanceof Inet4Address) return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+            if (mCancelSignal != null && mCancelSignal.isCanceled()) {
+                fail(mMsg + " should not have returned any answers");
+            }
+            for (InetAddress addr : answerList) {
+                Log.d(TAG, "Reported addr: " + addr.toString());
+            }
+            mAnswers.clear();
+            mAnswers.addAll(answerList);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull DnsResolver.DnsException error) {
+            fail(mMsg + error.getMessage());
+        }
+    }
+
+    public void testQueryForInetAddress() {
+        final String dname = "www.google.com";
+        final String msg = "Test query for InetAddress " + dname;
+        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");
+            }
+        }
+    }
+
+    public void testQueryCancelForInetAddress() throws ErrnoException {
+        final String dname = "www.google.com";
+        final String msg = "Test cancel query for InetAddress " + dname;
+        // 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.
+        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 VerifyCancelInetAddressCallback callback =
+                        new VerifyCancelInetAddressCallback(msg, cancelSignal);
+                mDns.query(network, dname, 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 testQueryForInetAddressIpv4() {
+        final String dname = "www.google.com";
+        final String msg = "Test query for IPv4 InetAddress " + dname;
+        for (Network network : getTestableNetworks()) {
+            final VerifyCancelInetAddressCallback callback =
+                    new VerifyCancelInetAddressCallback(msg, null);
+            mDns.query(network, dname, 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");
+            }
+        }
+    }
+
+    public void testQueryForInetAddressIpv6() {
+        final String dname = "www.google.com";
+        final String msg = "Test query for IPv6 InetAddress " + dname;
+        for (Network network : getTestableNetworks()) {
+            final VerifyCancelInetAddressCallback callback =
+                    new VerifyCancelInetAddressCallback(msg, null);
+            mDns.query(network, dname, 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");
             }
diff --git a/tests/cts/net/src/android/net/cts/DnsTest.java b/tests/cts/net/src/android/net/cts/DnsTest.java
index 84231c2..746dcb0 100644
--- a/tests/cts/net/src/android/net/cts/DnsTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsTest.java
@@ -287,7 +287,7 @@
         final NetworkCallback callback = new NetworkCallback() {
             @Override
             public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
-                if (lp.hasGlobalIPv6Address()) {
+                if (lp.hasGlobalIpv6Address()) {
                     latch.countDown();
                 }
             }
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index af096da..503ba51 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,15 +16,14 @@
 
 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.test.AndroidTestCase;
 import android.util.Log;
 
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -237,19 +236,37 @@
             uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaRxOtherPackets, 0));
 
         // Localhost traffic *does* count against total stats.
-        // Fudge by 132 packets of 1500 bytes not related to the test.
+        // Check the total stats increased after test data transfer over localhost has been made.
         assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
-            totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets &&
-            totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+                totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets);
         assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
-            totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets &&
-            totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+                totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets);
         assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
-            totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes &&
-            totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+                totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes);
         assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
-            totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes &&
-            totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+                totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes);
+
+        // 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.
@@ -265,6 +282,5 @@
         assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
             mobileRxBytesAfter >= mobileRxBytesBefore &&
             mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
-
     }
 }
diff --git a/tests/cts/net/src/android/net/http/cts/SslErrorTest.java b/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
index 850a8b6..0058c90 100644
--- a/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
+++ b/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
@@ -77,4 +77,9 @@
         SslError error = new SslError(SslError.SSL_EXPIRED, mCertificate);
         assertEquals(error.getUrl(), "");
     }
+
+    public void testGetCertificate() {
+        SslError error = new SslError(SslError.SSL_EXPIRED, mCertificate);
+        assertEquals(mCertificate, error.getCertificate());
+    }
 }
diff --git a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
index c23ad30..146fd83 100644
--- a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
@@ -61,7 +61,7 @@
 
     /** The beginning of an ICMPv6 echo request: type, code, and uninitialized checksum. */
     private static final byte[] PING_HEADER = new byte[] {
-        (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00
+        (byte) ICMP6_ECHO_REQUEST, (byte) 0x00, (byte) 0x00, (byte) 0x00
     };
 
     /**
@@ -135,7 +135,7 @@
         byte[] response = new byte[bytesRead];
         responseBuffer.flip();
         responseBuffer.get(response, 0, bytesRead);
-        assertEquals((byte) 0x81, response[0]);
+        assertEquals((byte) ICMP6_ECHO_REPLY, response[0]);
 
         // Find out what ICMP ID was used in the packet that was sent.
         int id = ((InetSocketAddress) Os.getsockname(s)).getPort();