1 /* 2 * Copyright (C) 2015 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 com.android.shell; 18 19 import android.app.Instrumentation; 20 import android.app.StatusBarManager; 21 import android.os.SystemClock; 22 import android.text.format.DateUtils; 23 import android.util.Log; 24 25 import androidx.test.uiautomator.By; 26 import androidx.test.uiautomator.UiDevice; 27 import androidx.test.uiautomator.UiObject; 28 import androidx.test.uiautomator.UiObject2; 29 import androidx.test.uiautomator.UiObjectNotFoundException; 30 import androidx.test.uiautomator.UiSelector; 31 import androidx.test.uiautomator.Until; 32 33 import static junit.framework.Assert.assertFalse; 34 import static junit.framework.Assert.assertNotNull; 35 import static junit.framework.Assert.assertTrue; 36 37 import java.util.List; 38 39 /** 40 * A helper class for UI-related testing tasks. 41 */ 42 final class UiBot { 43 44 private static final String TAG = "UiBot"; 45 private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; 46 private static final String ANDROID_PACKAGE = "android"; 47 48 private static final long SHORT_UI_TIMEOUT_MS = (3 * DateUtils.SECOND_IN_MILLIS); 49 50 private final Instrumentation mInstrumentation; 51 private final UiDevice mDevice; 52 private final int mTimeout; 53 UiBot(Instrumentation instrumentation, int timeout)54 public UiBot(Instrumentation instrumentation, int timeout) { 55 mInstrumentation = instrumentation; 56 mDevice = UiDevice.getInstance(instrumentation); 57 mTimeout = timeout; 58 } 59 60 /** 61 * Opens the system notification and gets a UiObject with the text. 62 * 63 * @param text Notification's text as displayed by the UI. 64 * @return notification object. 65 */ getNotification(String text)66 public UiObject getNotification(String text) { 67 boolean opened = mDevice.openNotification(); 68 Log.v(TAG, "openNotification(): " + opened); 69 boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout); 70 assertTrue("could not get system ui (" + SYSTEMUI_PACKAGE + ")", gotIt); 71 72 return getObject(text); 73 } 74 75 /** 76 * Opens the system notification and gets a notification containing the text. 77 * 78 * @param text Notification's text as displayed by the UI. 79 * @return notification object. 80 */ getNotification2(String text)81 public UiObject2 getNotification2(String text) { 82 boolean opened = mDevice.openNotification(); 83 Log.v(TAG, "openNotification(): " + opened); 84 final UiObject2 notificationScroller = mDevice.wait(Until.findObject( 85 By.res(SYSTEMUI_PACKAGE, "notification_stack_scroller")), mTimeout); 86 assertNotNull("could not get notification stack scroller", notificationScroller); 87 final List<UiObject2> notificationList = notificationScroller.getChildren(); 88 for (UiObject2 notification: notificationList) { 89 final UiObject2 notificationText = notification.findObject(By.textContains(text)); 90 if (notificationText != null) { 91 return notification; 92 } 93 } 94 return null; 95 } 96 97 /** 98 * Expands the notification. 99 * 100 * @param notification The notification object returned by {@link #getNotification2(String)}. 101 */ expandNotification(UiObject2 notification)102 public void expandNotification(UiObject2 notification) { 103 final UiObject2 expandBtn = notification.findObject( 104 By.res(ANDROID_PACKAGE, "expand_button")); 105 if (expandBtn.getContentDescription().equals("Collapse")) { 106 return; 107 } 108 expandBtn.click(); 109 mDevice.waitForIdle(); 110 } 111 collapseStatusBar()112 public void collapseStatusBar() throws Exception { 113 // TODO: mDevice should provide such method.. 114 StatusBarManager sbm = 115 (StatusBarManager) mInstrumentation.getContext().getSystemService("statusbar"); 116 sbm.collapsePanels(); 117 } 118 119 /** 120 * Opens the system notification and clicks a given notification. 121 * 122 * @param text Notificaton's text as displayed by the UI. 123 */ clickOnNotification(String text)124 public void clickOnNotification(String text) { 125 UiObject notification = getNotification(text); 126 click(notification, "bug report notification"); 127 } 128 129 /** 130 * Gets an object that might not yet be available in current UI. 131 * 132 * @param text Object's text as displayed by the UI. 133 */ getObject(String text)134 public UiObject getObject(String text) { 135 boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout); 136 assertTrue("object with text '(" + text + "') not visible yet", gotIt); 137 return getVisibleObject(text); 138 } 139 140 /** 141 * Gets an object that might not yet be available in current UI. 142 * 143 * @param id Object's fully-qualified resource id (like {@code android:id/button1}) 144 */ getObjectById(String id)145 public UiObject getObjectById(String id) { 146 boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), mTimeout); 147 assertTrue("object with id '(" + id + "') not visible yet", gotIt); 148 return getVisibleObjectById(id); 149 } 150 151 /** 152 * Gets an object which is guaranteed to be present in the current UI. 153 * 154 * @param text Object's text as displayed by the UI. 155 */ getVisibleObject(String text)156 public UiObject getVisibleObject(String text) { 157 UiObject uiObject = mDevice.findObject(new UiSelector().text(text)); 158 assertTrue("could not find object with text '" + text + "'", uiObject.exists()); 159 return uiObject; 160 } 161 162 /** 163 * Gets an object which is guaranteed to be present in the current UI. 164 * 165 * @param text Object's text as displayed by the UI. 166 */ getVisibleObjectById(String id)167 public UiObject getVisibleObjectById(String id) { 168 UiObject uiObject = mDevice.findObject(new UiSelector().resourceId(id)); 169 assertTrue("could not find object with id '" + id+ "'", uiObject.exists()); 170 return uiObject; 171 } 172 173 /** 174 * Asserts an object is not visible. 175 */ assertNotVisibleById(String id)176 public void assertNotVisibleById(String id) { 177 // TODO: not working when the bugreport dialog is shown, it hangs until the dialog is 178 // dismissed and hence always work. 179 boolean hasIt = mDevice.hasObject(By.res(id)); 180 assertFalse("should not have found object with id '" + id+ "'", hasIt); 181 } 182 183 184 /** 185 * Clicks on a UI element. 186 * 187 * @param uiObject UI element to be clicked. 188 * @param description Elements's description used on logging statements. 189 */ click(UiObject uiObject, String description)190 public void click(UiObject uiObject, String description) { 191 try { 192 boolean clicked = uiObject.click(); 193 // TODO: assertion below fails sometimes, even though the click succeeded, 194 // (specially when clicking the "Just Once" button), so it's currently just logged. 195 // assertTrue("could not click on object '" + description + "'", clicked); 196 197 Log.v(TAG, "onClick for " + description + ": " + clicked); 198 } catch (UiObjectNotFoundException e) { 199 throw new IllegalStateException("exception when clicking on object '" + description 200 + "'", e); 201 } 202 } 203 204 /** 205 * Chooses a given activity to handle an Intent. 206 * 207 * @param name name of the activity as displayed in the UI (typically the value set by 208 * {@code android:label} in the manifest). 209 */ chooseActivity(String name)210 public void chooseActivity(String name) { 211 // It uses an intent chooser now, so just getting the activity by text is enough... 212 final String share = mInstrumentation.getContext().getString( 213 com.android.internal.R.string.share); 214 boolean gotIt = mDevice.wait(Until.hasObject(By.text(share)), mTimeout); 215 assertTrue("could not get share activity (" + share + ")", gotIt); 216 swipeUp(); 217 SystemClock.sleep(SHORT_UI_TIMEOUT_MS); 218 UiObject activity = getObject(name); 219 click(activity, name); 220 } 221 pressBack()222 public void pressBack() { 223 mDevice.pressBack(); 224 } 225 turnScreenOn()226 public void turnScreenOn() throws Exception { 227 mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP"); 228 mDevice.executeShellCommand("wm dismiss-keyguard"); 229 mDevice.waitForIdle(); 230 } 231 swipeUp()232 public void swipeUp() { 233 mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() * 3 / 4, 234 mDevice.getDisplayWidth() / 2, 0, 30); 235 } 236 } 237