blob: f73d191b191713eee1943f0d76c72d3458bd0790 [file] [log] [blame]
/*
* 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.helpers
import android.app.Instrumentation
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.common.Rect
import com.android.server.wm.traces.parser.toFlickerComponent
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.testapp.Components
class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.PipActivity.LABEL,
Components.PipActivity.COMPONENT.toFlickerComponent()
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
?: error("Could not get MediaSessionManager")
private val mediaController: MediaController?
get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
it.packageName == component.packageName
}
fun clickObject(resId: String) {
val selector = By.res(component.packageName, resId)
val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
if (!isTelevision) {
obj.click()
} else {
focusOnObject(selector) || error("Could not focus on `$resId` object")
uiDevice.pressDPadCenter()
}
}
/**
* Launches the app through an intent instead of interacting with the launcher and waits
* until the app window is in PIP mode
*/
@JvmOverloads
fun launchViaIntentAndWaitForPip(
wmHelper: WindowManagerStateHelper,
expectedWindowName: String = "",
action: String? = null,
stringExtras: Map<String, String>
) {
launchViaIntentAndWaitShown(
wmHelper, expectedWindowName, action, stringExtras,
waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition)
)
}
/**
* Expand the PIP window back to full screen via intent and wait until the app is visible
*/
fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
launchViaIntentAndWaitShown(wmHelper)
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"
// from "the bottom".
repeat(FOCUS_ATTEMPTS) {
uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
?: error("The object we try to focus on is gone.")
uiDevice.pressDPadDown()
uiDevice.waitForIdle()
}
return false
}
@JvmOverloads
fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) {
clickObject(ENTER_PIP_BUTTON_ID)
// Wait on WMHelper or simply wait for 3 seconds
wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000)
// when entering pip, the dismiss button is visible at the start. to ensure the pip
// animation is complete, wait until the pip dismiss button is no longer visible.
// b/176822698: dismiss-only state will be removed in the future
uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
}
fun enableEnterPipOnUserLeaveHint() {
clickObject(ENTER_PIP_ON_USER_LEAVE_HINT)
}
fun clickStartMediaSessionButton() {
clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
}
fun checkWithCustomActionsCheckbox() = uiDevice
.findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
?.takeIf { it.isCheckable }
?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
?: error("'With custom actions' checkbox not found")
fun pauseMedia() = mediaController?.transportControls?.pause()
?: error("No active media session found")
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 {
closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
}
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
}
/**
* Taps the pip window and dismisses it by clicking on the X button.
*/
fun closePipWindow(wmHelper: WindowManagerStateHelper) {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// search and interact with the dismiss button
val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
val dismissPipObject = uiDevice.findObject(dismissSelector)
?: error("PIP window dismiss button not found")
val dismissButtonBounds = dismissPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
// Wait for animation to complete.
wmHelper.waitPipGone()
wmHelper.waitForHomeActivityVisible()
}
/**
* Close the pip window by pressing the expand button
*/
fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
// 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.waitPipGone()
wmHelper.waitForAppTransitionIdle()
}
/**
* Double click on the PIP window to expand it
*/
fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
wmHelper.waitForAppTransitionIdle()
}
companion object {
private const val FOCUS_ATTEMPTS = 20
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
}
}