Merge "Move automotive requirement to RequiredFeatureRule" into rvc-dev
diff --git a/hostsidetests/adb/OWNERS b/hostsidetests/adb/OWNERS
index 7529cb9..c8d74a9 100644
--- a/hostsidetests/adb/OWNERS
+++ b/hostsidetests/adb/OWNERS
@@ -1 +1,2 @@
+# Bug component: 1352
 include platform/system/core:/janitors/OWNERS
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 8d827c5..feb8947 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -97,8 +97,6 @@
 import android.util.Log;
 import android.util.Size;
 
-import androidx.test.filters.FlakyTest;
-
 import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -838,7 +836,6 @@
         });
     }
 
-    @FlakyTest(bugId = 142282126)
     @Test
     public void testConfigurationChangeOrderDuringTransition() throws Exception {
         // Launch a PiP activity and ensure configuration change only happened once, and that the
@@ -1042,7 +1039,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 139111392)
     public void testPinnedStackWithDockedStack() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index f7b7e54..94db5bc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -301,8 +301,8 @@
         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
                 ? Arrays.asList(
                 ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
-                ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
-                ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE)
+                ON_CREATE, ON_MULTI_WINDOW_MODE_CHANGED, ON_START, ON_POST_CREATE, ON_RESUME,
+                ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE)
                 : Arrays.asList(
                         ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
                 ON_RESUME, ON_PAUSE);
diff --git a/tests/tests/net/Android.bp b/tests/tests/net/Android.bp
index 76bb27e..46fae33 100644
--- a/tests/tests/net/Android.bp
+++ b/tests/tests/net/Android.bp
@@ -47,6 +47,7 @@
         "mockwebserver",
         "junit",
         "junit-params",
+        "libnanohttpd",
         "truth-prebuilt",
     ],
 
