1 /* <lambda>null2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm.flicker.helpers 18 19 import android.app.Instrumentation 20 import android.content.Intent 21 import android.graphics.Region 22 import android.media.session.MediaController 23 import android.media.session.MediaSessionManager 24 import android.tools.device.apphelpers.BasePipAppHelper 25 import android.tools.helpers.FIND_TIMEOUT 26 import android.tools.helpers.SYSTEMUI_PACKAGE 27 import android.tools.traces.ConditionsFactory 28 import android.tools.traces.component.ComponentNameMatcher 29 import android.tools.traces.component.IComponentMatcher 30 import android.tools.traces.parsers.WindowManagerStateHelper 31 import android.tools.traces.parsers.toFlickerComponent 32 import androidx.test.uiautomator.By 33 import androidx.test.uiautomator.Until 34 import com.android.server.wm.flicker.testapp.ActivityOptions 35 36 open class PipAppHelper( 37 instrumentation: Instrumentation, 38 appName: String = ActivityOptions.Pip.LABEL, 39 componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(), 40 ) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) { 41 private val mediaSessionManager: MediaSessionManager 42 get() = 43 context.getSystemService(MediaSessionManager::class.java) 44 ?: error("Could not get MediaSessionManager") 45 46 private val mediaController: MediaController? 47 get() = 48 mediaSessionManager.getActiveSessions(null).firstOrNull { 49 it.packageName == packageName 50 } 51 52 /** 53 * Launches the app through an intent instead of interacting with the launcher and waits until 54 * the app window is in PIP mode 55 */ 56 @JvmOverloads 57 fun launchViaIntentAndWaitForPip( 58 wmHelper: WindowManagerStateHelper, 59 launchedAppComponentMatcherOverride: IComponentMatcher? = null, 60 action: String? = null, 61 stringExtras: Map<String, String> 62 ) { 63 launchViaIntent( 64 wmHelper, 65 launchedAppComponentMatcherOverride, 66 action, 67 stringExtras 68 ) 69 70 wmHelper 71 .StateSyncBuilder() 72 .withWindowSurfaceAppeared(this) 73 .add(ConditionsFactory.isWMStateComplete()) 74 .withPipShown() 75 .waitForAndVerify() 76 } 77 78 /** Expand the PIP window back to full screen via intent and wait until the app is visible */ 79 fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) = launchViaIntent(wmHelper) 80 81 fun changeAspectRatio(wmHelper: WindowManagerStateHelper) { 82 val intent = Intent("com.android.wm.shell.flicker.testapp.ASPECT_RATIO") 83 context.sendBroadcast(intent) 84 // Wait on WMHelper on size change upon aspect ratio change 85 val windowRect = getWindowRect(wmHelper) 86 wmHelper 87 .StateSyncBuilder() 88 .add("pipAspectRatioChanged") { 89 val pipAppWindow = 90 it.wmState.visibleWindows.firstOrNull { window -> 91 this.windowMatchesAnyOf(window) 92 } 93 ?: return@add false 94 val pipRegion = pipAppWindow.frameRegion 95 return@add pipRegion != Region(windowRect) 96 } 97 .waitForAndVerify() 98 } 99 100 fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) { 101 clickObject(ENTER_PIP_BUTTON_ID) 102 103 // Wait on WMHelper or simply wait for 3 seconds 104 wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify() 105 // when entering pip, the dismiss button is visible at the start. to ensure the pip 106 // animation is complete, wait until the pip dismiss button is no longer visible. 107 // b/176822698: dismiss-only state will be removed in the future 108 uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT) 109 } 110 111 fun enableEnterPipOnUserLeaveHint() { 112 clickObject(ENTER_PIP_ON_USER_LEAVE_HINT) 113 } 114 115 fun enableAutoEnterForPipActivity() { 116 clickObject(ENTER_PIP_AUTOENTER) 117 } 118 119 fun clickStartMediaSessionButton() { 120 clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID) 121 } 122 123 fun setSourceRectHint() { 124 clickObject(SOURCE_RECT_HINT) 125 } 126 127 fun checkWithCustomActionsCheckbox() = 128 uiDevice 129 .findObject(By.res(packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID)) 130 ?.takeIf { it.isCheckable } 131 ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) } 132 ?: error("'With custom actions' checkbox not found") 133 134 fun pauseMedia() = 135 mediaController?.transportControls?.pause() ?: error("No active media session found") 136 137 fun stopMedia() = 138 mediaController?.transportControls?.stop() ?: error("No active media session found") 139 140 @Deprecated( 141 "Use PipAppHelper.closePipWindow(wmHelper) instead", 142 ReplaceWith("closePipWindow(wmHelper)") 143 ) 144 open fun closePipWindow() { 145 closePipWindow(WindowManagerStateHelper(instrumentation)) 146 } 147 148 companion object { 149 private const val TAG = "PipAppHelper" 150 private const val ENTER_PIP_BUTTON_ID = "enter_pip" 151 private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions" 152 private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start" 153 private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" 154 private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter" 155 private const val SOURCE_RECT_HINT = "set_source_rect_hint" 156 } 157 }