Merge "cts: skip wifi related tests if wifi is unsupported" into oc-dev
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 9945805..ad7f81b 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS net host test cases">
+    <option name="config-descriptor:metadata" key="component" value="networking" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsHostsideNetworkTests.jar" />
         <option name="runtime-hint" value="3m56s" />
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
index 09f3120..165f530 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
@@ -16,6 +16,7 @@
 
 package com.android.cts.net.hostside;
 
-oneway interface INetworkStateObserver {
+interface INetworkStateObserver {
+    boolean isForeground();
     void onNetworkStateChecked(String resultData);
 }
\ No newline at end of file
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index f994169..12b338f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -31,17 +31,20 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
 import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.cts.net.hostside.INetworkStateObserver;
@@ -90,11 +93,19 @@
     protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
     protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
 
+    private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
+    private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
+
     private static final int FOREGROUND_PROC_NETWORK_TIMEOUT_MS = 6000;
 
     // Must be higher than NETWORK_TIMEOUT_MS
     private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
 
+    private static final IntentFilter BATTERY_CHANGED_FILTER =
+            new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+
+    private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
+
     protected Context mContext;
     protected Instrumentation mInstrumentation;
     protected ConnectivityManager mCm;
@@ -298,6 +309,7 @@
             }
             Log.d(TAG, "App not on foreground state on attempt #" + i
                     + "; sleeping 1s before trying again");
+            turnScreenOn();
             SystemClock.sleep(SECOND_IN_MS);
         }
         fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
