• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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