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