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