Add CTS tests for ConnectivityDiagnostics callbacks.
Verify that the callbacks onConnectivityReport() and
onNetworkConnectivityReported() are invoked by the System when expected.
ConnectivityDiagnosticsManager provides an API for registering callbacks
with the System. These callbacks allow the System to notify registered
and permissioned callbacks on Network validation, suspected data stalls,
and Network connectivity reported.
Bug: 148032944
Test: android.net.cts.ConnectivityDiagnosticsManagerTest
Change-Id: I748229d41c16adf1561e03aa597d5aac00f12912
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 9d35705..6687fdc 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -17,19 +17,49 @@
package android.net.cts;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.os.Binder;
import android.os.Build;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.util.Pair;
import androidx.test.InstrumentationRegistry;
+import com.android.testutils.ArrayTrackRecord;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,33 +69,71 @@
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
public class ConnectivityDiagnosticsManagerTest {
+ private static final int CALLBACK_TIMEOUT_MILLIS = 5000;
+ private static final int NO_CALLBACK_INVOKED_TIMEOUT = 500;
+
private static final Executor INLINE_EXECUTOR = x -> x.run();
- private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
+
+ private static final NetworkRequest TEST_NETWORK_REQUEST =
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .build();
+
+ // Callback used to keep TestNetworks up when there are no other outstanding NetworkRequests
+ // for it.
+ private static final TestNetworkCallback TEST_NETWORK_CALLBACK = new TestNetworkCallback();
+
+ private static final IBinder BINDER = new Binder();
private Context mContext;
+ private ConnectivityManager mConnectivityManager;
private ConnectivityDiagnosticsManager mCdm;
- private ConnectivityDiagnosticsCallback mCallback;
+ private Network mTestNetwork;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
- mCallback = new ConnectivityDiagnosticsCallback() {};
+ mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, TEST_NETWORK_CALLBACK);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mConnectivityManager.unregisterNetworkCallback(TEST_NETWORK_CALLBACK);
+
+ if (mTestNetwork != null) {
+ runWithShellPermissionIdentity(() -> {
+ final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
+ tnm.teardownTestNetwork(mTestNetwork);
+ });
+ }
}
@Test
- public void testRegisterConnectivityDiagnosticsCallback() {
- mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+ public void testRegisterConnectivityDiagnosticsCallback() throws Exception {
+ mTestNetwork = setUpTestNetwork();
+
+ final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+ mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
+
+ final String interfaceName =
+ mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+
+ cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+ cb.assertNoCallback();
}
@Test
public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
- mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+ final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+ mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
try {
- mCdm.registerConnectivityDiagnosticsCallback(
- DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+ mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
fail("Registering the same callback twice should throw an IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
@@ -73,13 +141,155 @@
@Test
public void testUnregisterConnectivityDiagnosticsCallback() {
- mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
- mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+ final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+ mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
+ mCdm.unregisterConnectivityDiagnosticsCallback(cb);
}
@Test
public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
// Expected to silently ignore the unregister() call
- mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+ mCdm.unregisterConnectivityDiagnosticsCallback(new TestConnectivityDiagnosticsCallback());
+ }
+
+ @Test
+ public void testOnConnectivityReportAvailable() throws Exception {
+ mTestNetwork = setUpTestNetwork();
+
+ final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+ mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
+
+ final String interfaceName =
+ mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+
+ cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+ cb.assertNoCallback();
+ }
+
+ @Test
+ public void testOnNetworkConnectivityReportedTrue() throws Exception {
+ verifyOnNetworkConnectivityReported(true /* hasConnectivity */);
+ }
+
+ @Test
+ public void testOnNetworkConnectivityReportedFalse() throws Exception {
+ verifyOnNetworkConnectivityReported(false /* hasConnectivity */);
+ }
+
+ private void verifyOnNetworkConnectivityReported(boolean hasConnectivity) throws Exception {
+ mTestNetwork = setUpTestNetwork();
+
+ final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+ mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
+
+ // onConnectivityReportAvailable always invoked when the test network is established
+ final String interfaceName =
+ mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+ cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+ cb.assertNoCallback();
+
+ mConnectivityManager.reportNetworkConnectivity(mTestNetwork, hasConnectivity);
+
+ cb.expectOnNetworkConnectivityReported(mTestNetwork, hasConnectivity);
+
+ // if hasConnectivity does not match the network's known connectivity, it will be
+ // revalidated which will trigger another onConnectivityReportAvailable callback.
+ if (!hasConnectivity) {
+ cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+ }
+
+ cb.assertNoCallback();
+ }
+
+ @NonNull
+ private Network waitForConnectivityServiceIdleAndGetNetwork() throws InterruptedException {
+ // Get a new Network. This requires going through the ConnectivityService thread. Once it
+ // completes, all previously enqueued messages on the ConnectivityService main Handler have
+ // completed.
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, callback);
+ final Network network = callback.waitForAvailable();
+ mConnectivityManager.unregisterNetworkCallback(callback);
+ assertNotNull(network);
+ return network;
+ }
+
+ /**
+ * Registers a test NetworkAgent with ConnectivityService with limited capabilities, which leads
+ * to the Network being validated.
+ */
+ @NonNull
+ private Network setUpTestNetwork() throws Exception {
+ final int[] administratorUids = new int[] {Process.myUid()};
+ runWithShellPermissionIdentity(
+ () -> {
+ final TestNetworkManager tnm =
+ mContext.getSystemService(TestNetworkManager.class);
+ final TestNetworkInterface tni = tnm.createTunInterface(new LinkAddress[0]);
+ tnm.setupTestNetwork(tni.getInterfaceName(), administratorUids, BINDER);
+ });
+ return waitForConnectivityServiceIdleAndGetNetwork();
+ }
+
+ private static class TestConnectivityDiagnosticsCallback
+ extends ConnectivityDiagnosticsCallback {
+ private final ArrayTrackRecord<Object>.ReadHead mHistory =
+ new ArrayTrackRecord<Object>().newReadHead();
+
+ @Override
+ public void onConnectivityReportAvailable(ConnectivityReport report) {
+ mHistory.add(report);
+ }
+
+ @Override
+ public void onDataStallSuspected(DataStallReport report) {
+ mHistory.add(report);
+ }
+
+ @Override
+ public void onNetworkConnectivityReported(Network network, boolean hasConnectivity) {
+ mHistory.add(new Pair<Network, Boolean>(network, hasConnectivity));
+ }
+
+ public void expectOnConnectivityReportAvailable(
+ @NonNull Network network, @NonNull String interfaceName) {
+ final ConnectivityReport result =
+ (ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
+ assertEquals(network, result.getNetwork());
+
+ final NetworkCapabilities nc = result.getNetworkCapabilities();
+ assertNotNull(nc);
+ assertTrue(nc.hasTransport(TRANSPORT_TEST));
+ assertNotNull(result.getLinkProperties());
+ assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
+
+ final PersistableBundle extras = result.getAdditionalInfo();
+ assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT));
+ final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
+ assertEquals("Network validation result is not 'valid'",
+ NETWORK_VALIDATION_RESULT_VALID, validationResult);
+
+ assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK));
+ final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
+ assertTrue("PROBES_SUCCEEDED mask not in expected range", probesSucceeded >= 0);
+
+ assertTrue(extras.containsKey(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK));
+ final int probesAttempted = extras.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
+ assertTrue("PROBES_ATTEMPTED mask not in expected range", probesAttempted >= 0);
+ }
+
+ public void expectOnNetworkConnectivityReported(
+ @NonNull Network network, boolean hasConnectivity) {
+ final Pair<Network, Boolean> result =
+ (Pair<Network, Boolean>) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
+ assertEquals(network, result.first /* network */);
+ assertEquals(hasConnectivity, result.second /* hasConnectivity */);
+ }
+
+ public void assertNoCallback() {
+ // If no more callbacks exist, there should be nothing left in the ReadHead
+ assertNull("Unexpected event in history",
+ mHistory.poll(NO_CALLBACK_INVOKED_TIMEOUT, x -> true));
+ }
}
}