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