1 /*
2  * Copyright (C) 2009 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;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
22 
23 import android.app.PendingIntent;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageManager;
30 import android.net.ConnectivityManager;
31 import android.net.ConnectivityManager.NetworkCallback;
32 import android.net.Network;
33 import android.net.NetworkCapabilities;
34 import android.net.NetworkConfig;
35 import android.net.NetworkInfo;
36 import android.net.NetworkInfo.DetailedState;
37 import android.net.NetworkInfo.State;
38 import android.net.NetworkRequest;
39 import android.net.wifi.WifiManager;
40 import android.os.SystemProperties;
41 import android.system.Os;
42 import android.system.OsConstants;
43 import android.test.AndroidTestCase;
44 import android.util.Log;
45 
46 import com.android.internal.telephony.PhoneConstants;
47 
48 import java.io.File;
49 import java.io.FileNotFoundException;
50 import java.io.InputStream;
51 import java.io.IOException;
52 import java.io.OutputStream;
53 import java.net.Socket;
54 import java.net.InetSocketAddress;
55 import java.util.HashMap;
56 import java.util.Scanner;
57 import java.util.concurrent.CountDownLatch;
58 import java.util.concurrent.LinkedBlockingQueue;
59 import java.util.concurrent.TimeUnit;
60 
61 public class ConnectivityManagerTest extends AndroidTestCase {
62 
63     private static final String TAG = ConnectivityManagerTest.class.getSimpleName();
64 
65     private static final String FEATURE_ENABLE_HIPRI = "enableHIPRI";
66 
67     public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
68     public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
69 
70     private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
71     private static final String TEST_HOST = "connectivitycheck.gstatic.com";
72     private static final int SOCKET_TIMEOUT_MS = 2000;
73     private static final int SEND_BROADCAST_TIMEOUT = 30000;
74     private static final int HTTP_PORT = 80;
75     private static final String HTTP_REQUEST =
76             "GET /generate_204 HTTP/1.0\r\n" +
77             "Host: " + TEST_HOST + "\r\n" +
78             "Connection: keep-alive\r\n\r\n";
79 
80     // Base path for IPv6 sysctls
81     private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
82 
83     // Expected values for MIN|MAX_PLEN.
84     private static final int IPV6_WIFI_ACCEPT_RA_RT_INFO_MIN_PLEN = 48;
85     private static final int IPV6_WIFI_ACCEPT_RA_RT_INFO_MAX_PLEN = 64;
86 
87     // Expected values for RFC 7559 router soliciations.
88     // Maximum number of router solicitations to send. -1 means no limit.
89     private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
90 
91     // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
92     private static final String NETWORK_CALLBACK_ACTION =
93             "ConnectivityManagerTest.NetworkCallbackAction";
94 
95     // Intent string to get the number of wifi CONNECTIVITY_ACTION callbacks the test app has seen
96     public static final String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
97             "android.net.cts.appForApi23.getWifiConnectivityActionCount";
98 
99     // device could have only one interface: data, wifi.
100     private static final int MIN_NUM_NETWORK_TYPES = 1;
101 
102     private Context mContext;
103     private ConnectivityManager mCm;
104     private WifiManager mWifiManager;
105     private PackageManager mPackageManager;
106     private final HashMap<Integer, NetworkConfig> mNetworks =
107             new HashMap<Integer, NetworkConfig>();
108     boolean mWifiConnectAttempted;
109 
110     @Override
setUp()111     protected void setUp() throws Exception {
112         super.setUp();
113         mContext = getContext();
114         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
115         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
116         mPackageManager = mContext.getPackageManager();
117         mWifiConnectAttempted = false;
118 
119         // Get com.android.internal.R.array.networkAttributes
120         int resId = mContext.getResources().getIdentifier("networkAttributes", "array", "android");
121         String[] naStrings = mContext.getResources().getStringArray(resId);
122         //TODO: What is the "correct" way to determine if this is a wifi only device?
123         boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
124         for (String naString : naStrings) {
125             try {
126                 NetworkConfig n = new NetworkConfig(naString);
127                 if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
128                     continue;
129                 }
130                 mNetworks.put(n.type, n);
131             } catch (Exception e) {}
132         }
133     }
134 
135     @Override
tearDown()136     protected void tearDown() throws Exception {
137         // Return WiFi to its original disabled state after tests that explicitly connect.
138         if (mWifiConnectAttempted) {
139             disconnectFromWifi(null);
140         }
141     }
142 
143     /**
144      * Make sure WiFi is connected to an access point if it is not already. If
145      * WiFi is enabled as a result of this function, it will be disabled
146      * automatically in tearDown().
147      */
ensureWifiConnected()148     private Network ensureWifiConnected() {
149         if (mWifiManager.isWifiEnabled()) {
150             return getWifiNetwork();
151         }
152         mWifiConnectAttempted = true;
153         return connectToWifi();
154     }
155 
testIsNetworkTypeValid()156     public void testIsNetworkTypeValid() {
157         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE));
158         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_WIFI));
159         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_MMS));
160         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_SUPL));
161         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_DUN));
162         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_HIPRI));
163         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_WIMAX));
164         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_BLUETOOTH));
165         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_DUMMY));
166         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_ETHERNET));
167         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_FOTA));
168         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_IMS));
169         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_CBS));
170         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_WIFI_P2P));
171         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_IA));
172         assertFalse(mCm.isNetworkTypeValid(-1));
173         assertTrue(mCm.isNetworkTypeValid(0));
174         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.MAX_NETWORK_TYPE));
175         assertFalse(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.MAX_NETWORK_TYPE+1));
176 
177         NetworkInfo[] ni = mCm.getAllNetworkInfo();
178 
179         for (NetworkInfo n: ni) {
180             assertTrue(ConnectivityManager.isNetworkTypeValid(n.getType()));
181         }
182 
183     }
184 
testSetNetworkPreference()185     public void testSetNetworkPreference() {
186         // getNetworkPreference() and setNetworkPreference() are both deprecated so they do
187         // not preform any action.  Verify they are at least still callable.
188         mCm.setNetworkPreference(mCm.getNetworkPreference());
189     }
190 
testGetActiveNetworkInfo()191     public void testGetActiveNetworkInfo() {
192         NetworkInfo ni = mCm.getActiveNetworkInfo();
193 
194         assertNotNull("You must have an active network connection to complete CTS", ni);
195         assertTrue(ConnectivityManager.isNetworkTypeValid(ni.getType()));
196         assertTrue(ni.getState() == State.CONNECTED);
197     }
198 
testGetActiveNetwork()199     public void testGetActiveNetwork() {
200         Network network = mCm.getActiveNetwork();
201         assertNotNull("You must have an active network connection to complete CTS", network);
202 
203         NetworkInfo ni = mCm.getNetworkInfo(network);
204         assertNotNull("Network returned from getActiveNetwork was invalid", ni);
205 
206         // Similar to testGetActiveNetworkInfo above.
207         assertTrue(ConnectivityManager.isNetworkTypeValid(ni.getType()));
208         assertTrue(ni.getState() == State.CONNECTED);
209     }
210 
testGetNetworkInfo()211     public void testGetNetworkInfo() {
212         for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE+1; type++) {
213             if (isSupported(type)) {
214                 NetworkInfo ni = mCm.getNetworkInfo(type);
215                 assertTrue("Info shouldn't be null for " + type, ni != null);
216                 State state = ni.getState();
217                 assertTrue("Bad state for " + type, State.UNKNOWN.ordinal() >= state.ordinal()
218                            && state.ordinal() >= State.CONNECTING.ordinal());
219                 DetailedState ds = ni.getDetailedState();
220                 assertTrue("Bad detailed state for " + type,
221                            DetailedState.FAILED.ordinal() >= ds.ordinal()
222                            && ds.ordinal() >= DetailedState.IDLE.ordinal());
223             } else {
224                 assertNull("Info should be null for " + type, mCm.getNetworkInfo(type));
225             }
226         }
227     }
228 
testGetAllNetworkInfo()229     public void testGetAllNetworkInfo() {
230         NetworkInfo[] ni = mCm.getAllNetworkInfo();
231         assertTrue(ni.length >= MIN_NUM_NETWORK_TYPES);
232         for (int type = 0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
233             int desiredFoundCount = (isSupported(type) ? 1 : 0);
234             int foundCount = 0;
235             for (NetworkInfo i : ni) {
236                 if (i.getType() == type) foundCount++;
237             }
238             if (foundCount != desiredFoundCount) {
239                 Log.e(TAG, "failure in testGetAllNetworkInfo.  Dump of returned NetworkInfos:");
240                 for (NetworkInfo networkInfo : ni) Log.e(TAG, "  " + networkInfo);
241             }
242             assertTrue("Unexpected foundCount of " + foundCount + " for type " + type,
243                     foundCount == desiredFoundCount);
244         }
245     }
246 
assertStartUsingNetworkFeatureUnsupported(int networkType, String feature)247     private void assertStartUsingNetworkFeatureUnsupported(int networkType, String feature) {
248         try {
249             mCm.startUsingNetworkFeature(networkType, feature);
250             fail("startUsingNetworkFeature is no longer supported in the current API version");
251         } catch (UnsupportedOperationException expected) {}
252     }
253 
assertStopUsingNetworkFeatureUnsupported(int networkType, String feature)254     private void assertStopUsingNetworkFeatureUnsupported(int networkType, String feature) {
255         try {
256             mCm.startUsingNetworkFeature(networkType, feature);
257             fail("stopUsingNetworkFeature is no longer supported in the current API version");
258         } catch (UnsupportedOperationException expected) {}
259     }
260 
assertRequestRouteToHostUnsupported(int networkType, int hostAddress)261     private void assertRequestRouteToHostUnsupported(int networkType, int hostAddress) {
262         try {
263             mCm.requestRouteToHost(networkType, hostAddress);
264             fail("requestRouteToHost is no longer supported in the current API version");
265         } catch (UnsupportedOperationException expected) {}
266     }
267 
testStartUsingNetworkFeature()268     public void testStartUsingNetworkFeature() {
269 
270         final String invalidateFeature = "invalidateFeature";
271         final String mmsFeature = "enableMMS";
272         final int failureCode = -1;
273         final int wifiOnlyStartFailureCode = PhoneConstants.APN_REQUEST_FAILED;
274         final int wifiOnlyStopFailureCode = -1;
275 
276         assertStartUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
277         assertStopUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
278         assertStartUsingNetworkFeatureUnsupported(TYPE_WIFI, mmsFeature);
279     }
280 
isSupported(int networkType)281     private boolean isSupported(int networkType) {
282         // Change-Id I02eb5f22737720095f646f8db5c87fd66da129d6 added VPN support
283         // to all devices directly in software, independent of any external
284         // configuration.
285         return mNetworks.containsKey(networkType) ||
286                (networkType == ConnectivityManager.TYPE_VPN);
287     }
288 
testIsNetworkSupported()289     public void testIsNetworkSupported() {
290         for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
291             boolean supported = mCm.isNetworkSupported(type);
292             if (isSupported(type)) {
293                 assertTrue(supported);
294             } else {
295                 assertFalse(supported);
296             }
297         }
298     }
299 
testRequestRouteToHost()300     public void testRequestRouteToHost() {
301         for (int type = -1 ; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
302             assertRequestRouteToHostUnsupported(type, HOST_ADDRESS);
303         }
304     }
305 
testTest()306     public void testTest() {
307         mCm.getBackgroundDataSetting();
308     }
309 
makeWifiNetworkRequest()310     private NetworkRequest makeWifiNetworkRequest() {
311         return new NetworkRequest.Builder()
312                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
313                 .build();
314     }
315 
316     /**
317      * Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
318      * see if we get a callback for the TRANSPORT_WIFI transport type being available.
319      *
320      * <p>In order to test that a NetworkCallback occurs, we need some change in the network
321      * state (either a transport or capability is now available). The most straightforward is
322      * WiFi. We could add a version that uses the telephony data connection but it's not clear
323      * that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
324      */
testRegisterNetworkCallback()325     public void testRegisterNetworkCallback() {
326         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
327             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
328             return;
329         }
330 
331         // We will register for a WIFI network being available or lost.
332         final TestNetworkCallback callback = new TestNetworkCallback();
333         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
334 
335         final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback();
336         mCm.registerDefaultNetworkCallback(defaultTrackingCallback);
337 
338         Network wifiNetwork = null;
339 
340         try {
341             ensureWifiConnected();
342 
343             // Now we should expect to get a network callback about availability of the wifi
344             // network even if it was already connected as a state-based action when the callback
345             // is registered.
346             wifiNetwork = callback.waitForAvailable();
347             assertNotNull("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
348                     wifiNetwork);
349 
350             assertNotNull("Did not receive NetworkCallback.onAvailable for any default network",
351                     defaultTrackingCallback.waitForAvailable());
352         } catch (InterruptedException e) {
353             fail("Broadcast receiver or NetworkCallback wait was interrupted.");
354         } finally {
355             mCm.unregisterNetworkCallback(callback);
356             mCm.unregisterNetworkCallback(defaultTrackingCallback);
357         }
358     }
359 
360     /**
361      * Tests both registerNetworkCallback and unregisterNetworkCallback similarly to
362      * {@link #testRegisterNetworkCallback} except that a {@code PendingIntent} is used instead
363      * of a {@code NetworkCallback}.
364      */
testRegisterNetworkCallback_withPendingIntent()365     public void testRegisterNetworkCallback_withPendingIntent() {
366         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
367             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
368             return;
369         }
370 
371         // Create a ConnectivityActionReceiver that has an IntentFilter for our locally defined
372         // action, NETWORK_CALLBACK_ACTION.
373         IntentFilter filter = new IntentFilter();
374         filter.addAction(NETWORK_CALLBACK_ACTION);
375 
376         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
377                 ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
378         mContext.registerReceiver(receiver, filter);
379 
380         // Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
381         Intent intent = new Intent(NETWORK_CALLBACK_ACTION);
382         PendingIntent pendingIntent = PendingIntent.getBroadcast(
383                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
384 
385         // We will register for a WIFI network being available or lost.
386         mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
387 
388         try {
389             ensureWifiConnected();
390 
391             // Now we expect to get the Intent delivered notifying of the availability of the wifi
392             // network even if it was already connected as a state-based action when the callback
393             // is registered.
394             assertTrue("Did not receive expected Intent " + intent + " for TRANSPORT_WIFI",
395                     receiver.waitForState());
396         } catch (InterruptedException e) {
397             fail("Broadcast receiver or NetworkCallback wait was interrupted.");
398         } finally {
399             mCm.unregisterNetworkCallback(pendingIntent);
400             pendingIntent.cancel();
401             mContext.unregisterReceiver(receiver);
402         }
403     }
404 
405     /**
406      * Exercises the requestNetwork with NetworkCallback API. This checks to
407      * see if we get a callback for an INTERNET request.
408      */
testRequestNetworkCallback()409     public void testRequestNetworkCallback() {
410         final TestNetworkCallback callback = new TestNetworkCallback();
411         mCm.requestNetwork(new NetworkRequest.Builder()
412                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
413                 .build(), callback);
414 
415         try {
416             // Wait to get callback for availability of internet
417             Network internetNetwork = callback.waitForAvailable();
418             assertNotNull("Did not receive NetworkCallback#onAvailable for INTERNET",
419                     internetNetwork);
420         } catch (InterruptedException e) {
421             fail("NetworkCallback wait was interrupted.");
422         } finally {
423             mCm.unregisterNetworkCallback(callback);
424         }
425     }
426 
427     /**
428      * Exercises the requestNetwork with NetworkCallback API with timeout - expected to
429      * fail. Use WIFI and switch Wi-Fi off.
430      */
testRequestNetworkCallback_onUnavailable()431     public void testRequestNetworkCallback_onUnavailable() {
432         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
433         if (previousWifiEnabledState) {
434             disconnectFromWifi(null);
435         }
436 
437         final TestNetworkCallback callback = new TestNetworkCallback();
438         mCm.requestNetwork(new NetworkRequest.Builder()
439                 .addTransportType(TRANSPORT_WIFI)
440                 .build(), callback, 100);
441 
442         try {
443             // Wait to get callback for unavailability of requested network
444             assertTrue("Did not receive NetworkCallback#onUnavailable",
445                     callback.waitForUnavailable());
446         } catch (InterruptedException e) {
447             fail("NetworkCallback wait was interrupted.");
448         } finally {
449             mCm.unregisterNetworkCallback(callback);
450             if (previousWifiEnabledState) {
451                 connectToWifi();
452             }
453         }
454     }
455 
456     /**
457      * Tests reporting of connectivity changed.
458      */
testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent()459     public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
460         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
461             Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
462             return;
463         }
464         ConnectivityReceiver.prepare();
465 
466         toggleWifi();
467 
468         // The connectivity broadcast has been sent; push through a terminal broadcast
469         // to wait for in the receive to confirm it didn't see the connectivity change.
470         Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
471         finalIntent.setClass(mContext, ConnectivityReceiver.class);
472         mContext.sendBroadcast(finalIntent);
473         assertFalse(ConnectivityReceiver.waitForBroadcast());
474     }
475 
testConnectivityChanged_whenRegistered_shouldReceiveIntent()476     public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
477         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
478             Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
479             return;
480         }
481         ConnectivityReceiver.prepare();
482         ConnectivityReceiver receiver = new ConnectivityReceiver();
483         IntentFilter filter = new IntentFilter();
484         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
485         mContext.registerReceiver(receiver, filter);
486 
487         toggleWifi();
488         Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
489         finalIntent.setClass(mContext, ConnectivityReceiver.class);
490         mContext.sendBroadcast(finalIntent);
491 
492         assertTrue(ConnectivityReceiver.waitForBroadcast());
493     }
494 
testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()495     public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
496             throws InterruptedException {
497         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
498             Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
499             return;
500         }
501         Intent startIntent = new Intent();
502         startIntent.setComponent(new ComponentName("android.net.cts.appForApi23",
503                 "android.net.cts.appForApi23.ConnectivityListeningActivity"));
504         mContext.startActivity(startIntent);
505 
506         toggleWifi();
507 
508         Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
509         assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
510                 getConnectivityCount, SEND_BROADCAST_TIMEOUT));
511     }
512 
sendOrderedBroadcastAndReturnResultCode( Intent intent, int timeoutMs)513     private int sendOrderedBroadcastAndReturnResultCode(
514             Intent intent, int timeoutMs) throws InterruptedException {
515         final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
516         mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
517             @Override
518             public void onReceive(Context context, Intent intent) {
519                 result.offer(getResultCode());
520             }
521         }, null, 0, null, null);
522 
523         Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
524         assertNotNull("Timed out (more than " + timeoutMs +
525                 " milliseconds) waiting for result code for broadcast", resultCode);
526         return resultCode;
527     }
528 
529     // Toggle WiFi twice, leaving it in the state it started in
toggleWifi()530     private void toggleWifi() {
531         if (mWifiManager.isWifiEnabled()) {
532             Network wifiNetwork = getWifiNetwork();
533             disconnectFromWifi(wifiNetwork);
534             connectToWifi();
535         } else {
536             connectToWifi();
537             Network wifiNetwork = getWifiNetwork();
538             disconnectFromWifi(wifiNetwork);
539         }
540     }
541 
542     /** Enable WiFi and wait for it to become connected to a network. */
connectToWifi()543     private Network connectToWifi() {
544         final TestNetworkCallback callback = new TestNetworkCallback();
545         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
546         Network wifiNetwork = null;
547 
548         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
549                 ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
550         IntentFilter filter = new IntentFilter();
551         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
552         mContext.registerReceiver(receiver, filter);
553 
554         boolean connected = false;
555         try {
556             assertTrue(mWifiManager.setWifiEnabled(true));
557             // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
558             wifiNetwork = callback.waitForAvailable();
559             assertNotNull(wifiNetwork);
560             connected = receiver.waitForState();
561         } catch (InterruptedException ex) {
562             fail("connectToWifi was interrupted");
563         } finally {
564             mCm.unregisterNetworkCallback(callback);
565             mContext.unregisterReceiver(receiver);
566         }
567 
568         assertTrue("Wifi must be configured to connect to an access point for this test.",
569                 connected);
570         return wifiNetwork;
571     }
572 
getBoundSocket(Network network, String host, int port)573     private Socket getBoundSocket(Network network, String host, int port) throws IOException {
574         InetSocketAddress addr = new InetSocketAddress(host, port);
575         Socket s = network.getSocketFactory().createSocket();
576         try {
577             s.setSoTimeout(SOCKET_TIMEOUT_MS);
578             s.connect(addr, SOCKET_TIMEOUT_MS);
579         } catch (IOException e) {
580             s.close();
581             throw e;
582         }
583         return s;
584     }
585 
testHttpRequest(Socket s)586     private void testHttpRequest(Socket s) throws IOException {
587         OutputStream out = s.getOutputStream();
588         InputStream in = s.getInputStream();
589 
590         final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
591         byte[] responseBytes = new byte[4096];
592         out.write(requestBytes);
593         in.read(responseBytes);
594         assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
595     }
596 
597     /** Disable WiFi and wait for it to become disconnected from the network. */
disconnectFromWifi(Network wifiNetworkToCheck)598     private void disconnectFromWifi(Network wifiNetworkToCheck) {
599         final TestNetworkCallback callback = new TestNetworkCallback();
600         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
601         Network lostWifiNetwork = null;
602 
603         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
604                 ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
605         IntentFilter filter = new IntentFilter();
606         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
607         mContext.registerReceiver(receiver, filter);
608 
609         // Assert that we can establish a TCP connection on wifi.
610         Socket wifiBoundSocket = null;
611         if (wifiNetworkToCheck != null) {
612             try {
613                 wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
614                 testHttpRequest(wifiBoundSocket);
615             } catch (IOException e) {
616                 fail("HTTP request before wifi disconnected failed with: " + e);
617             }
618         }
619 
620         boolean disconnected = false;
621         try {
622             assertTrue(mWifiManager.setWifiEnabled(false));
623             // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
624             lostWifiNetwork = callback.waitForLost();
625             assertNotNull(lostWifiNetwork);
626             disconnected = receiver.waitForState();
627         } catch (InterruptedException ex) {
628             fail("disconnectFromWifi was interrupted");
629         } finally {
630             mCm.unregisterNetworkCallback(callback);
631             mContext.unregisterReceiver(receiver);
632         }
633 
634         assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
635 
636         // Check that the socket is closed when wifi disconnects.
637         if (wifiBoundSocket != null) {
638             try {
639                 testHttpRequest(wifiBoundSocket);
640                 fail("HTTP request should not succeed after wifi disconnects");
641             } catch (IOException expected) {
642                 assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
643             }
644         }
645     }
646 
647     /**
648      * Receiver that captures the last connectivity change's network type and state. Recognizes
649      * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
650      */
651     private class ConnectivityActionReceiver extends BroadcastReceiver {
652 
653         private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
654 
655         private final int mNetworkType;
656         private final NetworkInfo.State mNetState;
657 
ConnectivityActionReceiver(int networkType, NetworkInfo.State netState)658         ConnectivityActionReceiver(int networkType, NetworkInfo.State netState) {
659             mNetworkType = networkType;
660             mNetState = netState;
661         }
662 
onReceive(Context context, Intent intent)663         public void onReceive(Context context, Intent intent) {
664             String action = intent.getAction();
665             NetworkInfo networkInfo = null;
666 
667             // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
668             // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
669             // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
670             if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
671                 networkInfo = intent.getExtras()
672                         .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
673                 assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO", networkInfo);
674             } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
675                 Network network = intent.getExtras()
676                         .getParcelable(ConnectivityManager.EXTRA_NETWORK);
677                 assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
678                 networkInfo = mCm.getNetworkInfo(network);
679                 if (networkInfo == null) {
680                     // When disconnecting, it seems like we get an intent sent with an invalid
681                     // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
682                     // it is invalid. Ignore these.
683                     Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
684                             + "invalid network");
685                     return;
686                 }
687             } else {
688                 fail("ConnectivityActionReceiver received unxpected intent action: " + action);
689             }
690 
691             assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
692             int networkType = networkInfo.getType();
693             State networkState = networkInfo.getState();
694             Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
695             if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
696                 mReceiveLatch.countDown();
697             }
698         }
699 
waitForState()700         public boolean waitForState() throws InterruptedException {
701             return mReceiveLatch.await(30, TimeUnit.SECONDS);
702         }
703     }
704 
705     /**
706      * Callback used in testRegisterNetworkCallback that allows caller to block on
707      * {@code onAvailable}.
708      */
709     private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
710         private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
711         private final CountDownLatch mLostLatch = new CountDownLatch(1);
712         private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
713 
714         public Network currentNetwork;
715         public Network lastLostNetwork;
716 
waitForAvailable()717         public Network waitForAvailable() throws InterruptedException {
718             return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
719         }
720 
waitForLost()721         public Network waitForLost() throws InterruptedException {
722             return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
723         }
724 
waitForUnavailable()725         public boolean waitForUnavailable() throws InterruptedException {
726             return mUnavailableLatch.await(2, TimeUnit.SECONDS);
727         }
728 
729 
730         @Override
onAvailable(Network network)731         public void onAvailable(Network network) {
732             currentNetwork = network;
733             mAvailableLatch.countDown();
734         }
735 
736         @Override
onLost(Network network)737         public void onLost(Network network) {
738             lastLostNetwork = network;
739             if (network.equals(currentNetwork)) {
740                 currentNetwork = null;
741             }
742             mLostLatch.countDown();
743         }
744 
745         @Override
onUnavailable()746         public void onUnavailable() {
747             mUnavailableLatch.countDown();
748         }
749     }
750 
getWifiNetwork()751     private Network getWifiNetwork() {
752         TestNetworkCallback callback = new TestNetworkCallback();
753         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
754         Network network = null;
755         try {
756             network = callback.waitForAvailable();
757         } catch (InterruptedException e) {
758             fail("NetworkCallback wait was interrupted.");
759         } finally {
760             mCm.unregisterNetworkCallback(callback);
761         }
762         assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
763         return network;
764     }
765 
766     /** Verify restricted networks cannot be requested. */
testRestrictedNetworks()767     public void testRestrictedNetworks() {
768         // Verify we can request unrestricted networks:
769         NetworkRequest request = new NetworkRequest.Builder()
770                 .addCapability(NET_CAPABILITY_INTERNET).build();
771         NetworkCallback callback = new NetworkCallback();
772         mCm.requestNetwork(request, callback);
773         mCm.unregisterNetworkCallback(callback);
774         // Verify we cannot request restricted networks:
775         request = new NetworkRequest.Builder().addCapability(NET_CAPABILITY_IMS).build();
776         callback = new NetworkCallback();
777         try {
778             mCm.requestNetwork(request, callback);
779             fail("No exception thrown when restricted network requested.");
780         } catch (SecurityException expected) {}
781     }
782 
makeWifiSysctlScanner(String key)783     private Scanner makeWifiSysctlScanner(String key) throws FileNotFoundException {
784         Network network = ensureWifiConnected();
785         String iface = mCm.getLinkProperties(network).getInterfaceName();
786         String path = IPV6_SYSCTL_DIR + "/" + iface + "/" + key;
787         return new Scanner(new File(path));
788     }
789 
790     /** Verify that accept_ra_rt_info_min_plen exists and is set to the expected value */
testAcceptRaRtInfoMinPlen()791     public void testAcceptRaRtInfoMinPlen() throws Exception {
792         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
793             Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
794             return;
795         }
796         Scanner s = makeWifiSysctlScanner("accept_ra_rt_info_min_plen");
797         assertEquals(IPV6_WIFI_ACCEPT_RA_RT_INFO_MIN_PLEN, s.nextInt());
798     }
799 
800     /** Verify that accept_ra_rt_info_max_plen exists and is set to the expected value */
testAcceptRaRtInfoMaxPlen()801     public void testAcceptRaRtInfoMaxPlen() throws Exception {
802         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
803             Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
804             return;
805         }
806         Scanner s = makeWifiSysctlScanner("accept_ra_rt_info_max_plen");
807         assertEquals(IPV6_WIFI_ACCEPT_RA_RT_INFO_MAX_PLEN, s.nextInt());
808     }
809 
810     /** Verify that router_solicitations exists and is set to the expected value */
testRouterSolicitations()811     public void testRouterSolicitations() throws Exception {
812         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
813             Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
814             return;
815         }
816         Scanner s = makeWifiSysctlScanner("router_solicitations");
817         assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, s.nextInt());
818     }
819 
820     /** Verify that router_solicitation_max_interval exists and is in an acceptable interval */
testRouterSolicitationMaxInterval()821     public void testRouterSolicitationMaxInterval() throws Exception {
822         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
823             Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
824             return;
825         }
826         Scanner s = makeWifiSysctlScanner("router_solicitation_max_interval");
827         int interval = s.nextInt();
828         // Verify we're in the interval [15 minutes, 60 minutes]. Lower values may adversely
829         // impact battery life and higher values can decrease the probability of detecting
830         // network changes.
831         final int lowerBoundSec = 15 * 60;
832         final int upperBoundSec = 60 * 60;
833         assertTrue(lowerBoundSec <= interval);
834         assertTrue(interval <= upperBoundSec);
835     }
836 }
837