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