xref: /aosp_15_r20/cts/tests/tests/companion/uicommon/src/android/companion/cts/uicommon/CompanionDeviceManagerUi.kt (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2021 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.companion.cts.uicommon
18 
19 import android.os.SystemClock
20 import android.os.SystemClock.sleep
21 import androidx.test.uiautomator.By
22 import androidx.test.uiautomator.BySelector
23 import androidx.test.uiautomator.Direction
24 import androidx.test.uiautomator.SearchCondition
25 import androidx.test.uiautomator.UiDevice
26 import androidx.test.uiautomator.UiObject2
27 import androidx.test.uiautomator.Until
28 import kotlin.time.Duration
29 import kotlin.time.Duration.Companion.seconds
30 
31 open class CompanionDeviceManagerUi(private val ui: UiDevice) {
32     val isVisible: Boolean
33         get() = ui.hasObject(CONFIRMATION_UI)
34 
dismissnull35     fun dismiss() {
36         if (!isVisible) return
37         // Pressing back button should close (cancel) confirmation UI.
38         ui.pressBack()
39         waitUntilGone()
40     }
41 
waitUntilVisiblenull42     fun waitUntilVisible(timeout: Duration = 3.seconds) = ui.wait(
43         Until.hasObject(CONFIRMATION_UI),
44         "CDM UI has not appeared.",
45         timeout
46     )
47 
48     fun waitUntilNotificationVisible(isAuto: Boolean = false) = ui.wait(
49         if (isAuto) Until.hasObject(NOTIFICATION_UI_AUTO) else Until.hasObject(NOTIFICATION_UI),
50         "NOTIFICATION UI has not appeared."
51     )
52 
53     fun waitUntilGone() = ui.waitShort(Until.gone(CONFIRMATION_UI), "CDM UI has not disappeared")
54 
55     fun waitAndClickOnFirstFoundDevice() {
56         val firstDevice = ui.waitLongAndFind(
57                 Until.findObject(DEVICE_LIST_WITH_ITEMS),
58                 "The item in the Device List not found or empty"
59         ).children[0]
60 
61         val startTime = SystemClock.uptimeMillis()
62         var elapsedTime = 0L
63         // Keep trying to click the first item in the list until the device_list is disappeared
64         // or it times out after 5s.
65         while (ui.hasObject(DEVICE_LIST) && elapsedTime < 5.seconds.inWholeMilliseconds) {
66             firstDevice.click()
67             sleep(0.2.seconds.inWholeMilliseconds)
68             elapsedTime = SystemClock.uptimeMillis() - startTime
69         }
70     }
71 
waitUntilPositiveButtonIsEnabledAndClicknull72     fun waitUntilPositiveButtonIsEnabledAndClick() = ui.waitLongAndFind(
73         Until.findObject(POSITIVE_BUTTON),
74         "Positive button not found or not clickable"
75     ).click()
76 
77     fun waitUntilSystemDataTransferConfirmationVisible() = ui.wait(
78             Until.hasObject(SYSTEM_DATA_TRANSFER_CONFIRMATION_UI),
79             "System data transfer dialog has not appeared."
80     )
81 
82     fun clickPositiveButton() = click(POSITIVE_BUTTON, "Positive button")
83 
84     fun clickNegativeButton() = click(NEGATIVE_BUTTON, "Negative button")
85 
86     fun clickNegativeButtonMultipleDevices() {
87         ui.wait(Until.findObject(CONFIRMATION_UI), 2.seconds.inWholeMilliseconds)?.let {
88             // swipe up (or scroll down) until cancel button is enabled
89             val startTime = SystemClock.uptimeMillis()
90             var elapsedTime = 0L
91             // UiDevice.hasObject() takes a long time for some reason so wait at least 10 seconds
92             while (!ui.hasObject(NEGATIVE_BUTTON_MULTIPLE_DEVICES) &&
93                 elapsedTime < 10.seconds.inWholeMilliseconds
94             ) {
95                 it.swipe(Direction.UP, 1.0F)
96                 elapsedTime = SystemClock.uptimeMillis() - startTime
97             }
98         }
99         click(NEGATIVE_BUTTON_MULTIPLE_DEVICES, "Negative button for multiple devices")
100     }
101 
waitUntilAppAppearednull102     fun waitUntilAppAppeared() = ui.wait(
103         Until.hasObject(ASSOCIATION_REVOKE_APP_UI),
104         "The test app has not appeared."
105     )
106 
107     fun waitUntilPositiveButtonAppeared() = ui.waitLongAndFind(
108         Until.findObject(POSITIVE_BUTTON),
109         "Positive button"
110     )
111 
112     fun scrollToBottom() {
113         ui.wait(Until.findObject(SCROLLABLE_PERMISSION_LIST), 2.seconds.inWholeMilliseconds)?.let {
114             // swipe up (or scroll down) until "Allow" button is enabled
115             val startTime = SystemClock.uptimeMillis()
116             var elapsedTime = 0L
117             var positiveButton: UiObject2
118             while (elapsedTime < 5.seconds.inWholeMilliseconds) {
119                 it.swipe(Direction.UP, 1.0F)
120                 // Try to find the button in each iteration
121                 if (ui.hasObject(POSITIVE_BUTTON)) {
122                     positiveButton = waitUntilPositiveButtonAppeared()
123                     if (positiveButton.isEnabled == true) break
124                 }
125                 // Wait before consecutive swipes
126                 sleep(0.2.seconds.inWholeMilliseconds)
127                 elapsedTime = SystemClock.uptimeMillis() - startTime
128             }
129         }
130     }
131 
clicknull132     protected fun click(selector: BySelector, description: String) = ui.waitShortAndFind(
133         Until.findObject(selector),
134         "$description is not found"
135     ).click()
136 
137     companion object {
138         private const val PACKAGE_NAME = "com.android.companiondevicemanager"
139         private const val NOTIFICATION_PACKAGE_NAME = "com.android.settings"
140         private const val NOTIFICATION_PACKAGE_NAME_AUTO = "com.android.car.settings"
141 
142         private val CONFIRMATION_UI = By.pkg(PACKAGE_NAME)
143                 .res(PACKAGE_NAME, "activity_confirmation")
144         private val ASSOCIATION_REVOKE_APP_UI = By.pkg(ASSOCIATION_REVOKE_APP_NAME).depth(0)
145 
146         private val NOTIFICATION_UI = By.pkg(NOTIFICATION_PACKAGE_NAME).depth(0)
147 
148         private val NOTIFICATION_UI_AUTO = By.pkg(NOTIFICATION_PACKAGE_NAME_AUTO).depth(0)
149 
150         private val CLICKABLE_BUTTON =
151                 By.pkg(PACKAGE_NAME).clazz(".Button").clickable(true)
152         private val POSITIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_positive")
153         private val NEGATIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_negative")
154         private val NEGATIVE_BUTTON_MULTIPLE_DEVICES = By.pkg(PACKAGE_NAME)
155                 .res(PACKAGE_NAME, "negative_multiple_devices_layout")
156 
157         private val DEVICE_LIST = By.res(PACKAGE_NAME, "device_list")
158         private val DEVICE_LIST_ITEM = By.res(PACKAGE_NAME, "list_item_device")
159         private val DEVICE_LIST_WITH_ITEMS = By.copy(DEVICE_LIST)
160                 .hasDescendant(DEVICE_LIST_ITEM)
161 
162         private val SCROLLABLE_PERMISSION_LIST = By.pkg(PACKAGE_NAME)
163                 .res(PACKAGE_NAME, "permission_list")
164 
165         private val SYSTEM_DATA_TRANSFER_CONFIRMATION_UI = By.pkg(PACKAGE_NAME)
166                 .res(PACKAGE_NAME, "data_transfer_confirmation")
167     }
168 
waitnull169     protected fun UiDevice.wait(
170         condition: SearchCondition<Boolean>,
171         message: String,
172         timeout: Duration = 3.seconds
173     ) {
174         if (!wait(condition, timeout.inWholeMilliseconds)) error(message)
175     }
176 
waitShortnull177     protected fun UiDevice.waitShort(condition: SearchCondition<Boolean>, message: String) =
178             wait(condition, message, 1.seconds)
179 
180     protected fun UiDevice.waitAndFind(
181         condition: SearchCondition<UiObject2>,
182         message: String,
183         timeout: Duration = 3.seconds
184     ): UiObject2 =
185             wait(condition, timeout.inWholeMilliseconds) ?: error(message)
186 
187     protected fun UiDevice.waitShortAndFind(
188         condition: SearchCondition<UiObject2>,
189         message: String
190     ): UiObject2 = waitAndFind(condition, message, 1.seconds)
191 
192     protected fun UiDevice.waitLongAndFind(
193         condition: SearchCondition<UiObject2>,
194         message: String
195     ): UiObject2 = waitAndFind(condition, message, 10.seconds)
196 }
197