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