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