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