xref: /aosp_15_r20/platform_testing/libraries/systemui-tapl/src/android/platform/systemui_tapl/ui/Bouncer.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
1 /*
2  * Copyright (C) 2024 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 android.platform.systemui_tapl.ui
18 
19 import android.graphics.Point
20 import android.graphics.Rect
21 import android.os.SystemClock.sleep
22 import android.platform.helpers.Constants
23 import android.platform.helpers.LockscreenUtils
24 import android.platform.helpers.LockscreenUtils.LockscreenType
25 import android.platform.systemui_tapl.utils.DeviceUtils.sysuiResSelector
26 import android.platform.systemui_tapl.utils.SYSUI_PACKAGE
27 import android.platform.uiautomatorhelpers.BetterSwipe.from
28 import android.platform.uiautomatorhelpers.DeviceHelpers.assertInvisible
29 import android.platform.uiautomatorhelpers.DeviceHelpers.assertVisibility
30 import android.platform.uiautomatorhelpers.DeviceHelpers.assertVisible
31 import android.platform.uiautomatorhelpers.DeviceHelpers.click
32 import android.platform.uiautomatorhelpers.DeviceHelpers.doubleTapAt
33 import android.platform.uiautomatorhelpers.DeviceHelpers.shell
34 import android.platform.uiautomatorhelpers.DeviceHelpers.uiDevice
35 import android.platform.uiautomatorhelpers.DeviceHelpers.waitForFirstObj
36 import android.platform.uiautomatorhelpers.DeviceHelpers.waitForObj
37 import android.platform.uiautomatorhelpers.assertOnTheLeftSide
38 import android.platform.uiautomatorhelpers.assertOnTheRightSide
39 import android.platform.uiautomatorhelpers.stableBounds
40 import androidx.test.uiautomator.By
41 import androidx.test.uiautomator.UiObject2
42 import com.google.common.truth.Truth
43 import java.time.Duration
44 import java.util.regex.Pattern
45 
46 /** System UI test automation object representing the lockscreen bouncer. */
47 class Bouncer internal constructor(private val notification: Notification?) {
48     private val uiObject: UiObject2 = waitForFirstObj(*BOUNCER_SELECTORS, timeout = LONG_WAIT).first
49 
enterCodeOnBouncernull50     private fun enterCodeOnBouncer(lockscreenType: LockscreenType, lockCode: String) {
51         LOCKSCREEN_TEXT_BOX_SELECTOR.assertVisible { "Lockscreen text box is not visible" }
52         LOCKSCREEN_TEXT_BOX_SELECTOR.click()
53         LockscreenUtils.enterCodeOnLockscreen(lockscreenType, lockCode)
54     }
55 
56     /**
57      * Enters pattern based on a string which contains digits between 1-9 to represent a 3x3 grid.
58      *
59      * The constraint here is that the pattern must start with 5 (the center) as we use the center
60      * of the lock pattern view as a reference on where to swipe.
61      */
enterPatternnull62     fun enterPattern(pattern: String) {
63         Truth.assertWithMessage("#enterPattern argument does not start with 5")
64             .that(pattern.isNotEmpty() && pattern[0] == '5')
65             .isTrue()
66         val lockPatternView = waitForObj(PATTERN_SELECTOR)
67         val visibleCenter = lockPatternView.visibleCenter
68         val visibleBounds = lockPatternView.visibleBounds
69         val points = mutableListOf<Point>()
70         val centerPoint = Point(visibleCenter.x, visibleCenter.y)
71         for (c in pattern.substring(1).toCharArray()) {
72             points.add(centerPoint)
73             points.add(
74                 when (c) {
75                     '1' -> visibleBounds.left to visibleBounds.top
76                     '2' -> visibleCenter.x to visibleBounds.top
77                     '3' -> visibleBounds.right to visibleBounds.top
78                     '4' -> visibleBounds.left to visibleCenter.y
79                     '5' -> visibleCenter.x to visibleCenter.y
80                     '6' -> visibleBounds.right to visibleCenter.y
81                     '7' -> visibleBounds.left to visibleBounds.bottom
82                     '8' -> visibleCenter.x to visibleBounds.bottom
83                     '9' -> visibleBounds.right to visibleBounds.bottom
84                     else -> error("Entering invalid digit: $c")
85                 }.toPoint()
86             )
87         }
88         val swipe = from(centerPoint)
89         points.forEach { swipe.to(it) }
90         swipe.release()
91     }
92 
toPointnull93     private fun Pair<Int, Int>.toPoint() = Point(first, second)
94 
95     /**
96      * Enter the Lockscreen code in the enter lockscreen text box.
97      *
98      * @param lockscreenType type of lockscreen set
99      * @param lockCode code to unlock the lockscreen
100      */
101     fun unlockViaCode(lockscreenType: LockscreenType, lockCode: String) {
102         enterCodeOnBouncer(lockscreenType, lockCode)
103         LockscreenUtils.checkDeviceLock(false)
104         By.res(PAGE_TITLE_SELECTOR_PATTERN).assertInvisible()
105         notification?.verifyStartedApp()
106     }
107 
108     /**
109      * Enter invalid Lockscreen code in the enter lockscreen text box and fail to unlock.
110      *
111      * @param lockscreenType type of lockscreen set
112      * @param invalidCode invalid code to unlock the lockscreen
113      */
failedUnlockViaCodenull114     private fun failedUnlockViaCode(lockscreenType: LockscreenType, invalidCode: String) {
115         enterCodeOnBouncer(lockscreenType, invalidCode)
116 
117         // Making sure device is still locked. The action happens really fast. Making sure
118         // previous action got completed
119         sleep((Constants.SHORT_WAIT_TIME_IN_SECONDS * 1000).toLong())
120         LockscreenUtils.checkDeviceLock(true)
121     }
122 
123     /**
124      * Enter invalid Lockscreen pin in the enter lockscreen text box and fail to unlock.
125      *
126      * @param invalidPin invalid pin to unlock the lockscreen
127      */
failedUnlockViaPinnull128     fun failedUnlockViaPin(invalidPin: String) {
129         failedUnlockViaCode(LockscreenType.PIN, invalidPin)
130     }
131 
132     /**
133      * Enter invalid Lockscreen password in the enter lockscreen text box and fail to unlock.
134      *
135      * @param invalidPassword invalid password to unlock the lockscreen
136      */
failedUnlockViaPasswordnull137     fun failedUnlockViaPassword(invalidPassword: String) {
138         failedUnlockViaCode(LockscreenType.PASSWORD, invalidPassword)
139     }
140 
141     /** Check bouncer input UI is on the left side of the screen */
assertOnTheLeftSidenull142     fun assertOnTheLeftSide(lockscreenType: LockscreenType) {
143         getInputUI(lockscreenType).assertOnTheLeftSide()
144     }
145 
146     /** Check bouncer is on the right side of the screen */
assertOnTheRightSidenull147     fun assertOnTheRightSide(lockscreenType: LockscreenType) {
148         getInputUI(lockscreenType).assertOnTheRightSide()
149     }
150 
getInputUInull151     private fun getInputUI(lockscreenType: LockscreenType): UiObject2 {
152         return when (lockscreenType) {
153             LockscreenType.PIN -> waitForObj(KEYPAD_SELECTOR)
154             LockscreenType.PATTERN -> waitForObj(PATTERN_SELECTOR)
155             LockscreenType.PASSWORD,
156             LockscreenType.SWIPE,
157             LockscreenType.NONE -> throw NotImplementedError("Not supported for these auth types")
158         }
159     }
160 
161     /** Double-taps on the left side of the screen. */
doubleTapOnTheLeftSidenull162     fun doubleTapOnTheLeftSide() {
163         doubleTapAtXPosition(uiDevice.displayWidth / 4)
164     }
165 
166     /** Double-taps on the right side of the screen. */
doubleTapOnTheRightSidenull167     fun doubleTapOnTheRightSide() {
168         doubleTapAtXPosition(uiDevice.displayWidth * 3 / 4)
169     }
170 
doubleTapAtXPositionnull171     private fun doubleTapAtXPosition(touchX: Int) {
172         val touchY = uiDevice.displayHeight / 2
173         uiDevice.doubleTapAt(touchX, touchY)
174     }
175 
176     /** https://hsv.googleplex.com/5840630509993984?node=26 */
177     val pinContainerRect: Rect?
178         get() {
179             return waitForFirstObj(*PIN_CONTAINER_SELECTOR).first.visibleBounds
180         }
181 
182     /** https://hsv.googleplex.com/5550967647895552?node=25 */
183     val pinBouncerContainerRect: Rect
184         get() {
185             return waitForObj(sysuiResSelector("keyguard_pin_view")).stableBounds
186         }
187 
188     /** https://hsv.googleplex.com/6358737448075264?node=25 */
189     val patternBouncerContainerRect: Rect
190         get() {
191             return waitForObj(sysuiResSelector("keyguard_pattern_view")).stableBounds
192         }
193 
194     /** https://hsv.googleplex.com/4951362564521984?node=25 */
195     val passwordBouncerContainerRect: Rect
196         get() {
197             return waitForObj(sysuiResSelector("keyguard_password_view")).stableBounds
198         }
199 
200     /** Checks whether the delete button exists or not. */
assertDeleteButtonVisibilitynull201     fun assertDeleteButtonVisibility(visible: Boolean) {
202         assertVisibility(PIN_BOUNCER_DELETE_BUTTON, visible)
203     }
204 
205     /** Checks whether the enter button exists or not. */
assertEnterButtonVisibilitynull206     fun assertEnterButtonVisibility(visible: Boolean) {
207         assertVisibility(PIN_BOUNCER_ENTER_BUTTON, visible)
208     }
209 
210     /** Inputs key on the bouncer. */
inputKeynull211     fun inputKey(key: String) {
212         shell("input keyboard text $key")
213     }
214 
215     companion object {
216         // Default wait used by waitForObj. waitForFirstObj uses a shorter wait.
217         private val LONG_WAIT = Duration.ofSeconds(10)
218 
219         private val IS_COMPOSE_BOUNCER_ENABLED =
220             com.android.systemui.Flags.composeBouncer() ||
221                 com.android.systemui.Flags.sceneContainer()
222         /**
223          * Possible selectors for container holding security view like pin, bouncer etc HSV:
224          * https://hsv.googleplex.com/5452172222267392?node=22
225          *
226          * It can be one of these three selectors depending on the flags that are active.
227          */
228         private val BOUNCER_SELECTORS =
229             arrayOf(
230                 sysuiResSelector("bouncer_root"),
231                 By.res("element:BouncerContent"),
232                 sysuiResSelector("view_flipper"),
233             )
234 
235         private val LOCKSCREEN_TEXT_BOX_SELECTOR =
236             if (IS_COMPOSE_BOUNCER_ENABLED) {
237                 sysuiResSelector("bouncer_text_entry")
238             } else {
239                 By.res(Pattern.compile(SYSUI_PACKAGE + ":id/(pinEntry|passwordEntry)"))
240                     .focused(true)
241             }
242 
243         /** The compose bouncer_text_entry isn't the same as pin_container, but close enough */
244         private val PIN_CONTAINER_SELECTOR =
245             arrayOf(
246                 sysuiResSelector("bouncer_text_entry"),
247                 sysuiResSelector("pin_container"),
248             )
249         /** https://hsv.googleplex.com/5225465733185536?node=54 */
250         private val PIN_BOUNCER_DELETE_BUTTON = sysuiResSelector("delete_button")
251         /** https://hsv.googleplex.com/5554629610831872?node=52 */
252         private val PIN_BOUNCER_ENTER_BUTTON = sysuiResSelector("key_enter")
253 
254         // https://hsv.googleplex.com/5130837462876160?node=117
255         private val PAGE_TITLE_SELECTOR_PATTERN =
256             Pattern.compile(String.format("%s:id/%s", SYSUI_PACKAGE, "keyguard_clock_container"))
257 
258         public val PATTERN_SELECTOR =
259             if (IS_COMPOSE_BOUNCER_ENABLED) {
260                 sysuiResSelector("bouncer_pattern_root")
261             } else {
262                 sysuiResSelector("lockPatternView")
263             }
264 
265         public val PASSWORD_SELECTOR =
266             if (IS_COMPOSE_BOUNCER_ENABLED) {
267                 sysuiResSelector("bouncer_text_entry")
268             } else {
269                 sysuiResSelector("passwordEntry")
270             }
271 
272         public val KEYPAD_SELECTOR =
273             if (IS_COMPOSE_BOUNCER_ENABLED) {
274                 sysuiResSelector("pin_pad_grid")
275             } else {
276                 sysuiResSelector("flow1")
277             }
278 
279         const val VALID_PIN = "1234"
280 
281         const val VALID_PASSWORD = "abcd"
282 
283         const val DEFAULT_PATTERN = "5624"
284 
285         public val USER_ICON_SELECTOR = sysuiResSelector("user_icon")
286     }
287 }
288