• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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         BetterSwipe.swipe(centerPoint) { points.forEach { to(it) } }
89     }
90 
toPointnull91     private fun Pair<Int, Int>.toPoint() = Point(first, second)
92 
93     /**
94      * Enter the Lockscreen code in the enter lockscreen text box.
95      *
96      * @param lockscreenType type of lockscreen set
97      * @param lockCode code to unlock the lockscreen
98      */
99     fun unlockViaCode(lockscreenType: LockscreenType, lockCode: String) {
100         enterCodeOnBouncer(lockscreenType, lockCode)
101         LockscreenUtils.checkDeviceLock(false)
102         By.res(PAGE_TITLE_SELECTOR_PATTERN).assertInvisible()
103         notification?.verifyStartedApp()
104     }
105 
106     /**
107      * Enter invalid Lockscreen code in the enter lockscreen text box and fail to unlock.
108      *
109      * @param lockscreenType type of lockscreen set
110      * @param invalidCode invalid code to unlock the lockscreen
111      */
failedUnlockViaCodenull112     private fun failedUnlockViaCode(lockscreenType: LockscreenType, invalidCode: String) {
113         enterCodeOnBouncer(lockscreenType, invalidCode)
114 
115         // Making sure device is still locked. The action happens really fast. Making sure
116         // previous action got completed
117         sleep((Constants.SHORT_WAIT_TIME_IN_SECONDS * 1000).toLong())
118         LockscreenUtils.checkDeviceLock(true)
119     }
120 
121     /**
122      * Enter invalid Lockscreen pin in the enter lockscreen text box and fail to unlock.
123      *
124      * @param invalidPin invalid pin to unlock the lockscreen
125      */
failedUnlockViaPinnull126     fun failedUnlockViaPin(invalidPin: String) {
127         failedUnlockViaCode(LockscreenType.PIN, invalidPin)
128     }
129 
130     /**
131      * Enter invalid Lockscreen password in the enter lockscreen text box and fail to unlock.
132      *
133      * @param invalidPassword invalid password to unlock the lockscreen
134      */
failedUnlockViaPasswordnull135     fun failedUnlockViaPassword(invalidPassword: String) {
136         failedUnlockViaCode(LockscreenType.PASSWORD, invalidPassword)
137     }
138 
139     /** Check bouncer input UI is on the left side of the screen */
assertOnTheLeftSidenull140     fun assertOnTheLeftSide(lockscreenType: LockscreenType) {
141         getInputUI(lockscreenType).assertOnTheLeftSide()
142     }
143 
144     /** Check bouncer is on the right side of the screen */
assertOnTheRightSidenull145     fun assertOnTheRightSide(lockscreenType: LockscreenType) {
146         getInputUI(lockscreenType).assertOnTheRightSide()
147     }
148 
getInputUInull149     private fun getInputUI(lockscreenType: LockscreenType): UiObject2 {
150         return when (lockscreenType) {
151             LockscreenType.PIN -> waitForObj(KEYPAD_SELECTOR)
152             LockscreenType.PATTERN -> waitForObj(PATTERN_SELECTOR)
153             LockscreenType.PASSWORD,
154             LockscreenType.SWIPE,
155             LockscreenType.NONE -> throw NotImplementedError("Not supported for these auth types")
156         }
157     }
158 
159     /** Double-taps on the left side of the screen. */
doubleTapOnTheLeftSidenull160     fun doubleTapOnTheLeftSide() {
161         doubleTapAtXPosition(uiDevice.displayWidth / 4)
162     }
163 
164     /** Double-taps on the right side of the screen. */
doubleTapOnTheRightSidenull165     fun doubleTapOnTheRightSide() {
166         doubleTapAtXPosition(uiDevice.displayWidth * 3 / 4)
167     }
168 
doubleTapAtXPositionnull169     private fun doubleTapAtXPosition(touchX: Int) {
170         val touchY = uiDevice.displayHeight / 2
171         uiDevice.doubleTapAt(touchX, touchY)
172     }
173 
174     /** https://hsv.googleplex.com/5840630509993984?node=26 */
175     val pinContainerRect: Rect?
176         get() {
177             return waitForFirstObj(*PIN_CONTAINER_SELECTOR).first.visibleBounds
178         }
179 
180     /** https://hsv.googleplex.com/5550967647895552?node=25 */
181     val pinBouncerContainerRect: Rect
182         get() {
183             return waitForObj(sysuiResSelector("keyguard_pin_view")).stableBounds
184         }
185 
186     /** https://hsv.googleplex.com/6358737448075264?node=25 */
187     val patternBouncerContainerRect: Rect
188         get() {
189             return waitForObj(sysuiResSelector("keyguard_pattern_view")).stableBounds
190         }
191 
192     /** https://hsv.googleplex.com/4951362564521984?node=25 */
193     val passwordBouncerContainerRect: Rect
194         get() {
195             return waitForObj(sysuiResSelector("keyguard_password_view")).stableBounds
196         }
197 
198     /** Checks whether the delete button exists or not. */
assertDeleteButtonVisibilitynull199     fun assertDeleteButtonVisibility(visible: Boolean) {
200         assertVisibility(PIN_BOUNCER_DELETE_BUTTON, visible)
201     }
202 
203     /** Checks whether the enter button exists or not. */
assertEnterButtonVisibilitynull204     fun assertEnterButtonVisibility(visible: Boolean) {
205         assertVisibility(PIN_BOUNCER_ENTER_BUTTON, visible)
206     }
207 
208     /** Inputs key on the bouncer. */
inputKeynull209     fun inputKey(key: String) {
210         shell("input keyboard text $key")
211     }
212 
213     companion object {
214         // Default wait used by waitForObj. waitForFirstObj uses a shorter wait.
215         private val LONG_WAIT = Duration.ofSeconds(10)
216 
217         private val IS_COMPOSE_BOUNCER_ENABLED =
218             com.android.systemui.Flags.composeBouncer() ||
219                 com.android.systemui.Flags.sceneContainer()
220         /**
221          * Possible selectors for container holding security view like pin, bouncer etc HSV:
222          * https://hsv.googleplex.com/5452172222267392?node=22
223          *
224          * It can be one of these three selectors depending on the flags that are active.
225          */
226         private val BOUNCER_SELECTORS =
227             arrayOf(
228                 sysuiResSelector("bouncer_root"),
229                 By.res("element:BouncerContent"),
230                 sysuiResSelector("view_flipper"),
231             )
232 
233         private val LOCKSCREEN_TEXT_BOX_SELECTOR =
234             if (IS_COMPOSE_BOUNCER_ENABLED) {
235                 sysuiResSelector("bouncer_text_entry")
236             } else {
237                 By.res(Pattern.compile(SYSUI_PACKAGE + ":id/(pinEntry|passwordEntry)"))
238                     .focused(true)
239             }
240 
241         /** The compose bouncer_text_entry isn't the same as pin_container, but close enough */
242         private val PIN_CONTAINER_SELECTOR =
243             arrayOf(sysuiResSelector("bouncer_text_entry"), sysuiResSelector("pin_container"))
244         /** https://hsv.googleplex.com/5225465733185536?node=54 */
245         private val PIN_BOUNCER_DELETE_BUTTON = sysuiResSelector("delete_button")
246         /** https://hsv.googleplex.com/5554629610831872?node=52 */
247         private val PIN_BOUNCER_ENTER_BUTTON = sysuiResSelector("key_enter")
248 
249         // https://hsv.googleplex.com/5130837462876160?node=117
250         private val PAGE_TITLE_SELECTOR_PATTERN =
251             Pattern.compile(String.format("%s:id/%s", SYSUI_PACKAGE, "keyguard_indication_area"))
252 
253         public val PATTERN_SELECTOR =
254             if (IS_COMPOSE_BOUNCER_ENABLED) {
255                 sysuiResSelector("bouncer_pattern_root")
256             } else {
257                 sysuiResSelector("lockPatternView")
258             }
259 
260         public val PASSWORD_SELECTOR =
261             if (IS_COMPOSE_BOUNCER_ENABLED) {
262                 sysuiResSelector("bouncer_text_entry")
263             } else {
264                 sysuiResSelector("passwordEntry")
265             }
266 
267         public val KEYPAD_SELECTOR =
268             if (IS_COMPOSE_BOUNCER_ENABLED) {
269                 sysuiResSelector("pin_pad_grid")
270             } else {
271                 sysuiResSelector("flow1")
272             }
273 
274         const val VALID_PIN = "1234"
275 
276         const val VALID_PASSWORD = "abcd"
277 
278         const val DEFAULT_PATTERN = "5624"
279 
280         public val USER_ICON_SELECTOR = sysuiResSelector("user_icon")
281     }
282 }
283