diff options
Diffstat (limited to 'tests')
51 files changed, 3824 insertions, 725 deletions
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 217a72b90fd4..7731e098d9f5 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -25,11 +25,17 @@ package { android_test { name: "FlickerTests", - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", platform_apis: true, certificate: "platform", + optimize: { + enabled: false, + }, test_suites: ["device-tests"], libs: ["android.test.runner"], static_libs: [ @@ -46,6 +52,9 @@ android_test { java_library { name: "wm-flicker-common-assertions", platform_apis: true, + optimize: { + enabled: false, + }, srcs: [ "src/**/*Assertions.java", "src/**/*Assertions.kt", @@ -56,20 +65,23 @@ java_library { static_libs: [ "flickerlib", "truth-prebuilt", - "app-helpers-core" + "app-helpers-core", ], } java_library { name: "wm-flicker-common-app-helpers", platform_apis: true, + optimize: { + enabled: false, + }, srcs: [ - "**/helpers/*" + "**/helpers/*", ], static_libs: [ "flickerlib", "flickertestapplib", "truth-prebuilt", - "app-helpers-core" + "app-helpers-core", ], -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index a540dffb3c9c..64cb790d324b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -14,210 +14,157 @@ * limitations under the License. */ +@file:JvmName("CommonAssertions") package com.android.server.wm.flicker -import android.platform.helpers.IAppHelper import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_WINDOW_NAME -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME +import com.android.server.wm.traces.common.FlickerComponentName -val HOME_WINDOW_TITLE = arrayOf("Wallpaper", "Launcher") +val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher", + "com.google.android.apps.nexuslauncher.NexusLauncherActivity") -fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() { - assertWm { - this.showsAboveAppWindow(STATUS_BAR_WINDOW_NAME) - } -} - -fun FlickerTestParameter.navBarWindowIsAlwaysVisible() { - assertWm { - this.showsAboveAppWindow(NAV_BAR_WINDOW_NAME) - } -} - -fun FlickerTestParameter.launcherReplacesAppWindowAsTopWindow(testApp: IAppHelper) { - assertWm { - this.showsAppWindowOnTop(testApp.getPackage()) - .then() - .showsAppWindowOnTop(*HOME_WINDOW_TITLE) - } -} - -fun FlickerTestParameter.launcherWindowBecomesVisible() { - assertWm { - this.hidesBelowAppWindow(*HOME_WINDOW_TITLE) - .then() - .showsBelowAppWindow(*HOME_WINDOW_TITLE) - } -} - -fun FlickerTestParameter.launcherWindowBecomesInvisible() { - assertWm { - this.showsBelowAppWindow(*HOME_WINDOW_TITLE) - .then() - .hidesBelowAppWindow(*HOME_WINDOW_TITLE) - } -} - -fun FlickerTestParameter.appWindowAlwaysVisibleOnTop(packageName: String) { - assertWm { - this.showsAppWindowOnTop(packageName) - } -} - -fun FlickerTestParameter.appWindowBecomesVisible(appName: String) { +/** + * Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in + * all WM trace entries + */ +fun FlickerTestParameter.statusBarWindowIsVisible() { assertWm { - this.hidesAppWindow(appName) - .then() - .showsAppWindow(appName) + this.isAboveAppWindowVisible(FlickerComponentName.STATUS_BAR) } } -fun FlickerTestParameter.appWindowBecomesInVisible(appName: String) { +/** + * Checks that [FlickerComponentName.NAV_BAR] window is visible and above the app windows in + * all WM trace entries + */ +fun FlickerTestParameter.navBarWindowIsVisible() { assertWm { - this.showsAppWindow(appName) - .then() - .hidesAppWindow(appName) + this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) } } +/** + * If [allStates] is true, checks if the stack space of all displays is fully covered + * by any visible layer, during the whole transitions + * + * Otherwise, checks if the stack space of all displays is fully covered + * by any visible layer, at the start and end of the transition + * + * @param allStates if all states should be checked, othersie, just initial and final + */ @JvmOverloads -fun FlickerTestParameter.noUncoveredRegions( - beginRotation: Int, - endRotation: Int = beginRotation, - allStates: Boolean = true -) { - val startingBounds = WindowUtils.getDisplayBounds(beginRotation) - val endingBounds = WindowUtils.getDisplayBounds(endRotation) +fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) { if (allStates) { assertLayers { - if (startingBounds == endingBounds) { - this.coversAtLeast(startingBounds) - } else { - this.coversAtLeast(startingBounds) - .then() - .coversAtLeast(endingBounds) + this.invoke("entireScreenCovered") { entry -> + entry.entry.displays.forEach { display -> + entry.visibleRegion().coversAtLeast(display.layerStackSpace) + } } } } else { assertLayersStart { - this.visibleRegion().coversAtLeast(startingBounds) + this.entry.displays.forEach { display -> + this.visibleRegion().coversAtLeast(display.layerStackSpace) + } } assertLayersEnd { - this.visibleRegion().coversAtLeast(endingBounds) + this.entry.displays.forEach { display -> + this.visibleRegion().coversAtLeast(display.layerStackSpace) + } } } } -@JvmOverloads -fun FlickerTestParameter.navBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { - if (rotatesScreen) { - assertLayers { - this.isVisible(NAV_BAR_LAYER_NAME) - .then() - .isInvisible(NAV_BAR_LAYER_NAME) - .then() - .isVisible(NAV_BAR_LAYER_NAME) - } - } else { - assertLayers { - this.isVisible(NAV_BAR_LAYER_NAME) - } +/** + * Checks that [FlickerComponentName.NAV_BAR] layer is visible at the start and end of the SF + * trace + */ +fun FlickerTestParameter.navBarLayerIsVisible() { + assertLayersStart { + this.isVisible(FlickerComponentName.NAV_BAR) } -} - -@JvmOverloads -fun FlickerTestParameter.statusBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { - if (rotatesScreen) { - assertLayers { - this.isVisible(STATUS_BAR_LAYER_NAME) - .then() - .isInvisible(STATUS_BAR_LAYER_NAME) - .then() - .isVisible(STATUS_BAR_LAYER_NAME) - } - } else { - assertLayers { - this.isVisible(STATUS_BAR_LAYER_NAME) - } + assertLayersEnd { + this.isVisible(FlickerComponentName.NAV_BAR) } } -@JvmOverloads -fun FlickerTestParameter.navBarLayerRotatesAndScales( - beginRotation: Int, - endRotation: Int = beginRotation -) { - val startingPos = WindowUtils.getNavigationBarPosition(beginRotation) - val endingPos = WindowUtils.getNavigationBarPosition(endRotation) - +/** + * Checks that [FlickerComponentName.STATUS_BAR] layer is visible at the start and end of the SF + * trace + */ +fun FlickerTestParameter.statusBarLayerIsVisible() { assertLayersStart { - this.visibleRegion(NAV_BAR_LAYER_NAME).coversExactly(startingPos) + this.isVisible(FlickerComponentName.STATUS_BAR) } assertLayersEnd { - this.visibleRegion(NAV_BAR_LAYER_NAME).coversExactly(endingPos) + this.isVisible(FlickerComponentName.STATUS_BAR) } } -@JvmOverloads -fun FlickerTestParameter.statusBarLayerRotatesScales( - beginRotation: Int, - endRotation: Int = beginRotation -) { - val startingPos = WindowUtils.getStatusBarPosition(beginRotation) - val endingPos = WindowUtils.getStatusBarPosition(endRotation) - +fun FlickerTestParameter.navBarLayerRotatesAndScales() { assertLayersStart { - this.visibleRegion(STATUS_BAR_LAYER_NAME).coversExactly(startingPos) + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.NAV_BAR) + .coversExactly(WindowUtils.getNavigationBarPosition(display)) } assertLayersEnd { - this.visibleRegion(STATUS_BAR_LAYER_NAME).coversExactly(endingPos) + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.NAV_BAR) + .coversExactly(WindowUtils.getNavigationBarPosition(display)) } } -fun FlickerTestParameter.appLayerReplacesLauncher(appName: String) { - assertLayers { - this.isVisible(*HOME_WINDOW_TITLE) - .then() - .isVisible(appName) +fun FlickerTestParameter.statusBarLayerRotatesScales() { + assertLayersStart { + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.STATUS_BAR) + .coversExactly(WindowUtils.getStatusBarPosition(display)) } -} - -fun FlickerTestParameter.launcherLayerReplacesApp(testApp: IAppHelper) { - assertLayers { - this.isVisible(testApp.getPackage()) - .then() - .isInvisible(testApp.getPackage()) - .isVisible(*HOME_WINDOW_TITLE) + assertLayersEnd { + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.STATUS_BAR) + .coversExactly(WindowUtils.getStatusBarPosition(display)) } } -fun FlickerTestParameter.layerBecomesVisible(packageName: String) { +/** + * Asserts that: + * [originalLayer] is visible at the start of the trace + * [originalLayer] becomes invisible during the trace and (in the same entry) [newLayer] + * becomes visible + * [newLayer] remains visible until the end of the trace + * + * @param originalLayer Layer that should be visible at the start + * @param newLayer Layer that should be visible at the end + * @param ignoreSnapshot If the snapshot layer should be ignored during the transition + * (useful mostly for app launch) + */ +fun FlickerTestParameter.replacesLayer( + originalLayer: FlickerComponentName, + newLayer: FlickerComponentName, + ignoreSnapshot: Boolean = false +) { assertLayers { - this.isInvisible(packageName) - .then() - .isVisible(packageName) + val assertion = this.isVisible(originalLayer) + if (ignoreSnapshot) { + assertion.then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + } + assertion.then().isVisible(newLayer) } -} -fun FlickerTestParameter.layerBecomesInvisible(packageName: String) { - assertLayers { - this.isVisible(packageName) - .then() - .isInvisible(packageName) + assertLayersStart { + this.isVisible(originalLayer) + .isInvisible(newLayer) } -} -fun FlickerTestParameter.focusChanges(vararg windows: String) { - assertEventLog { - this.focusChanges(windows) + assertLayersEnd { + this.isInvisible(originalLayer) + .isVisible(newLayer) } } - -fun FlickerTestParameter.focusDoesNotChange() { - assertEventLog { - this.focusDoesNotChange() - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 71184c2e0aa2..9f26c31a6d63 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -1,3 +1,4 @@ + /* * Copyright (C) 2020 The Android Open Source Project * @@ -16,26 +17,53 @@ package com.android.server.wm.flicker.close +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** * Test app closes by pressing back button + * * To run this test: `atest FlickerTests:CloseAppBackButtonTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] and wait animation to complete + * Press back button + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -46,7 +74,18 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio } } + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 6786279ae107..795766fccfbd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -16,26 +16,53 @@ package com.android.server.wm.flicker.close +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test app closes by pressing home button. + * Test app closes by pressing home button + * * To run this test: `atest FlickerTests:CloseAppHomeButtonTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] and wait animation to complete + * Press home button + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -46,7 +73,18 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio } } + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index f7f977d7bd0a..511fc26fdb38 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -18,31 +18,35 @@ package com.android.server.wm.flicker.close import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.launcherReplacesAppWindowAsTopWindow -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.launcherLayerReplacesApp -import com.android.server.wm.flicker.launcherWindowBecomesVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.flicker.replacesLayer import org.junit.Test +/** + * Base test class for transitions that close an app back to the launcher screen + */ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + + /** + * Specification of the test transition to execute + */ protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { setup { eachRun { @@ -57,6 +61,10 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -64,42 +72,60 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that the navigation bar window is visible during the whole transition + */ @Presubmit @Test - open fun navBarWindowIsAlwaysVisible() { - testSpec.navBarWindowIsAlwaysVisible() + open fun navBarWindowIsVisible() { + testSpec.navBarWindowIsVisible() } + /** + * Checks that the status bar window is visible during the whole transition + */ @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() { - testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarWindowIsVisible() { + testSpec.statusBarWindowIsVisible() } - @FlakyTest + /** + * Checks that the navigation bar layer is visible during the whole transition + */ + @Presubmit @Test - open fun navBarLayerIsAlwaysVisible() { - testSpec.navBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated) + open fun navBarLayerIsVisible() { + testSpec.navBarLayerIsVisible() } + /** + * Checks that the status bar layer is visible during the whole transition + */ @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() { - testSpec.statusBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated) + open fun statusBarLayerIsVisible() { + testSpec.statusBarLayerIsVisible() } - @FlakyTest + /** + * Checks the position of the navigation bar at the start and end of the transition + */ + @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + /** + * Checks the position of the status bar at the start and end of the transition + */ @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { @@ -108,6 +134,10 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { @@ -116,27 +146,47 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that all parts of the screen are covered during the transition + */ @Presubmit @Test - open fun noUncoveredRegions() { - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - } + open fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks that [testApp] is the top visible app window at the start of the transition and + * that it is replaced by [LAUNCHER_COMPONENT] during the transition + */ @Presubmit @Test open fun launcherReplacesAppWindowAsTopWindow() { - testSpec.launcherReplacesAppWindowAsTopWindow(testApp) + testSpec.assertWm { + this.isAppWindowOnTop(testApp.component) + .then() + .isAppWindowOnTop(LAUNCHER_COMPONENT) + } } + /** + * Checks that [LAUNCHER_COMPONENT] is invisible at the start of the transition and that + * it becomes visible during the transition + */ @Presubmit @Test open fun launcherWindowBecomesVisible() { - testSpec.launcherWindowBecomesVisible() + testSpec.assertWm { + this.isAppWindowNotOnTop(LAUNCHER_COMPONENT) + .then() + .isAppWindowOnTop(LAUNCHER_COMPONENT) + } } + /** + * Checks that [LAUNCHER_COMPONENT] layer becomes visible when [testApp] becomes invisible + */ @Presubmit @Test open fun launcherLayerReplacesApp() { - testSpec.launcherLayerReplacesApp(testApp) + testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt index fad25b4fa0b9..75900df978df 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:JvmName("FlickerExtensions") package com.android.server.wm.flicker.helpers import com.android.server.wm.flicker.Flicker diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index bd7c1855fea9..0b1748a6bda4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import androidx.test.uiautomator.UiDevice import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class ImeAppAutoFocusHelper @JvmOverloads constructor( @@ -27,7 +28,8 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( private val rotation: Int, private val imePackageName: String = IME_PACKAGE, launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME + component: FlickerComponentName = + ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { override fun openIME( device: UiDevice, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index 83fddae5b1a7..7ee6451b2797 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -17,19 +17,21 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper open class ImeAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.IME_ACTIVITY_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy @@ -61,7 +63,8 @@ open class ImeAppHelper @JvmOverloads constructor( if (wmHelper == null) { device.waitForIdle() } else { - wmHelper.waitImeWindowShown() + wmHelper.waitImeShown() + wmHelper.waitForAppTransitionIdle() } } @@ -78,7 +81,23 @@ open class ImeAppHelper @JvmOverloads constructor( if (wmHelper == null) { device.waitForIdle() } else { - wmHelper.waitImeWindowGone() + wmHelper.waitImeGone() + } + } + + @JvmOverloads + open fun finishActivity(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) { + val finishButton = device.wait( + Until.findObject(By.res(getPackage(), "finish_activity_btn")), + FIND_TIMEOUT) + require(finishButton != null) { + "Finish activity button not found, probably IME activity is not on the screen ?" + } + finishButton.click() + if (wmHelper == null) { + device.waitForIdle() + } else { + wmHelper.waitForActivityRemoved(component) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt new file mode 100644 index 000000000000..be68704fc32d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class NewTasksAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val button = device.wait( + Until.findObject(By.res(getPackage(), "launch_new_task")), + FIND_TIMEOUT) + + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. in split screen)" + } + button.click() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp(component) + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt new file mode 100644 index 000000000000..f7ca5ce1c001 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + +class NonResizeableAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt index 02be3cf0a8a3..7bab981ce231 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt @@ -17,15 +17,17 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent class SeamlessRotationAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt index d7cbaaee2627..f6a8817e5b08 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt @@ -17,15 +17,17 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent class SimpleAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt new file mode 100644 index 000000000000..59e8dc826007 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class TwoActivitiesAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val button = device.wait( + Until.findObject(By.res(getPackage(), "launch_second_activity")), + FIND_TIMEOUT) + + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. in split screen)" + } + button.click() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp(component) + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index b5757fd21ee0..5e21aff94769 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -18,8 +18,8 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.view.Surface import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -28,16 +28,16 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -46,6 +46,14 @@ import org.junit.runners.Parameterized /** * Test IME window closing back to app window transitions. + * + * This test doesn't work on 90 degrees. According to the InputMethodService documentation: + * + * Don't show if this is not explicitly requested by the user and the input method + * is fullscreen. That would be too disruptive. + * + * More details on b/190352379 + * * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest` */ @RequiresDevice @@ -79,60 +87,78 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)) + this.visibleWindowsShownMoreThanOneConsecutiveEntry() } } @Presubmit @Test - fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp) + fun imeAppWindowIsAlwaysVisible() { + testSpec.assertWm { + this.isAppWindowOnTop(testApp.component) + } + } @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - @FlakyTest + @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() + fun imeLayerVisibleStart() { + testSpec.assertLayersStart { + this.isVisible(FlickerComponentName.IME) + } + } @Presubmit @Test - fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp) + fun imeLayerInvisibleEnd() { + testSpec.assertLayersEnd { + this.isInvisible(FlickerComponentName.IME) + } + } - @FlakyTest + @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) - } + fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - @FlakyTest + @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) + fun imeAppLayerIsAlwaysVisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + } } @Presubmit @Test + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + + @Presubmit + @Test + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + + @Presubmit + @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() @@ -145,8 +171,11 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(repetitions = 5, + // b/190352379 (IME doesn't show on app launch in 90 degrees) + supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY) + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 549e44c511b9..0582685f2c54 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -30,15 +30,15 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -47,6 +47,14 @@ import org.junit.runners.Parameterized /** * Test IME window closing back to app window transitions. + * + * This test doesn't work on 90 degrees. According to the InputMethodService documentation: + * + * Don't show if this is not explicitly requested by the user and the input method + * is fullscreen. That would be too disruptive. + * + * More details on b/190352379 + * * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest` */ @RequiresDevice @@ -75,76 +83,94 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete transitions { device.pressHome() wmHelper.waitForHomeActivityVisible() - wmHelper.waitImeWindowGone() + wmHelper.waitImeGone() } } } @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)) + this.visibleWindowsShownMoreThanOneConsecutiveEntry() } } - @FlakyTest + @FlakyTest(bugId = 190189685) @Test - fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() + fun imeAppWindowBecomesInvisible() { + testSpec.assertWm { + this.isAppWindowOnTop(testApp.component) + .then() + .isAppWindowNotOnTop(testApp.component) + } + } - @FlakyTest + @Presubmit @Test - fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - Surface.ROTATION_0) + fun imeLayerVisibleStart() { + testSpec.assertLayersStart { + this.isVisible(FlickerComponentName.IME) + } + } - @FlakyTest + @Presubmit @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() + fun imeLayerInvisibleEnd() { + testSpec.assertLayersEnd { + this.isInvisible(FlickerComponentName.IME) + } + } @Presubmit @Test - fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp) + fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - @FlakyTest + @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + fun imeAppLayerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + .then() + .isInvisible(testApp.component) + } } @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - @FlakyTest + @Presubmit + @Test + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() + + @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() @Presubmit @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(IME_WINDOW_TITLE, WindowManagerStateHelper.SPLASH_SCREEN_NAME)) + this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf( + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN)) } } @@ -154,8 +180,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(repetitions = 1, + // b/190352379 (IME doesn't show on app launch in 90 degrees) + supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY) + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 82ca074b5ef2..91b3d3dae3cd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -28,15 +28,14 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Assume -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -61,7 +60,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { return FlickerBuilder(instrumentation).apply { setup { test { - testApp.launchViaIntent() + testApp.launchViaIntent(wmHelper) } eachRun { testApp.openIME(device, wmHelper) @@ -80,57 +79,60 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)) + this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT)) } } @Presubmit @Test - fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp) + fun imeAppWindowIsAlwaysVisible() { + testSpec.assertWm { + this.isAppWindowOnTop(testApp.component) + } + } @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test fun navBarLayerRotatesAndScales() { Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + testSpec.navBarLayerRotatesAndScales() } @FlakyTest @Test fun navBarLayerRotatesAndScales_Flaky() { Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + testSpec.navBarLayerRotatesAndScales() } @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test @@ -146,7 +148,11 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp) + fun imeAppLayerIsAlwaysVisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + } + } companion object { @Parameterized.Parameters(name = "{0}") diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 703e4a125440..b589969dee14 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -30,14 +30,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -68,7 +67,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { transitions { device.pressHome() wmHelper.waitForHomeActivityVisible() - wmHelper.waitImeWindowGone() + wmHelper.waitImeGone() } teardown { eachRun { @@ -84,19 +83,20 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE, - WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)) + this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT)) } } @@ -106,20 +106,25 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @FlakyTest @Test - fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp) + fun imeAppWindowBecomesInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp.component) + .then() + .isAppWindowInvisible(testApp.component) + } + } @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - Surface.ROTATION_0) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test @@ -127,25 +132,29 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp) + fun imeAppLayerBecomesInvisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + .then() + .isInvisible(testApp.component) + } + } @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(IME_WINDOW_TITLE, WindowManagerStateHelper.SPLASH_SCREEN_NAME)) + this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf( + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN)) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index 7e34469b8188..ba78e25580ec 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -14,128 +14,56 @@ * limitations under the License. */ +@file:JvmName("CommonAssertions") package com.android.server.wm.flicker.ime -import android.platform.helpers.IAppHelper import com.android.server.wm.flicker.FlickerTestParameter - -const val IME_WINDOW_TITLE = "InputMethod" - -fun FlickerTestParameter.imeLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { - if (rotatesScreen) { - assertLayers { - this.isVisible(IME_WINDOW_TITLE) - .then() - .isInvisible(IME_WINDOW_TITLE) - .then() - .isVisible(IME_WINDOW_TITLE) - } - } else { - assertLayers { - this.isVisible(IME_WINDOW_TITLE) - } - } -} +import com.android.server.wm.traces.common.FlickerComponentName fun FlickerTestParameter.imeLayerBecomesVisible() { assertLayers { - this.isInvisible(IME_WINDOW_TITLE) + this.isInvisible(FlickerComponentName.IME) .then() - .isVisible(IME_WINDOW_TITLE) + .isVisible(FlickerComponentName.IME) } } fun FlickerTestParameter.imeLayerBecomesInvisible() { assertLayers { - this.isVisible(IME_WINDOW_TITLE) + this.isVisible(FlickerComponentName.IME) .then() - .isInvisible(IME_WINDOW_TITLE) - } -} - -fun FlickerTestParameter.imeAppLayerIsAlwaysVisible(testApp: IAppHelper) { - assertLayers { - this.isVisible(testApp.getPackage()) - } -} - -fun FlickerTestParameter.imeAppWindowIsAlwaysVisible(testApp: IAppHelper) { - assertWm { - this.showsAppWindowOnTop(testApp.getPackage()) + .isInvisible(FlickerComponentName.IME) } } fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertWm { - this.showsNonAppWindow(IME_WINDOW_TITLE) + this.isNonAppWindowVisible(FlickerComponentName.IME) .then() - .hidesNonAppWindow(IME_WINDOW_TITLE) + .isNonAppWindowInvisible(FlickerComponentName.IME) .then() - .showsNonAppWindow(IME_WINDOW_TITLE) + .isNonAppWindowVisible(FlickerComponentName.IME) } } else { assertWm { - this.showsNonAppWindow(IME_WINDOW_TITLE) + this.isNonAppWindowVisible(FlickerComponentName.IME) } } } fun FlickerTestParameter.imeWindowBecomesVisible() { assertWm { - this.hidesNonAppWindow(IME_WINDOW_TITLE) + this.isNonAppWindowInvisible(FlickerComponentName.IME) .then() - .showsNonAppWindow(IME_WINDOW_TITLE) + .isNonAppWindowVisible(FlickerComponentName.IME) } } fun FlickerTestParameter.imeWindowBecomesInvisible() { assertWm { - this.showsNonAppWindow(IME_WINDOW_TITLE) + this.isNonAppWindowVisible(FlickerComponentName.IME) .then() - .hidesNonAppWindow(IME_WINDOW_TITLE) + .isNonAppWindowInvisible(FlickerComponentName.IME) } } - -fun FlickerTestParameter.imeAppWindowIsAlwaysVisible( - testApp: IAppHelper, - rotatesScreen: Boolean = false -) { - if (rotatesScreen) { - assertWm { - this.showsAppWindow(testApp.getPackage()) - .then() - .hidesAppWindow(testApp.getPackage()) - .then() - .showsAppWindow(testApp.getPackage()) - } - } else { - assertWm { - this.showsAppWindow(testApp.getPackage()) - } - } -} - -fun FlickerTestParameter.imeAppWindowBecomesVisible(windowName: String) { - assertWm { - this.hidesAppWindow(windowName) - .then() - .showsAppWindow(windowName) - } -} - -fun FlickerTestParameter.imeAppWindowBecomesInvisible(testApp: IAppHelper) { - assertWm { - this.showsAppWindowOnTop(testApp.getPackage()) - .then() - .appWindowNotOnTop(testApp.getPackage()) - } -} - -fun FlickerTestParameter.imeAppLayerBecomesInvisible(testApp: IAppHelper) { - assertLayers { - this.isVisible(testApp.getPackage()) - .then() - .isInvisible(testApp.getPackage()) - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt new file mode 100644 index 000000000000..a9568b325af2 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Launch an app that automatically displays the IME + * + * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] that automatically displays IME and wait animation to complete + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + this.setRotation(testSpec.config.startRotation) + } + } + teardown { + eachRun { + testApp.exit() + } + } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.waitImeShown() + } + } + } + + /** + * Checks that [FlickerComponentName.IME] window becomes visible during the transition + */ + @Presubmit + @Test + fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() + + /** + * Checks that [FlickerComponentName.IME] layer becomes visible during the transition + */ + @Presubmit + @Test + fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() + + /** + * Checks that [FlickerComponentName.IME] layer is invisible at the start of the transition + */ + @Presubmit + @Test + fun imeLayerNotExistsStart() { + testSpec.assertLayersStart { + this.isInvisible(FlickerComponentName.IME) + } + } + + /** + * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition + */ + @Presubmit + @Test + fun imeLayerExistsEnd() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.IME) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt new file mode 100644 index 000000000000..972918e28fa7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.* +import com.android.server.wm.flicker.annotation.Group2 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify + * there is no flickering when back to the simple activity without requesting IME to show. + * + * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group2 +class OpenImeWindowAndCloseTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val simpleApp = SimpleAppHelper(instrumentation) + private val testApp = ImeAppHelper(instrumentation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + simpleApp.launchViaIntent(wmHelper) + testApp.launchViaIntent(wmHelper) + testApp.openIME(device, wmHelper) + } + } + transitions { + testApp.finishActivity(device, wmHelper) + } + teardown { + test { + simpleApp.exit() + } + } + } + } + + @Presubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + @Presubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + @Presubmit + @Test + fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() + + @Presubmit + @Test + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() + + @Presubmit + @Test + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() + + @Presubmit + @Test + fun entireScreenCovered() = testSpec.entireScreenCovered() + + @Presubmit + @Test + fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() + + @Presubmit + @Test + fun visibleLayersShownMoreThanOneConsecutiveEntry() { + testSpec.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry() + } + } + + @Test + fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + testSpec.assertWm { + this.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index cae1b16c1c8c..7bf0186cd857 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -28,16 +29,14 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -81,11 +80,11 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test @@ -93,19 +92,23 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun appWindowAlwaysVisibleOnTop() = testSpec.appWindowAlwaysVisibleOnTop(testApp.`package`) + fun appWindowAlwaysVisibleOnTop() { + testSpec.assertWm { + this.isAppWindowOnTop(testApp.component) + } + } @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() @Presubmit @Test - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test @@ -115,21 +118,17 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Test fun layerAlwaysVisible() { testSpec.assertLayers { - this.isVisible(testApp.`package`) + this.isVisible(testApp.component) } } @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test @@ -139,7 +138,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Presubmit + @FlakyTest @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index b7673d5b0107..f6febe9e2234 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation +import android.os.SystemProperties import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -26,23 +27,22 @@ import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.launcherWindowBecomesInvisible -import com.android.server.wm.flicker.appLayerReplacesLauncher +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.endRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -61,7 +61,8 @@ import org.junit.runners.Parameterized class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) - private val testAppComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME + private val isShellTransitionsEnabled = + SystemProperties.getBoolean("persist.debug.shell_transit", false) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -73,14 +74,14 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } eachRun { device.pressRecentApps() - wmHelper.waitImeWindowGone() + wmHelper.waitImeGone() wmHelper.waitForAppTransitionIdle() this.setRotation(testSpec.config.startRotation) } } transitions { device.reopenAppFromOverview(wmHelper) - wmHelper.waitImeWindowShown() + wmHelper.waitImeShown() } teardown { test { @@ -92,72 +93,136 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + val component = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() + this.visibleWindowsShownMoreThanOneConsecutiveEntry( + ignoreWindows = listOf(FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + component) + ) } } @Presubmit @Test - fun launcherWindowBecomesInvisible() = testSpec.launcherWindowBecomesInvisible() + fun launcherWindowBecomesInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(LAUNCHER_COMPONENT) + .then() + .isAppWindowInvisible(LAUNCHER_COMPONENT) + } + } @Presubmit @Test - fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(true) + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) @Presubmit @Test - fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp, true) + fun imeAppWindowVisibilityLegacy() { + Assume.assumeFalse(isShellTransitionsEnabled) + // the app starts visible in live tile, and stays visible for the duration of entering + // and exiting overview. However, legacy transitions seem to have a bug which causes + // everything to restart during the test, so expect the app to disappear and come back. + // Since we log 1x per frame, sometimes the activity visibility and the app visibility + // are updated together, sometimes not, thus ignore activity check at the start + testSpec.assertWm { + this.isAppWindowVisible(testApp.component) + .then() + .isAppWindowInvisible(testApp.component) + .then() + .isAppWindowVisible(testApp.component) + } + } @Presubmit @Test - // During testing the launcher is always in portrait mode - fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, - testSpec.config.endRotation) + fun imeAppWindowVisibility() { + Assume.assumeTrue(isShellTransitionsEnabled) + // the app starts visible in live tile, and stays visible for the duration of entering + // and exiting overview. Since we log 1x per frame, sometimes the activity visibility + // and the app visibility are updated together, sometimes not, thus ignore activity + // check at the start + testSpec.assertWm { + this.isAppWindowVisible(testApp.component) + } + } @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + // During testing the launcher is always in portrait mode + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() @Presubmit @Test - fun imeLayerIsAlwaysVisible() = testSpec.imeLayerIsAlwaysVisible(true) + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() @Presubmit @Test - fun appLayerReplacesLauncher() = - testSpec.appLayerReplacesLauncher(testAppComponentName.className) + fun imeLayerIsBecomesVisibleLegacy() { + Assume.assumeFalse(isShellTransitionsEnabled) + testSpec.assertLayers { + this.isVisible(FlickerComponentName.IME) + .then() + .isInvisible(FlickerComponentName.IME) + .then() + .isVisible(FlickerComponentName.IME) + } + } @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) + fun imeLayerIsBecomesVisible() { + Assume.assumeTrue(isShellTransitionsEnabled) + testSpec.assertLayers { + this.isVisible(FlickerComponentName.IME) + } } @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) + fun appLayerReplacesLauncher() { + testSpec.assertLayers { + this.isVisible(LAUNCHER_COMPONENT) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp.component) + } } @Presubmit @Test + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + + @Presubmit + @Test + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + + @Presubmit + @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { + // depends on how much of the animation transactions are sent to SF at once + // sometimes this layer appears for 2-3 frames, sometimes for only 1 + val recentTaskComponent = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() + this.visibleLayersShownMoreThanOneConsecutiveEntry( + listOf(FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, recentTaskComponent) + ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt index 0cae37c8d5ab..4c506b0fea4d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt @@ -18,26 +18,24 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.view.Surface import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry - import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test @@ -53,11 +51,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 +@Group4 +@Presubmit class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = SimpleAppHelper(instrumentation) - private val imeTestApp = ImeAppHelper(instrumentation) + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -66,7 +65,13 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame eachRun { this.setRotation(testSpec.config.startRotation) testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + wmHelper.waitForAppTransitionIdle() + imeTestApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + wmHelper.waitForAppTransitionIdle() + imeTestApp.openIME(device, wmHelper) } } @@ -74,57 +79,87 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame eachRun { device.pressHome() wmHelper.waitForHomeActivityVisible() - } - test { - imeTestApp.exit(wmHelper) + testApp.exit() + imeTestApp.exit() } } transitions { // [Step1]: Swipe right from imeTestApp to testApp task + createTag(TAG_IME_VISIBLE) val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - val displayCenterX = displayBounds.bounds.width() / 2 - device.swipe(displayCenterX, displayBounds.bounds.height(), - displayBounds.bounds.width(), displayBounds.bounds.height(), 20) + device.swipe(0, displayBounds.bounds.height(), + displayBounds.bounds.width(), displayBounds.bounds.height(), 50) + wmHelper.waitForFullScreenApp(testApp.component) + wmHelper.waitForAppTransitionIdle() + createTag(TAG_IME_INVISIBLE) } transitions { // [Step2]: Swipe left to back to imeTestApp task val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - val displayCenterX = displayBounds.bounds.width() / 2 device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(), - displayCenterX, displayBounds.bounds.height(), 20) + 0, displayBounds.bounds.height(), 50) wmHelper.waitForFullScreenApp(imeTestApp.component) } } } - @FlakyTest @Test - fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(imeTestApp) + fun imeAppWindowVisibility() { + testSpec.assertWm { + isAppWindowVisible(imeTestApp.component) + .then() + .isAppWindowVisible(testApp.component) + .then() + .isAppWindowVisible(imeTestApp.component) + } + } - @FlakyTest @Test - fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() + fun navBarLayerIsVisibleAroundSwitching() { + testSpec.assertLayersStart { + isVisible(FlickerComponentName.NAV_BAR) + } + testSpec.assertLayersEnd { + isVisible(FlickerComponentName.NAV_BAR) + } + } - @FlakyTest @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() + fun statusBarLayerIsVisibleAroundSwitching() { + testSpec.assertLayersStart { + isVisible(FlickerComponentName.STATUS_BAR) + } + testSpec.assertLayersEnd { + isVisible(FlickerComponentName.STATUS_BAR) + } + } - @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + fun imeLayerIsVisibleWhenSwitchingToImeApp() { + testSpec.assertLayersStart { + isVisible(FlickerComponentName.IME) + } + testSpec.assertLayersTag(TAG_IME_VISIBLE) { + isVisible(FlickerComponentName.IME) + } + testSpec.assertLayersEnd { + isVisible(FlickerComponentName.IME) + } + } - @FlakyTest @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + fun imeLayerIsInvisibleWhenSwitchingToTestApp() { + testSpec.assertLayersTag(TAG_IME_INVISIBLE) { + isInvisible(FlickerComponentName.IME) + } + } - @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - @FlakyTest @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() companion object { @Parameterized.Parameters(name = "{0}") @@ -134,10 +169,13 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame .getConfigNonRotationTests( repetitions = 3, supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) + ), + supportedRotations = listOf(Surface.ROTATION_0) ) } + + private const val TAG_IME_VISIBLE = "imeVisible" + private const val TAG_IME_INVISIBLE = "imeInVisible" } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt new file mode 100644 index 000000000000..f74a7718461f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.launch + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.parser.toFlickerComponent +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test the back and forward transition between 2 activities. + * + * To run this test: `atest FlickerTests:ActivitiesTransitionTest` + * + * Actions: + * Launch an app + * Launch a secondary activity within the app + * Close the secondary activity back to the initial one + * + * Notes: + * 1. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation) + + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + eachRun { + testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + } + } + teardown { + test { + testApp.exit() + } + } + transitions { + testApp.openSecondActivity(device, wmHelper) + device.pressBack() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp(testApp.component) + } + } + } + + /** + * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at + * the start of the transition, that + * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the + * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible + * at the end + */ + @Presubmit + @Test + fun finishSubActivity() { + val buttonActivityComponent = ActivityOptions + .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + val imeAutoFocusActivityComponent = ActivityOptions + .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + testSpec.assertWm { + this.isAppWindowOnTop(buttonActivityComponent) + .then() + .isAppWindowOnTop(imeAutoFocusActivityComponent) + .then() + .isAppWindowOnTop(buttonActivityComponent) + } + } + + /** + * Checks that all parts of the screen are covered during the transition + */ + @Presubmit + @Test + fun entireScreenCovered() = testSpec.entireScreenCovered() + + /** + * Checks that the [LAUNCHER_COMPONENT] window is not on top. The launcher cannot be + * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name, + * and both are never simultaneously visible + */ + @Presubmit + @Test + fun launcherWindowNotOnTop() { + testSpec.assertWm { + this.isAppWindowNotOnTop(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the [LAUNCHER_COMPONENT] layer is never visible during the transition + */ + @Presubmit + @Test + fun launcherLayerNotVisible() { + testSpec.assertLayers { this.isInvisible(LAUNCHER_COMPONENT) } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 5) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt deleted file mode 100644 index 01e34d9f8f97..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 com.android.server.wm.flicker.launch - -import android.platform.helpers.IAppHelper -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.HOME_WINDOW_TITLE - -fun FlickerTestParameter.appWindowReplacesLauncherAsTopWindow(testApp: IAppHelper) { - assertWm { - this.showsAppWindowOnTop(*HOME_WINDOW_TITLE) - .then() - .showsAppWindowOnTop("Snapshot", testApp.getPackage()) - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index 9ff0bdfe66ba..be919cd67c1e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -16,6 +16,8 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -33,8 +35,21 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test cold launch app from launcher. + * Test cold launching an app from launcher + * * To run this test: `atest FlickerTests:OpenAppColdTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] and wait animation to complete + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -42,6 +57,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) @@ -62,43 +80,46 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } } + /** {@inheritDoc} */ @FlakyTest @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - super.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun navBarLayerRotatesAndScales() { + super.navBarLayerRotatesAndScales() } - @FlakyTest + /** {@inheritDoc} */ + @Postsubmit @Test - override fun navBarLayerIsAlwaysVisible() { - super.navBarLayerIsAlwaysVisible() - } + override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() - @FlakyTest + /** {@inheritDoc} */ + @Postsubmit @Test - override fun navBarLayerRotatesAndScales() { - super.navBarLayerRotatesAndScales() - } + override fun appWindowReplacesLauncherAsTopWindow() = + super.appWindowReplacesLauncherAsTopWindow() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun statusBarLayerIsAlwaysVisible() { - super.statusBarLayerIsAlwaysVisible() - } + override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun appLayerReplacesLauncher() { - super.appLayerReplacesLauncher() - } + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun appWindowReplacesLauncherAsTopWindow() { - super.appWindowReplacesLauncherAsTopWindow() - } + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index b073a7ca1495..663af703f76d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -26,6 +27,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.traces.common.WindowManagerConditionsFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -33,8 +35,23 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Launch an app from the recents app view (the overview) + * Test launching an app from the recents app view (the overview) + * * To run this test: `atest FlickerTests:OpenAppFromOverviewTest` + * + * Actions: + * Launch [testApp] + * Press recents + * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to + * complete (only this action is traced) + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -42,6 +59,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) @@ -59,41 +79,47 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio } transitions { device.reopenAppFromOverview(wmHelper) + wmHelper.waitFor( + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isWMStateComplete(), + WindowManagerConditionsFactory.isHomeActivityVisible().negate() + ) wmHelper.waitForFullScreenApp(testApp.component) } } + /** {@inheritDoc} */ @FlakyTest @Test - override fun navBarLayerIsAlwaysVisible() { - super.navBarLayerIsAlwaysVisible() - } + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun statusBarLayerIsAlwaysVisible() { - super.statusBarLayerIsAlwaysVisible() - } + override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun navBarLayerRotatesAndScales() { - super.navBarLayerRotatesAndScales() - } + override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun statusBarLayerRotatesScales() { - super.statusBarLayerRotatesScales() - } + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - super.visibleLayersShownMoreThanOneConsecutiveEntry() - } + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt new file mode 100644 index 000000000000..08aaea70762f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.launch + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.traces.common.FlickerComponentName +import com.google.common.truth.Truth +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching an app while the device is locked + * + * To run this test: `atest FlickerTests:OpenAppNonResizeableTest` + * + * Actions: + * Lock the device. + * Launch an app on top of the lock screen [testApp] and wait animation to complete + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + override val testApp = NonResizeableAppHelper(instrumentation) + private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#") + + /** + * Defines the transition used to run the test + */ + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { args -> + super.transition(this, args) + setup { + eachRun { + device.sleep() + wmHelper.waitFor("noAppWindowsOnTop") { + it.wmState.topVisibleAppWindow.isEmpty() + } + } + } + teardown { + eachRun { + testApp.exit(wmHelper) + } + } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp.component) + } + } + + /** + * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation + * and becomes visible at the end + */ + @Postsubmit + @Test + fun navBarLayerVisibilityChanges() { + testSpec.assertLayers { + this.isVisible(FlickerComponentName.NAV_BAR) + .then() + .isInvisible(FlickerComponentName.NAV_BAR) + .then() + .isVisible(FlickerComponentName.NAV_BAR) + } + } + + /** + * Checks that the app layer doesn't exist at the start of the transition, that it is + * created (invisible) and becomes visible during the transition + */ + @FlakyTest + @Test + fun appLayerBecomesVisible() { + testSpec.assertLayers { + this.notContains(testApp.component) + .then() + .isInvisible(testApp.component) + .then() + .isVisible(testApp.component) + } + } + + /** + * Checks that the app window doesn't exist at the start of the transition, that it is + * created (invisible - optional) and becomes visible during the transition + * + * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, + * the window may be visible or not depending on what was processed until that moment. + */ + @Presubmit + @Test + fun appWindowBecomesVisible() { + testSpec.assertWm { + this.notContains(testApp.component) + .then() + .isAppWindowInvisible(testApp.component, isOptional = true) + .then() + .isAppWindowVisible(testApp.component) + } + } + + /** + * Checks if [testApp] is visible at the end of the transition + */ + @Presubmit + @Test + fun appWindowBecomesVisibleAtEnd() { + testSpec.assertWmEnd { + this.isAppWindowVisible(testApp.component) + } + } + + /** + * Checks that the nav bar starts the transition visible, then becomes invisible during + * then unlocking animation and becomes visible at the end of the transition + */ + @Postsubmit + @Test + fun navBarWindowsVisibilityChanges() { + testSpec.assertWm { + this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) + .then() + .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) + .then() + .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) + } + } + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible at the end of the trace + * + * It is not possible to check at the start because the animation is working differently + * in devices with and without blur (b/202936526) + */ + @Presubmit + @Test + override fun statusBarLayerIsVisible() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.STATUS_BAR) + } + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 202936526) + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + /** {@inheritDoc} */ + @Presubmit + @Test + fun statusBarLayerPositionAtEnd() { + testSpec.assertLayersEnd { + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.STATUS_BAR) + .coversExactly(WindowUtils.getStatusBarPosition(display)) + } + } + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** + * Checks that the focus changes from the launcher to [testApp] + */ + @FlakyTest + @Test + override fun focusChanges() = super.focusChanges() + + /** + * Checks that the screen is locked at the start of the transition ([colorFadComponent]) + * layer is visible + */ + @Postsubmit + @Test + fun screenLockedStart() { + testSpec.assertLayersStart { + isVisible(colorFadComponent) + } + } + + /** + * This test checks if the launcher is visible at the start and the app at the end, + * it cannot use the regular assertion (check over time), because on lock screen neither + * the app not the launcher are visible, and there is no top visible window. + */ + @Postsubmit + @Test + override fun appWindowReplacesLauncherAsTopWindow() { + testSpec.assertWm { + this.invoke("noAppWindowsOnTop") { + Truth.assertWithMessage("Should not have any app window on top " + + "when the screen is locked") + .that(it.wmState.topVisibleAppWindow) + .isEmpty() + }.then() + .isAppWindowOnTop(testApp.component) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedNavigationModes = + listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY), + supportedRotations = listOf(Surface.ROTATION_0) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index b304d5f999df..7af7b3ab6f24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -18,34 +18,38 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.appLayerReplacesLauncher +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation -import com.android.server.wm.flicker.focusChanges +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.replacesLayer import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.launcherWindowBecomesInvisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test +/** + * Base class for app launch tests + */ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + /** + * Defines the transition used to run the test + */ protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { withTestName { testSpec.name } repeat { testSpec.config.repetitions } @@ -62,6 +66,10 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { } } + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -69,42 +77,56 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { } } - @Presubmit - @Test - open fun navBarWindowIsAlwaysVisible() { - testSpec.navBarWindowIsAlwaysVisible() + /** + * Checks that the navigation bar window is visible during the whole transition + */ + open fun navBarWindowIsVisible() { + testSpec.navBarWindowIsVisible() } - @Presubmit - @Test - open fun navBarLayerIsAlwaysVisible() { - testSpec.navBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated) + /** + * Checks that the navigation bar layer is visible at the start and end of the trace + */ + open fun navBarLayerIsVisible() { + testSpec.navBarLayerIsVisible() } + /** + * Checks the position of the navigation bar at the start and end of the transition + */ @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + /** + * Checks that the status bar window is visible during the whole transition + */ @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() { - testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarWindowIsVisible() { + testSpec.statusBarWindowIsVisible() } + /** + * Checks that the status bar layer is visible at the start and end of the trace + */ @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() { - testSpec.statusBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated) + open fun statusBarLayerIsVisible() { + testSpec.statusBarLayerIsVisible() } + /** + * Checks the position of the status bar at the start and end of the transition + */ @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { @@ -113,6 +135,10 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { } } + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { @@ -121,34 +147,60 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { } } + /** + * Checks that all parts of the screen are covered during the transition + */ @Presubmit @Test - // During testing the launcher is always in portrait mode - open fun noUncoveredRegions() { - testSpec.noUncoveredRegions(Surface.ROTATION_0, testSpec.config.endRotation) - } + open fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks that the focus changes from the launcher to [testApp] + */ @Presubmit @Test open fun focusChanges() { - testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) + testSpec.assertEventLog { + this.focusChanges("NexusLauncherActivity", testApp.`package`) + } } - @Presubmit - @Test + /** + * Checks that [LAUNCHER_COMPONENT] layer is visible at the start of the transition, and + * is replaced by [testApp], which remains visible until the end + */ open fun appLayerReplacesLauncher() { - testSpec.appLayerReplacesLauncher(testApp.`package`) + testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component) } + /** + * Checks that [LAUNCHER_COMPONENT] window is visible at the start of the transition, and + * is replaced by a snapshot or splash screen (optional), and finally, is replaced by + * [testApp], which remains visible until the end + */ @Presubmit @Test open fun appWindowReplacesLauncherAsTopWindow() { - testSpec.appWindowReplacesLauncherAsTopWindow(testApp) + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCHER_COMPONENT) + .then() + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(testApp.component) + } } - @Presubmit - @Test + /** + * Checks that [LAUNCHER_COMPONENT] window is visible at the start, and + * becomes invisible during the transition + */ open fun launcherWindowBecomesInvisible() { - testSpec.launcherWindowBecomesInvisible() + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCHER_COMPONENT) + .then() + .isAppWindowNotOnTop(LAUNCHER_COMPONENT) + } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index e2705c764917..5edee0cf0ca0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -32,8 +33,22 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test warm launch app. + * Test warm launching an app from launcher + * * To run this test: `atest FlickerTests:OpenAppWarmTest` + * + * Actions: + * Launch [testApp] + * Press home + * Relaunch an app [testApp] and wait animation to complete (only this action is traced) + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -41,6 +56,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { + /** + * Defines the transition used to run the test + */ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) @@ -65,19 +83,38 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } } + /** {@inheritDoc} */ @FlakyTest @Test - override fun navBarLayerIsAlwaysVisible() { - super.navBarLayerIsAlwaysVisible() - } + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + /** {@inheritDoc} */ + @Presubmit @Test - override fun navBarLayerRotatesAndScales() { - super.navBarLayerRotatesAndScales() - } + override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt new file mode 100644 index 000000000000..495e2d62a11d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.launch + +import android.app.Instrumentation +import android.app.WallpaperManager +import android.platform.test.annotations.Postsubmit +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.helpers.NewTasksAppHelper +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME +import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.FlickerComponentName.Companion.SPLASH_SCREEN +import com.android.server.wm.traces.common.FlickerComponentName.Companion.WALLPAPER_BBQ_WRAPPER +import com.android.server.wm.traces.parser.toFlickerComponent +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test the back and forward transition between 2 activities. + * + * To run this test: `atest FlickerTests:ActivitiesTransitionTest` + * + * Actions: + * Launch the NewTaskLauncherApp [mTestApp] + * Open a new task (SimpleActivity) from the NewTaskLauncherApp [mTestApp] + * Go back to the NewTaskLauncherApp [mTestApp] + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class TaskTransitionTest(val testSpec: FlickerTestParameter) { + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + eachRun { + mTestApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(mTestApp.component) + } + } + teardown { + test { + mTestApp.exit() + } + } + transitions { + mTestApp.openNewTask(device, wmHelper) + device.pressBack() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp(mTestApp.component) + } + } + } + + /** + * Checks that the wallpaper window is never visible when performing task transitions. + * A solid color background should be shown instead. + */ + @Postsubmit + @Test + fun wallpaperWindowIsNeverVisible() { + testSpec.assertWm { + this.isNonAppWindowInvisible(WALLPAPER) + } + } + + /** + * Checks that the wallpaper layer is never visible when performing task transitions. + * A solid color background should be shown instead. + */ + @Postsubmit + @Test + fun wallpaperLayerIsNeverVisible() { + testSpec.assertLayers { + this.isInvisible(WALLPAPER) + this.isInvisible(WALLPAPER_BBQ_WRAPPER) + } + } + + /** + * Check that the launcher window is never visible when performing task transitions. + * A solid color background should be shown above it. + */ + @Postsubmit + @Test + fun launcherWindowIsNeverVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the launcher layer is never visible when performing task transitions. + * A solid color background should be shown above it. + */ + @Postsubmit + @Test + fun launcherLayerIsNeverVisible() { + testSpec.assertLayers { + this.isInvisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that a color background is visible while the task transition is occurring. + */ + @Postsubmit + @Test + fun colorLayerIsVisibleDuringTransition() { + val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer") + val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + testSpec.assertLayers { + this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) + .isInvisible(bgColorLayer) + .then() + // Transitioning + .isVisible(bgColorLayer) + .then() + // Fully transitioned to simple SIMPLE_ACTIVITY + .coversExactly(displayBounds, SIMPLE_ACTIVITY) + .isInvisible(bgColorLayer) + .then() + // Transitioning back + .isVisible(bgColorLayer) + .then() + // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY + .isInvisible(bgColorLayer) + .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) + } + } + + /** + * Checks that we start with the LaunchNewTask activity on top and then open up + * the SimpleActivity and then go back to the LaunchNewTask activity. + */ + @Postsubmit + @Test + fun newTaskOpensOnTopAndThenCloses() { + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY) + .then() + .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(SIMPLE_ACTIVITY) + .then() + .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY) + } + } + + /** + * Checks that all parts of the screen are covered at the start and end of the transition + */ + @Postsubmit + @Test + fun entireScreenCovered() = testSpec.entireScreenCovered() + + /** + * Checks that the navbar window is visible throughout the transition + */ + @Postsubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + /** + * Checks that the navbar layer is visible throughout the transition + */ + @Postsubmit + @Test + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() + + /** + * Checks that the status bar window is visible throughout the transition + */ + @Postsubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible throughout the transition + */ + @Postsubmit + @Test + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() + + companion object { + private val WALLPAPER = getWallpaperPackage(InstrumentationRegistry.getInstrumentation()) + private val LAUNCH_NEW_TASK_ACTIVITY = + LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + + private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName { + val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext) + + return wallpaperManager.wallpaperInfo.component.toFlickerComponent() + } + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 5) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt new file mode 100644 index 000000000000..52904cce8772 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.quickswitch + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isRotated +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching back to previous app from last opened app + * + * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` + * + * Actions: + * Launch an app [testApp1] + * Launch another app [testApp2] + * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] + * + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + private val testApp1 = SimpleAppHelper(instrumentation) + private val testApp2 = NonResizeableAppHelper(instrumentation) + + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + testApp1.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp1.component) + + testApp2.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp2.component) + } + } + transitions { + // Swipe right from bottom to quick switch back + // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle + // as to not accidentally trigger a swipe back or forward action which would result + // in the same behavior but not testing quick swap. + device.swipe( + startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + 2 * startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + if (testSpec.config.startRotation.isRotated()) 75 else 30 + ) + + wmHelper.waitForFullScreenApp(testApp1.component) + wmHelper.waitForAppTransitionIdle() + } + + teardown { + test { + testApp1.exit() + testApp2.exit() + } + } + } + } + + /** + * Checks that the transition starts with [testApp2]'s windows filling/covering exactly the + * entirety of the display. + */ + @Postsubmit + @Test + fun startsWithApp2WindowsCoverFullScreen() { + testSpec.assertWmStart { + this.frameRegion(testApp2.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the + * entirety of the display. + */ + @Postsubmit + @Test + fun startsWithApp2LayersCoverFullScreen() { + testSpec.assertLayersStart { + this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with [testApp2] being the top window. + */ + @Postsubmit + @Test + fun startsWithApp2WindowBeingOnTop() { + testSpec.assertWmStart { + this.isAppWindowOnTop(testApp2.component) + } + } + + /** + * Checks that [testApp1] windows fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from [testApp2] back to the [testApp1]. + */ + @Postsubmit + @Test + fun endsWithApp1WindowsCoveringFullScreen() { + testSpec.assertWmEnd { + this.frameRegion(testApp1.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp1] layers fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from [testApp2] back to the [testApp1]. + */ + @Postsubmit + @Test + fun endsWithApp1LayersCoveringFullScreen() { + testSpec.assertLayersEnd { + this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp1] is the top window at the end of the transition once we have fully quick + * switched from [testApp2] back to the [testApp1]. + */ + @Postsubmit + @Test + fun endsWithApp1BeingOnTop() { + testSpec.assertWmEnd { + this.isAppWindowOnTop(testApp1.component) + } + } + + /** + * Checks that [testApp1]'s window starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun app1WindowBecomesAndStaysVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(testApp1.component) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp1.component) + } + } + + /** + * Checks that [testApp1]'s layer starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun app1LayerBecomesAndStaysVisible() { + testSpec.assertLayers { + this.isInvisible(testApp1.component) + .then() + .isVisible(testApp1.component) + } + } + + /** + * Checks that [testApp2]'s window starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Postsubmit + @Test + fun app2WindowBecomesAndStaysInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp2.component) + .then() + .isAppWindowInvisible(testApp2.component) + } + } + + /** + * Checks that [testApp2]'s layer starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Postsubmit + @Test + fun app2LayerBecomesAndStaysInvisible() { + testSpec.assertLayers { + this.isVisible(testApp2.component) + .then() + .isInvisible(testApp2.component) + } + } + + /** + * Checks that [testApp2]'s window is visible at least until [testApp1]'s window is visible. + * Ensures that at any point, either [testApp1] or [testApp2]'s windows are at least partially + * visible. + */ + @Postsubmit + @Test + fun app1WindowIsVisibleOnceApp2WindowIsInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp2.component) + .then() + // TODO: Do we actually want to test this? Seems too implementation specific... + .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp1.component) + } + } + + /** + * Checks that [testApp2]'s layer is visible at least until [testApp1]'s window is visible. + * Ensures that at any point, either [testApp1] or [testApp2]'s windows are at least partially + * visible. + */ + @Postsubmit + @Test + fun app1LayerIsVisibleOnceApp2LayerIsInvisible() { + testSpec.assertLayers { + this.isVisible(testApp2.component) + .then() + .isVisible(LAUNCHER_COMPONENT, isOptional = true) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp1.component) + } + } + + /** + * Checks that the navbar window is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() + + /** + * Checks that the navbar layer is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() + + /** + * Checks that the navbar is always in the right position and covers the expected region. + * + * NOTE: This doesn't check that the navbar is visible or not. + */ + @Postsubmit + @Test + fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() + + /** + * Checks that the status bar window is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ), + supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt new file mode 100644 index 000000000000..842aa2b548db --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.quickswitch + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isRotated +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching back to previous app from last opened app + * + * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` + * + * Actions: + * Launch an app [testApp1] + * Launch another app [testApp2] + * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] + * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2] + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + private val testApp1 = SimpleAppHelper(instrumentation) + private val testApp2 = NonResizeableAppHelper(instrumentation) + + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + eachRun { + testApp1.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp1.component) + + testApp2.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp2.component) + + // Swipe right from bottom to quick switch back + // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle + // as to not accidentally trigger a swipe back or forward action which would result + // in the same behavior but not testing quick swap. + device.swipe( + startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + 2 * startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + if (testSpec.config.startRotation.isRotated()) 75 else 30 + ) + + wmHelper.waitForFullScreenApp(testApp1.component) + wmHelper.waitForAppTransitionIdle() + } + } + transitions { + // Swipe left from bottom to quick switch forward + // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle + // as to not accidentally trigger a swipe back or forward action which would result + // in the same behavior but not testing quick swap. + device.swipe( + 2 * startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + if (testSpec.config.startRotation.isRotated()) 75 else 30 + ) + + wmHelper.waitForFullScreenApp(testApp2.component) + wmHelper.waitForAppTransitionIdle() + } + + teardown { + test { + testApp1.exit() + testApp2.exit() + } + } + } + } + + /** + * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the + * entirety of the display. + */ + @Postsubmit + @Test + fun startsWithApp1WindowsCoverFullScreen() { + testSpec.assertWmStart { + this.frameRegion(testApp1.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the + * entirety of the display. + */ + @Postsubmit + @Test + fun startsWithApp1LayersCoverFullScreen() { + testSpec.assertLayersStart { + this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with [testApp1] being the top window. + */ + @Postsubmit + @Test + fun startsWithApp1WindowBeingOnTop() { + testSpec.assertWmStart { + this.isAppWindowOnTop(testApp1.component) + } + } + + /** + * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from [testApp1] back to the [testApp2]. + */ + @Postsubmit + @Test + fun endsWithApp2WindowsCoveringFullScreen() { + testSpec.assertWmEnd { + this.frameRegion(testApp2.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from [testApp1] back to the [testApp2]. + */ + @Postsubmit + @Test + fun endsWithApp2LayersCoveringFullScreen() { + testSpec.assertLayersEnd { + this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp2] is the top window at the end of the transition once we have fully quick + * switched from [testApp1] back to the [testApp2]. + */ + @Postsubmit + @Test + fun endsWithApp2BeingOnTop() { + testSpec.assertWmEnd { + this.isAppWindowOnTop(testApp2.component) + } + } + + /** + * Checks that [testApp2]'s window starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun app2WindowBecomesAndStaysVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(testApp2.component) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp2.component) + } + } + + /** + * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun app2LayerBecomesAndStaysVisible() { + testSpec.assertLayers { + this.isInvisible(testApp2.component) + .then() + .isVisible(testApp2.component) + } + } + + /** + * Checks that [testApp1]'s window starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Postsubmit + @Test + fun app1WindowBecomesAndStaysInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp1.component) + .then() + .isAppWindowInvisible(testApp1.component) + } + } + + /** + * Checks that [testApp1]'s layer starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Postsubmit + @Test + fun app1LayerBecomesAndStaysInvisible() { + testSpec.assertLayers { + this.isVisible(testApp1.component) + .then() + .isInvisible(testApp1.component) + } + } + + /** + * Checks that [testApp1]'s window is visible at least until [testApp2]'s window is visible. + * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially + * visible. + */ + @Postsubmit + @Test + fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp1.component) + .then() + .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp2.component) + } + } + + /** + * Checks that [testApp1]'s layer is visible at least until [testApp2]'s window is visible. + * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially + * visible. + */ + @Postsubmit + @Test + fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { + testSpec.assertLayers { + this.isVisible(testApp1.component) + .then() + .isVisible(LAUNCHER_COMPONENT, isOptional = true) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp2.component) + } + } + + /** + * Checks that the navbar window is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() + + /** + * Checks that the navbar layer is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() + + /** + * Checks that the navbar is always in the right position and covers the expected region. + * + * NOTE: This doesn't check that the navbar is visible or not. + */ + @Postsubmit + @Test + fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() + + /** + * Checks that the status bar window is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ), + supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt new file mode 100644 index 000000000000..10ca0d9b323b --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.quickswitch + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching to last opened app from launcher + * + * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest` + * + * Actions: + * Launch an app + * Navigate home to show launcher + * Swipe right from the bottom of the screen to quick switch back to the app + * + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = SimpleAppHelper(instrumentation) + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + testApp.launchViaIntent(wmHelper) + device.pressHome() + wmHelper.waitForHomeActivityVisible() + wmHelper.waitForWindowSurfaceDisappeared(testApp.component) + } + } + transitions { + // Swipe right from bottom to quick switch back + // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle + // as to not accidentally trigger a swipe back or forward action which would result + // in the same behavior but not testing quick swap. + device.swipe( + startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + 2 * startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + 50 + ) + + wmHelper.waitForFullScreenApp(testApp.component) + wmHelper.waitForAppTransitionIdle() + } + + teardown { + eachRun { + testApp.exit() + } + } + } + } + + /** + * Checks that [testApp] windows fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from the launcher back to the [testApp]. + */ + @Presubmit + @Test + fun endsWithAppWindowsCoveringFullScreen() { + testSpec.assertWmEnd { + this.frameRegion(testApp.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp] layers fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from the launcher back to the [testApp]. + */ + @Presubmit + @Test + fun endsWithAppLayersCoveringFullScreen() { + testSpec.assertLayersEnd { + this.visibleRegion(testApp.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp] is the top window at the end of the transition once we have fully quick + * switched from the launcher back to the [testApp]. + */ + @Presubmit + @Test + fun endsWithAppBeingOnTop() { + testSpec.assertWmEnd { + this.isAppWindowOnTop(testApp.component) + } + } + + /** + * Checks that the transition starts with the home activity being tagged as visible. + */ + @Presubmit + @Test + fun startsWithHomeActivityFlaggedVisible() { + testSpec.assertWmStart { + this.isHomeActivityVisible() + } + } + + /** + * Checks that the transition starts with the launcher windows filling/covering exactly the + * entirety of the display. + */ + @Presubmit + @Test + fun startsWithLauncherWindowsCoverFullScreen() { + testSpec.assertWmStart { + this.frameRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the launcher layers filling/covering exactly the + * entirety of the display. + */ + @Presubmit + @Test + fun startsWithLauncherLayersCoverFullScreen() { + testSpec.assertLayersStart { + this.visibleRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the launcher being the top window. + */ + @Presubmit + @Test + fun startsWithLauncherBeingOnTop() { + testSpec.assertWmStart { + this.isAppWindowOnTop(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the transition ends with the home activity being flagged as not visible. By this + * point we should have quick switched away from the launcher back to the [testApp]. + */ + @Presubmit + @Test + fun endsWithHomeActivityFlaggedInvisible() { + testSpec.assertWmEnd { + this.isHomeActivityInvisible() + } + } + + /** + * Checks that [testApp]'s window starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Presubmit + @Test + fun appWindowBecomesAndStaysVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(testApp.component) + .then() + .isAppWindowVisible(testApp.component) + } + } + + /** + * Checks that [testApp]'s layer starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Presubmit + @Test + fun appLayerBecomesAndStaysVisible() { + testSpec.assertLayers { + this.isInvisible(testApp.component) + .then() + .isVisible(testApp.component) + } + } + + /** + * Checks that the launcher window starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Presubmit + @Test + fun launcherWindowBecomesAndStaysInvisible() { + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCHER_COMPONENT) + .then() + .isAppWindowNotOnTop(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the launcher layer starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Presubmit + @Test + fun launcherLayerBecomesAndStaysInvisible() { + testSpec.assertLayers { + this.isVisible(LAUNCHER_COMPONENT) + .then() + .isInvisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the launcher window is visible at least until the app window is visible. Ensures + * that at any point, either the launcher or [testApp] windows are at least partially visible. + */ + @Presubmit + @Test + fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCHER_COMPONENT) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT) + .then() + .isAppWindowVisible(testApp.component) + } + } + + /** + * Checks that the launcher layer is visible at least until the app layer is visible. Ensures + * that at any point, either the launcher or [testApp] layers are at least partially visible. + */ + @Presubmit + @Test + fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { + testSpec.assertLayers { + this.isVisible(LAUNCHER_COMPONENT) + .then() + .isVisible(FlickerComponentName.SNAPSHOT) + .then() + .isVisible(testApp.component) + } + } + + /** + * Checks that the navbar window is visible throughout the entire transition. + */ + @Presubmit + @Test + fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() + + /** + * Checks that the navbar layer is visible throughout the entire transition. + */ + @Presubmit + @Test + fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() + + /** + * Checks that the navbar is always in the right position and covers the expected region. + * + * NOTE: This doesn't check that the navbar is visible or not. + */ + @Presubmit + @Test + fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() + + /** + * Checks that the status bar window is visible throughout the entire transition. + */ + @Presubmit + @Test + fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible throughout the entire transition. + */ + @Presubmit + @Test + fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() + + /** + * Checks that the screen is always fully covered by visible layers throughout the transition. + */ + @Presubmit + @Test + fun screenIsAlwaysFilled() = testSpec.entireScreenCovered() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ), + // TODO: Test with 90 rotation + supportedRotations = listOf(Surface.ROTATION_0) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 69e8a8d08e58..fd8abc621b33 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -26,15 +26,52 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Cycle through supported app rotations. + * Test opening an app and cycling through app rotations + * + * Currently runs: + * 0 -> 90 degrees + * 90 -> 0 degrees + * + * Actions: + * Launch an app (via intent) + * Set initial device orientation + * Start tracing + * Change device orientation + * Stop tracing + * * To run this test: `atest FlickerTests:ChangeAppRotationTest` + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [RotationTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -44,6 +81,9 @@ import org.junit.runners.Parameterized class ChangeAppRotationTest( testSpec: FlickerTestParameter ) : RotationTransition(testSpec) { + @get:Rule + val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) + override val testApp = SimpleAppHelper(instrumentation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -55,45 +95,94 @@ class ChangeAppRotationTest( } } + @Postsubmit + @Test + fun runPresubmitAssertion() { + flickerRule.checkPresubmitAssertions() + } + + @Postsubmit + @Test + fun runPostsubmitAssertion() { + flickerRule.checkPostsubmitAssertions() + } + + @FlakyTest + @Test + fun runFlakyAssertion() { + flickerRule.checkFlakyAssertions() + } + + /** {@inheritDoc} */ @FlakyTest(bugId = 190185577) @Test override fun focusDoesNotChange() { super.focusDoesNotChange() } - @Postsubmit + /** + * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, + * doesn't flicker, and disappears before the transition is complete + */ + @Presubmit @Test - fun screenshotLayerBecomesInvisible() { + fun rotationLayerAppearsAndVanishes() { testSpec.assertLayers { - this.isVisible(testApp.getPackage()) + this.isVisible(testApp.component) .then() - .isVisible(SCREENSHOT_LAYER) + .isVisible(FlickerComponentName.ROTATION) .then() - .isVisible(testApp.getPackage()) + .isVisible(testApp.component) + .isInvisible(FlickerComponentName.ROTATION) } } - @Postsubmit + /** + * Checks that the status bar window is visible and above the app windows in all WM + * trace entries + */ + @Presubmit @Test - override fun statusBarLayerRotatesScales() { - super.statusBarLayerRotatesScales() + fun statusBarWindowIsVisible() { + testSpec.statusBarWindowIsVisible() } + /** + * Checks that the status bar layer is visible at the start and end of the transition + */ @Presubmit @Test - override fun navBarWindowIsAlwaysVisible() { - super.navBarWindowIsAlwaysVisible() + fun statusBarLayerIsVisible() { + testSpec.statusBarLayerIsVisible() } + /** + * Checks the position of the status bar at the start and end of the transition + */ + @Presubmit + @Test + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + + /** {@inheritDoc} */ @FlakyTest @Test - override fun statusBarLayerIsAlwaysVisible() { - super.statusBarLayerIsAlwaysVisible() + override fun navBarLayerRotatesAndScales() { + super.navBarLayerRotatesAndScales() } - companion object { - private const val SCREENSHOT_LAYER = "RotationLayer" + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 4b888cd5aad0..e850632ed8af 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -18,33 +18,28 @@ package com.android.server.wm.flicker.rotation import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation -import com.android.server.wm.flicker.focusDoesNotChange import com.android.server.wm.flicker.helpers.StandardAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.noUncoveredRegions +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test +/** + * Base class for app rotation tests + */ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) { protected abstract val testApp: StandardAppHelper protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected val startingPos get() = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - protected val endingPos get() = WindowUtils.getDisplayBounds(testSpec.config.endRotation) protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { setup { @@ -62,6 +57,10 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -69,57 +68,53 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } - @FlakyTest - @Test - open fun navBarWindowIsAlwaysVisible() { - testSpec.navBarWindowIsAlwaysVisible() - } - - @FlakyTest - @Test - open fun navBarLayerIsAlwaysVisible() { - testSpec.navBarLayerIsAlwaysVisible(rotatesScreen = true) - } - - @FlakyTest - @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales( - testSpec.config.startRotation, testSpec.config.endRotation) - } - + /** + * Checks that the navigation bar window is visible and above the app windows in all WM + * trace entries + */ @Presubmit @Test - open fun statusBarWindowIsAlwaysVisible() { - testSpec.statusBarWindowIsAlwaysVisible() + open fun navBarWindowIsVisible() { + testSpec.navBarWindowIsVisible() } - @FlakyTest + /** + * Checks that the navigation bar layer is visible at the start and end of the transition + */ + @Presubmit @Test - open fun statusBarLayerIsAlwaysVisible() { - testSpec.statusBarLayerIsAlwaysVisible(rotatesScreen = true) + open fun navBarLayerIsVisible() { + testSpec.navBarLayerIsVisible() } - @FlakyTest + /** + * Checks the position of the navigation bar at the start and end of the transition + */ + @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales( - testSpec.config.startRotation, testSpec.config.endRotation) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - @FlakyTest + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ + @Presubmit @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( - ignoreLayers = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME, - WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, - "SecondaryHomeHandle" + ignoreLayers = listOf(FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + FlickerComponentName("", "SecondaryHomeHandle") ) ) } } + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { @@ -128,32 +123,47 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that all parts of the screen are covered during the transition + */ @Presubmit @Test - open fun noUncoveredRegions() { - testSpec.noUncoveredRegions(testSpec.config.startRotation, - testSpec.config.endRotation, allStates = false) - } + open fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks that the focus doesn't change during animation + */ @Presubmit @Test open fun focusDoesNotChange() { - testSpec.focusDoesNotChange() + testSpec.assertEventLog { + this.focusDoesNotChange() + } } + /** + * Checks that [testApp] layer covers the entire screen at the start of the transition + */ @Presubmit @Test open fun appLayerRotates_StartingPos() { testSpec.assertLayersStart { - this.visibleRegion(testApp.getPackage()).coversExactly(startingPos) + this.entry.displays.map { display -> + this.visibleRegion(testApp.component).coversExactly(display.layerStackSpace) + } } } + /** + * Checks that [testApp] layer covers the entire screen at the end of the transition + */ @Presubmit @Test open fun appLayerRotates_EndingPos() { testSpec.assertLayersEnd { - this.visibleRegion(testApp.getPackage()).coversExactly(endingPos) + this.entry.displays.map { display -> + this.visibleRegion(testApp.component).coversExactly(display.layerStackSpace) + } } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index b153bece1133..310f04b9710f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.rotation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.view.WindowManager import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -27,6 +27,7 @@ import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -34,8 +35,41 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Cycle through supported app rotations using seamless rotations. + * Test opening an app and cycling through app rotations using seamless rotations + * + * Currently runs: + * 0 -> 90 degrees + * 0 -> 90 degrees (with starved UI thread) + * 90 -> 0 degrees + * 90 -> 0 degrees (with starved UI thread) + * + * Actions: + * Launch an app in fullscreen and supporting seamless rotation (via intent) + * Set initial device orientation + * Start tracing + * Change device orientation + * Stop tracing + * * To run this test: `atest FlickerTests:SeamlessAppRotationTest` + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [RotationTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -60,45 +94,97 @@ class SeamlessAppRotationTest( } } - @FlakyTest(bugId = 140855415) + /** + * Checks that [testApp] window is always in full screen + */ + @Presubmit @Test - override fun statusBarWindowIsAlwaysVisible() { - super.statusBarWindowIsAlwaysVisible() + fun appWindowFullScreen() { + testSpec.assertWm { + this.invoke("isFullScreen") { + val appWindow = it.windowState(testApp.`package`) + val flags = appWindow.windowState?.attributes?.flags ?: 0 + appWindow.verify("isFullScreen") + .that(flags.and(WindowManager.LayoutParams.FLAG_FULLSCREEN)) + .isGreaterThan(0) + } + } } - @FlakyTest(bugId = 140855415) + /** + * Checks that [testApp] window is always with seamless rotation + */ + @Presubmit @Test - override fun statusBarLayerIsAlwaysVisible() { - super.statusBarLayerIsAlwaysVisible() + fun appWindowSeamlessRotation() { + testSpec.assertWm { + this.invoke("isRotationSeamless") { + val appWindow = it.windowState(testApp.`package`) + val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0 + appWindow.verify("isRotationSeamless") + .that(rotationAnimation + .and(WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS)) + .isGreaterThan(0) + } + } } + /** + * Checks that [testApp] window is always visible + */ @Presubmit @Test fun appLayerAlwaysVisible() { testSpec.assertLayers { - isVisible(testApp.`package`) + isVisible(testApp.component) } } - @FlakyTest(bugId = 185400889) + /** + * Checks that [testApp] layer covers the entire screen during the whole transition + */ + @Presubmit @Test fun appLayerRotates() { testSpec.assertLayers { - this.coversExactly(startingPos, testApp.`package`) - .then() - .coversExactly(endingPos, testApp.`package`) + this.invoke("entireScreenCovered") { entry -> + entry.entry.displays.map { display -> + entry.visibleRegion(testApp.component).coversExactly(display.layerStackSpace) + } + } } } - @Postsubmit + /** + * Checks that the [FlickerComponentName.STATUS_BAR] window is invisible during the whole + * transition + */ + @Presubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - super.visibleLayersShownMoreThanOneConsecutiveEntry() + fun statusBarWindowIsAlwaysInvisible() { + testSpec.assertWm { + this.isAboveAppWindowInvisible(FlickerComponentName.STATUS_BAR) + } } - companion object { - private val testFactory = FlickerTestParameterFactory.getInstance() + /** + * Checks that the [FlickerComponentName.STATUS_BAR] layer is invisible during the whole + * transition + */ + @Presubmit + @Test + fun statusBarLayerIsAlwaysInvisible() { + testSpec.assertLayers { + this.isInvisible(FlickerComponentName.STATUS_BAR) + } + } + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + companion object { private val Map<String, Any?>.starveUiThread get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean @@ -110,20 +196,34 @@ class SeamlessAppRotationTest( return config } + /** + * Creates the test configurations for seamless rotation based on the default rotation + * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an + * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app + * should starve the UI thread of not + */ @JvmStatic private fun getConfigurations(): List<FlickerTestParameter> { - return testFactory.getConfigRotationTests(repetitions = 2).flatMap { - val defaultRun = it.createConfig(starveUiThread = false) - val busyUiRun = it.createConfig(starveUiThread = true) - listOf( - FlickerTestParameter(defaultRun), - FlickerTestParameter(busyUiRun, - name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD" + return FlickerTestParameterFactory.getInstance() + .getConfigRotationTests(repetitions = 2) + .flatMap { + val defaultRun = it.createConfig(starveUiThread = false) + val busyUiRun = it.createConfig(starveUiThread = true) + listOf( + FlickerTestParameter(defaultRun), + FlickerTestParameter(busyUiRun, + name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD" + ) ) - ) - } + } } + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 1599ed4b280f..cb37fc7b47e9 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -59,5 +59,36 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".NonResizeableActivity" + android:resizeableActivity="false" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" + android:label="NonResizeableApp" + android:exported="true" + android:showOnLockScreen="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".ButtonActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity" + android:configChanges="orientation|screenSize" + android:label="ButtonActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".LaunchNewTaskActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity" + android:configChanges="orientation|screenSize" + android:label="LaunchNewTaskActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml new file mode 100644 index 000000000000..fe7bced690f9 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_orange_light"> + <Button + android:id="@+id/launch_second_activity" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Second activity" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index 4708cfd48381..2620ff407efc 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -18,10 +18,17 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:orientation="vertical" android:focusableInTouchMode="true" android:background="@android:color/holo_green_light"> <EditText android:id="@+id/plain_text_input" android:layout_height="wrap_content" android:layout_width="match_parent" + android:imeOptions="flagNoExtractUi" android:inputType="text"/> + <Button + android:id="@+id/finish_activity_btn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Finish activity" /> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml new file mode 100644 index 000000000000..6d5a9dd29248 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/holo_orange_light"> + + <TextView + android:id="@+id/NonResizeableTest" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="center_vertical|center_horizontal" + android:text="NonResizeableActivity" + android:textAppearance="?android:attr/textAppearanceLarge"/> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml new file mode 100644 index 000000000000..8f75d175d00a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_orange_light"> + <Button + android:id="@+id/launch_new_task" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="New task" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 0ccc49897202..baf36ab0e132 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -41,4 +41,19 @@ public class ActivityOptions { public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".SimpleActivity"); + + public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp"; + public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeableActivity"); + + public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp"; + public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ButtonActivity"); + + public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp"; + public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java new file mode 100644 index 000000000000..b42ac2a6fd97 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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 com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.Button; + +public class ButtonActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_button); + + Button button = findViewById(R.id.launch_second_activity); + button.setOnClickListener(v -> { + Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class); + startActivity(intent); + }); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java index df60460e7ae3..d7ee2af44111 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java @@ -19,6 +19,7 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; import android.os.Bundle; import android.view.WindowManager; +import android.widget.Button; public class ImeActivity extends Activity { @Override @@ -29,5 +30,9 @@ public class ImeActivity extends Activity { .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(p); setContentView(R.layout.activity_ime); + Button button = findViewById(R.id.finish_activity_btn); + button.setOnClickListener(view -> { + finish(); + }); } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java new file mode 100644 index 000000000000..1809781b33e6 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 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 com.android.server.wm.flicker.testapp; + +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.Button; + +public class LaunchNewTaskActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.task_button); + + Button button = findViewById(R.id.launch_new_task); + button.setOnClickListener(v -> { + Intent intent = new Intent(LaunchNewTaskActivity.this, SimpleActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); + startActivity(intent); + }); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java new file mode 100644 index 000000000000..61019d8b3716 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 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 com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.os.Bundle; + +public class NonResizeableActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_non_resizeable); + + setShowWhenLocked(true); + setTurnScreenOn(true); + KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); + if (keyguardManager != null) { + keyguardManager.requestDismissKeyguard(this, null); + } + } +} diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index bcd6ed73e133..824f91e1e826 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -45,6 +45,7 @@ public final class FrameworksTestsFilter extends SelectTest { // Test specifications for FrameworksMockingCoreTests. "android.app.activity.ActivityThreadClientTest", "android.view.DisplayTest", + "android.window.ConfigurationHelperTest", // Test specifications for FrameworksCoreTests. "android.app.servertransaction.", // all tests under the package. "android.view.CutoutSpecificationTest", @@ -59,10 +60,8 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.RoundedCornersTest", "android.view.WindowMetricsTest", "android.view.PendingInsetsControllerTest", - "android.window.WindowContextTest", - "android.window.WindowMetricsHelperTest", + "android.window.", // all tests under the package. "android.app.activity.ActivityThreadTest", - "android.window.WindowContextControllerTest" }; public FrameworksTestsFilter(Bundle testArgs) { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index c9a8947ab5ef..a687bb893c4a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -118,6 +118,8 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { + doReturn(false).when(mDeps).isAirplaneModeOn(any()); + mGatewayConnection .getUnderlyingNetworkTrackerCallback() .onSelectedUnderlyingNetworkChanged(null); @@ -129,6 +131,19 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testNullNetworkAirplaneModeDisconnects() throws Exception { + doReturn(true).when(mDeps).isAirplaneModeOn(any()); + + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + verify(mIkeSession).kill(); + } + + @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() @@ -292,7 +307,10 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection ncCaptor.capture(), lpCaptor.capture(), any(), - argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE), + // Subtype integer/name and extras do not have getters; cannot be tested. + argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE + && nac.getLegacyTypeName().equals( + VcnGatewayConnection.NETWORK_INFO_NETWORK_TYPE_STRING)), any(), any(), any()); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index a7001713533c..b8eefabea3c6 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -16,6 +16,7 @@ package com.android.server.vcn; +import static android.net.IpSecManager.IpSecTunnelInterface; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; @@ -24,6 +25,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; +import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; @@ -36,8 +39,11 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.net.IpSecManager; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -59,8 +65,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.net.InetAddress; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -70,6 +79,8 @@ import java.util.UUID; public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { private static final int TEST_UID = Process.myUid() + 1; + private static final String LOOPBACK_IFACE = "lo"; + private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); private static final int TEST_SIM_SLOT_INDEX = 1; private static final int TEST_SUBSCRIPTION_ID_1 = 2; @@ -77,6 +88,12 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { private static final int TEST_SUBSCRIPTION_ID_2 = 3; private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class); private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP; + private static final String TEST_TCP_BUFFER_SIZES = "1,2,3,4,5,6"; + private static final int TEST_MTU = 1300; + private static final int TEST_MTU_DELTA = 64; + private static final List<LinkAddress> TEST_INTERNAL_ADDRESSES = + Arrays.asList(new LinkAddress(DUMMY_ADDR, 16)); + private static final List<InetAddress> TEST_DNS_ADDRESSES = Arrays.asList(DUMMY_ADDR); private static final int TEST_UPSTREAM_BANDWIDTH = 1234; private static final int TEST_DOWNSTREAM_BANDWIDTH = 2345; @@ -166,6 +183,59 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { } @Test + public void testBuildLinkProperties() throws Exception { + final IpSecTunnelInterface tunnelIface = + mContext.getSystemService(IpSecManager.class) + .createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network); + + final LinkProperties underlyingLp = new LinkProperties(); + underlyingLp.setInterfaceName(LOOPBACK_IFACE); + underlyingLp.setTcpBufferSizes(TEST_TCP_BUFFER_SIZES); + doReturn(TEST_MTU).when(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE); + + final VcnChildSessionConfiguration childSessionConfig = + mock(VcnChildSessionConfiguration.class); + doReturn(TEST_INTERNAL_ADDRESSES).when(childSessionConfig).getInternalAddresses(); + doReturn(TEST_DNS_ADDRESSES).when(childSessionConfig).getInternalDnsServers(); + + UnderlyingNetworkRecord record = + new UnderlyingNetworkRecord( + mock(Network.class, CALLS_REAL_METHODS), + new NetworkCapabilities.Builder().build(), + underlyingLp, + false); + + final LinkProperties vcnLp1 = + mGatewayConnection.buildConnectedLinkProperties( + VcnGatewayConnectionConfigTest.buildTestConfig(), + tunnelIface, + childSessionConfig, + record); + + verify(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE); + + // Instead of having to recalculate the final MTU (after accounting for IPsec overhead), + // calculate another set of Link Properties with a lower MTU, and calculate the delta. + doReturn(TEST_MTU - TEST_MTU_DELTA).when(mDeps).getUnderlyingIfaceMtu(LOOPBACK_IFACE); + + final LinkProperties vcnLp2 = + mGatewayConnection.buildConnectedLinkProperties( + VcnGatewayConnectionConfigTest.buildTestConfig(), + tunnelIface, + childSessionConfig, + record); + + verify(mDeps, times(2)).getUnderlyingIfaceMtu(LOOPBACK_IFACE); + + assertEquals(tunnelIface.getInterfaceName(), vcnLp1.getInterfaceName()); + assertEquals(TEST_INTERNAL_ADDRESSES, vcnLp1.getLinkAddresses()); + assertEquals(TEST_DNS_ADDRESSES, vcnLp1.getDnsServers()); + assertEquals(TEST_TCP_BUFFER_SIZES, vcnLp1.getTcpBufferSizes()); + assertEquals(TEST_MTU_DELTA, vcnLp1.getMtu() - vcnLp2.getMtu()); + } + + @Test public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { verifyWakeLockSetUp(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 5d2f9d748581..6d269686e42f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -58,6 +58,7 @@ import android.util.ArraySet; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; +import com.android.server.vcn.Vcn.VcnUserMobileDataStateListener; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; import org.junit.Before; @@ -208,6 +209,13 @@ public class VcnTest { } @Test + public void testMobileDataStateListenersRegistered() { + // Validate state from setUp() + verify(mTelephonyManager, times(3)) + .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); + } + + @Test public void testMobileDataStateCheckedOnInitialization_enabled() { // Validate state from setUp() assertTrue(mVcn.isMobileDataEnabled()); @@ -263,6 +271,24 @@ public class VcnTest { assertFalse(mVcn.isMobileDataEnabled()); } + @Test + public void testSubscriptionSnapshotUpdatesMobileDataStateListeners() { + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + + doReturn(new ArraySet<>(Arrays.asList(2, 4))) + .when(updatedSnapshot) + .getAllSubIdsInGroup(any()); + + mVcn.updateSubscriptionSnapshot(updatedSnapshot); + mTestLooper.dispatchAll(); + + verify(mTelephonyManager, times(4)) + .registerTelephonyCallback(any(), any(VcnUserMobileDataStateListener.class)); + verify(mTelephonyManager, times(2)) + .unregisterTelephonyCallback(any(VcnUserMobileDataStateListener.class)); + } + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { for (final int[] caps : TEST_CAPS) { startVcnGatewayWithCapabilities(requestListener, caps); @@ -402,24 +428,17 @@ public class VcnTest { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } - private void verifyMobileDataToggled(boolean startingToggleState, boolean endingToggleState) { - final ArgumentCaptor<ContentObserver> captor = - ArgumentCaptor.forClass(ContentObserver.class); - verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); - final ContentObserver contentObserver = captor.getValue(); - + private void setupForMobileDataTest(boolean startingToggleState) { // Start VcnGatewayConnections final NetworkRequestListener requestListener = verifyAndGetRequestListener(); mVcn.setMobileDataEnabled(startingToggleState); triggerVcnRequestListeners(requestListener); - final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = - mVcn.getVcnGatewayConnectionConfigMap(); - - // Trigger data toggle change. - doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); - contentObserver.onChange(false /* selfChange, ignored */); - mTestLooper.dispatchAll(); + } + private void verifyMobileDataToggledUpdatesGatewayConnections( + boolean startingToggleState, + boolean endingToggleState, + Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways) { // Verify that data toggle changes restart ONLY INTERNET or DUN networks, and only if the // toggle state changed. for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : gateways.entrySet()) { @@ -433,29 +452,98 @@ public class VcnTest { } } + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); if (startingToggleState != endingToggleState) { verify(mVcnNetworkProvider).resendAllRequests(requestListener); } assertEquals(endingToggleState, mVcn.isMobileDataEnabled()); } + private void verifyGlobalMobileDataToggled( + boolean startingToggleState, boolean endingToggleState) { + setupForMobileDataTest(startingToggleState); + final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change + final ArgumentCaptor<ContentObserver> captor = + ArgumentCaptor.forClass(ContentObserver.class); + verify(mContentResolver).registerContentObserver(any(), anyBoolean(), captor.capture()); + final ContentObserver contentObserver = captor.getValue(); + + doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); + contentObserver.onChange(false /* selfChange, ignored */); + mTestLooper.dispatchAll(); + + // Verify resultant behavior + verifyMobileDataToggledUpdatesGatewayConnections( + startingToggleState, endingToggleState, gateways); + } + + @Test + public void testGlobalMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataObserverFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); + } + + @Test + public void testGlobalMobileDataObserverFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + } + + private void verifySubscriptionMobileDataToggled( + boolean startingToggleState, boolean endingToggleState) { + setupForMobileDataTest(startingToggleState); + final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> gateways = + mVcn.getVcnGatewayConnectionConfigMap(); + + // Trigger data toggle change. + final ArgumentCaptor<VcnUserMobileDataStateListener> captor = + ArgumentCaptor.forClass(VcnUserMobileDataStateListener.class); + verify(mTelephonyManager, times(3)).registerTelephonyCallback(any(), captor.capture()); + final VcnUserMobileDataStateListener listener = captor.getValue(); + + doReturn(endingToggleState).when(mTelephonyManager).isDataEnabled(); + listener.onUserMobileDataStateChanged(false /* enabled, ignored */); + mTestLooper.dispatchAll(); + + // Verify resultant behavior + verifyMobileDataToggledUpdatesGatewayConnections( + startingToggleState, endingToggleState, gateways); + } + @Test - public void testMobileDataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, true /* endingToggleState */); } @Test - public void testMobileDataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataDisabled() { + verifyGlobalMobileDataToggled( + true /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataEnabled() { - verifyMobileDataToggled(false /* startingToggleState */, false /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataEnabled() { + verifyGlobalMobileDataToggled( + false /* startingToggleState */, false /* endingToggleState */); } @Test - public void testMobileDataObserverFiredWithoutChanges_dataDisabled() { - verifyMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); + public void testSubscriptionMobileDataListenerFiredWithoutChanges_dataDisabled() { + verifyGlobalMobileDataToggled(true /* startingToggleState */, true /* endingToggleState */); } } |