Migrate Pip tests to new DSL format

Make sure the test can run multiple times and is stable on flame

Introduce methods to allow waitForPipWindowShow/Gone using the WindowManagerState to determine if a PIP window is active (windowingMode = PINNED), since the UiDevice method is unstable and doesn't work on TVs.

Bug: 171049720
Bug: 172321238
Bug: 171049762
Test: atest WMShellFlickerTests
Change-Id: Iade19fadeb0ba07e047eb5f64479e592920ca11e
This commit is contained in:
Nataniel Borges
2021-02-01 14:10:00 +01:00
parent 015c5af31d
commit 068156db02
16 changed files with 844 additions and 644 deletions

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
@file:JvmName("Utils")
package com.android.wm.shell.flicker
import android.app.ActivityTaskManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
fun removeAllTasksButHome() {
val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
ACTIVITY_TYPE_UNDEFINED)
val atm = ActivityTaskManager.getService()
atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
}

View File

@@ -32,13 +32,12 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
/**
* Opens the IME and wait for it to be displayed
*
* @param device UIDevice instance to interact with the device
* @param wmHelper Helper used to wait for WindowManager states
*/
@JvmOverloads
open fun openIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
open fun openIME(wmHelper: WindowManagerStateHelper? = null) {
if (!isTelevision) {
val editText = device.wait(
val editText = uiDevice.wait(
Until.findObject(By.res(getPackage(), "plain_text_input")),
FIND_TIMEOUT)
@@ -47,7 +46,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
waitAndAssertIMEShown(device, wmHelper)
waitAndAssertIMEShown(uiDevice, wmHelper)
} else {
// If we do the same thing as above - editText.click() - on TV, that's going to force TV
// into the touch mode. We really don't want that.
@@ -69,16 +68,15 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
/**
* Opens the IME and wait for it to be gone
*
* @param device UIDevice instance to interact with the device
* @param wmHelper Helper used to wait for WindowManager states
*/
@JvmOverloads
open fun closeIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
open fun closeIME(wmHelper: WindowManagerStateHelper? = null) {
if (!isTelevision) {
device.pressBack()
uiDevice.pressBack()
// Using only the AccessibilityInfo it is not possible to identify if the IME is active
if (wmHelper == null) {
device.waitForIdle()
uiDevice.waitForIdle()
} else {
require(wmHelper.waitImeWindowGone()) { "IME did did not close" }
}

View File

@@ -17,17 +17,20 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.graphics.Point
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.os.SystemClock
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
import com.android.wm.shell.flicker.pip.waitPipWindowGone
import com.android.wm.shell.flicker.pip.waitPipWindowShown
import com.android.wm.shell.flicker.testapp.Components
import org.junit.Assert.fail
class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
@@ -55,6 +58,17 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
}
/** {@inheritDoc} */
override fun launchViaIntent(
wmHelper: WindowManagerStateHelper,
expectedWindowName: String,
action: String?,
stringExtras: Map<String, String>
) {
super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
wmHelper.waitPipWindowShown()
}
private fun focusOnObject(selector: BySelector): Boolean {
// We expect all the focusable UI elements to be arranged in a way so that it is possible
// to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
@@ -69,16 +83,12 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
return false
}
fun clickEnterPipButton() {
@JvmOverloads
fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) {
clickObject(ENTER_PIP_BUTTON_ID)
// TODO(b/172321238): remove this check once hasPipWindow is fixed on TVs
if (!isTelevision) {
uiDevice.hasPipWindow()
} else {
// Simply wait for 3 seconds
SystemClock.sleep(3_000)
}
// Wait on WMHelper or simply wait for 3 seconds
wmHelper?.waitPipWindowShown() ?: SystemClock.sleep(3_000)
}
fun clickStartMediaSessionButton() {
@@ -97,16 +107,75 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
fun stopMedia() = mediaController?.transportControls?.stop()
?: error("No active media session found")
@Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead",
ReplaceWith("closePipWindow(wmHelper)"))
fun closePipWindow() {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
uiDevice.closePipWindow()
}
}
if (!waitUntilClosed()) {
fail("Couldn't close Pip")
/**
* Expands the pip window and dismisses it by clicking on the X button.
*
* Note, currently the View coordinates reported by the accessibility are relative to
* the window, so the correct coordinates need to be calculated
*
* For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the
* dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in
* Point(716, 66), instead of Point(970, 1403)
*
* See b/179337864
*/
fun closePipWindow(wmHelper: WindowManagerStateHelper) {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
expandPipWindow(wmHelper)
val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"))
requireNotNull(exitPipObject) { "PIP window dismiss button not found" }
val coordinatesInWindow = exitPipObject.visibleBounds
val windowOffset = wmHelper.getWindowRegion(component).bounds
val newCoordinates = Point(windowOffset.left + coordinatesInWindow.centerX(),
windowOffset.top + coordinatesInWindow.centerY())
uiDevice.click(newCoordinates.x, newCoordinates.y)
}
// Wait for animation to complete.
wmHelper.waitPipWindowGone()
wmHelper.waitForHomeActivityVisible()
}
/**
* Click once on the PIP window to expand it
*/
fun expandPipWindow(wmHelper: WindowManagerStateHelper) {
val windowRegion = wmHelper.getWindowRegion(component)
require(!windowRegion.isEmpty) {
"Unable to find a PIP window in the current state"
}
val windowRect = windowRegion.bounds
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// Ensure WindowManagerService wait until all animations have completed
wmHelper.waitForAppTransitionIdle()
mInstrumentation.uiAutomation.syncInputTransactions()
}
/**
* Double click on the PIP window to reopen to app
*/
fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
val windowRegion = wmHelper.getWindowRegion(component)
require(!windowRegion.isEmpty) {
"Unable to find a PIP window in the current state"
}
val windowRect = windowRegion.bounds
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
wmHelper.waitPipWindowGone()
wmHelper.waitForAppTransitionIdle()
}
companion object {

View File

@@ -16,11 +16,6 @@
package com.android.wm.shell.flicker.pip
import android.app.ActivityTaskManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.os.SystemClock
import com.android.wm.shell.flicker.NonRotationTestBase
@@ -29,14 +24,6 @@ abstract class AppTestBase(
rotation: Int
) : NonRotationTestBase(rotationName, rotation) {
companion object {
fun removeAllTasksButHome() {
val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf(
ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
ACTIVITY_TYPE_UNDEFINED)
val atm = ActivityTaskManager.getService()
atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME)
}
fun waitForAnimationComplete() {
// TODO: UiDevice doesn't have reliable way to wait for the completion of animation.
// Consider to introduce WindowManagerStateHelper to access Activity state.

View File

@@ -16,21 +16,21 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.dsl.runFlicker
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,82 +39,67 @@ import org.junit.runners.Parameterized
* Test Pip launch and exit.
* To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EnterExitPipTest(
rotationName: String,
rotation: Int
) : AppTestBase(rotationName, rotation) {
private val pipApp = PipAppHelper(instrumentation)
private val testApp = FixedAppHelper(instrumentation)
@Test
fun testDisplayMetricsPinUnpin() {
runFlicker(instrumentation) {
withTestName { "testDisplayMetricsPinUnpin" }
setup {
test {
removeAllTasksButHome()
device.wakeUpAndGoToHomeScreen()
pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
testApp.launchViaIntent()
waitForAnimationComplete()
}
}
transitions {
// This will bring PipApp to fullscreen
pipApp.launchViaIntent()
waitForAnimationComplete()
}
teardown {
test {
removeAllTasksButHome()
}
}
assertions {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
windowManagerTrace {
all("pipApp must remain inside visible bounds") {
coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
}
all("Initially shows both app windows then pipApp hides testApp") {
showsAppWindow(testApp.defaultWindowName)
.showsAppWindowOnTop(pipApp.defaultWindowName)
.then()
.hidesAppWindow(testApp.defaultWindowName)
}
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
all("Initially shows both app layers then pipApp hides testApp") {
showsLayer(testApp.defaultWindowName)
.showsLayer(pipApp.defaultWindowName)
.then()
.hidesLayer(testApp.defaultWindowName)
}
start("testApp covers the fullscreen, pipApp remains inside display") {
hasVisibleRegion(testApp.defaultWindowName, displayBounds)
coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
}
end("pipApp covers the fullscreen") {
hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
}
navBarLayerIsAlwaysVisible()
statusBarLayerIsAlwaysVisible()
}
}
}
}
companion object {
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val supportedRotations = intArrayOf(Surface.ROTATION_0)
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
fun getParams(): List<Array<Any>> {
val testApp = FixedAppHelper(instrumentation)
val baseConfig = getTransitionLaunch(eachRun = true)
val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
}
}
transitions {
// This will bring PipApp to fullscreen
pipApp.launchViaIntent(wmHelper)
}
assertions {
val displayBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
presubmit {
windowManagerTrace {
all("pipApp must remain inside visible bounds") {
coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
}
all("Initially shows both app windows then pipApp hides testApp") {
showsAppWindow(testApp.defaultWindowName)
.showsAppWindowOnTop(pipApp.defaultWindowName)
.then()
.hidesAppWindow(testApp.defaultWindowName)
}
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
all("Initially shows both app layers then pipApp hides testApp") {
showsLayer(testApp.defaultWindowName)
.showsLayer(pipApp.defaultWindowName)
.then()
.hidesLayer(testApp.defaultWindowName)
}
start("testApp covers the fullscreen, pipApp remains inside display") {
hasVisibleRegion(testApp.defaultWindowName, displayBounds)
coversAtMostRegion(displayBounds, pipApp.defaultWindowName)
}
end("pipApp covers the fullscreen") {
hasVisibleRegion(pipApp.defaultWindowName, displayBounds)
}
navBarLayerIsAlwaysVisible()
statusBarLayerIsAlwaysVisible()
}
}
}
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}

View File

@@ -16,18 +16,13 @@
package com.android.wm.shell.flicker.pip
import android.os.Bundle
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.expandPipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -35,9 +30,7 @@ import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -50,80 +43,58 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class EnterPipTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val testApp = PipAppHelper(instrumentation)
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
withTestName { buildTestTag("enterPip", testApp, configuration) }
repeat { configuration.repetitions }
setup {
test {
device.wakeUpAndGoToHomeScreen()
}
eachRun {
device.pressHome()
testApp.launchViaIntent(wmHelper)
this.setRotation(configuration.startRotation)
}
}
teardown {
eachRun {
if (device.hasPipWindow()) {
device.closePipWindow()
}
testApp.exit()
this.setRotation(Surface.ROTATION_0)
}
test {
if (device.hasPipWindow()) {
device.closePipWindow()
}
}
}
transitions {
testApp.clickEnterPipButton()
device.expandPipWindow()
}
assertions {
val baseConfig = getTransitionLaunch(
eachRun = true, stringExtras = emptyMap())
val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
transitions {
pipApp.clickEnterPipButton()
pipApp.expandPipWindow(wmHelper)
}
assertions {
presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
all("pipWindowBecomesVisible") {
this.showsAppWindow(testApp.`package`)
.then()
.showsAppWindow(PIP_WINDOW_TITLE)
this.showsAppWindow(pipApp.defaultWindowName)
}
}
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible()
noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
enabled = false)
navBarLayerRotatesAndScales(configuration.startRotation,
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
}
layersTrace {
all("pipLayerBecomesVisible") {
this.showsLayer(testApp.launcherName)
.then()
.showsLayer(PIP_WINDOW_TITLE)
this.showsLayer(pipApp.launcherName)
}
}
}
flaky {
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
navBarLayerRotatesAndScales(configuration.startRotation,
Surface.ROTATION_0, bugId = 140855415)
}
}
}
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.wm.shell.flicker.pip
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip with orientation changes.
* To run this test: `atest WMShellFlickerTests:PipOrientationTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EnterPipToOtherOrientationTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
private val testApp = FixedAppHelper(instrumentation)
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5) { configuration ->
setupAndTeardown(this, configuration)
setup {
eachRun {
// Launch a portrait only app on the fullscreen stack
testApp.launchViaIntent(wmHelper, stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
// Launch the PiP activity fixed as landscape
pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
}
}
teardown {
eachRun {
pipApp.exit()
testApp.exit()
}
}
transitions {
// Enter PiP, and assert that the PiP is within bounds now that the device is back
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
wmHelper.waitPipWindowShown()
wmHelper.waitForAppTransitionIdle()
}
assertions {
val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
presubmit {
windowManagerTrace {
all("pipApp window is always on top") {
showsAppWindowOnTop(pipApp.defaultWindowName)
}
start("pipApp window hides testApp") {
isInvisible(testApp.defaultWindowName)
}
end("testApp windows is shown") {
isVisible(testApp.defaultWindowName)
}
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
start("pipApp layer hides testApp") {
hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
isInvisible(testApp.defaultWindowName)
}
}
}
flaky {
layersTrace {
end("testApp layer covers fullscreen") {
hasVisibleRegion(testApp.defaultWindowName, endingBounds)
}
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible(bugId = 140855415)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.wm.shell.flicker.pip
import android.app.WindowConfiguration
import android.content.ComponentName
import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject
import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.parser.toWindowName
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.google.common.truth.Truth
inline val WindowManagerState.pinnedWindows
get() = visibleWindows
.filter { it.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED }
/**
* Checks if the state has any window in PIP mode
*/
fun WindowManagerState.hasPipWindow(): Boolean = pinnedWindows.isNotEmpty()
/**
* Checks that an activity [activity] is in PIP mode
*/
fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean {
val windowName = activity.toWindowName()
return pinnedWindows.any { it.title == windowName }
}
/**
* Asserts that an activity [activity] exists and is in PIP mode
*/
fun WindowManagerStateSubject.isInPipMode(
activity: ComponentName
): WindowManagerStateSubject = apply {
val windowName = activity.toWindowName()
hasWindow(windowName)
val pinnedWindows = wmState.pinnedWindows
.map { it.title }
Truth.assertWithMessage("Window not in PIP mode")
.that(pinnedWindows)
.contains(windowName)
}
/**
* Waits until the state has a window in PIP mode, i.e., with
* windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
*/
fun WindowManagerStateHelper.waitPipWindowShown(): Boolean =
waitFor("PIP window shown") {
val result = it.wmState.hasPipWindow()
result
}
/**
* Waits until the state doesn't have a window in PIP mode, i.e., with
* windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED
*/
fun WindowManagerStateHelper.waitPipWindowGone(): Boolean =
waitFor("PIP window gone") {
val result = !it.wmState.hasPipWindow()
result
}

View File

@@ -16,21 +16,19 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.dsl.runWithFlicker
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
import com.android.wm.shell.flicker.IME_WINDOW_NAME
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import com.android.wm.shell.flicker.testapp.Components
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,114 +37,61 @@ import org.junit.runners.Parameterized
* Test Pip launch.
* To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipKeyboardTest(
rotationName: String,
rotation: Int
) : PipTestBase(rotationName, rotation) {
private val keyboardApp = ImeAppHelper(instrumentation)
private val keyboardComponent = Components.ImeActivity.COMPONENT
private val helper = WindowManagerStateHelper()
private val keyboardScenario: FlickerBuilder
get() = FlickerBuilder(instrumentation).apply {
repeat { TEST_REPETITIONS }
// disable layer tracing
withLayerTracing { null }
setup {
test {
device.wakeUpAndGoToHomeScreen()
device.pressHome()
// launch our target pip app
testApp.launchViaIntent(wmHelper)
this.setRotation(rotation)
testApp.clickEnterPipButton()
// open an app with an input field and a keyboard
// UiAutomator doesn't support to launch the multiple Activities in a task.
// So use launchActivity() for the Keyboard Activity.
keyboardApp.launchViaIntent()
helper.waitForAppTransitionIdle()
helper.waitForFullScreenApp(keyboardComponent)
}
}
teardown {
test {
keyboardApp.exit()
if (device.hasPipWindow()) {
device.closePipWindow()
}
testApp.exit()
this.setRotation(Surface.ROTATION_0)
}
}
}
/** Ensure the pip window remains visible throughout any keyboard interactions. */
@Test
fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() {
val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose"
runWithFlicker(keyboardScenario) {
withTestName { testTag }
transitions {
// open the soft keyboard
keyboardApp.openIME(device, wmHelper)
helper.waitImeWindowShown()
// then close it again
keyboardApp.closeIME(device, wmHelper)
helper.waitImeWindowGone()
}
assertions {
windowManagerTrace {
all("PiP window must remain inside visible bounds") {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
coversAtMostRegion(testApp.defaultWindowName, displayBounds)
}
}
}
}
}
/** Ensure the pip window does not obscure the keyboard. */
@Test
fun pipWindow_doesNotObscure_keyboard() {
val testTag = "pipWindow_doesNotObscure_keyboard"
runWithFlicker(keyboardScenario) {
withTestName { testTag }
transitions {
// open the soft keyboard
keyboardApp.openIME(device, wmHelper)
helper.waitImeWindowShown()
}
teardown {
eachRun {
// close the keyboard
keyboardApp.closeIME(device, wmHelper)
helper.waitImeWindowGone()
}
}
assertions {
windowManagerTrace {
end("imeWindowAboveApp") {
isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName)
}
}
}
}
}
companion object {
private const val TEST_REPETITIONS = 5
class PipKeyboardTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTestRunner(testSpec) {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
private const val TAG_IME_VISIBLE = "imeIsVisible"
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val supportedRotations = intArrayOf(Surface.ROTATION_0)
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
val imeApp = ImeAppHelper(instrumentation)
val baseConfig = getTransitionLaunch(eachRun = false)
val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
setup {
test {
imeApp.launchViaIntent(wmHelper)
setRotation(configuration.startRotation)
}
}
teardown {
test {
imeApp.exit()
setRotation(Surface.ROTATION_0)
}
}
transitions {
// open the soft keyboard
imeApp.openIME(wmHelper)
createTag(TAG_IME_VISIBLE)
// then close it again
imeApp.closeIME(wmHelper)
}
assertions {
presubmit {
windowManagerTrace {
// Ensure the pip window remains visible throughout
// any keyboard interactions
all("pipInVisibleBounds") {
val displayBounds = WindowUtils.getDisplayBounds(
configuration.startRotation)
coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
}
// Ensure that the pip window does not obscure the keyboard
tag(TAG_IME_VISIBLE) {
isAboveWindow(IME_WINDOW_NAME, pipApp.defaultWindowName)
}
}
}
}
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}

View File

@@ -32,6 +32,7 @@ import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.removeAllTasksButHome
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test

View File

@@ -1,205 +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.wm.shell.flicker.pip
import android.content.Intent
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.dsl.runFlicker
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
import org.junit.Assert.assertEquals
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip with orientation changes.
* To run this test: `atest WMShellFlickerTests:PipOrientationTest`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipOrientationTest(
rotationName: String,
rotation: Int
) : AppTestBase(rotationName, rotation) {
// Helper class to process test actions by broadcast.
private inner class BroadcastActionTrigger {
private fun createIntentWithAction(broadcastAction: String): Intent {
return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
}
fun doAction(broadcastAction: String) {
instrumentation.getContext().sendBroadcast(createIntentWithAction(broadcastAction))
}
fun requestOrientationForPip(orientation: Int) {
instrumentation.getContext()
.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
.putExtra(EXTRA_PIP_ORIENTATION, orientation.toString()))
}
}
private val broadcastActionTrigger = BroadcastActionTrigger()
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
private val ORIENTATION_LANDSCAPE = 0
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
private val ORIENTATION_PORTRAIT = 1
private val testApp = FixedAppHelper(instrumentation)
private val pipApp = PipAppHelper(instrumentation)
@Test
fun testEnterPipToOtherOrientation() {
runFlicker(instrumentation) {
withTestName { "testEnterPipToOtherOrientation" }
setup {
test {
removeAllTasksButHome()
device.wakeUpAndGoToHomeScreen()
// Launch a portrait only app on the fullscreen stack
testApp.launchViaIntent(stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()))
waitForAnimationComplete()
// Launch the PiP activity fixed as landscape
pipApp.launchViaIntent(stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
waitForAnimationComplete()
}
}
transitions {
// Enter PiP, and assert that the PiP is within bounds now that the device is back
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
waitForAnimationComplete()
}
teardown {
test {
removeAllTasksButHome()
}
}
assertions {
windowManagerTrace {
all("pipApp window is always on top") {
showsAppWindowOnTop(pipApp.defaultWindowName)
}
start("pipApp window hides testApp") {
isInvisible(testApp.defaultWindowName)
}
end("testApp windows is shown") {
isVisible(testApp.defaultWindowName)
}
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
start("pipApp layer hides testApp") {
hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
isInvisible(testApp.defaultWindowName)
}
end("testApp layer covers fullscreen", enabled = false) {
hasVisibleRegion(testApp.defaultWindowName, endingBounds)
}
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible(bugId = 140855415)
}
}
}
}
@Test
fun testSetRequestedOrientationWhilePinned() {
runFlicker(instrumentation) {
withTestName { "testSetRequestedOrientationWhilePinned" }
setup {
test {
removeAllTasksButHome()
device.wakeUpAndGoToHomeScreen()
// Launch the PiP activity fixed as landscape
pipApp.launchViaIntent(stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
EXTRA_ENTER_PIP to "true"))
waitForAnimationComplete()
assertEquals(Surface.ROTATION_0, device.displayRotation)
}
}
transitions {
// Request that the orientation is set to landscape
broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
// Launch the activity back into fullscreen and ensure that it is now in landscape
pipApp.launchViaIntent()
waitForAnimationComplete()
assertEquals(Surface.ROTATION_90, device.displayRotation)
}
teardown {
test {
removeAllTasksButHome()
}
}
assertions {
val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
windowManagerTrace {
start("PIP window must remain inside display") {
coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
}
end("pipApp shows on top") {
showsAppWindowOnTop(pipApp.defaultWindowName)
}
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
start("PIP layer must remain inside display") {
coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
}
end("pipApp layer covers fullscreen") {
hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
}
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible(bugId = 140855415)
}
}
}
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val supportedRotations = intArrayOf(Surface.ROTATION_0)
return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
}

View File

@@ -16,21 +16,18 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -38,7 +35,6 @@ import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -48,80 +44,77 @@ import org.junit.runners.Parameterized
* Test Pip Stack in bounds after rotations.
* To run this test: `atest WMShellFlickerTests:PipRotationTest`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipRotationTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val testApp = FixedAppHelper(instrumentation)
val pipApp = PipAppHelper(instrumentation)
return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
configuration ->
withTestName { buildTestTag("PipRotationTest", testApp, configuration) }
repeat { configuration.repetitions }
setup {
test {
AppTestBase.removeAllTasksButHome()
device.wakeUpAndGoToHomeScreen()
pipApp.launchViaIntent(stringExtras = mapOf(
EXTRA_ENTER_PIP to "true"))
testApp.launchViaIntent()
AppTestBase.waitForAnimationComplete()
}
eachRun {
setRotation(configuration.startRotation)
}
val fixedApp = FixedAppHelper(instrumentation)
val baseConfig = getTransitionLaunch(eachRun = false)
val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
setup {
test {
fixedApp.launchViaIntent(wmHelper)
}
eachRun {
setRotation(configuration.startRotation)
}
}
transitions {
setRotation(configuration.endRotation)
}
teardown {
eachRun {
setRotation(Surface.ROTATION_0)
}
}
assertions {
val startingBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
val endingBounds = WindowUtils.getDisplayBounds(configuration.endRotation)
presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
transitions {
setRotation(configuration.endRotation)
layersTrace {
noUncoveredRegions(configuration.startRotation,
configuration.endRotation, allStates = false)
}
teardown {
eachRun {
setRotation(Surface.ROTATION_0)
}
flaky {
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible(bugId = 140855415)
navBarLayerRotatesAndScales(configuration.startRotation,
configuration.endRotation, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
configuration.endRotation, bugId = 140855415)
start("appLayerRotates_StartingBounds", bugId = 140855415) {
hasVisibleRegion(fixedApp.defaultWindowName, startingBounds)
coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
}
test {
AppTestBase.removeAllTasksButHome()
}
}
assertions {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible(bugId = 140855415)
noUncoveredRegions(configuration.startRotation,
configuration.endRotation, allStates = false)
navBarLayerRotatesAndScales(configuration.startRotation,
configuration.endRotation, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
configuration.endRotation, bugId = 140855415)
}
layersTrace {
val startingBounds = WindowUtils.getDisplayBounds(
configuration.startRotation)
val endingBounds = WindowUtils.getDisplayBounds(
configuration.endRotation)
start("appLayerRotates_StartingBounds", bugId = 140855415) {
hasVisibleRegion(testApp.defaultWindowName, startingBounds)
coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
}
end("appLayerRotates_EndingBounds", bugId = 140855415) {
hasVisibleRegion(testApp.defaultWindowName, endingBounds)
coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
}
end("appLayerRotates_EndingBounds", bugId = 140855415) {
hasVisibleRegion(fixedApp.defaultWindowName, endingBounds)
coversAtMostRegion(endingBounds, pipApp.defaultWindowName)
}
}
}
}
}
return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
baseConfig, testSpec,
supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
repetitions = 5)
}
}
}

View File

@@ -16,29 +16,23 @@
package com.android.wm.shell.flicker.pip
import android.os.Bundle
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.expandPipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
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.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.repetitions
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.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -51,48 +45,30 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class PipToAppTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val testApp = PipAppHelper(instrumentation)
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
repeat { configuration.repetitions }
setup {
test {
device.wakeUpAndGoToHomeScreen()
device.pressHome()
testApp.launchViaIntent(wmHelper)
}
eachRun {
this.setRotation(configuration.startRotation)
testApp.clickEnterPipButton()
device.hasPipWindow()
}
val baseConfig = getTransitionLaunch(eachRun = true)
val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
}
teardown {
eachRun {
this.setRotation(Surface.ROTATION_0)
}
test {
if (device.hasPipWindow()) {
device.closePipWindow()
}
testApp.exit()
}
}
teardown {
eachRun {
this.setRotation(Surface.ROTATION_0)
}
transitions {
device.expandPipWindow()
device.waitForIdle()
}
assertions {
}
transitions {
pipApp.expandPipWindowToApp(wmHelper)
}
assertions {
presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
@@ -100,34 +76,42 @@ class PipToAppTest(
all("appReplacesPipWindow") {
this.showsAppWindow(PIP_WINDOW_TITLE)
.then()
.showsAppWindowOnTop(testApp.launcherName)
.showsAppWindowOnTop(pipApp.launcherName)
}
}
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible()
noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
enabled = false)
navBarLayerRotatesAndScales(configuration.startRotation,
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
all("appReplacesPipLayer") {
this.showsLayer(PIP_WINDOW_TITLE)
.then()
.showsLayer(testApp.launcherName)
.showsLayer(pipApp.launcherName)
}
}
}
flaky {
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
navBarLayerRotatesAndScales(configuration.startRotation,
Surface.ROTATION_0, bugId = 140855415)
}
eventLog {
focusChanges(
"NexusLauncherActivity", testApp.launcherName,
"NexusLauncherActivity", pipApp.launcherName,
"NexusLauncherActivity", bugId = 151179149)
}
}
}
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
}

View File

@@ -16,28 +16,23 @@
package com.android.wm.shell.flicker.pip
import android.os.Bundle
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
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.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.repetitions
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.wm.shell.flicker.helpers.PipAppHelper
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -50,50 +45,30 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class PipToHomeTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val testApp = PipAppHelper(instrumentation)
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
repeat { configuration.repetitions }
setup {
test {
device.wakeUpAndGoToHomeScreen()
device.pressHome()
}
eachRun {
testApp.launchViaIntent(wmHelper)
this.setRotation(configuration.startRotation)
testApp.clickEnterPipButton()
device.hasPipWindow()
}
val baseConfig = getTransitionLaunch(eachRun = true)
val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
}
teardown {
eachRun {
this.setRotation(Surface.ROTATION_0)
if (device.hasPipWindow()) {
device.closePipWindow()
}
}
test {
if (device.hasPipWindow()) {
device.closePipWindow()
}
testApp.exit()
}
}
teardown {
eachRun {
this.setRotation(Surface.ROTATION_0)
}
transitions {
testApp.closePipWindow()
}
assertions {
}
transitions {
pipApp.closePipWindow(wmHelper)
}
assertions {
presubmit {
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
@@ -106,12 +81,7 @@ class PipToHomeTest(
}
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible()
noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
enabled = false)
navBarLayerRotatesAndScales(configuration.startRotation,
Surface.ROTATION_0, bugId = 140855415)
statusBarLayerRotatesScales(configuration.startRotation,
Surface.ROTATION_0)
@@ -121,13 +91,28 @@ class PipToHomeTest(
.hidesLayer(PIP_WINDOW_TITLE)
}
}
}
postsubmit {
layersTrace {
navBarLayerIsAlwaysVisible()
noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0)
navBarLayerRotatesAndScales(configuration.startRotation,
Surface.ROTATION_0)
}
}
flaky {
eventLog {
focusChanges(testApp.launcherName, "NexusLauncherActivity",
focusChanges(pipApp.launcherName, "NexusLauncherActivity",
bugId = 151179149)
}
}
}
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* 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.wm.shell.flicker.pip
import android.app.Instrumentation
import android.content.Intent
import android.os.Bundle
import android.view.Surface
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.wm.shell.flicker.removeAllTasksButHome
import com.android.wm.shell.flicker.testapp.Components
abstract class PipTransitionBase(protected val instrumentation: Instrumentation) {
// Helper class to process test actions by broadcast.
protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
private fun createIntentWithAction(broadcastAction: String): Intent {
return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
}
fun doAction(broadcastAction: String) {
instrumentation.context
.sendBroadcast(createIntentWithAction(broadcastAction))
}
fun requestOrientationForPip(orientation: Int) {
instrumentation.context.sendBroadcast(
createIntentWithAction(Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION)
.putExtra(Components.PipActivity.EXTRA_PIP_ORIENTATION, orientation.toString())
)
}
companion object {
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
@JvmStatic
val ORIENTATION_LANDSCAPE = 0
// Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
@JvmStatic
val ORIENTATION_PORTRAIT = 1
}
}
protected val pipApp = PipAppHelper(instrumentation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
/**
* Gets a configuration that handles basic setup and teardown of pip tests
*/
protected val setupAndTeardown: FlickerBuilder.(Bundle) -> Unit
get() = { configuration ->
withTestName { buildTestTag(configuration) }
repeat { configuration.repetitions }
setup {
test {
removeAllTasksButHome()
device.wakeUpAndGoToHomeScreen()
}
}
teardown {
eachRun {
setRotation(Surface.ROTATION_0)
}
test {
removeAllTasksButHome()
if (device.hasPipWindow()) {
device.closePipWindow()
}
pipApp.exit()
}
}
}
/**
* Gets a configuration that handles basic setup and teardown of pip tests and that
* launches the Pip app for test
*
* @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
* @param stringExtras Arguments to pass to the PIP launch intent
*/
@JvmOverloads
fun getTransitionLaunch(
eachRun: Boolean,
stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
): FlickerBuilder.(Bundle) -> Unit {
return { configuration ->
setupAndTeardown(this, configuration)
setup {
test {
removeAllTasksButHome()
if (!eachRun) {
pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
wmHelper.waitPipWindowShown()
}
}
eachRun {
if (eachRun) {
pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
wmHelper.waitPipWindowShown()
}
}
}
teardown {
eachRun {
if (eachRun) {
pipApp.exit()
}
}
test {
if (!eachRun) {
pipApp.exit()
}
removeAllTasksButHome()
}
}
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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.wm.shell.flicker.pip
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.Assert.assertEquals
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip with orientation changes.
* To run this test: `atest WMShellFlickerTests:PipOrientationTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class SetRequestedOrientationWhilePinnedTest(
testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<Array<Any>> {
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 1) { configuration ->
setupAndTeardown(this, configuration)
setup {
eachRun {
// Launch the PiP activity fixed as landscape
pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
EXTRA_ENTER_PIP to "true"))
}
}
teardown {
eachRun {
pipApp.exit()
}
}
transitions {
// Request that the orientation is set to landscape
broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
// Launch the activity back into fullscreen and
// ensure that it is now in landscape
pipApp.launchViaIntent(wmHelper)
wmHelper.waitForFullScreenApp(pipApp.component)
wmHelper.waitForRotation(Surface.ROTATION_90)
assertEquals(Surface.ROTATION_90, device.displayRotation)
}
assertions {
val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
presubmit {
windowManagerTrace {
start("PIP window must remain inside display") {
coversAtMostRegion(pipApp.defaultWindowName, startingBounds)
}
end("pipApp shows on top") {
showsAppWindowOnTop(pipApp.defaultWindowName)
}
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
}
layersTrace {
start("PIP layer must remain inside display") {
coversAtMostRegion(startingBounds, pipApp.defaultWindowName)
}
end("pipApp layer covers fullscreen") {
hasVisibleRegion(pipApp.defaultWindowName, endingBounds)
}
}
}
flaky {
layersTrace {
navBarLayerIsAlwaysVisible(bugId = 140855415)
statusBarLayerIsAlwaysVisible(bugId = 140855415)
}
}
}
}
}
}
}