Merge "Add more test coverage to ConnectivityManagerTest." into nyc-dev
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 4112466..231db97 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -38,9 +38,16 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.os.SystemProperties;
+import android.system.Os;
+import android.system.OsConstants;
 
 import com.android.internal.telephony.PhoneConstants;
 
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -57,7 +64,15 @@
 
     public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
     public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
+
     private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
+    private static final String TEST_HOST = "connectivitycheck.gstatic.com";
+    private static final int SOCKET_TIMEOUT_MS = 2000;
+    private static final int HTTP_PORT = 80;
+    private static final String HTTP_REQUEST =
+            "GET /generate_204 HTTP/1.0\r\n" +
+            "Host: " + TEST_HOST + "\r\n" +
+            "Connection: keep-alive\r\n\r\n";
 
     // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
     private static final String NETWORK_CALLBACK_ACTION =
@@ -249,6 +264,12 @@
         mCm.getBackgroundDataSetting();
     }
 
+    private NetworkRequest makeWifiNetworkRequest() {
+        return new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
+    }
+
     /**
      * Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
      * see if we get a callback for the TRANSPORT_WIFI transport type being available.
@@ -265,16 +286,14 @@
         }
 
         // We will register for a WIFI network being available or lost.
-        final NetworkRequest request = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .build();
         final TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(request, callback);
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
 
         final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultTrackingCallback);
 
         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+        Network wifiNetwork = null;
 
         try {
             // Make sure WiFi is connected to an access point to start with.
@@ -285,10 +304,11 @@
             // Now we should expect to get a network callback about availability of the wifi
             // network even if it was already connected as a state-based action when the callback
             // is registered.
-            assertTrue("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
-                    callback.waitForAvailable());
+            wifiNetwork = callback.waitForAvailable();
+            assertNotNull("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
+                    wifiNetwork);
 
-            assertTrue("Did not receive NetworkCallback.onAvailable for any default network",
+            assertNotNull("Did not receive NetworkCallback.onAvailable for any default network",
                     defaultTrackingCallback.waitForAvailable());
         } catch (InterruptedException e) {
             fail("Broadcast receiver or NetworkCallback wait was interrupted.");
@@ -298,7 +318,7 @@
 
             // Return WiFi to its original enabled/disabled state.
             if (!previousWifiEnabledState) {
-                disconnectFromWifi();
+                disconnectFromWifi(wifiNetwork);
             }
         }
     }
@@ -329,10 +349,7 @@
                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
 
         // We will register for a WIFI network being available or lost.
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .build();
-        mCm.registerNetworkCallback(request, pendingIntent);
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
 
         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
 
@@ -356,7 +373,7 @@
 
             // Return WiFi to its original enabled/disabled state.
             if (!previousWifiEnabledState) {
-                disconnectFromWifi();
+                disconnectFromWifi(null);
             }
         }
     }
@@ -370,8 +387,10 @@
 
         // We will toggle the state of wifi to generate a connectivity change.
         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
+
         if (previousWifiEnabledState) {
-            disconnectFromWifi();
+            Network wifiNetwork = getWifiNetwork();
+            disconnectFromWifi(wifiNetwork);
         } else {
             connectToWifi();
         }
@@ -387,12 +406,16 @@
         if (previousWifiEnabledState) {
             connectToWifi();
         } else {
-            disconnectFromWifi();
+            disconnectFromWifi(null);
         }
     }
 
     /** Enable WiFi and wait for it to become connected to a network. */
