Merge changes Ic19b3b64,I26ca370f am: c5ceda2354 am: f29182d403
am: 6188ebe70d
Change-Id: I7aa0abf2308ede2025bb8bfdf044fd37275f6afb
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");
+ }
+}
+