• 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.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
20 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.net.ConnectivityManager;
32 import android.net.ConnectivityManager.NetworkCallback;
33 import android.net.Network;
34 import android.net.NetworkCapabilities;
35 import android.net.NetworkInfo;
36 import android.net.NetworkInfo.State;
37 import android.net.NetworkRequest;
38 import android.net.wifi.WifiManager;
39 import android.system.Os;
40 import android.system.OsConstants;
41 import android.util.Log;
42 
43 import com.android.compatibility.common.util.SystemUtil;
44 
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.OutputStream;
48 import java.net.InetSocketAddress;
49 import java.net.Socket;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.TimeUnit;
52 
53 public final class CtsNetUtils {
54     private static final String TAG = CtsNetUtils.class.getSimpleName();
55     private static final int DURATION = 10000;
56     private static final int SOCKET_TIMEOUT_MS = 2000;
57 
58     public static final int HTTP_PORT = 80;
59     public static final String TEST_HOST = "connectivitycheck.gstatic.com";
60     public static final String HTTP_REQUEST =
61             "GET /generate_204 HTTP/1.0\r\n" +
62                     "Host: " + TEST_HOST + "\r\n" +
63                     "Connection: keep-alive\r\n\r\n";
64     // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
65     public static final String NETWORK_CALLBACK_ACTION =
66             "ConnectivityManagerTest.NetworkCallbackAction";
67 
68     private Context mContext;
69     private ConnectivityManager mCm;
70     private WifiManager mWifiManager;
71     private TestNetworkCallback mCellNetworkCallback;
72 
CtsNetUtils(Context context)73     public CtsNetUtils(Context context) {
74         mContext = context;
75         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
76         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
77     }
78 
79     // Toggle WiFi twice, leaving it in the state it started in
toggleWifi()80     public void toggleWifi() {
81         if (mWifiManager.isWifiEnabled()) {
82             Network wifiNetwork = getWifiNetwork();
83             disconnectFromWifi(wifiNetwork);
84             connectToWifi();
85         } else {
86             connectToWifi();
87             Network wifiNetwork = getWifiNetwork();
88             disconnectFromWifi(wifiNetwork);
89         }
90     }
91 
92     /** Enable WiFi and wait for it to become connected to a network. */
connectToWifi()93     public Network connectToWifi() {
94         final TestNetworkCallback callback = new TestNetworkCallback();
95         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
96         Network wifiNetwork = null;
97 
98         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
99                 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
100         IntentFilter filter = new IntentFilter();
101         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
102         mContext.registerReceiver(receiver, filter);
103 
104         boolean connected = false;
105         try {
106             SystemUtil.runShellCommand("svc wifi enable");
107             // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
108             wifiNetwork = callback.waitForAvailable();
109             assertNotNull(wifiNetwork);
110             connected = receiver.waitForState();
111         } catch (InterruptedException ex) {
112             fail("connectToWifi was interrupted");
113         } finally {
114             mCm.unregisterNetworkCallback(callback);
115             mContext.unregisterReceiver(receiver);
116         }
117 
118         assertTrue("Wifi must be configured to connect to an access point for this test.",
119                 connected);
120         return wifiNetwork;
121     }
122 
123     /** Disable WiFi and wait for it to become disconnected from the network. */
disconnectFromWifi(Network wifiNetworkToCheck)124     public void disconnectFromWifi(Network wifiNetworkToCheck) {
125         final TestNetworkCallback callback = new TestNetworkCallback();
126         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
127         Network lostWifiNetwork = null;
128 
129         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
130                 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
131         IntentFilter filter = new IntentFilter();
132         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
133         mContext.registerReceiver(receiver, filter);
134 
135         // Assert that we can establish a TCP connection on wifi.
136         Socket wifiBoundSocket = null;
137         if (wifiNetworkToCheck != null) {
138             try {
139                 wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
140                 testHttpRequest(wifiBoundSocket);
141             } catch (IOException e) {
142                 fail("HTTP request before wifi disconnected failed with: " + e);
143             }
144         }
145 
146         boolean disconnected = false;
147         try {
148             SystemUtil.runShellCommand("svc wifi disable");
149             // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
150             lostWifiNetwork = callback.waitForLost();
151             assertNotNull(lostWifiNetwork);
152             disconnected = receiver.waitForState();
153         } catch (InterruptedException ex) {
154             fail("disconnectFromWifi was interrupted");
155         } finally {
156             mCm.unregisterNetworkCallback(callback);
157             mContext.unregisterReceiver(receiver);
158         }
159 
160         assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
161 
162         // Check that the socket is closed when wifi disconnects.
163         if (wifiBoundSocket != null) {
164             try {
165                 testHttpRequest(wifiBoundSocket);
166                 fail("HTTP request should not succeed after wifi disconnects");
167             } catch (IOException expected) {
168                 assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
169             }
170         }
171     }
172 
getWifiNetwork()173     public Network getWifiNetwork() {
174         TestNetworkCallback callback = new TestNetworkCallback();
175         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
176         Network network = null;
177         try {
178             network = callback.waitForAvailable();
179         } catch (InterruptedException e) {
180             fail("NetworkCallback wait was interrupted.");
181         } finally {
182             mCm.unregisterNetworkCallback(callback);
183         }
184         assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
185         return network;
186     }
187 
connectToCell()188     public Network connectToCell() throws InterruptedException {
189         if (cellConnectAttempted()) {
190             throw new IllegalStateException("Already connected");
191         }
192         NetworkRequest cellRequest = new NetworkRequest.Builder()
193                 .addTransportType(TRANSPORT_CELLULAR)
194                 .addCapability(NET_CAPABILITY_INTERNET)
195                 .build();
196         mCellNetworkCallback = new TestNetworkCallback();
197         mCm.requestNetwork(cellRequest, mCellNetworkCallback);
198         final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
199         assertNotNull("Cell network not available. " +
200                 "Please ensure the device has working mobile data.", cellNetwork);
201         return cellNetwork;
202     }
203 
disconnectFromCell()204     public void disconnectFromCell() {
205         if (!cellConnectAttempted()) {
206             throw new IllegalStateException("Cell connection not attempted");
207         }
208         mCm.unregisterNetworkCallback(mCellNetworkCallback);
209         mCellNetworkCallback = null;
210     }
211 
cellConnectAttempted()212     public boolean cellConnectAttempted() {
213         return mCellNetworkCallback != null;
214     }
215 
makeWifiNetworkRequest()216     private NetworkRequest makeWifiNetworkRequest() {
217         return new NetworkRequest.Builder()
218                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
219                 .build();
220     }
221 
testHttpRequest(Socket s)222     private void testHttpRequest(Socket s) throws IOException {
223         OutputStream out = s.getOutputStream();
224         InputStream in = s.getInputStream();
225 
226         final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
227         byte[] responseBytes = new byte[4096];
228         out.write(requestBytes);
229         in.read(responseBytes);
230         assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
231     }
232 
getBoundSocket(Network network, String host, int port)233     private Socket getBoundSocket(Network network, String host, int port) throws IOException {
234         InetSocketAddress addr = new InetSocketAddress(host, port);
235         Socket s = network.getSocketFactory().createSocket();
236         try {
237             s.setSoTimeout(SOCKET_TIMEOUT_MS);
238             s.connect(addr, SOCKET_TIMEOUT_MS);
239         } catch (IOException e) {
240             s.close();
241             throw e;
242         }
243         return s;
244     }
245 
246     /**
247      * Receiver that captures the last connectivity change's network type and state. Recognizes
248      * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
249      */
250     public static class ConnectivityActionReceiver extends BroadcastReceiver {
251 
252         private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
253 
254         private final int mNetworkType;
255         private final NetworkInfo.State mNetState;
256         private final ConnectivityManager mCm;
257 
ConnectivityActionReceiver(ConnectivityManager cm, int networkType, NetworkInfo.State netState)258         public ConnectivityActionReceiver(ConnectivityManager cm, int networkType,
259                 NetworkInfo.State netState) {
260             this.mCm = cm;
261             mNetworkType = networkType;
262             mNetState = netState;
263         }
264 
onReceive(Context context, Intent intent)265         public void onReceive(Context context, Intent intent) {
266             String action = intent.getAction();
267             NetworkInfo networkInfo = null;
268 
269             // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
270             // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
271             // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
272             if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
273                 networkInfo = intent.getExtras()
274                         .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
275                 assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO",
276                         networkInfo);
277             } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
278                 Network network = intent.getExtras()
279                         .getParcelable(ConnectivityManager.EXTRA_NETWORK);
280                 assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
281                 networkInfo = this.mCm.getNetworkInfo(network);
282                 if (networkInfo == null) {
283                     // When disconnecting, it seems like we get an intent sent with an invalid
284                     // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
285                     // it is invalid. Ignore these.
286                     Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
287                             + "invalid network");
288                     return;
289                 }
290             } else {
291                 fail("ConnectivityActionReceiver received unxpected intent action: " + action);
292             }
293 
294             assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
295             int networkType = networkInfo.getType();
296             State networkState = networkInfo.getState();
297             Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
298             if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
299                 mReceiveLatch.countDown();
300             }
301         }
302 
waitForState()303         public boolean waitForState() throws InterruptedException {
304             return mReceiveLatch.await(30, TimeUnit.SECONDS);
305         }
306     }
307 
308     /**
309      * Callback used in testRegisterNetworkCallback that allows caller to block on
310      * {@code onAvailable}.
311      */
312     public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
313         private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
314         private final CountDownLatch mLostLatch = new CountDownLatch(1);
315         private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
316 
317         public Network currentNetwork;
318         public Network lastLostNetwork;
319 
waitForAvailable()320         public Network waitForAvailable() throws InterruptedException {
321             return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
322         }
323 
waitForLost()324         public Network waitForLost() throws InterruptedException {
325             return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
326         }
327 
waitForUnavailable()328         public boolean waitForUnavailable() throws InterruptedException {
329             return mUnavailableLatch.await(2, TimeUnit.SECONDS);
330         }
331 
332 
333         @Override
onAvailable(Network network)334         public void onAvailable(Network network) {
335             currentNetwork = network;
336             mAvailableLatch.countDown();
337         }
338 
339         @Override
onLost(Network network)340         public void onLost(Network network) {
341             lastLostNetwork = network;
342             if (network.equals(currentNetwork)) {
343                 currentNetwork = null;
344             }
345             mLostLatch.countDown();
346         }
347 
348         @Override
onUnavailable()349         public void onUnavailable() {
350             mUnavailableLatch.countDown();
351         }
352     }
353 }
354