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

Commit 7599ffc7 authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

Fix Tv Pip tests

Work around the eccentricities of the Accessibility framework on TV in
Pip tests.

Bug: 174818743
Test: atest WMShellFlickerTests:TvPipBasicTest
Test: atest WMShellFlickerTests:TvPipMenuTests
Test: atest WMShellFlickerTests:TvPipNotificationTests
Change-Id: I00e12c5e88a7c4a6e9f3c18d7c978288d28ab43c
parent 19911209
Loading
Loading
Loading
Loading
+89 −50
Original line number Diff line number Diff line
@@ -33,71 +33,102 @@ private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "tv_pip_menu_fullscreen_but
private const val FOCUS_ATTEMPTS = 10
private const val WAIT_TIME_MS = 3_000L

private val TV_PIP_MENU_SELECTOR =
        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID)
private val TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR =
        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID)
private val TV_PIP_MENU_CLOSE_BUTTON_SELECTOR =
        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CLOSE_BUTTON_ID)
private val TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR =
        By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID)

private val tvPipMenuSelector = By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_ROOT_ID)

fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW)

fun UiDevice.waitForTvPipMenu(): UiObject2? =
        wait(Until.findObject(tvPipMenuSelector), WAIT_TIME_MS)
        wait(Until.findObject(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)

fun UiDevice.waitForTvPipMenuToClose(): Boolean = wait(Until.gone(tvPipMenuSelector), WAIT_TIME_MS)
fun UiDevice.waitForTvPipMenuToClose(): Boolean =
        wait(Until.gone(TV_PIP_MENU_SELECTOR), WAIT_TIME_MS)

fun UiDevice.findTvPipMenuControls(): UiObject2? =
        findObject(tvPipMenuSelector)
                ?.findObject(By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID))
        findTvPipMenuElement(TV_PIP_MENU_BUTTONS_CONTAINER_SELECTOR)

fun UiDevice.findTvPipMenuCloseButton(): UiObject2? =
        findObject(tvPipMenuSelector)?.findObject(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)
        findTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)

fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? =
        findTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR)

fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? =
        findTvPipMenuElement(By.desc(desc))

private fun UiDevice.findTvPipMenuElement(selector: BySelector): UiObject2? =
    findObject(TV_PIP_MENU_SELECTOR)?.findObject(selector)

fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
    // Ideally, we'd want to wait for an element with the given description that has the Pip Menu as
    // its parent, but the API does not allow us to construct a query exactly that way.
    // So instead we'll wait for a Pip Menu that has the element, which we are looking for, as a
    // descendant and then retrieve the element from the menu and return to the caller of this
    // method.
    val elementSelector = By.desc(desc)
    val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector)

    return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS)
            ?.findObject(elementSelector)
}

fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? {
    val elementSelector = By.desc(desc)
    val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector)

    return wait(Until.gone(menuContainingElementSelector), WAIT_TIME_MS)
}

fun UiDevice.clickTvPipMenuCloseButton() {
    focusOnObjectInTvPipMenu(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) ||
    focusOnAndClickTvPipMenuElement(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR) ||
            error("Could not focus on the Close button")
    pressDPadCenter()
}

fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? =
        findObject(tvPipMenuSelector)?.findObject(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR)

fun UiDevice.clickTvPipMenuFullscreenButton() {
    focusOnObjectInTvPipMenu(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) ||
    focusOnAndClickTvPipMenuElement(TV_PIP_MENU_FULLSCREEN_BUTTON_SELECTOR) ||
            error("Could not focus on the Fullscreen button")
    pressDPadCenter()
}

fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? =
        findObject(tvPipMenuSelector)?.findObject(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME))

fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) {
    focusOnObjectInTvPipMenu(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) ||
    focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) ||
            error("Could not focus on the Pip menu object with \"$desc\" description")
    pressDPadCenter()
    // So apparently Accessibility framework on TV is not very reliable and sometimes the state of
    // the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of
    // the "real" state of the "UI tree". It seems, however, that moving focus around the tree
    // forces the AccessibilityNodeInfo tree to get properly updated.
    // So since we suspect that clicking on a Pip Menu element may cause some UI changes and we want
    // those changes to be seen by the UiAutomator, which is using Accessibility framework under the
    // hood for inspecting UI, we'll move the focus around a little.
    moveFocus()
}

fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
    val buttonSelector = By.desc(desc)
    val menuWithButtonSelector = By.copy(tvPipMenuSelector).hasDescendant(buttonSelector)
    return wait(Until.findObject(menuWithButtonSelector), WAIT_TIME_MS)
            ?.findObject(buttonSelector)
}
private fun UiDevice.focusOnAndClickTvPipMenuElement(selector: BySelector): Boolean {
    repeat(FOCUS_ATTEMPTS) {
        val element = findTvPipMenuElement(selector)
                ?: error("The Pip Menu element we try to focus on is gone.")

fun UiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(desc: String): Boolean? =
    wait(Until.gone(By.copy(tvPipMenuSelector).hasDescendant(By.desc(desc))), WAIT_TIME_MS)
        if (element.isFocusedOrHasFocusedChild) {
            pressDPadCenter()
            return true
        }

fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
    height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
        findTvPipMenuElement(By.focused(true))?.let { focused ->
            if (element.visibleCenter.x < focused.visibleCenter.x)
                pressDPadLeft() else pressDPadRight()
            waitForIdle()
        } ?: error("Pip menu does not contain a focused element")
    }

val UiObject2.isFocusedOrHasFocusedChild: Boolean
    get() = isFocused || findObject(By.focused(true)) != null
    return false
}

fun UiDevice.closeTvPipWindow() {
    // Check if Pip menu is Open. If it's not, open it.
    if (findObject(tvPipMenuSelector) == null) {
    if (findObject(TV_PIP_MENU_SELECTOR) == null) {
        pressWindowKey()
        waitForTvPipMenu() ?: error("Could not open Pip menu")
    }
@@ -106,17 +137,25 @@ fun UiDevice.closeTvPipWindow() {
    waitForTvPipMenuToClose()
}

private fun UiDevice.focusOnObjectInTvPipMenu(objectSelector: BySelector): Boolean {
    repeat(FOCUS_ATTEMPTS) {
        val menu = findObject(tvPipMenuSelector) ?: error("Pip Menu is now shown")
        val objectToFocus = menu.findObject(objectSelector)
                .apply { if (isFocusedOrHasFocusedChild) return true }
                ?: error("The object we try to focus on is gone.")
        val currentlyFocused = menu.findObject(By.focused(true))
                ?: error("Pip menu does not contain a focused element")
        if (objectToFocus.visibleCenter.x < currentlyFocused.visibleCenter.x)
            pressDPadLeft() else pressDPadRight()
/**
 * Simply presses the D-Pad Left and Right buttons once, which should move the focus on the screen,
 * which should cause Accessibility events to be fired, which should, hopefully, properly update
 * AccessibilityNodeInfo tree dispatched by the platform to the Accessibility services, one of which
 * is the UiAutomator.
 */
private fun UiDevice.moveFocus() {
    waitForIdle()
    pressDPadLeft()
    waitForIdle()
    pressDPadRight()
    waitForIdle()
}
    return false

fun UiDevice.pressWindowKey() = pressKeyCode(KeyEvent.KEYCODE_WINDOW)

fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
    height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
}

val UiObject2.isFocusedOrHasFocusedChild: Boolean
    get() = isFocused || findObject(By.focused(true)) != null