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