Merge "Fix pip expand tests (via button, intent and double click)"

This commit is contained in:
Nataniel Borges
2021-08-11 10:29:04 +00:00
committed by Android (Google) Code Review
7 changed files with 445 additions and 198 deletions

View File

@@ -17,11 +17,14 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.graphics.Rect
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 androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
@@ -113,25 +116,25 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
}
private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
val windowRegion = wmHelper.getWindowRegion(component)
require(!windowRegion.isEmpty) {
"Unable to find a PIP window in the current state"
}
return windowRegion.bounds
}
/**
* 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 windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"))
requireNotNull(exitPipObject) { "PIP window dismiss button not found" }
?: error("PIP window dismiss button not found")
val dismissButtonBounds = exitPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
@@ -142,32 +145,28 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
/**
* Click once on the PIP window to expand it
* Close the pip window by pressing the expand button
*/
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
fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// Ensure WindowManagerService wait until all animations have completed
wmHelper.waitForAppTransitionIdle()
mInstrumentation.uiAutomation.syncInputTransactions()
// search and interact with the expand button
val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
val expandPipObject = uiDevice.findObject(expandSelector)
?: error("PIP window expand button not found")
val expandButtonBounds = expandPipObject.visibleBounds
uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
}
/**
* Double click on the PIP window to reopen to app
* Double click on the PIP window to expand it
*/
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
fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
}

View File

@@ -64,7 +64,6 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
transitions {
pipApp.clickEnterPipButton(wmHelper)
pipApp.expandPipWindow(wmHelper)
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.traces.parser.toLayerName
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Test
/**
* Base class for pip expand tests
*/
abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
protected val testApp = FixedAppHelper(instrumentation)
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
* animation
*/
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
testSpec.assertWm {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks that the pip app layer remains inside the display bounds throughout the whole
* animation
*/
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
testSpec.assertLayers {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks both app windows are visible at the start of the transition (with [pipApp] on top).
* Then, during the transition, [testApp] becomes invisible and [pipApp] remains visible
*/
@Presubmit
@Test
open fun showBothAppWindowsThenHidePip() {
testSpec.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
isAppWindowVisible(testApp.component, ignoreActivity = true)
.isAppWindowOnTop(pipApp.component)
.then()
.isAppWindowInvisible(testApp.component)
.isAppWindowVisible(pipApp.component)
}
}
/**
* Checks both app layers are visible at the start of the transition. Then, during the
* transition, [testApp] becomes invisible and [pipApp] remains visible
*/
@Presubmit
@Test
open fun showBothAppLayersThenHidePip() {
testSpec.assertLayers {
isVisible(testApp.component)
.isVisible(pipApp.component)
.then()
.isInvisible(testApp.component)
.isVisible(pipApp.component)
}
}
/**
* Checks that the visible region of [testApp] plus the visible region of [pipApp]
* cover the full display area at the start of the transition
*/
@Presubmit
@Test
open fun testPlusPipAppsCoverFullScreenAtStart() {
testSpec.assertLayersStart {
val pipRegion = visibleRegion(pipApp.component).region
visibleRegion(testApp.component)
.plus(pipRegion)
.coversExactly(displayBounds)
}
}
/**
* Checks that the visible region of [pipApp] covers the full display area at the end of
* the transition
*/
@Presubmit
@Test
open fun pipAppCoversFullScreenAtEnd() {
testSpec.assertLayersEnd {
visibleRegion(pipApp.component).coversExactly(displayBounds)
}
}
/**
* Checks that the visible region of [pipApp] always expands during the animation
*/
@Presubmit
@Test
open fun pipLayerExpands() {
val layerName = pipApp.component.toLayerName()
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.platform.test.annotations.Postsubmit
import android.view.Surface
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.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test expanding a pip window back to full screen via the expand button
*
* To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
*
* Actions:
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 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)
@Group3
@Postsubmit
class ExitPipViaExpandButtonClickTest(
testSpec: FlickerTestParameter
) : ExitPipToAppTransition(testSpec) {
/**
* Defines the transition used to run the test
*/
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
// launch an app behind the pip one
testApp.launchViaIntent(wmHelper)
}
}
transitions {
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
}
}
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> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
}

View File

