Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 645dfa7f authored by Nataniel Borges's avatar Nataniel Borges
Browse files

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

1. Rename tests
  Expand to app via intent
  Expand to app via button
  Expand to max size via double click

2. Improve existing assertions

3. Create new assertions, e.g.:
    pip layer expands
    pip layer in bounds

4. Add documentation for test and assertions

Bug: 171049504
Bug: 174238218

Test: atest WMShellFlickerTests:com.android.wm.shell.flicker.pip
Change-Id: I9bfad1ef97ddc31be9d897114e23d4645b4a63d5
parent 768bdfe9
Loading
Loading
Loading
Loading
+28 −29
Original line number Diff line number Diff line
@@ -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()
    }

+0 −1
Original line number Diff line number Diff line
@@ -45,7 +45,6 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
        get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
            transitions {
                pipApp.clickEnterPipButton(wmHelper)
                pipApp.expandPipWindow(wmHelper)
            }
        }

+131 −0
Original line number Diff line number Diff line
/*
 * 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)
            }
        }
    }
}
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 * 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.
@@ -16,98 +16,77 @@

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.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`
 * 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
class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@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) { configuration ->
        get() = buildTransition(eachRun = true) {
            setup {
                eachRun {
                    this.setRotation(configuration.startRotation)
                }
            }
            teardown {
                eachRun {
                    this.setRotation(Surface.ROTATION_0)
                    // launch an app behind the pip one
                    testApp.launchViaIntent(wmHelper)
                }
            }
            transitions {
                // This will bring PipApp to fullscreen
                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 {
        /**
         * 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)
            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
                    supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
        }
    }
}
 No newline at end of file
+28 −55
Original line number Diff line number Diff line
@@ -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> {
Loading