• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.content.Context
20 import android.content.pm.PackageManager
21 import android.graphics.Point
22 import android.graphics.Rect
23 import android.os.RemoteException
24 import android.os.SystemClock
25 import android.util.Log
26 import android.util.Rational
27 import android.view.Display
28 import android.view.Surface
29 import android.view.View
30 import android.view.ViewConfiguration
31 import androidx.annotation.VisibleForTesting
32 import androidx.test.uiautomator.By
33 import androidx.test.uiautomator.BySelector
34 import androidx.test.uiautomator.Configurator
35 import androidx.test.uiautomator.UiDevice
36 import androidx.test.uiautomator.Until
37 import com.android.compatibility.common.util.SystemUtil
38 import com.android.server.wm.flicker.helpers.WindowUtils.displayBounds
39 import com.android.server.wm.flicker.helpers.WindowUtils.estimateNavigationBarPosition
40 import com.android.server.wm.traces.common.FlickerComponentName
41 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
42 import com.android.server.wm.traces.parser.toAndroidRect
43 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
44 import org.junit.Assert
45 import org.junit.Assert.assertNotNull
46 
47 const val FIND_TIMEOUT: Long = 10000
48 const val FAST_WAIT_TIMEOUT: Long = 0
49 const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
50 const val IME_PACKAGE = "com.google.android.inputmethod.latin"
51 @VisibleForTesting
52 const val SYSTEMUI_PACKAGE = "com.android.systemui"
53 private val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L
54 private const val TAG = "FLICKER"
55 
56 /**
57  * Sets [android.app.UiAutomation.waitForIdle] global timeout to 0 causing the
58  * [android.app.UiAutomation.waitForIdle] function to timeout instantly. This
59  * removes some delays when using the UIAutomator library required to create fast UI
60  * transitions.
61  */
setFastWaitnull62 fun setFastWait() {
63     Configurator.getInstance().waitForIdleTimeout = FAST_WAIT_TIMEOUT
64 }
65 
66 /**
67  * Reverts [android.app.UiAutomation.waitForIdle] to default behavior.
68  */
setDefaultWaitnull69 fun setDefaultWait() {
70     Configurator.getInstance().waitForIdleTimeout = FIND_TIMEOUT
71 }
72 
73 /**
74  * Checks if the device is running on gestural or 2-button navigation modes
75  */
isQuickstepEnablednull76 fun UiDevice.isQuickstepEnabled(): Boolean {
77     val enabled = this.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null
78     Log.d(TAG, "Quickstep enabled: $enabled")
79     return enabled
80 }
81 
82 /**
83  * Checks if the display is rotated or not
84  */
UiDevicenull85 fun UiDevice.isRotated(): Boolean {
86     return this.displayRotation.isRotated()
87 }
88 
89 /**
90  * Reopens the first device window from the list of recent apps (overview)
91  */
reopenAppFromOverviewnull92 fun UiDevice.reopenAppFromOverview(
93     wmHelper: WindowManagerStateHelper
94 ) {
95     val x = this.displayWidth / 2
96     val y = this.displayHeight / 2
97     this.click(x, y)
98 
99     wmHelper.waitFor(
100         WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
101         WindowManagerConditionsFactory.isLayerVisible(FlickerComponentName.SNAPSHOT).negate(),
102         WindowManagerConditionsFactory.isLayerVisible(FlickerComponentName.SPLASH_SCREEN).negate()
103     )
104 }
105 
106 /**
107  * Shows quickstep
108  *
109  * @throws AssertionError When quickstep does not appear
110  */
openQuickstepnull111 fun UiDevice.openQuickstep(
112     wmHelper: WindowManagerStateHelper
113 ) {
114     if (this.isQuickstepEnabled()) {
115         val navBar = this.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"))
116         val navBarVisibleBounds: Rect
117 
118         // TODO(vishnun) investigate why this object cannot be found.
119         navBarVisibleBounds = if (navBar != null) {
120             navBar.visibleBounds
121         } else {
122             Log.e(TAG, "Could not find nav bar, infer location")
123             estimateNavigationBarPosition(Surface.ROTATION_0).bounds.toAndroidRect()
124         }
125 
126         val startX = navBarVisibleBounds.centerX()
127         val startY = navBarVisibleBounds.centerY()
128         val endX: Int
129         val endY: Int
130         val height: Int
131         val steps: Int
132         if (this.isRotated()) {
133             height = this.displayWidth
134             endX = height * 2 / 3
135             endY = navBarVisibleBounds.centerY()
136             steps = (endX - startX) / 100 // 100 px/step
137         } else {
138             height = this.displayHeight
139             endX = navBarVisibleBounds.centerX()
140             endY = height * 2 / 3
141             steps = (startY - endY) / 100 // 100 px/step
142         }
143         // Swipe from nav bar to 2/3rd down the screen.
144         this.swipe(startX, startY, endX, endY, steps)
145     }
146 
147     // use a long timeout to wait until recents populated
148     val recentsSysUISelector = By.res(this.launcherPackageName, "overview_panel")
149     var recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
150 
151     // Quickstep detection is flaky on AOSP, UIDevice doesn't always find SysUI elements
152     // If it couldn't find, try pressing 'recent items' button
153     if (recents == null) {
154         try {
155             this.pressRecentApps()
156         } catch (e: RemoteException) {
157             throw RuntimeException(e)
158         }
159         recents = this.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT)
160     }
161     assertNotNull("Recent items didn't appear", recents)
162     wmHelper.waitFor(
163         WindowManagerConditionsFactory.isNavBarVisible(),
164         WindowManagerConditionsFactory.isStatusBarVisible(),
165         WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY)
166     )
167 }
168 
getLauncherOverviewSelectornull169 private fun getLauncherOverviewSelector(device: UiDevice): BySelector {
170     return By.res(device.launcherPackageName, "overview_panel")
171 }
172 
longPressRecentsnull173 private fun longPressRecents(device: UiDevice) {
174     val recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps")
175     val recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT)
176     assertNotNull("Unable to find 'recent items' button", recentsButton)
177     recentsButton.click(LONG_PRESS_TIMEOUT)
178 }
179 
180 /**
181  * Wait for any IME view to appear
182  */
UiDevicenull183 fun UiDevice.waitForIME(): Boolean {
184     val ime = this.wait(Until.findObject(By.pkg(IME_PACKAGE)), FIND_TIMEOUT)
185     return ime != null
186 }
187 
openQuickStepAndLongPressOverviewIconnull188 private fun openQuickStepAndLongPressOverviewIcon(
189     device: UiDevice,
190     wmHelper: WindowManagerStateHelper
191 ) {
192     if (device.isQuickstepEnabled()) {
193         device.openQuickstep(wmHelper)
194     } else {
195         try {
196             device.pressRecentApps()
197         } catch (e: RemoteException) {
198             Log.e(TAG, "launchSplitScreen", e)
199         }
200     }
201     val overviewIconSelector = By.res(device.launcherPackageName, "icon")
202         .clazz(View::class.java)
203     val overviewIcon = device.wait(Until.findObject(overviewIconSelector), FIND_TIMEOUT)
204     assertNotNull("Unable to find app icon in Overview", overviewIcon)
205     overviewIcon.click()
206 }
207 
openQuickStepAndClearRecentAppsFromOverviewnull208 fun UiDevice.openQuickStepAndClearRecentAppsFromOverview(
209     wmHelper: WindowManagerStateHelper
210 ) {
211     if (this.isQuickstepEnabled()) {
212         this.openQuickstep(wmHelper)
213     } else {
214         try {
215             this.pressRecentApps()
216         } catch (e: RemoteException) {
217             Log.e(TAG, "launchSplitScreen", e)
218         }
219     }
220     for (i in 0..9) {
221         this.swipe(
222                 this.getDisplayWidth() / 2,
223                 this.getDisplayHeight() / 2,
224                 this.getDisplayWidth(),
225                 this.getDisplayHeight() / 2,
226                 5)
227         // If "Clear all"  button appears, use it
228         val clearAllSelector = By.res(this.getLauncherPackageName(), "clear_all")
229         val clearAllButton = this.wait(Until.findObject(clearAllSelector), FAST_WAIT_TIMEOUT)
230         if (clearAllButton != null) {
231             clearAllButton.click()
232         }
233     }
234     this.pressHome()
235 }
236 
237 /**
238  * Opens quick step and puts the first app from the list of recently used apps into
239  * split-screen
240  *
241  * @throws AssertionError when unable to open the list of recently used apps, or when it does
242  * not contain a button to enter split screen mode
243  */
UiDevicenull244 fun UiDevice.launchSplitScreen(
245     wmHelper: WindowManagerStateHelper
246 ) {
247     openQuickStepAndLongPressOverviewIcon(this, wmHelper)
248     val splitScreenButtonSelector = By.text("Split screen")
249     val splitScreenButton =
250             this.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT)
251     assertNotNull("Unable to find Split screen button in Overview", splitScreenButton)
252     splitScreenButton.click()
253 
254     // Wait for animation to complete.
255     this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
256     wmHelper.waitFor(
257         WindowManagerConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER),
258         WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
259 
260     if (!this.isInSplitScreen()) {
261         Assert.fail("Unable to find Split screen divider")
262     }
263 }
264 
265 /**
266  * Checks if the recent application is able to split screen(resizeable)
267  */
UiDevicenull268 fun UiDevice.canSplitScreen(
269     wmHelper: WindowManagerStateHelper
270 ): Boolean {
271     openQuickStepAndLongPressOverviewIcon(this, wmHelper)
272     val splitScreenButtonSelector = By.text("Split screen")
273     val canSplitScreen =
274             this.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT) != null
275     this.pressHome()
276     return canSplitScreen
277 }
278 
279 /**
280  * Checks if the device is in split screen by searching for the split screen divider
281  */
isInSplitScreennull282 fun UiDevice.isInSplitScreen(): Boolean {
283     return this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT) != null
284 }
285 
waitSplitScreenGonenull286 fun waitSplitScreenGone(wmHelper: WindowManagerStateHelper): Boolean {
287     return wmHelper.waitFor(
288         WindowManagerConditionsFactory.isLayerVisible(DOCKED_STACK_DIVIDER),
289         WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
290 }
291 
292 private val splitScreenDividerSelector: BySelector
293     get() = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle")
294 
295 /**
296  * Drags the split screen divider to the top of the screen to close it
297  *
298  * @throws AssertionError when unable to find the split screen divider
299  */
UiDevicenull300 fun UiDevice.exitSplitScreen() {
301     // Quickstep enabled
302     val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
303     assertNotNull("Unable to find Split screen divider", divider)
304 
305     // Drag the split screen divider to the top of the screen
306     val dstPoint = if (this.isRotated()) {
307         Point(0, this.displayWidth / 2)
308     } else {
309         Point(this.displayWidth / 2, 0)
310     }
311     divider.drag(dstPoint, 400)
312     // Wait for animation to complete.
313     SystemClock.sleep(2000)
314 }
315 
316 /**
317  * Drags the split screen divider to the bottom of the screen to close it
318  *
319  * @throws AssertionError when unable to find the split screen divider
320  */
exitSplitScreenFromBottomnull321 fun UiDevice.exitSplitScreenFromBottom(wmHelper: WindowManagerStateHelper) {
322     // Quickstep enabled
323     val divider = this.wait(Until.findObject(splitScreenDividerSelector), FIND_TIMEOUT)
324     assertNotNull("Unable to find Split screen divider", divider)
325 
326     // Drag the split screen divider to the bottom of the screen
327     val dstPoint = if (this.isRotated()) {
328         Point(this.displayWidth, this.displayWidth / 2)
329     } else {
330         Point(this.displayWidth / 2, this.displayHeight)
331     }
332     divider.drag(dstPoint, 400)
333     if (!waitSplitScreenGone(wmHelper)) {
334         Assert.fail("Split screen divider never disappeared")
335     }
336 }
337 
338 /**
339  * Drags the split screen divider to resize the windows in split screen
340  *
341  * @throws AssertionError when unable to find the split screen divider
342  */
resizeSplitScreennull343 fun UiDevice.resizeSplitScreen(windowHeightRatio: Rational) {
344     val dividerSelector = splitScreenDividerSelector
345     val divider = this.wait(Until.findObject(dividerSelector), FIND_TIMEOUT)
346     assertNotNull("Unable to find Split screen divider", divider)
347     val destHeight = (displayBounds.height() * windowHeightRatio.toFloat()).toInt()
348 
349     // Drag the split screen divider to so that the ratio of top window height and bottom
350     // window height is windowHeightRatio
351     this.drag(
352             divider.visibleBounds.centerX(),
353             divider.visibleBounds.centerY(),
354             this.displayWidth / 2,
355             destHeight,
356             10)
357     this.wait(Until.findObject(dividerSelector), FIND_TIMEOUT)
358     // Wait for animation to complete.
359     SystemClock.sleep(2000)
360 }
361 
362 /**
363  * Checks if the device has a window with the package name
364  */
hasWindownull365 fun UiDevice.hasWindow(packageName: String): Boolean {
366     return this.wait(Until.findObject(By.pkg(packageName)), FIND_TIMEOUT) != null
367 }
368 
369 /**
370  * Waits until the package with that name is gone
371  */
waitUntilGonenull372 fun UiDevice.waitUntilGone(packageName: String): Boolean {
373     return this.wait(Until.gone(By.pkg(packageName)), FIND_TIMEOUT) != null
374 }
375 
stopPackagenull376 fun stopPackage(context: Context, packageName: String) {
377     SystemUtil.runShellCommand("am force-stop $packageName")
378     val packageUid = try {
379         context.packageManager.getPackageUid(packageName, /* flags= */0)
380     } catch (e: PackageManager.NameNotFoundException) {
381         return
382     }
383     while (targetPackageIsRunning(packageUid)) {
384         try {
385             Thread.sleep(100)
386         } catch (e: InterruptedException) { // ignore
387         }
388     }
389 }
390 
targetPackageIsRunningnull391 private fun targetPackageIsRunning(uid: Int): Boolean {
392     val result = SystemUtil.runShellCommand("cmd activity get-uid-state $uid")
393     return !result.contains("(NONEXISTENT)")
394 }
395 
396 /**
397  * Turns on the device display and presses the home button to reach the launcher screen
398  */
wakeUpAndGoToHomeScreennull399 fun UiDevice.wakeUpAndGoToHomeScreen() {
400     try {
401         this.wakeUp()
402     } catch (e: RemoteException) {
403         throw RuntimeException(e)
404     }
405     this.pressHome()
406 }
407