• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 waitUntilTimeoutMessageVisible() = ui.wait(
54         Until.hasObject(TIMEOUT_MESSAGE),
55         "Device discovery timeout message has not appeared."
56     )
57 
58     fun waitUntilGone() = ui.waitShort(Until.gone(CONFIRMATION_UI), "CDM UI has not disappeared")
59 
60     fun waitAndClickOnFirstFoundDevice() {
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 < 10.seconds.inWholeMilliseconds) {
66             val firstDevice = ui.waitLongAndFind(
67                 Until.findObject(DEVICE_LIST_WITH_ITEMS),
68                 "The item in the Device List not found or empty"
69             ).children[0]
70 
71             firstDevice.click()
72             sleep(0.2.seconds.inWholeMilliseconds)
73             elapsedTime = SystemClock.uptimeMillis() - startTime
74         }
75     }
76 
waitUntilPositiveButtonIsEnabledAndClicknull77     fun waitUntilPositiveButtonIsEnabledAndClick() = ui.waitLongAndFind(
78         Until.findObject(POSITIVE_BUTTON),
79         "Positive button not found or not clickable"
80     ).click()
81 
82     fun waitUntilSystemDataTransferConfirmationVisible() = ui.wait(
83             Until.hasObject(SYSTEM_DATA_TRANSFER_CONFIRMATION_UI),
84             "System data transfer dialog has not appeared."
85     )
86 
87     fun clickPositiveButton() = click(POSITIVE_BUTTON, "Positive button")
88 
89     fun clickNegativeButton() = click(NEGATIVE_BUTTON, "Negative button")
90 
91     fun clickNegativeButtonMultipleDevices() {
92         ui.wait(Until.findObject(CONFIRMATION_UI), 2.seconds.inWholeMilliseconds)?.let {
93             // swipe up (or scroll down) until cancel button is enabled
94             val startTime = SystemClock.uptimeMillis()
95             var elapsedTime = 0L
96             // UiDevice.hasObject() takes a long time for some reason so wait at least 10 seconds
97             while (!ui.hasObject(NEGATIVE_BUTTON_MULTIPLE_DEVICES) &&
98                 elapsedTime < 10.seconds.inWholeMilliseconds
99             ) {
100                 it.swipe(Direction.UP, 1.0F)
101                 elapsedTime = SystemClock.uptimeMillis() - startTime
102             }
103         }
104         click(NEGATIVE_BUTTON_MULTIPLE_DEVICES, "Negative button for multiple devices")
105     }
106 
waitUntilAppAppearednull107     fun waitUntilAppAppeared() = ui.wait(
108         Until.hasObject(ASSOCIATION_REVOKE_APP_UI),
109         "The test app has not appeared."
110     )
111 
112     fun waitUntilPositiveButtonAppeared() = ui.waitLongAndFind(
113         Until.findObject(POSITIVE_BUTTON),
114         "Positive button"
115     )
116 
117     fun scrollToBottom(selector: BySelector = CONFIRMATION_UI) {
118         // Perform the swipe and check for the button.
119         fun scrollAndCheckButton(scrollableObject: UiObject2?, positiveButtonCheck: () -> Boolean) {
120             scrollableObject?.let {
121                 val startTime = SystemClock.uptimeMillis()
122                 var elapsedTime = 0L
123                 while (elapsedTime < 5.seconds.inWholeMilliseconds) {
124                     it.swipe(Direction.UP, 1.0F)
125                     if (positiveButtonCheck()) {
126                         break
127                     }
128                     sleep(0.2.seconds.inWholeMilliseconds)
129                     elapsedTime = SystemClock.uptimeMillis() - startTime
130                 }
131             }
132         }
133 
134         // First, swipe the CDM dialog down to ensure reached the end.
135         scrollAndCheckButton(
136             ui.wait(Until.findObject(selector), 2.seconds.inWholeMilliseconds)
137         ) {
138             ui.hasObject(POSITIVE_BUTTON) && ui.hasObject(NEGATIVE_BUTTON)
139         }
140 
141         // Can return here since no permission list in system transfer dialog.
142         if (selector == SYSTEM_DATA_TRANSFER_CONFIRMATION_UI) return
143 
144         // Second, scroll the permission list until both the Allow and Deny buttons are enabled.
145         scrollAndCheckButton(
146             ui.wait(Until.findObject(SCROLLABLE_PERMISSION_LIST), 2.seconds.inWholeMilliseconds)
147         ) {
148             ui.hasObject(POSITIVE_BUTTON) &&
149                     ui.hasObject(NEGATIVE_BUTTON) && waitUntilPositiveButtonAppeared().isEnabled
150         }
151     }
152 
clicknull153     protected fun click(selector: BySelector, description: String) = ui.waitShortAndFind(
154         Until.findObject(selector),
155         "$description is not found"
156     ).click()
157 
158     companion object {
159         private const val PACKAGE_NAME = "com.android.companiondevicemanager"
160         private const val NOTIFICATION_PACKAGE_NAME = "com.android.settings"
161         private const val NOTIFICATION_PACKAGE_NAME_AUTO = "com.android.car.settings"
162 
163         val CONFIRMATION_UI = By.pkg(PACKAGE_NAME)
164                 .res(PACKAGE_NAME, "activity_confirmation")
165         private val ASSOCIATION_REVOKE_APP_UI = By.pkg(ASSOCIATION_REVOKE_APP_NAME).depth(0)
166 
167         private val NOTIFICATION_UI = By.pkg(NOTIFICATION_PACKAGE_NAME).depth(0)
168 
169         private val NOTIFICATION_UI_AUTO = By.pkg(NOTIFICATION_PACKAGE_NAME_AUTO).depth(0)
170 
171         private val TIMEOUT_MESSAGE = By.res(PACKAGE_NAME, "timeout_message")
172 
173         private val CLICKABLE_BUTTON =
174                 By.pkg(PACKAGE_NAME).clazz(".Button").clickable(true)
175         private val POSITIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_positive")
176         private val NEGATIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_negative")
177         private val NEGATIVE_BUTTON_MULTIPLE_DEVICES = By.pkg(PACKAGE_NAME)
178                 .res(PACKAGE_NAME, "negative_multiple_devices_layout")
179 
180         private val DEVICE_LIST = By.res(PACKAGE_NAME, "device_list")
181         private val DEVICE_LIST_ITEM = By.res(PACKAGE_NAME, "list_item_device")
182         private val DEVICE_LIST_WITH_ITEMS = By.copy(DEVICE_LIST)
183                 .hasDescendant(DEVICE_LIST_ITEM)
184 
185         private val SCROLLABLE_PERMISSION_LIST = By.pkg(PACKAGE_NAME)
186                 .res(PACKAGE_NAME, "permission_list")
187 
188         val SYSTEM_DATA_TRANSFER_CONFIRMATION_UI = By.pkg(PACKAGE_NAME)
189                 .res(PACKAGE_NAME, "data_transfer_confirmation")
190     }
191 
waitnull192     protected fun UiDevice.wait(
193         condition: SearchCondition<Boolean>,
194         message: String,
195         timeout: Duration = 3.seconds
196     ) {
197         if (!wait(condition, timeout.inWholeMilliseconds)) error(message)
198     }
199 
waitShortnull200     protected fun UiDevice.waitShort(condition: SearchCondition<Boolean>, message: String) =
201             wait(condition, message, 1.seconds)
202 
203     protected fun UiDevice.waitAndFind(
204         condition: SearchCondition<UiObject2>,
205         message: String,
206         timeout: Duration = 3.seconds
207     ): UiObject2 =
208             wait(condition, timeout.inWholeMilliseconds) ?: error(message)
209 
210     protected fun UiDevice.waitShortAndFind(
211         condition: SearchCondition<UiObject2>,
212         message: String
213     ): UiObject2 = waitAndFind(condition, message, 1.seconds)
214 
215     protected fun UiDevice.waitLongAndFind(
216         condition: SearchCondition<UiObject2>,
217         message: String
218     ): UiObject2 = waitAndFind(condition, message, 10.seconds)
219 }
220