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