-    private void connectToWifi() {
+    private Network connectToWifi() {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+        Network wifiNetwork = null;
+
         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
                 ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
         IntentFilter filter = new IntentFilter();
@@ -402,36 +425,94 @@
         boolean connected = false;
         try {
             assertTrue(mWifiManager.setWifiEnabled(true));
+            // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
+            wifiNetwork = callback.waitForAvailable();
+            assertNotNull(wifiNetwork);
             connected = receiver.waitForState();
         } catch (InterruptedException ex) {
             fail("connectToWifi was interrupted");
         } finally {
+            mCm.unregisterNetworkCallback(callback);
             mContext.unregisterReceiver(receiver);
         }
 
         assertTrue("Wifi must be configured to connect to an access point for this test.",
                 connected);
+        return wifiNetwork;
+    }
+
+    private Socket getBoundSocket(Network network, String host, int port) throws IOException {
+        InetSocketAddress addr = new InetSocketAddress(host, port);
+        Socket s = network.getSocketFactory().createSocket();
+        try {
+            s.setSoTimeout(SOCKET_TIMEOUT_MS);
+            s.connect(addr, SOCKET_TIMEOUT_MS);
+        } catch (IOException e) {
+            s.close();
+            throw e;
+        }
+        return s;
+    }
+
+    private void testHttpRequest(Socket s) throws IOException {
+        OutputStream out = s.getOutputStream();
+        InputStream in = s.getInputStream();
+
+        final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+        byte[] responseBytes = new byte[4096];
+        out.write(requestBytes);
+        in.read(responseBytes);
+        assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
     }
 
     /** Disable WiFi and wait for it to become disconnected from the network. */
-    private void disconnectFromWifi() {
+    private void disconnectFromWifi(Network wifiNetworkToCheck) {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+        Network lostWifiNetwork = null;
+
         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
                 ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
         IntentFilter filter = new IntentFilter();
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiver(receiver, filter);
 
+        // Assert that we can establish a TCP connection on wifi.
+        Socket wifiBoundSocket = null;
+        if (wifiNetworkToCheck != null) {
+            try {
+                wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
+                testHttpRequest(wifiBoundSocket);
+            } catch (IOException e) {
+                fail("HTTP request before wifi disconnected failed with: " + e);
+            }
+        }
+
         boolean disconnected = false;
         try {
             assertTrue(mWifiManager.setWifiEnabled(false));
+            // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
+            lostWifiNetwork = callback.waitForLost();
+            assertNotNull(lostWifiNetwork);
             disconnected = receiver.waitForState();
         } catch (InterruptedException ex) {
             fail("disconnectFromWifi was interrupted");
         } finally {
+            mCm.unregisterNetworkCallback(callback);
             mContext.unregisterReceiver(receiver);
         }
 
         assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
+
+        // Check that the socket is closed when wifi disconnects.
+        if (wifiBoundSocket != null) {
+            try {
+                testHttpRequest(wifiBoundSocket);
+                fail("HTTP request should not succeed after wifi disconnects");
+            } catch (IOException expected) {
+                assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
+            }
+        }
     }
 
     /**
@@ -498,15 +579,48 @@
      */
     private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
         private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
+        private final CountDownLatch mLostLatch = new CountDownLatch(1);
 
-        public boolean waitForAvailable() throws InterruptedException {
-            return mAvailableLatch.await(30, TimeUnit.SECONDS);
+        public Network currentNetwork;
+        public Network lastLostNetwork;
+
+        public Network waitForAvailable() throws InterruptedException {
+            return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
+        }
+
+        public Network waitForLost() throws InterruptedException {
+            return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
         }
 
         @Override
         public void onAvailable(Network network) {
+            currentNetwork = network;
             mAvailableLatch.countDown();
         }
+
+        @Override
+        public void onLost(Network network) {
+            lastLostNetwork = network;
+            if (network.equals(currentNetwork)) {
+                currentNetwork = null;
+            }
+            mLostLatch.countDown();
+        }
+    }
+
+    private Network getWifiNetwork() {
+        TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+        Network network = null;
+        try {
+            network = callback.waitForAvailable();
+        } catch (InterruptedException e) {
+            fail("NetworkCallback wait was interrupted.");
+        } finally {
+            mCm.unregisterNetworkCallback(callback);
+        }
+        assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
+        return network;
     }
 
     /** Verify restricted networks cannot be requested. */
@@ -523,8 +637,6 @@
         try {
             mCm.requestNetwork(request, callback);
             fail("No exception thrown when restricted network requested.");
-        } catch (SecurityException e) {
-            // Expected.
-        }
+        } catch (SecurityException expected) {}
     }
 }