diff --git a/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt b/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt
new file mode 100644
index 0000000..4418e17
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.Uri
+import android.net.cts.util.CtsNetUtils
+import android.net.wifi.WifiManager
+import android.os.ConditionVariable
+import android.platform.test.annotations.AppModeFull
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.text.TextUtils
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil
+import fi.iki.elonen.NanoHTTPD
+import fi.iki.elonen.NanoHTTPD.Response.IStatus
+import fi.iki.elonen.NanoHTTPD.Response.Status
+import junit.framework.AssertionFailedError
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import kotlin.test.Test
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+private const val TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING = "test_captive_portal_https_url"
+private const val TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING = "test_captive_portal_http_url"
+private const val TEST_URL_EXPIRATION_TIME = "test_url_expiration_time"
+
+private const val TEST_HTTPS_URL_PATH = "https_path"
+private const val TEST_HTTP_URL_PATH = "http_path"
+private const val TEST_PORTAL_URL_PATH = "portal_path"
+
+private const val LOCALHOST_HOSTNAME = "localhost"
+
+// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
+private const val WIFI_CONNECT_TIMEOUT_MS = 120_000L
+private const val TEST_TIMEOUT_MS = 10_000L
+
+private fun <T> CompletableFuture<T>.assertGet(timeoutMs: Long, message: String): T {
+    try {
+        return get(timeoutMs, TimeUnit.MILLISECONDS)
+    } catch (e: TimeoutException) {
+        throw AssertionFailedError(message)
+    }
+}
+
+@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+@RunWith(AndroidJUnit4::class)
+class CaptivePortalTest {
+    private val context: android.content.Context by lazy { getInstrumentation().context }
+    private val wm by lazy { context.getSystemService(WifiManager::class.java) }
+    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+    private val pm by lazy { context.packageManager }
+    private val utils by lazy { CtsNetUtils(context) }
+
+    private val server = HttpServer()
+
+    @Before
+    fun setUp() {
+        doAsShell(READ_DEVICE_CONFIG) {
+            // Verify that the test URLs are not normally set on the device, but do not fail if the
+            // test URLs are set to what this test uses (URLs on localhost), in case the test was
+            // interrupted manually and rerun.
+            assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING)
+            assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING)
+        }
+        clearTestUrls()
+        server.start()
+    }
+
+    @After
+    fun tearDown() {
+        clearTestUrls()
+        server.stop()
+    }
+
+    private fun assertEmptyOrLocalhostUrl(urlKey: String) {
+        val url = DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, urlKey)
+        assertTrue(TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME == Uri.parse(url).host,
+                "$urlKey must not be set in production scenarios (current value: $url)")
+    }
+
+    private fun clearTestUrls() {
+        setHttpsUrl(null)
+        setHttpUrl(null)
+        setUrlExpiration(null)
+    }
+
+    @Test
+    fun testCaptivePortalIsNotDefaultNetwork() {
+        assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
+        assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
+        utils.connectToWifi()
+        utils.connectToCell()
+
+        // Have network validation use a local server that serves a HTTPS error / HTTP redirect
+        server.addResponse(TEST_PORTAL_URL_PATH, Status.OK,
+                content = "Test captive portal content")
+        server.addResponse(TEST_HTTPS_URL_PATH, Status.INTERNAL_ERROR)
+        server.addResponse(TEST_HTTP_URL_PATH, Status.REDIRECT,
+                locationHeader = server.makeUrl(TEST_PORTAL_URL_PATH))
+        setHttpsUrl(server.makeUrl(TEST_HTTPS_URL_PATH))
+        setHttpUrl(server.makeUrl(TEST_HTTP_URL_PATH))
+        // URL expiration needs to be in the next 10 minutes
+        setUrlExpiration(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(9))
+
+        // Expect the portal content to be fetched at some point after detecting the portal.
+        // Some implementations may fetch the URL before startCaptivePortalApp is called.
+        val portalContentRequestCv = server.addExpectRequestCv(TEST_PORTAL_URL_PATH)
+
+        // Wait for a captive portal to be detected on the network
+        val wifiNetworkFuture = CompletableFuture<Network>()
+        val wifiCb = object : NetworkCallback() {
+            override fun onCapabilitiesChanged(
+                network: Network,
+                nc: NetworkCapabilities
+            ) {
+                if (nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
+                    wifiNetworkFuture.complete(network)
+                }
+            }
+        }
+        cm.requestNetwork(NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(), wifiCb)
+
+        try {
+            reconnectWifi()
+            val network = wifiNetworkFuture.assertGet(WIFI_CONNECT_TIMEOUT_MS,
+                    "Captive portal not detected after ${WIFI_CONNECT_TIMEOUT_MS}ms")
+
+            val wifiDefaultMessage = "Wifi should not be the default network when a captive " +
+                    "portal was detected and another network (mobile data) can provide internet " +
+                    "access."
+            assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
+
+            doAsShell(NETWORK_SETTINGS) { cm.startCaptivePortalApp(network) }
+            assertTrue(portalContentRequestCv.block(TEST_TIMEOUT_MS), "The captive portal login " +
+                    "page was still not fetched ${TEST_TIMEOUT_MS}ms after startCaptivePortalApp.")
+
+            assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
+        } finally {
+            cm.unregisterNetworkCallback(wifiCb)
+            server.stop()
+            // disconnectFromCell should be called after connectToCell
+            utils.disconnectFromCell()
+        }
+
+        clearTestUrls()
+        reconnectWifi()
+    }
+
+    private fun setHttpsUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING, url)
+    private fun setHttpUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTP_URL_SETTING, url)
+    private fun setUrlExpiration(timestamp: Long?) = setConfig(TEST_URL_EXPIRATION_TIME,
+            timestamp?.toString())
+
+    private fun setConfig(configKey: String, value: String?) {
+        doAsShell(WRITE_DEVICE_CONFIG) {
+            DeviceConfig.setProperty(
+                    NAMESPACE_CONNECTIVITY, configKey, value, false /* makeDefault */)
+        }
+    }
+
+    private fun doAsShell(vararg permissions: String, action: () -> Unit) {
+        // Wrap the below call to allow for more kotlin-like syntax
+        SystemUtil.runWithShellPermissionIdentity(action, permissions)
+    }
+
+    private fun reconnectWifi() {
+        doAsShell(NETWORK_SETTINGS) {
+            assertTrue(wm.disconnect())
+            assertTrue(wm.reconnect())
+        }
+    }
+
+    /**
+     * A minimal HTTP server running on localhost (loopback), on a random available port.
+     */
+    private class HttpServer : NanoHTTPD("localhost", 0 /* auto-select the port */) {
+        // Map of URL path -> HTTP response code
+        private val responses = HashMap<String, Response>()
+
+        // Map of path -> CV to open as soon as a request to the path is received
+        private val waitForRequestCv = HashMap<String, ConditionVariable>()
+
+        /**
+         * Create a URL string that, when fetched, will hit this server with the given URL [path].
+         */
+        fun makeUrl(path: String): String {
+            return Uri.Builder()
+                    .scheme("http")
+                    .encodedAuthority("localhost:$listeningPort")
+                    .query(path)
+                    .build()
+                    .toString()
+        }
+
+        fun addResponse(
+            path: String,
+            statusCode: IStatus,
+            locationHeader: String? = null,
+            content: String = ""
+        ) {
+            val response = newFixedLengthResponse(statusCode, "text/plain", content)
+            locationHeader?.let { response.addHeader("Location", it) }
+            responses[path] = response
+        }
+
+        /**
+         * Create a [ConditionVariable] that will open when a request to [path] is received.
+         */
+        fun addExpectRequestCv(path: String): ConditionVariable {
+            return ConditionVariable().apply { waitForRequestCv[path] = this }
+        }
+
+        override fun serve(session: IHTTPSession): Response {
+            waitForRequestCv[session.queryParameterString]?.open()
+            return responses[session.queryParameterString]
+                    // Default response is a 404
+                    ?: super.serve(session)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index 19465b8..a2b8189 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -545,7 +545,7 @@
     @Test
     public void testOnNotificationVisibilityChanged() throws Exception {
         setUpListeners();
-
+        turnScreenOn();
         mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
 
         mNotificationAssistantService.resetNotificationVisibilityCounts();
@@ -573,7 +573,7 @@
     @Test
     public void testOnNotificationsSeen() throws Exception {
         setUpListeners();
-
+        turnScreenOn();
         mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
 
         mNotificationAssistantService.resetNotificationVisibilityCounts();
@@ -595,7 +595,7 @@
     @Test
     public void testOnPanelRevealedAndHidden() throws Exception {
         setUpListeners();
-
+        turnScreenOn();
         mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
 
         // Initialize as closed
@@ -669,6 +669,12 @@
         mNotificationManager.notify(id, notification);
     }
 
+   private void turnScreenOn() throws IOException {
+       Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+       runCommand("input keyevent KEYCODE_WAKEUP", instrumentation);
+       runCommand("wm dismiss-keyguard", instrumentation);
+   }
+
     private void toggleListenerAccess(boolean on) throws IOException {
 
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index 6e4bb41..c268c14 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -57,6 +57,9 @@
     // Valid Mac Address
     private static final MacAddress MAC = MacAddress.fromString("00:01:02:03:04:05");
 
+    // Interval between two ranging request.
+    private static final int intervalMs = 200;
+
     /**
      * Test Wi-Fi RTT ranging operation:
      * - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
@@ -145,6 +148,8 @@
             } else {
                 numFailures++;
             }
+            // Sleep a while to avoid stress AP.
+            Thread.sleep(intervalMs);
         }
 
         // Save results to log