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 original task via intent and wait until the app is visible */ 79 open fun exitPipToOriginalTaskViaIntent(wmHelper: WindowManagerStateHelper) = 80 launchViaIntent(wmHelper) 81 82 fun changeAspectRatio(wmHelper: WindowManagerStateHelper) { 83 val intent = Intent("com.android.wm.shell.flicker.testapp.ASPECT_RATIO") 84 context.sendBroadcast(intent) 85 // Wait on WMHelper on size change upon aspect ratio change 86 val windowRect = getWindowRect(wmHelper) 87 wmHelper 88 .StateSyncBuilder() 89 .add("pipAspectRatioChanged") { 90 val pipAppWindow = 91 it.wmState.visibleWindows.firstOrNull { window -> 92 this.windowMatchesAnyOf(window) 93 } 94 ?: return@add false 95 val pipRegion = pipAppWindow.frameRegion 96 return@add pipRegion != Region(windowRect) 97 } 98 .waitForAndVerify() 99 } 100 101 fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) { 102 clickObject(ENTER_PIP_BUTTON_ID) 103 104 // Wait on WMHelper or simply wait for 3 seconds 105 wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify() 106 // when entering pip, the dismiss button is visible at the start. to ensure the pip 107 // animation is complete, wait until the pip dismiss button is no longer visible. 108 // b/176822698: dismiss-only state will be removed in the future 109 uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT) 110 } 111 112 fun enableEnterPipOnUserLeaveHint() { 113 clickObject(ENTER_PIP_ON_USER_LEAVE_HINT) 114 } 115 116 fun enableAutoEnterForPipActivity() { 117 clickObject(ENTER_PIP_AUTOENTER) 118 } 119 120 fun clickStartMediaSessionButton() { 121 clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID) 122 } 123 124 fun setSourceRectHint() { 125 clickObject(SOURCE_RECT_HINT) 126 } 127 128 fun checkWithCustomActionsCheckbox() = 129 uiDevice 130 .findObject(By.res(packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID)) 131 ?.takeIf { it.isCheckable } 132 ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) } 133 ?: error("'With custom actions' checkbox not found") 134 135 fun pauseMedia() = 136 mediaController?.transportControls?.pause() ?: error("No active media session found") 137 138 fun stopMedia() = 139 mediaController?.transportControls?.stop() ?: error("No active media session found") 140 141 @Deprecated( 142 "Use PipAppHelper.closePipWindow(wmHelper) instead", 143 ReplaceWith("closePipWindow(wmHelper)") 144 ) 145 open fun closePipWindow() { 146 closePipWindow(WindowManagerStateHelper(instrumentation)) 147 } 148 149 companion object { 150 private const val TAG = "PipAppHelper" 151 private const val ENTER_PIP_BUTTON_ID = "enter_pip" 152 private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions" 153 private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start" 154 private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" 155 private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter" 156 private const val SOURCE_RECT_HINT = "set_source_rect_hint" 157 } 158 }