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

Commit 068156db authored by Nataniel Borges's avatar Nataniel Borges
Browse files

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
parent 015c5af3
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
/*
 * 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)
}
+6 −8
Original line number Diff line number Diff line
@@ -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" }
            }
+81 −12
Original line number Diff line number Diff line
@@ -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 {
+0 −13
Original line number Diff line number Diff line
@@ -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.
+58 −73
Original line number Diff line number Diff line
@@ -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,42 +39,31 @@ 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" }
    testSpec: FlickerTestRunnerFactory.TestSpec
) : FlickerTestRunner(testSpec) {
    companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) {
        @Parameterized.Parameters(name = "{0}")
        @JvmStatic
        fun getParams(): List<Array<Any>> {
            val testApp = FixedAppHelper(instrumentation)
            val baseConfig = getTransitionLaunch(eachRun = true)
            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
                setup {
                test {
                    removeAllTasksButHome()
                    device.wakeUpAndGoToHomeScreen()
                    pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"))
                    testApp.launchViaIntent()
                    waitForAnimationComplete()
                    eachRun {
                        testApp.launchViaIntent(wmHelper)
                    }
                }
                transitions {
                    // This will bring PipApp to fullscreen
                pipApp.launchViaIntent()
                waitForAnimationComplete()
            }
            teardown {
                test {
                    removeAllTasksButHome()
                }
                    pipApp.launchViaIntent(wmHelper)
                }
                assertions {
                val displayBounds = WindowUtils.getDisplayBounds(rotation)
                    val displayBounds = WindowUtils.getDisplayBounds(configuration.startRotation)
                    presubmit {
                        windowManagerTrace {
                            all("pipApp must remain inside visible bounds") {
                                coversAtMostRegion(pipApp.defaultWindowName, displayBounds)
@@ -108,13 +97,9 @@ class EnterExitPipTest(
                    }
                }
            }

    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) }
            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
                testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                repetitions = 5)
        }
    }
}
 No newline at end of file
Loading