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 }