Merge "Wifi: Rename class name WifiManager_WifiLockTest"
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 c6e80a2..4e4d1f6 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
@@ -373,6 +373,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) {
@@ -979,6 +996,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/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/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/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/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 308f1ed..f6cc768 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -17,16 +17,22 @@
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;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DnsPacket;
import android.net.DnsResolver;
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;
@@ -37,6 +43,7 @@
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;
@@ -46,16 +53,17 @@
'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 Handler mHandler;
+ private Executor mExecutor;
private DnsResolver mDns;
protected void setUp() throws Exception {
super.setUp();
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
- mHandler = new Handler(Looper.getMainLooper());
mDns = DnsResolver.getInstance();
+ mExecutor = new Handler(Looper.getMainLooper())::post;
}
private static String byteArrayToHexString(byte[] bytes) {
@@ -86,41 +94,56 @@
return testableNetworks.toArray(new Network[0]);
}
- public void testInetAddressQuery() throws ErrnoException {
+ public void testQueryWithInetAddressCallback() {
final String dname = "www.google.com";
- final String msg = "InetAddress query " + dname;
+ final String msg = "Query with InetAddressAnswerCallback " + dname;
for (Network network : getTestableNetworks()) {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<List<InetAddress>> answers = new AtomicReference<>();
-
- mDns.query(network, dname, FLAG_NO_CACHE_LOOKUP, mHandler, answerList -> {
- answers.set(answerList);
- for (InetAddress addr : answerList) {
- Log.d(TAG, "Reported addr:" + addr.toString());
- }
- latch.countDown();
+ 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, null, 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 a, int b) {
- assertTrue(msg + ": " + a + " > " + b, a > b);
+ 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.
- assertTrue(msg + " No answer found", ans.getANCount() > 0);
+ assertGreaterThan(msg + " No answer found", ans.getANCount(), 0);
// Check question counts.
- assertTrue(msg + " No question found", ans.getQDCount() > 0);
+ assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
}
static private void assertValidEmptyAnswer(String msg, @NonNull DnsAnswer ans) {
@@ -129,10 +152,10 @@
// Check answer counts. Expect 0 answer.
assertTrue(msg + " Not an empty answer", ans.getANCount() == 0);
// Check question counts.
- assertTrue(msg + " No question found", ans.getQDCount() > 0);
+ assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
}
- private class DnsAnswer extends DnsPacket {
+ private static class DnsAnswer extends DnsPacket {
DnsAnswer(@NonNull byte[] data) throws ParseException {
super(data);
// Check QR field.(query (0), or a response (1)).
@@ -152,34 +175,63 @@
}
}
- public void testRawQuery() throws ErrnoException {
+ class RawAnswerCallbackImpl extends DnsResolver.RawAnswerCallback {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mMsg;
+ 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(mTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onAnswer(@NonNull byte[] answer) {
+ try {
+ assertValidAnswer(mMsg, new DnsAnswer(answer));
+ Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
+ mLatch.countDown();
+ } catch (ParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(mMsg + e.getMessage());
+ }
+ }
+
+ public void testQueryWithRawAnswerCallback() {
final String dname = "www.google.com";
- final String msg = "Raw query " + dname;
+ final String msg = "Query with RawAnswerCallback " + dname;
for (Network network : getTestableNetworks()) {
- final CountDownLatch latch = new CountDownLatch(1);
- mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP, mHandler,
- answer -> {
- if (answer == null) {
- fail(msg + " no answer returned");
- }
- try {
- assertValidAnswer(msg, new DnsAnswer(answer));
- Log.d(TAG, "Reported blob:" + byteArrayToHexString(answer));
- latch.countDown();
- } catch (DnsPacket.ParseException e) {
- fail(msg + e.getMessage());
- }
- }
- );
+ final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
+ mDns.query(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());
} catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
- public void testRawQueryWithBlob() throws ErrnoException {
+ public void testQueryBlobWithRawAnswerCallback() {
final byte[] blob = new byte[]{
/* Header */
0x55, 0x66, /* Transaction ID */
@@ -194,74 +246,227 @@
0x00, 0x01, /* Type */
0x00, 0x01 /* Class */
};
- final String msg = "Raw query with blob " + byteArrayToHexString(blob);
+ final String msg = "Query with RawAnswerCallback " + byteArrayToHexString(blob);
for (Network network : getTestableNetworks()) {
- final CountDownLatch latch = new CountDownLatch(1);
- mDns.query(network, blob, FLAG_NO_CACHE_LOOKUP, mHandler, answer -> {
- if (answer == null) {
- fail(msg + " no answer returned");
- }
- try {
- assertValidAnswer(msg, new DnsAnswer(answer));
- Log.d(TAG, "Reported blob:" + byteArrayToHexString(answer));
- latch.countDown();
- } catch (DnsPacket.ParseException e) {
- fail(msg + e.getMessage());
- }
- }
- );
+ final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
+ mDns.query(network, blob, 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());
} catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
}
}
}
- public void testEmptyQuery() throws ErrnoException {
+ public void testQueryRoot() {
final String dname = "";
- final String msg = "Raw query empty dname(ROOT)";
+ final String msg = "Query with RawAnswerCallback empty dname(ROOT) ";
for (Network network : getTestableNetworks()) {
final CountDownLatch latch = new CountDownLatch(1);
- mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP, mHandler,
- answer -> {
- if (answer == null) {
- fail(msg + " no answer returned");
- }
- try {
- // Except no answer record because of querying with empty dname(ROOT)
- assertValidEmptyAnswer(msg, new DnsAnswer(answer));
- latch.countDown();
- } catch (DnsPacket.ParseException e) {
- fail(msg + e.getMessage());
- }
+ 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, null, callback);
try {
- assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ assertTrue(msg + "but no answer after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
+ fail(msg + "Waiting for DNS lookup was interrupted");
}
}
}
- public void testNXQuery() throws ErrnoException {
+ public void testQueryNXDomain() {
final String dname = "test1-nx.metric.gstatic.com";
- final String msg = "InetAddress query " + dname;
+ final String msg = "Query with InetAddressAnswerCallback " + dname;
for (Network network : getTestableNetworks()) {
final CountDownLatch latch = new CountDownLatch(1);
- mDns.query(network, dname, FLAG_NO_CACHE_LOOKUP, mHandler, answerList -> {
- if (answerList.size() == 0) {
- latch.countDown();
- return;
- }
- fail(msg + " but get valid answers");
+ 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, null, callback);
try {
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ /**
+ * 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");
}
}
}
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/wifi/cts/WifiManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
index 8e66cb8..93795b2 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -94,6 +94,8 @@
private static final int WIFI_SCAN_TEST_CACHE_DELAY_MILLIS = 3 * 60 * 1000;
private static final int WIFI_SCAN_TEST_ITERATIONS = 5;
+ private static final int ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP = 50;
+
private static final String TEST_PAC_URL = "http://www.example.com/proxy.pac";
private static final String MANAGED_PROVISIONING_PACKAGE_NAME
= "com.android.managedprovisioning";
@@ -246,7 +248,6 @@
private void connectWifi() throws Exception {
synchronized (mMySync) {
if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED) return;
- assertTrue(mWifiManager.reconnect());
long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
while (System.currentTimeMillis() < timeout
&& mNetworkInfo.getState() != NetworkInfo.State.CONNECTED)
@@ -982,6 +983,19 @@
assertWifiScanningIsOn();
}
+ /**
+ * Verify that the platform supports a reasonable number of suggestions per app.
+ * @throws Exception
+ */
+ public void testMaxNumberOfNetworkSuggestionsPerApp() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ assertTrue(mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()
+ > ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP);
+ }
+
private void assertWifiScanningIsOn() {
if(!mWifiManager.isScanAlwaysAvailable()) {
fail("Wi-Fi scanning should be on.");