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