@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -25,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter
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.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,23 +31,38 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test Pip launch and exit.
* To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
* Test expanding a pip window back to full screen via an intent
*
* To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
*
* Actions:
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen via an intent
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited from [PipTransition]
* 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)
@Group3
class EnterExitPipTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
private val testApp = FixedAppHelper(instrumentation)
class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
/**
* Defines the transition used to run the test
*/
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
// launch an app behind the pip one
testApp.launchViaIntent(wmHelper)
}
}
@@ -59,57 +72,17 @@ class EnterExitPipTest(
}
}
@Presubmit
@Test
fun pipAppRemainInsideVisibleBounds() {
testSpec.assertWm {
coversAtMost(displayBounds, pipApp.component)
}
}
@Postsubmit
@Test
fun showBothAppWindowsThenHidePip() {
testSpec.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
isAppWindowVisible(testApp.component, ignoreActivity = true)
.isAppWindowOnTop(pipApp.component)
.then()
.isAppWindowInvisible(testApp.component)
}
}
@Presubmit
@Test
fun showBothAppLayersThenHidePip() {
testSpec.assertLayers {
isVisible(testApp.component)
.isVisible(pipApp.component)
.then()
.isInvisible(testApp.component)
}
}
@Presubmit
@Test
fun testAppCoversFullScreenWithPipOnDisplay() {
testSpec.assertLayersStart {
visibleRegion(testApp.component).coversExactly(displayBounds)
visibleRegion(pipApp.component).coversAtMost(displayBounds)
}
}
@Presubmit
@Test
fun pipAppCoversFullScreen() {
testSpec.assertLayersEnd {
visibleRegion(pipApp.component).coversExactly(displayBounds)
}
}
override fun pipLayerExpands() = super.pipLayerExpands()
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> {

View File

@@ -0,0 +1,166 @@
/*
* 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.platform.test.annotations.Postsubmit
import android.view.Surface
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.traces.parser.toLayerName
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test expanding a pip window by double clicking it
*
* To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
*
* Actions:
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 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)
@Group3
@Postsubmit
class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
transitions {
pipApp.doubleClickPipWindow(wmHelper)
}
}
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
* animation
*/
@Test
fun pipWindowRemainInsideVisibleBounds() {
testSpec.assertWm {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks that the pip app layer remains inside the display bounds throughout the whole
* animation
*/
@Test
fun pipLayerRemainInsideVisibleBounds() {
testSpec.assertLayers {
coversAtMost(displayBounds, pipApp.component)
}
}
/**
* Checks [pipApp] window remains visible throughout the animation
*/
@Test
fun pipWindowIsAlwaysVisible() {
testSpec.assertWm {
isAppWindowVisible(pipApp.component)
}
}
/**
* Checks [pipApp] layer remains visible throughout the animation
*/
@Test
fun pipLayerIsAlwaysVisible() {
testSpec.assertLayers {
isVisible(pipApp.component)
}
}
/**
* Checks that the visible region of [pipApp] always expands during the animation
*/
@Test
fun pipLayerExpands() {
val layerName = pipApp.component.toLayerName()
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
}
}
}
@Test
fun windowIsAlwaysPinned() {
testSpec.assertWm {
this.invoke("hasPipWindow") { it.isPinned(pipApp.component) }
}
}
/**
* Checks [pipApp] layer remains visible throughout the animation
*/
@Test
fun launcherIsAlwaysVisible() {
testSpec.assertLayers {
isVisible(LAUNCHER_COMPONENT)
}
}
/**
* Checks that the focus doesn't change between windows during the transition
*/
@FlakyTest
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
this.focusDoesNotChange()
}
}
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> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}

View File

@@ -1,113 +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.view.Surface
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
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 launch.
* To run this test: `atest WMShellFlickerTests:PipToAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
}
}
teardown {
eachRun {
this.setRotation(Surface.ROTATION_0)
}
}
transitions {
pipApp.expandPipWindowToApp(wmHelper)
}
}
@FlakyTest
@Test
fun appReplacesPipWindow() {
testSpec.assertWm {
this.invoke("hasPipWindow") { it.isPinned(pipApp.component) }
.isAppWindowOnTop(pipApp.component)
.then()
.invoke("hasNotPipWindow") { it.isNotPinned(pipApp.component) }
.isAppWindowOnTop(pipApp.component)
}
}
@FlakyTest
@Test
fun appReplacesPipLayer() {
testSpec.assertLayers {
this.isVisible(pipApp.component)
.isVisible(LAUNCHER_COMPONENT)
.then()
.isVisible(pipApp.component)
.isInvisible(LAUNCHER_COMPONENT)
}
}
@FlakyTest
@Test
fun testAppCoversFullScreen() {
testSpec.assertLayersStart {
visibleRegion(pipApp.component).coversExactly(displayBounds)
}
}
@FlakyTest(bugId = 151179149)
@Test
fun focusChanges() {
testSpec.assertEventLog {
this.focusChanges("NexusLauncherActivity",
pipApp.launcherName, "NexusLauncherActivity")
}
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
}