1 /*
2  * Copyright (C) 2022 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.os.SystemClock
21 import android.tools.PlatformConsts
22 import android.tools.device.apphelpers.StandardAppHelper
23 import android.tools.helpers.FIND_TIMEOUT
24 import android.tools.traces.component.ComponentNameMatcher
25 import android.tools.traces.parsers.WindowManagerStateHelper
26 import android.tools.traces.parsers.toFlickerComponent
27 import android.util.Log
28 import androidx.test.uiautomator.By
29 import androidx.test.uiautomator.Direction
30 import androidx.test.uiautomator.Until
31 import androidx.window.extensions.WindowExtensions
32 import androidx.window.extensions.WindowExtensionsProvider
33 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
34 import com.android.server.wm.flicker.testapp.ActivityOptions
35 import org.junit.Assume.assumeNotNull
36 
37 class ActivityEmbeddingAppHelper
38 @JvmOverloads
39 constructor(
40     instr: Instrumentation,
41     launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
42     component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT
43 ) : StandardAppHelper(instr, launcherName, component) {
44 
45     /**
46      * Clicks the button to launch the secondary activity, which should split with the main activity
47      * based on the split pair rule.
48      */
launchSecondaryActivitynull49     fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) {
50         launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_button")
51     }
52 
53     /**
54      * Clicks the button to launch the secondary activity in RTL, which should split with the main
55      * activity based on the split pair rule.
56      */
launchSecondaryActivityRTLnull57     fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) {
58         launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_rtl_button")
59     }
60 
61     /** Clicks the button to launch the secondary activity in a horizontal split. */
launchSecondaryActivityHorizontallynull62     fun launchSecondaryActivityHorizontally(wmHelper: WindowManagerStateHelper) {
63         launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_horizontally_button")
64     }
65 
66     /** Clicks the button to launch a third activity over a secondary activity. */
launchThirdActivitynull67     fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
68         val launchButton =
69             uiDevice.wait(
70                 Until.findObject(By.res(packageName, "launch_third_activity_button")),
71                 FIND_TIMEOUT
72             )
73         require(launchButton != null) { "Can't find launch third activity button on screen." }
74         launchButton.click()
75         wmHelper
76             .StateSyncBuilder()
77             .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
78             .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED)
79             .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
80             .waitForAndVerify()
81     }
82 
83     /**
84      * Clicks the button to launch the trampoline activity, which should launch the secondary
85      * activity and finish itself.
86      */
launchTrampolineActivitynull87     fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
88         scrollToBottom()
89         val launchButton =
90             uiDevice.wait(
91                 Until.findObject(By.res(packageName, "launch_trampoline_button")),
92                 FIND_TIMEOUT
93             )
94         require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
95         launchButton.click()
96         wmHelper
97             .StateSyncBuilder()
98             .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
99             .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
100             .waitForAndVerify()
101     }
102 
103     /**
104      * Clicks the button to finishes the secondary activity launched through
105      * [launchSecondaryActivity], waits for the main activity to resume.
106      */
finishSecondaryActivitynull107     fun finishSecondaryActivity(wmHelper: WindowManagerStateHelper) {
108         val finishButton =
109             uiDevice.wait(
110                 Until.findObject(By.res(packageName, "finish_secondary_activity_button")),
111                 FIND_TIMEOUT
112             )
113         require(finishButton != null) { "Can't find finish secondary activity button on screen." }
114         finishButton.click()
115         wmHelper
116             .StateSyncBuilder()
117             .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
118             .waitForAndVerify()
119     }
120 
121     /** Clicks the button to toggle the split ratio of secondary activity. */
changeSecondaryActivityRationull122     fun changeSecondaryActivityRatio(wmHelper: WindowManagerStateHelper) {
123         val launchButton =
124             uiDevice.wait(
125                 Until.findObject(By.res(packageName, "toggle_split_ratio_button")),
126                 FIND_TIMEOUT
127             )
128         require(launchButton != null) {
129             "Can't find toggle ratio for secondary activity button on screen."
130         }
131         launchButton.click()
132         wmHelper
133             .StateSyncBuilder()
134             .withAppTransitionIdle()
135             .withTransitionSnapshotGone()
136             .waitForAndVerify()
137     }
138 
secondaryActivityEnterPipnull139     fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
140         val pipButton =
141             uiDevice.wait(
142                 Until.findObject(By.res(packageName, "secondary_enter_pip_button")),
143                 FIND_TIMEOUT
144             )
145         require(pipButton != null) { "Can't find enter pip button on screen." }
146         pipButton.click()
147         wmHelper.StateSyncBuilder().withAppTransitionIdle().withPipShown().waitForAndVerify()
148     }
149 
150     /**
151      * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
152      * a fullscreen window on top of the visible region.
153      */
launchAlwaysExpandActivitynull154     fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
155         val launchButton =
156             uiDevice.wait(
157                 Until.findObject(By.res(packageName, "launch_always_expand_activity_button")),
158                 FIND_TIMEOUT
159             )
160         require(launchButton != null) {
161             "Can't find launch always expand activity button on screen."
162         }
163         launchButton.click()
164         wmHelper
165             .StateSyncBuilder()
166             .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
167             .withActivityState(
168                 MAIN_ACTIVITY_COMPONENT,
169                 PlatformConsts.STATE_PAUSED,
170                 PlatformConsts.STATE_STOPPED
171             )
172             .waitForAndVerify()
173     }
174 
launchSecondaryActivityFromButtonnull175     private fun launchSecondaryActivityFromButton(
176         wmHelper: WindowManagerStateHelper,
177         buttonName: String
178     ) {
179         val launchButton =
180             uiDevice.wait(Until.findObject(By.res(packageName, buttonName)), FIND_TIMEOUT)
181         require(launchButton != null) {
182             "Can't find launch secondary activity button : " + buttonName + "on screen."
183         }
184         launchButton.click()
185         wmHelper
186             .StateSyncBuilder()
187             .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
188             .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
189             .waitForAndVerify()
190     }
191 
192     /**
193      * Clicks the button to launch the placeholder primary activity, which should launch the
194      * placeholder secondary activity based on the placeholder rule.
195      */
launchPlaceholderSplitnull196     fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
197         val launchButton =
198             uiDevice.wait(
199                 Until.findObject(By.res(packageName, "launch_placeholder_split_button")),
200                 FIND_TIMEOUT
201             )
202         require(launchButton != null) { "Can't find launch placeholder split button on screen." }
203         launchButton.click()
204         wmHelper
205             .StateSyncBuilder()
206             .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
207             .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
208             .waitForAndVerify()
209     }
210 
211     /**
212      * Clicks the button to launch the placeholder primary activity in RTL, which should launch the
213      * placeholder secondary activity based on the placeholder rule.
214      */
launchPlaceholderSplitRTLnull215     fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) {
216         scrollToBottom()
217         val launchButton =
218             uiDevice.wait(
219                 Until.findObject(By.res(packageName, "launch_placeholder_split_rtl_button")),
220                 FIND_TIMEOUT
221             )
222         require(launchButton != null) { "Can't find launch placeholder split button on screen." }
223         launchButton.click()
224         wmHelper
225             .StateSyncBuilder()
226             .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
227             .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
228             .waitForAndVerify()
229     }
230 
231     /**
232      * Scrolls to the bottom of the launch options. This is needed if the launch button is at the
233      * bottom. Otherwise the click may trigger touch on navBar.
234      */
scrollToBottomnull235     private fun scrollToBottom() {
236         val launchOptionsList = uiDevice.wait(
237             Until.findObject(By.res(packageName, "launch_options_list")),
238             FIND_TIMEOUT
239         )
240         requireNotNull(launchOptionsList) { "Unable to find the list of launch options" }
241         launchOptionsList.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN))
242         // Wait a bit after scrolling, otherwise the immediate click may not be treated as "click".
243         SystemClock.sleep(1000L)
244     }
245 
246     companion object {
247         private const val TAG = "ActivityEmbeddingAppHelper"
248 
249         val MAIN_ACTIVITY_COMPONENT =
250             ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
251 
252         val SECONDARY_ACTIVITY_COMPONENT =
253             ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
254 
255         val THIRD_ACTIVITY_COMPONENT =
256             ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent()
257 
258         val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
259             ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
260 
261         val PLACEHOLDER_PRIMARY_COMPONENT =
262             ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
263                 .toFlickerComponent()
264 
265         val PLACEHOLDER_SECONDARY_COMPONENT =
266             ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
267                 .toFlickerComponent()
268 
269         val TRAMPOLINE_ACTIVITY_COMPONENT =
270             ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
271 
272         @JvmStatic
getWindowExtensionsnull273         fun getWindowExtensions(): WindowExtensions? {
274             try {
275                 return WindowExtensionsProvider.getWindowExtensions()
276             } catch (e: NoClassDefFoundError) {
277                 Log.d(TAG, "Extension implementation not found")
278             } catch (e: UnsupportedOperationException) {
279                 Log.d(TAG, "Stub Extension")
280             }
281             return null
282         }
283 
284         @JvmStatic
getActivityEmbeddingComponentnull285         fun getActivityEmbeddingComponent(): ActivityEmbeddingComponent? {
286             return getWindowExtensions()?.activityEmbeddingComponent
287         }
288 
289         @JvmStatic
assumeActivityEmbeddingSupportedDevicenull290         fun assumeActivityEmbeddingSupportedDevice() {
291             assumeNotNull(getActivityEmbeddingComponent())
292         }
293     }
294 }
295