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