WifiManagerTest: add LOHS tests

Add new CTS tests for the LocalOnlyHotspot feature.  This CL also
creates a reusable method for confirming that wifi is enabled via the
setWifiEnabled call.  This should reduce flake in automated test runs by
setting local variables before wifi is enabled instead of after the
call.  This change removes a race condition on confirming if wifi is
enabled.

Bug: 31466854
Test: ran new CTS tests
Test: no new failures with 'run cts-dev --module CtsNetTestCases -t ran n
android.net.wifi.cts.WifiManagerTest'

Change-Id: Id119170fea742402dda10d83014d27b8dbc98e92
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
index 33c184a..cbf3e93 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -59,6 +59,7 @@
     private static MySync mMySync;
     private List<ScanResult> mScanResults = null;
     private NetworkInfo mNetworkInfo;
+    private Object mLOHSLock = new Object();
 
     // Please refer to WifiManager
     private static final int MIN_RSSI = -100;
@@ -173,14 +174,25 @@
 
     private void setWifiEnabled(boolean enable) throws Exception {
         synchronized (mMySync) {
-            assertTrue(mWifiManager.setWifiEnabled(enable));
             if (mWifiManager.isWifiEnabled() != enable) {
+                // the new state is different, we expect it to change
                 mMySync.expectedState = STATE_WIFI_CHANGING;
-                long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
-                int expectedState = (enable ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
-                while (System.currentTimeMillis() < timeout
-                        && mMySync.expectedState != expectedState)
-                    mMySync.wait(WAIT_MSEC);
+            } else {
+                mMySync.expectedState = (enable ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
+            }
+            // now trigger the change
+            assertTrue(mWifiManager.setWifiEnabled(enable));
+            waitForExpectedWifiState(enable);
+        }
+    }
+
+    private void waitForExpectedWifiState(boolean enabled) throws InterruptedException {
+        synchronized (mMySync) {
+            long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
+            int expected = (enabled ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
+            while (System.currentTimeMillis() < timeout
+                    && mMySync.expectedState != expected) {
+                mMySync.wait(WAIT_MSEC);
             }
         }
     }
@@ -517,6 +529,13 @@
         }
         assertTrue(mWifiManager.isWifiEnabled());
 
+        // give the test a chance to autoconnect
+        Thread.sleep(DURATION);
+        if (mNetworkInfo.getState() != NetworkInfo.State.CONNECTED) {
+            // this test requires a connectable network be configured
+            fail("This test requires a wifi network connection.");
+        }
+
         // This will generate a distinct stack trace if the initial connection fails.
         connectWifi();
 
@@ -686,4 +705,148 @@
             // Passpoint build config |config_wifi_hotspot2_enabled| is disabled, so noop.
         }
     }
+
+    public class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
+        Object hotspotLock;
+        WifiManager.LocalOnlyHotspotReservation reservation = null;
+        boolean onStartedCalled = false;
+        boolean onStoppedCalled = false;
+        boolean onFailedCalled = false;
+        int failureReason = -1;
+
+        TestLocalOnlyHotspotCallback(Object lock) {
+            hotspotLock = lock;
+        }
+
+        @Override
+        public void onStarted(WifiManager.LocalOnlyHotspotReservation r) {
+            synchronized (hotspotLock) {
+                reservation = r;
+                onStartedCalled = true;
+                hotspotLock.notify();
+            }
+        }
+
+        @Override
+        public void onStopped() {
+            synchronized (hotspotLock) {
+                onStoppedCalled = true;
+                hotspotLock.notify();
+            }
+        }
+
+        @Override
+        public void onFailed(int reason) {
+            synchronized (hotspotLock) {
+                onFailedCalled = true;
+                failureReason = reason;
+                hotspotLock.notify();
+            }
+        }
+    }
+
+    private TestLocalOnlyHotspotCallback startLocalOnlyHotspot() {
+        // Location mode must be enabled for this test
+        if (!isLocationEnabled()) {
+            fail("Please enable location for this test");
+        }
+
+        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLOHSLock);
+        synchronized (mLOHSLock) {
+            try {
+                mWifiManager.startLocalOnlyHotspot(callback, null);
+                // now wait for callback
+                mLOHSLock.wait(DURATION);
+            } catch (InterruptedException e) {
+            }
+            // check if we got the callback
+            assertTrue(callback.onStartedCalled);
+            assertNotNull(callback.reservation.getWifiConfiguration());
+            assertFalse(callback.onFailedCalled);
+            assertFalse(callback.onStoppedCalled);
+        }
+        return callback;
+    }
+
+    private void stopLocalOnlyHotspot(TestLocalOnlyHotspotCallback callback, boolean wifiEnabled) {
+       synchronized (mMySync) {
+           // we are expecting a new state
+           mMySync.expectedState = STATE_WIFI_CHANGING;
+
+           // now shut down LocalOnlyHotspot
+           callback.reservation.close();
+
+           try {
+               waitForExpectedWifiState(wifiEnabled);
+           } catch (InterruptedException e) {}
+        }
+    }
+
+    /**
+     * Verify that calls to startLocalOnlyHotspot succeed with proper permissions.
+     *
+     * Note: Location mode must be enabled for this test.
+     */
+    public void testStartLocalOnlyHotspotSuccess() {
+        boolean wifiEnabled = mWifiManager.isWifiEnabled();
+
+        TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
+
+        // at this point, wifi should be off
+        assertFalse(mWifiManager.isWifiEnabled());
+
+        stopLocalOnlyHotspot(callback, wifiEnabled);
+        assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
+    }
+
+    /**
+     * Verify calls to setWifiEnabled from a non-settings app while softap mode is active do not
+     * exit softap mode.
+     *
+     * This test uses the LocalOnlyHotspot API to enter softap mode.  This should also be true when
+     * tethering is started.
+     * Note: Location mode must be enabled for this test.
+     */
+    public void testSetWifiEnabledByAppDoesNotStopHotspot() {
+        boolean wifiEnabled = mWifiManager.isWifiEnabled();
+
+        TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
+        // at this point, wifi should be off
+        assertFalse(mWifiManager.isWifiEnabled());
+
+        // now we should fail to turn on wifi
+        assertFalse(mWifiManager.setWifiEnabled(true));
+
+        stopLocalOnlyHotspot(callback, wifiEnabled);
+        assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
+    }
+
+    /**
+     * Verify that applications can only have one registered LocalOnlyHotspot request at a time.
+     *
+     * Note: Location mode must be enabled for this test.
+     */
+    public void testStartLocalOnlyHotspotSingleRequestByApps() {
+        boolean caughtException = false;
+
+        boolean wifiEnabled = mWifiManager.isWifiEnabled();
+
+        TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
+
+        // at this point, wifi should be off
+        assertFalse(mWifiManager.isWifiEnabled());
+
+        // now make a second request - this should fail.
+        TestLocalOnlyHotspotCallback callback2 = new TestLocalOnlyHotspotCallback(mLOHSLock);
+        try {
+            mWifiManager.startLocalOnlyHotspot(callback2, null);
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "Caught the IllegalStateException we expected: called startLOHS twice");
+            caughtException = true;
+        }
+        assertTrue(caughtException);
+
+        stopLocalOnlyHotspot(callback, wifiEnabled);
+        assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
+    }
 }