@@ -722,10 +734,29 @@
 
     protected void turnBatteryOff() throws Exception {
         executeSilentShellCommand("cmd battery unplug");
+        assertBatteryState(false);
     }
 
     protected void turnBatteryOn() throws Exception {
         executeSilentShellCommand("cmd battery reset");
+        assertBatteryState(true);
+
+    }
+
+    private void assertBatteryState(boolean pluggedIn) throws Exception {
+        final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
+        while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
+            Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
+        }
+        if (isDevicePluggedIn() != pluggedIn) {
+            fail("Timed out waiting for the plugged-in state to change,"
+                    + " expected pluggedIn: " + pluggedIn);
+        }
+    }
+
+    private boolean isDevicePluggedIn() {
+        final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
+        return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
     }
 
     protected void turnScreenOff() throws Exception {
@@ -777,12 +808,40 @@
 
     protected void setAppIdle(boolean enabled) throws Exception {
         Log.i(TAG, "Setting app idle to " + enabled);
+        final String beforeStats = getUsageStatsDump();
         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
-        assertAppIdle(enabled); // Sanity check
+        try {
+            assertAppIdle(enabled); // Sanity check
+        } catch (Throwable e) {
+            final String afterStats = getUsageStatsDump();
+            Log.d(TAG, "UsageStats before:\n" + beforeStats);
+            Log.d(TAG, "UsageStats after:\n" + afterStats);
+            throw e;
+        }
+    }
+
+    private String getUsageStatsDump() throws Exception {
+        final String output = runShellCommand(mInstrumentation, "dumpsys usagestats").trim();
+        final StringBuilder sb = new StringBuilder();
+        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
+        splitter.setString(output);
+        String str;
+        while (splitter.hasNext()) {
+            str = splitter.next();
+            if (str.contains("package=")
+                    && !str.contains(TEST_PKG) && !str.contains(TEST_APP2_PKG)) {
+                continue;
+            }
+            if (str.contains("config=")) {
+                continue;
+            }
+            sb.append(str).append('\n');
+        }
+        return sb.toString();
     }
 
     protected void assertAppIdle(boolean enabled) throws Exception {
-        assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 10, 2, "Idle=" + enabled);
+        assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
     }
 
     /**
@@ -795,11 +854,12 @@
     protected void registerBroadcastReceiver() throws Exception {
         mServiceClient.registerBroadcastReceiver();
 
+        final Intent intent = new Intent(ACTION_RECEIVER_READY)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         // Wait until receiver is ready.
         final int maxTries = 10;
         for (int i = 1; i <= maxTries; i++) {
-            final String message =
-                    sendOrderedBroadcast(new Intent(ACTION_RECEIVER_READY), SECOND_IN_MS);
+            final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
             Log.d(TAG, "app2 receiver acked: " + message);
             if (message != null) {
                 return;
@@ -848,6 +908,8 @@
             return;
         } else if (type == TYPE_COMPONENT_ACTIVTIY) {
             turnScreenOn();
+            // Wait for screen-on state to propagate through the system.
+            SystemClock.sleep(2000);
             final CountDownLatch latch = new CountDownLatch(1);
             final Intent launchIntent = getIntentForComponent(type);
             final Bundle extras = new Bundle();
@@ -857,7 +919,12 @@
             mContext.startActivity(launchIntent);
             if (latch.await(FOREGROUND_PROC_NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                 if (!errors[0].isEmpty()) {
-                    fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
+                    if (errors[0] == APP_NOT_FOREGROUND_ERROR) {
+                        // App didn't come to foreground when the activity is started, so try again.
+                        assertForegroundNetworkAccess();
+                    } else {
+                        fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
+                    }
                 }
             } else {
                 fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
@@ -896,8 +963,21 @@
             final String[] errors) {
         return new INetworkStateObserver.Stub() {
             @Override
+            public boolean isForeground() {
+                try {
+                    final ProcessState state = getProcessStateByUid(mUid);
+                    return !isBackground(state.state);
+                } catch (Exception e) {
+                    Log.d(TAG, "Error while reading the proc state for " + mUid + ": " + e);
+                    return false;
+                }
+            }
+
+            @Override
             public void onNetworkStateChecked(String resultData) {
-                errors[0] = checkForAvailabilityInResultData(resultData, true);
+                errors[0] = resultData == null
+                        ? APP_NOT_FOREGROUND_ERROR
+                        : checkForAvailabilityInResultData(resultData, true);
                 latch.countDown();
             }
         };
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
index 20bbd5a..351733e 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -72,12 +72,21 @@
         final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
                 extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
         if (observer != null) {
+            try {
+                if (!observer.isForeground()) {
+                    Log.e(TAG, "App didn't come to foreground");
+                    observer.onNetworkStateChecked(null);
+                    return;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error occurred while reading the proc state: " + e);
+            }
             AsyncTask.execute(() -> {
                 try {
                     observer.onNetworkStateChecked(
                             MyBroadcastReceiver.checkNetworkStatus(context));
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Error occured while notifying the observer: " + e);
+                    Log.e(TAG, "Error occurred while notifying the observer: " + e);
                 }
             });
         }
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index da7e704..286cc2f 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -41,6 +41,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        Log.d(TAG, "MyActivity.onCreate()");
         Common.notifyNetworkStateObserver(this, getIntent());
         finishCommandReceiver = new BroadcastReceiver() {
             @Override
diff --git a/tests/cts/net/AndroidTest.xml b/tests/cts/net/AndroidTest.xml
index 389b926..4a578ea 100644
--- a/tests/cts/net/AndroidTest.xml
+++ b/tests/cts/net/AndroidTest.xml
@@ -13,7 +13,8 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Net test cases">
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsNetTestCases.apk" />
         <option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
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 e065378..ea63f78 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);
             }
         }
     }
@@ -373,6 +385,7 @@
 
             wifiConfiguration = new WifiConfiguration();
             wifiConfiguration.SSID = SSID1;
+            wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
             int netId = mWifiManager.addNetwork(wifiConfiguration);
             assertTrue(existSSID(SSID1));
 
@@ -517,6 +530,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();
 
@@ -698,4 +718,163 @@
             // 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() {
+        // first check that softap mode is supported by the device
+        if (!mWifiManager.isPortableHotspotSupported()) {
+            return;
+        }
+
+        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() {
+        // first check that softap mode is supported by the device
+        if (!mWifiManager.isPortableHotspotSupported()) {
+            return;
+        }
+
+        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() {
+        // first check that softap mode is supported by the device
+        if (!mWifiManager.isPortableHotspotSupported()) {
+            return;
+        }
+
+        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());
+    }
 }