1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.cts.util; 18 19 import static android.Manifest.permission.MODIFY_PHONE_STATE; 20 import static android.Manifest.permission.NETWORK_SETTINGS; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 22 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 23 import static android.net.NetworkCapabilities.TRANSPORT_TEST; 24 25 import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel; 26 import static com.android.testutils.TestPermissionUtil.runAsShell; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertNotNull; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assert.fail; 32 33 import android.annotation.NonNull; 34 import android.app.AppOpsManager; 35 import android.content.BroadcastReceiver; 36 import android.content.ContentResolver; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.content.pm.PackageManager; 41 import android.net.ConnectivityManager; 42 import android.net.ConnectivityManager.NetworkCallback; 43 import android.net.LinkProperties; 44 import android.net.Network; 45 import android.net.NetworkCapabilities; 46 import android.net.NetworkInfo; 47 import android.net.NetworkInfo.State; 48 import android.net.NetworkRequest; 49 import android.net.TestNetworkManager; 50 import android.net.wifi.WifiInfo; 51 import android.net.wifi.WifiManager; 52 import android.os.Binder; 53 import android.os.Build; 54 import android.os.ConditionVariable; 55 import android.os.IBinder; 56 import android.system.Os; 57 import android.system.OsConstants; 58 import android.telephony.SubscriptionManager; 59 import android.telephony.TelephonyManager; 60 import android.text.TextUtils; 61 import android.util.Log; 62 63 import androidx.annotation.Nullable; 64 65 import com.android.compatibility.common.util.PollingCheck; 66 import com.android.compatibility.common.util.ShellIdentityUtils; 67 import com.android.compatibility.common.util.SystemUtil; 68 import com.android.modules.utils.build.SdkLevel; 69 import com.android.net.module.util.ConnectivitySettingsUtils; 70 import com.android.testutils.ConnectUtil; 71 72 import java.io.IOException; 73 import java.io.InputStream; 74 import java.io.OutputStream; 75 import java.net.InetSocketAddress; 76 import java.net.Socket; 77 import java.util.ArrayList; 78 import java.util.Objects; 79 import java.util.concurrent.CompletableFuture; 80 import java.util.concurrent.CountDownLatch; 81 import java.util.concurrent.TimeUnit; 82 import java.util.concurrent.TimeoutException; 83 84 public final class CtsNetUtils { 85 private static final String TAG = CtsNetUtils.class.getSimpleName(); 86 87 // Redefine this flag here so that IPsec code shipped in a mainline module can build on old 88 // platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released. 89 // TODO: b/275378783 Remove this flag and use the platform API when it is available. 90 private static final String FEATURE_IPSEC_TUNNEL_MIGRATION = 91 "android.software.ipsec_tunnel_migration"; 92 93 private static final int SOCKET_TIMEOUT_MS = 10_000; 94 private static final int PRIVATE_DNS_PROBE_MS = 1_000; 95 96 private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 30_000; 97 private static final int CONNECTIVITY_CHANGE_TIMEOUT_SECS = 30; 98 99 private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; 100 private static final String PRIVATE_DNS_MODE_STRICT = "hostname"; 101 public static final int HTTP_PORT = 80; 102 public static final String TEST_HOST = "connectivitycheck.gstatic.com"; 103 public static final String HTTP_REQUEST = 104 "GET /generate_204 HTTP/1.0\r\n" + 105 "Host: " + TEST_HOST + "\r\n" + 106 "Connection: keep-alive\r\n\r\n"; 107 // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent. 108 public static final String NETWORK_CALLBACK_ACTION = 109 "ConnectivityManagerTest.NetworkCallbackAction"; 110 111 private final IBinder mBinder = new Binder(); 112 private final Context mContext; 113 private final ConnectivityManager mCm; 114 private final ContentResolver mCR; 115 private final WifiManager mWifiManager; 116 private TestNetworkCallback mCellNetworkCallback; 117 private int mOldPrivateDnsMode = 0; 118 private String mOldPrivateDnsSpecifier; 119 CtsNetUtils(Context context)120 public CtsNetUtils(Context context) { 121 mContext = context; 122 mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 123 mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 124 mCR = context.getContentResolver(); 125 } 126 127 /** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */ hasIpsecTunnelsFeature()128 public boolean hasIpsecTunnelsFeature() { 129 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS) 130 || getFirstApiLevel() >= Build.VERSION_CODES.Q; 131 } 132 133 /** Checks if FEATURE_IPSEC_TUNNEL_MIGRATION is enabled on the device */ hasIpsecTunnelMigrateFeature()134 public boolean hasIpsecTunnelMigrateFeature() { 135 return mContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION); 136 } 137 138 /** 139 * Sets the given appop using shell commands 140 * 141 * <p>Expects caller to hold the shell permission identity. 142 */ setAppopPrivileged(int appop, boolean allow)143 public void setAppopPrivileged(int appop, boolean allow) { 144 final String opName = AppOpsManager.opToName(appop); 145 for (final String pkg : new String[] {"com.android.shell", mContext.getPackageName()}) { 146 final String cmd = 147 String.format( 148 "appops set %s %s %s", 149 pkg, // Package name 150 opName, // Appop 151 (allow ? "allow" : "deny")); // Action 152 SystemUtil.runShellCommand(cmd); 153 } 154 } 155 156 /** Sets up a test network using the provided interface name */ setupAndGetTestNetwork(String ifname)157 public TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception { 158 // Build a network request 159 final NetworkRequest nr = 160 new NetworkRequest.Builder() 161 .clearCapabilities() 162 .addTransportType(TRANSPORT_TEST) 163 .setNetworkSpecifier(ifname) 164 .build(); 165 166 final TestNetworkCallback cb = new TestNetworkCallback(); 167 mCm.requestNetwork(nr, cb); 168 169 // Setup the test network after network request is filed to prevent Network from being 170 // reaped due to no requests matching it. 171 mContext.getSystemService(TestNetworkManager.class).setupTestNetwork(ifname, mBinder); 172 173 return cb; 174 } 175 176 /** 177 * Toggle Wi-Fi off and on, waiting for the {@link ConnectivityManager#CONNECTIVITY_ACTION} 178 * broadcast in both cases. 179 */ reconnectWifiAndWaitForConnectivityAction()180 public void reconnectWifiAndWaitForConnectivityAction() throws Exception { 181 assertTrue(mWifiManager.isWifiEnabled()); 182 Network wifiNetwork = getWifiNetwork(); 183 // Ensure system default network is WIFI because it's expected in disconnectFromWifi() 184 expectNetworkIsSystemDefault(wifiNetwork); 185 disconnectFromWifi(wifiNetwork, true /* expectLegacyBroadcast */); 186 connectToWifi(true /* expectLegacyBroadcast */); 187 } 188 189 /** 190 * Turn Wi-Fi off, then back on and make sure it connects, if it is supported. 191 */ reconnectWifiIfSupported()192 public void reconnectWifiIfSupported() throws Exception { 193 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { 194 return; 195 } 196 disableWifi(); 197 ensureWifiConnected(); 198 } 199 200 /** 201 * Turn cell data off, then back on and make sure it connects, if it is supported. 202 */ reconnectCellIfSupported()203 public void reconnectCellIfSupported() throws Exception { 204 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { 205 return; 206 } 207 setMobileDataEnabled(false); 208 setMobileDataEnabled(true); 209 } 210 expectNetworkIsSystemDefault(Network network)211 public Network expectNetworkIsSystemDefault(Network network) 212 throws Exception { 213 final CompletableFuture<Network> future = new CompletableFuture(); 214 final NetworkCallback cb = new NetworkCallback() { 215 @Override 216 public void onAvailable(Network n) { 217 if (n.equals(network)) future.complete(network); 218 } 219 }; 220 221 try { 222 mCm.registerDefaultNetworkCallback(cb); 223 return future.get(CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS); 224 } catch (TimeoutException e) { 225 throw new AssertionError("Timed out waiting for system default network to switch" 226 + " to network " + network + ". Current default network is network " 227 + mCm.getActiveNetwork(), e); 228 } finally { 229 mCm.unregisterNetworkCallback(cb); 230 } 231 } 232 233 /** 234 * Enable WiFi and wait for it to become connected to a network. 235 * 236 * This method expects to receive a legacy broadcast on connect, which may not be sent if the 237 * network does not become default or if it is not the first network. 238 */ connectToWifi()239 public Network connectToWifi() { 240 return connectToWifi(true /* expectLegacyBroadcast */); 241 } 242 243 /** 244 * Enable WiFi and wait for it to become connected to a network. 245 * 246 * A network is considered connected when a {@link NetworkRequest} with TRANSPORT_WIFI 247 * receives a {@link NetworkCallback#onAvailable(Network)} callback. 248 */ ensureWifiConnected()249 public Network ensureWifiConnected() { 250 return connectToWifi(false /* expectLegacyBroadcast */); 251 } 252 253 /** 254 * Enable WiFi and wait for it to become connected to a network. 255 * 256 * @param expectLegacyBroadcast Whether to check for a legacy CONNECTIVITY_ACTION connected 257 * broadcast. The broadcast is typically not sent if the network 258 * does not become the default network, and is not the first 259 * network to appear. 260 * @return The network that was newly connected. 261 */ connectToWifi(boolean expectLegacyBroadcast)262 private Network connectToWifi(boolean expectLegacyBroadcast) { 263 ConnectivityActionReceiver receiver = new ConnectivityActionReceiver( 264 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED); 265 IntentFilter filter = new IntentFilter(); 266 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 267 mContext.registerReceiver(receiver, filter); 268 269 try { 270 final Network network = new ConnectUtil(mContext).ensureWifiConnected(); 271 if (expectLegacyBroadcast) { 272 assertTrue("CONNECTIVITY_ACTION not received after connecting to " + network, 273 receiver.waitForState()); 274 } 275 return network; 276 } catch (InterruptedException ex) { 277 throw new AssertionError("connectToWifi was interrupted", ex); 278 } finally { 279 mContext.unregisterReceiver(receiver); 280 } 281 } 282 283 /** 284 * Disable WiFi and wait for it to become disconnected from the network. 285 * 286 * This method expects to receive a legacy broadcast on disconnect, which may not be sent if the 287 * network was not default, or was not the first network. 288 * 289 * @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network 290 * is expected to be able to establish a TCP connection to a remote 291 * server before disconnecting, and to have that connection closed in 292 * the process. 293 */ disconnectFromWifi(Network wifiNetworkToCheck)294 public void disconnectFromWifi(Network wifiNetworkToCheck) { 295 disconnectFromWifi(wifiNetworkToCheck, true /* expectLegacyBroadcast */); 296 } 297 298 /** 299 * Disable WiFi and wait for it to become disconnected from the network. 300 * 301 * @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network 302 * is expected to be able to establish a TCP connection to a remote 303 * server before disconnecting, and to have that connection closed in 304 * the process. 305 */ ensureWifiDisconnected(Network wifiNetworkToCheck)306 public void ensureWifiDisconnected(Network wifiNetworkToCheck) { 307 disconnectFromWifi(wifiNetworkToCheck, false /* expectLegacyBroadcast */); 308 } 309 310 /** 311 * Disable WiFi and wait for the connection info to be cleared. 312 */ disableWifi()313 public void disableWifi() throws Exception { 314 SystemUtil.runShellCommand("svc wifi disable"); 315 PollingCheck.check( 316 "Wifi not disconnected! Current network is not null " 317 + mWifiManager.getConnectionInfo().getNetworkId(), 318 TimeUnit.SECONDS.toMillis(CONNECTIVITY_CHANGE_TIMEOUT_SECS), 319 () -> ShellIdentityUtils.invokeWithShellPermissions( 320 () -> mWifiManager.getConnectionInfo().getNetworkId()) == -1); 321 } 322 323 /** 324 * Disable WiFi and wait for it to become disconnected from the network. 325 * 326 * @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network 327 * is expected to be able to establish a TCP connection to a remote 328 * server before disconnecting, and to have that connection closed in 329 * the process. 330 * @param expectLegacyBroadcast Whether to check for a legacy CONNECTIVITY_ACTION disconnected 331 * broadcast. The broadcast is typically not sent if the network 332 * was not the default network and not the first network to appear. 333 * The check will always be skipped if the device was not connected 334 * to wifi in the first place. 335 */ disconnectFromWifi(Network wifiNetworkToCheck, boolean expectLegacyBroadcast)336 private void disconnectFromWifi(Network wifiNetworkToCheck, boolean expectLegacyBroadcast) { 337 final TestNetworkCallback callback = new TestNetworkCallback(); 338 mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback); 339 340 ConnectivityActionReceiver receiver = new ConnectivityActionReceiver( 341 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED); 342 IntentFilter filter = new IntentFilter(); 343 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 344 mContext.registerReceiver(receiver, filter); 345 346 final WifiInfo wifiInfo = runAsShell(NETWORK_SETTINGS, 347 () -> mWifiManager.getConnectionInfo()); 348 final boolean wasWifiConnected = wifiInfo != null && wifiInfo.getNetworkId() != -1; 349 // Assert that we can establish a TCP connection on wifi. 350 Socket wifiBoundSocket = null; 351 if (wifiNetworkToCheck != null) { 352 assertTrue("Cannot check network " + wifiNetworkToCheck + ": wifi is not connected", 353 wasWifiConnected); 354 final NetworkCapabilities nc = mCm.getNetworkCapabilities(wifiNetworkToCheck); 355 assertNotNull("Network " + wifiNetworkToCheck + " is not connected", nc); 356 try { 357 wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT); 358 testHttpRequest(wifiBoundSocket); 359 } catch (IOException e) { 360 fail("HTTP request before wifi disconnected failed with: " + e); 361 } 362 } 363 364 try { 365 if (wasWifiConnected) { 366 // Make sure the callback is registered before turning off WiFi. 367 callback.waitForAvailable(); 368 } 369 SystemUtil.runShellCommand("svc wifi disable"); 370 if (wasWifiConnected) { 371 // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION. 372 assertNotNull("Did not receive onLost callback after disabling wifi", 373 callback.waitForLost()); 374 if (expectLegacyBroadcast) { 375 assertTrue("Wifi failed to reach DISCONNECTED state.", receiver.waitForState()); 376 } 377 } 378 } catch (InterruptedException ex) { 379 fail("disconnectFromWifi was interrupted"); 380 } finally { 381 mCm.unregisterNetworkCallback(callback); 382 mContext.unregisterReceiver(receiver); 383 } 384 385 // Check that the socket is closed when wifi disconnects. 386 if (wifiBoundSocket != null) { 387 try { 388 testHttpRequest(wifiBoundSocket); 389 fail("HTTP request should not succeed after wifi disconnects"); 390 } catch (IOException expected) { 391 assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage()); 392 } 393 } 394 } 395 getWifiNetwork()396 public Network getWifiNetwork() { 397 TestNetworkCallback callback = new TestNetworkCallback(); 398 mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback); 399 Network network = null; 400 try { 401 network = callback.waitForAvailable(); 402 } catch (InterruptedException e) { 403 fail("NetworkCallback wait was interrupted."); 404 } finally { 405 mCm.unregisterNetworkCallback(callback); 406 } 407 assertNotNull("Cannot find Network for wifi. Is wifi connected?", network); 408 return network; 409 } 410 cellConnectAttempted()411 public boolean cellConnectAttempted() { 412 return mCellNetworkCallback != null; 413 } 414 makeWifiNetworkRequest()415 private NetworkRequest makeWifiNetworkRequest() { 416 return new NetworkRequest.Builder() 417 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 418 .build(); 419 } 420 testHttpRequest(Socket s)421 public void testHttpRequest(Socket s) throws IOException { 422 OutputStream out = s.getOutputStream(); 423 InputStream in = s.getInputStream(); 424 425 final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8"); 426 byte[] responseBytes = new byte[4096]; 427 out.write(requestBytes); 428 in.read(responseBytes); 429 final String response = new String(responseBytes, "UTF-8"); 430 assertTrue("Received unexpected response: " + response, 431 response.startsWith("HTTP/1.0 204 No Content\r\n")); 432 } 433 getBoundSocket(Network network, String host, int port)434 private Socket getBoundSocket(Network network, String host, int port) throws IOException { 435 InetSocketAddress addr = new InetSocketAddress(host, port); 436 Socket s = network.getSocketFactory().createSocket(); 437 try { 438 s.setSoTimeout(SOCKET_TIMEOUT_MS); 439 s.connect(addr, SOCKET_TIMEOUT_MS); 440 } catch (IOException e) { 441 s.close(); 442 throw e; 443 } 444 return s; 445 } 446 storePrivateDnsSetting()447 public void storePrivateDnsSetting() { 448 mOldPrivateDnsMode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext); 449 mOldPrivateDnsSpecifier = ConnectivitySettingsUtils.getPrivateDnsHostname(mContext); 450 } 451 restorePrivateDnsSetting()452 public void restorePrivateDnsSetting() throws InterruptedException { 453 if (mOldPrivateDnsMode == 0) { 454 fail("restorePrivateDnsSetting without storing settings first"); 455 } 456 457 if (mOldPrivateDnsMode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) { 458 // Also restore hostname even if the value is not used since private dns is not in 459 // the strict mode to prevent setting being changed after test. 460 ConnectivitySettingsUtils.setPrivateDnsHostname(mContext, mOldPrivateDnsSpecifier); 461 ConnectivitySettingsUtils.setPrivateDnsMode(mContext, mOldPrivateDnsMode); 462 return; 463 } 464 // restore private DNS setting 465 // In case of invalid setting, set to opportunistic to avoid a bad state and fail 466 if (TextUtils.isEmpty(mOldPrivateDnsSpecifier)) { 467 ConnectivitySettingsUtils.setPrivateDnsMode(mContext, 468 ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC); 469 fail("Invalid private DNS setting: no hostname specified in strict mode"); 470 } 471 setPrivateDnsStrictMode(mOldPrivateDnsSpecifier); 472 473 // There might be a race before private DNS setting is applied and the next test is 474 // running. So waiting private DNS to be validated can reduce the flaky rate of test. 475 awaitPrivateDnsSetting("restorePrivateDnsSetting timeout", 476 mCm.getActiveNetwork(), 477 mOldPrivateDnsSpecifier, true /* requiresValidatedServer */); 478 } 479 setPrivateDnsStrictMode(String server)480 public void setPrivateDnsStrictMode(String server) { 481 // To reduce flake rate, set PRIVATE_DNS_SPECIFIER before PRIVATE_DNS_MODE. This ensures 482 // that if the previous private DNS mode was not strict, the system only sees one 483 // EVENT_PRIVATE_DNS_SETTINGS_CHANGED event instead of two. 484 ConnectivitySettingsUtils.setPrivateDnsHostname(mContext, server); 485 final int mode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext); 486 // If current private DNS mode is strict, we only need to set PRIVATE_DNS_SPECIFIER. 487 if (mode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) { 488 ConnectivitySettingsUtils.setPrivateDnsMode(mContext, 489 ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); 490 } 491 } 492 493 /** 494 * Waiting for the new private DNS setting to be validated. 495 * This method is helpful when the new private DNS setting is configured and ensure the new 496 * setting is applied and workable. It can also reduce the flaky rate when the next test is 497 * running. 498 * 499 * @param msg A message that will be printed when the validation of private DNS is timeout. 500 * @param network A network which will apply the new private DNS setting. 501 * @param server The hostname of private DNS. 502 * @param requiresValidatedServer A boolean to decide if it's needed to wait private DNS to be 503 * validated or not. 504 * @throws InterruptedException If the thread is interrupted. 505 */ awaitPrivateDnsSetting(@onNull String msg, @NonNull Network network, @Nullable String server, boolean requiresValidatedServer)506 public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network, 507 @Nullable String server, boolean requiresValidatedServer) throws InterruptedException { 508 final CountDownLatch latch = new CountDownLatch(1); 509 final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); 510 final NetworkCallback callback = new NetworkCallback() { 511 @Override 512 public void onLinkPropertiesChanged(Network n, LinkProperties lp) { 513 Log.i(TAG, "Link properties of network " + n + " changed to " + lp); 514 if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) { 515 return; 516 } 517 Log.i(TAG, "Set private DNS server to " + server); 518 if (network.equals(n) && Objects.equals(server, lp.getPrivateDnsServerName())) { 519 latch.countDown(); 520 } 521 } 522 }; 523 mCm.registerNetworkCallback(request, callback); 524 assertTrue(msg, latch.await(PRIVATE_DNS_SETTING_TIMEOUT_MS, TimeUnit.MILLISECONDS)); 525 mCm.unregisterNetworkCallback(callback); 526 // Wait some time for NetworkMonitor's private DNS probe to complete. If we do not do 527 // this, then the test could complete before the NetworkMonitor private DNS probe 528 // completes. This would result in tearDown disabling private DNS, and the NetworkMonitor 529 // private DNS probe getting stuck because there are no longer any private DNS servers to 530 // query. This then results in the next test not being able to change the private DNS 531 // setting within the timeout, because the NetworkMonitor thread is blocked in the 532 // private DNS probe. There is no way to know when the probe has completed: because the 533 // network is likely already validated, there is no callback that we can listen to, so 534 // just sleep. 535 if (requiresValidatedServer) { 536 Thread.sleep(PRIVATE_DNS_PROBE_MS); 537 } 538 } 539 540 /** 541 * Get all testable Networks with internet capability. 542 */ getTestableNetworks()543 public Network[] getTestableNetworks() { 544 final ArrayList<Network> testableNetworks = new ArrayList<Network>(); 545 for (Network network : mCm.getAllNetworks()) { 546 final NetworkCapabilities nc = mCm.getNetworkCapabilities(network); 547 if (nc != null 548 && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) 549 && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { 550 testableNetworks.add(network); 551 } 552 } 553 554 assertTrue("This test requires that at least one public Internet-providing" 555 + " network be connected. Please ensure that the device is connected to" 556 + " a network.", 557 testableNetworks.size() >= 1); 558 return testableNetworks.toArray(new Network[0]); 559 } 560 561 /** 562 * Enables or disables the mobile data and waits for the state to change. 563 * 564 * @param enabled - true to enable, false to disable the mobile data. 565 */ setMobileDataEnabled(boolean enabled)566 public void setMobileDataEnabled(boolean enabled) throws InterruptedException { 567 final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class) 568 .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId()); 569 final NetworkRequest request = new NetworkRequest.Builder() 570 .addTransportType(TRANSPORT_CELLULAR) 571 .addCapability(NET_CAPABILITY_INTERNET) 572 .build(); 573 final TestNetworkCallback callback = new TestNetworkCallback(); 574 mCm.requestNetwork(request, callback); 575 576 try { 577 if (!enabled) { 578 assertNotNull("Cannot disable mobile data unless mobile data is connected", 579 callback.waitForAvailable()); 580 } 581 582 if (SdkLevel.isAtLeastS()) { 583 runAsShell(MODIFY_PHONE_STATE, () -> tm.setDataEnabledForReason( 584 TelephonyManager.DATA_ENABLED_REASON_USER, enabled)); 585 } else { 586 runAsShell(MODIFY_PHONE_STATE, () -> tm.setDataEnabled(enabled)); 587 } 588 if (enabled) { 589 assertNotNull("Enabling mobile data did not connect mobile data", 590 callback.waitForAvailable()); 591 } else { 592 assertNotNull("Disabling mobile data did not disconnect mobile data", 593 callback.waitForLost()); 594 } 595 596 } finally { 597 mCm.unregisterNetworkCallback(callback); 598 } 599 } 600 601 /** 602 * Receiver that captures the last connectivity change's network type and state. Recognizes 603 * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents. 604 */ 605 public static class ConnectivityActionReceiver extends BroadcastReceiver { 606 607 private final CountDownLatch mReceiveLatch = new CountDownLatch(1); 608 609 private final int mNetworkType; 610 private final NetworkInfo.State mNetState; 611 private final ConnectivityManager mCm; 612 ConnectivityActionReceiver(ConnectivityManager cm, int networkType, NetworkInfo.State netState)613 public ConnectivityActionReceiver(ConnectivityManager cm, int networkType, 614 NetworkInfo.State netState) { 615 this.mCm = cm; 616 mNetworkType = networkType; 617 mNetState = netState; 618 } 619 onReceive(Context context, Intent intent)620 public void onReceive(Context context, Intent intent) { 621 String action = intent.getAction(); 622 NetworkInfo networkInfo = null; 623 624 // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable 625 // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is 626 // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo. 627 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { 628 networkInfo = intent.getExtras() 629 .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO); 630 assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO", 631 networkInfo); 632 } else if (NETWORK_CALLBACK_ACTION.equals(action)) { 633 Network network = intent.getExtras() 634 .getParcelable(ConnectivityManager.EXTRA_NETWORK); 635 assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network); 636 networkInfo = this.mCm.getNetworkInfo(network); 637 if (networkInfo == null) { 638 // When disconnecting, it seems like we get an intent sent with an invalid 639 // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(), 640 // it is invalid. Ignore these. 641 Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring " 642 + "invalid network"); 643 return; 644 } 645 } else { 646 fail("ConnectivityActionReceiver received unxpected intent action: " + action); 647 } 648 649 assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo); 650 int networkType = networkInfo.getType(); 651 State networkState = networkInfo.getState(); 652 Log.i(TAG, "Network type: " + networkType + " state: " + networkState); 653 if (networkType == mNetworkType && networkInfo.getState() == mNetState) { 654 mReceiveLatch.countDown(); 655 } 656 } 657 waitForState()658 public boolean waitForState() throws InterruptedException { 659 return mReceiveLatch.await(CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS); 660 } 661 } 662 663 /** 664 * Callback used in testRegisterNetworkCallback that allows caller to block on 665 * {@code onAvailable}. 666 */ 667 public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback { 668 private final ConditionVariable mAvailableCv = new ConditionVariable(false); 669 private final CountDownLatch mLostLatch = new CountDownLatch(1); 670 private final CountDownLatch mUnavailableLatch = new CountDownLatch(1); 671 672 public Network currentNetwork; 673 public Network lastLostNetwork; 674 675 /** 676 * Wait for a network to be available. 677 * 678 * If onAvailable was previously called but was followed by onLost, this will wait for the 679 * next available network. 680 */ waitForAvailable()681 public Network waitForAvailable() throws InterruptedException { 682 final long timeoutMs = TimeUnit.SECONDS.toMillis(CONNECTIVITY_CHANGE_TIMEOUT_SECS); 683 while (mAvailableCv.block(timeoutMs)) { 684 final Network n = currentNetwork; 685 if (n != null) return n; 686 Log.w(TAG, "onAvailable called but network was lost before it could be returned." 687 + " Waiting for the next call to onAvailable."); 688 } 689 return null; 690 } 691 waitForLost()692 public Network waitForLost() throws InterruptedException { 693 return mLostLatch.await(CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS) 694 ? lastLostNetwork : null; 695 } 696 waitForUnavailable()697 public boolean waitForUnavailable() throws InterruptedException { 698 return mUnavailableLatch.await(2, TimeUnit.SECONDS); 699 } 700 701 @Override onAvailable(Network network)702 public void onAvailable(Network network) { 703 Log.i(TAG, "CtsNetUtils TestNetworkCallback onAvailable " + network); 704 currentNetwork = network; 705 mAvailableCv.open(); 706 } 707 708 @Override onLost(Network network)709 public void onLost(Network network) { 710 Log.i(TAG, "CtsNetUtils TestNetworkCallback onLost " + network); 711 lastLostNetwork = network; 712 if (network.equals(currentNetwork)) { 713 mAvailableCv.close(); 714 currentNetwork = null; 715 } 716 mLostLatch.countDown(); 717 } 718 719 @Override onUnavailable()720 public void onUnavailable() { 721 mUnavailableLatch.countDown(); 722 } 723 } 724 } 725