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.tools.common.PlatformConsts
21 import android.tools.common.traces.component.ComponentNameMatcher
22 import android.tools.device.apphelpers.StandardAppHelper
23 import android.tools.device.helpers.FIND_TIMEOUT
24 import android.tools.device.traces.parsers.WindowManagerStateHelper
25 import android.tools.device.traces.parsers.toFlickerComponent
26 import android.util.Log
27 import androidx.test.uiautomator.By
28 import androidx.test.uiautomator.Until
29 import androidx.window.extensions.WindowExtensions
30 import androidx.window.extensions.WindowExtensionsProvider
31 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
32 import com.android.server.wm.flicker.testapp.ActivityOptions
33 import org.junit.Assume.assumeNotNull
34 
35 class ActivityEmbeddingAppHelper
36 @JvmOverloads
37 constructor(
38     instr: Instrumentation,
39     launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
40     component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT
41 ) : StandardAppHelper(instr, launcherName, component) {
42 
43     /**
44      * Clicks the button to launch the secondary activity, which should split with the main activity
45      * based on the split pair rule.
46      */
47     fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) {
48         launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_button")
49     }
50 
51     /**
52      * Clicks the button to launch the secondary activity in RTL, which should split with the main
53      * activity based on the split pair rule.
54      */
55     fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) {
56         launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_rtl_button")
57     }
58 
59     /**
60      * Clicks the button to launch the secondary activity in a horizontal split.
61      */
62     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. */
67     fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
68         val launchButton =
69             uiDevice.wait(
70                 Until.findObject(By.res(getPackage(), "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      */
87     fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
88         val launchButton =
89                 uiDevice.wait(
90                         Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
91                         FIND_TIMEOUT
92                 )
93         require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
94         launchButton.click()
95         wmHelper
96                 .StateSyncBuilder()
97                 .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
98                 .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
99                 .waitForAndVerify()
100     }
101 
102     /**
103      * Clicks the button to finishes the secondary activity launched through
104      * [launchSecondaryActivity], waits for the main activity to resume.
105      */
106     fun finishSecondaryActivity(wmHelper: WindowManagerStateHelper) {
107         val finishButton =
108                 uiDevice.wait(
109                         Until.findObject(By.res(getPackage(), "finish_secondary_activity_button")),
110                         FIND_TIMEOUT
111                 )
112         require(finishButton != null) { "Can't find finish secondary activity button on screen." }
113         finishButton.click()
114         wmHelper
115                 .StateSyncBuilder()
116                 .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
117                 .waitForAndVerify()
118      }
119 
120     /**
121      * Clicks the button to toggle the split ratio of secondary activity.
122      */
123     fun changeSecondaryActivityRatio(wmHelper: WindowManagerStateHelper) {
124         val launchButton =
125                 uiDevice.wait(
126                         Until.findObject(
127                                 By.res(getPackage(),
128                                         "toggle_split_ratio_button")),
129                         FIND_TIMEOUT
130                 )
131         require(launchButton != null) {
132             "Can't find toggle ratio for secondary activity button on screen."
133         }
134         launchButton.click()
135         wmHelper
136                 .StateSyncBuilder()
137                 .withAppTransitionIdle()
138                 .withTransitionSnapshotGone()
139                 .waitForAndVerify()
140     }
141 
142     fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
143         val pipButton =
144                 uiDevice.wait(
145                         Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")),
146                         FIND_TIMEOUT
147                 )
148         require(pipButton != null) { "Can't find enter pip button on screen." }
149         pipButton.click()
150         wmHelper
151                 .StateSyncBuilder()
152                 .withAppTransitionIdle()
153                 .withPipShown()
154                 .waitForAndVerify()
155     }
156 
157     /**
158      * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
159      * a fullscreen window on top of the visible region.
160      */
161     fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
162         val launchButton =
163             uiDevice.wait(
164                 Until.findObject(By.res(getPackage(), "launch_always_expand_activity_button")),
165                 FIND_TIMEOUT
166             )
167         require(launchButton != null) {
168             "Can't find launch always expand activity button on screen."
169         }
170         launchButton.click()
171         wmHelper
172             .StateSyncBuilder()
173             .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
174             .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
175             .waitForAndVerify()
176     }
177 
178     private fun launchSecondaryActivityFromButton(
179             wmHelper: WindowManagerStateHelper, buttonName: String) {
180         val launchButton =
181                 uiDevice.wait(Until.findObject(By.res(getPackage(), buttonName)), FIND_TIMEOUT)
182         require(launchButton != null) {
183             "Can't find launch secondary activity button : " + buttonName + "on screen."
184         }
185         launchButton.click()
186         wmHelper
187                 .StateSyncBuilder()
188                 .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
189                 .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
190                 .waitForAndVerify()
191     }
192 
193     /**
194      * Clicks the button to launch the placeholder primary activity, which should launch the
195      * placeholder secondary activity based on the placeholder rule.
196      */
197     fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
198         val launchButton =
199             uiDevice.wait(
200                 Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
201                 FIND_TIMEOUT
202             )
203         require(launchButton != null) { "Can't find launch placeholder split button on screen." }
204         launchButton.click()
205         wmHelper
206             .StateSyncBuilder()
207             .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
208             .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
209             .waitForAndVerify()
210     }
211 
212     /**
213      * Clicks the button to launch the placeholder primary activity in RTL, which should launch the
214      * placeholder secondary activity based on the placeholder rule.
215      */
216     fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) {
217         val launchButton =
218             uiDevice.wait(
219                 Until.findObject(By.res(getPackage(), "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     companion object {
232         private const val TAG = "ActivityEmbeddingAppHelper"
233 
234         val MAIN_ACTIVITY_COMPONENT =
235             ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
236 
237         val SECONDARY_ACTIVITY_COMPONENT =
238             ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
239 
240         val THIRD_ACTIVITY_COMPONENT =
241             ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent()
242 
243         val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
244             ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
245 
246         val PLACEHOLDER_PRIMARY_COMPONENT =
247             ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
248                 .toFlickerComponent()
249 
250         val PLACEHOLDER_SECONDARY_COMPONENT =
251             ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
252                 .toFlickerComponent()
253 
254         val TRAMPOLINE_ACTIVITY_COMPONENT =
255                 ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
256 
257         @JvmStatic
258         fun getWindowExtensions(): WindowExtensions? {
259             try {
260                 return WindowExtensionsProvider.getWindowExtensions()
261             } catch (e: NoClassDefFoundError) {
262                 Log.d(TAG, "Extension implementation not found")
263             } catch (e: UnsupportedOperationException) {
264                 Log.d(TAG, "Stub Extension")
265             }
266             return null
267         }
268 
269         @JvmStatic
270         fun getActivityEmbeddingComponent(): ActivityEmbeddingComponent? {
271             return getWindowExtensions()?.activityEmbeddingComponent
272         }
273 
274         @JvmStatic
275         fun assumeActivityEmbeddingSupportedDevice() {
276             assumeNotNull(getActivityEmbeddingComponent())
277         }
278     }
279 }
280