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.documentsui.bots; 18 19 import static junit.framework.Assert.assertEquals; 20 import static junit.framework.Assert.assertFalse; 21 import static junit.framework.Assert.assertNotNull; 22 import static junit.framework.Assert.assertTrue; 23 import static junit.framework.Assert.fail; 24 25 import android.app.UiAutomation; 26 import android.content.Context; 27 import android.graphics.Point; 28 import android.graphics.Rect; 29 import android.os.SystemClock; 30 import android.support.test.uiautomator.By; 31 import android.support.test.uiautomator.BySelector; 32 import android.support.test.uiautomator.Configurator; 33 import android.support.test.uiautomator.UiDevice; 34 import android.support.test.uiautomator.UiObject; 35 import android.support.test.uiautomator.UiObject2; 36 import android.support.test.uiautomator.UiObjectNotFoundException; 37 import android.support.test.uiautomator.UiSelector; 38 import android.support.test.uiautomator.Until; 39 import android.view.InputDevice; 40 import android.view.KeyEvent; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.widget.ImageView; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.List; 48 import java.util.regex.Pattern; 49 50 /** 51 * A test helper class that provides support for controlling directory list 52 * and making assertions against the state of it. 53 */ 54 public class DirectoryListBot extends Bots.BaseBot { 55 private static final String DIR_CONTAINER_ID = "com.android.documentsui:id/container_directory"; 56 private static final String DIR_LIST_ID = "com.android.documentsui:id/dir_list"; 57 58 private static final BySelector SNACK_DELETE = 59 By.desc(Pattern.compile("^Deleting [0-9]+ file.+")); 60 private UiAutomation mAutomation; 61 62 public DirectoryListBot( 63 UiDevice device, UiAutomation automation, Context context, int timeout) { 64 super(device, context, timeout); 65 mAutomation = automation; 66 } 67 68 public void assertDocumentsCount(int count) throws UiObjectNotFoundException { 69 UiObject docsList = findDocumentsList(); 70 assertEquals(count, docsList.getChildCount()); 71 } 72 73 public void assertDocumentsPresent(String... labels) throws UiObjectNotFoundException { 74 List<String> absent = new ArrayList<>(); 75 for (String label : labels) { 76 if (!findDocument(label).exists()) { 77 absent.add(label); 78 } 79 } 80 if (!absent.isEmpty()) { 81 fail("Expected documents " + Arrays.asList(labels) 82 + ", but missing " + absent); 83 } 84 } 85 86 public void assertDocumentsAbsent(String... labels) throws UiObjectNotFoundException { 87 List<String> found = new ArrayList<>(); 88 for (String label : labels) { 89 if (findDocument(label).exists()) { 90 found.add(label); 91 } 92 } 93 if (!found.isEmpty()) { 94 fail("Expected documents not present" + Arrays.asList(labels) 95 + ", but present " + found); 96 } 97 } 98 99 public void assertDocumentsCountOnList(boolean exists, int count) throws UiObjectNotFoundException { 100 UiObject docsList = findDocumentsList(); 101 assertEquals(exists, docsList.exists()); 102 if(docsList.exists()) { 103 assertEquals(count, docsList.getChildCount()); 104 } 105 } 106 107 public void assertHasMessage(String expected) throws UiObjectNotFoundException { 108 UiObject messageTextView = findHeaderMessageTextView(); 109 String msg = String.valueOf(expected); 110 assertEquals(msg, messageTextView.getText()); 111 } 112 113 public void assertHasMessage(boolean expected) throws UiObjectNotFoundException { 114 UiObject messageTextView = findHeaderMessageTextView(); 115 if (expected) { 116 assertTrue(messageTextView.exists()); 117 } else { 118 assertFalse(messageTextView.exists()); 119 } 120 } 121 122 public void assertHasMessageButtonText(String expected) throws UiObjectNotFoundException { 123 UiObject button = findHeaderMessageButton(); 124 String msg = String.valueOf(expected); 125 assertEquals(msg, button.getText()); 126 } 127 128 public void clickMessageButton() throws UiObjectNotFoundException { 129 UiObject button = findHeaderMessageButton(); 130 button.click(); 131 } 132 133 /** 134 * Checks against placeholder text. Placeholder can be Empty page, No results page, or the 135 * "Hourglass" page (ie. something-went-wrong page). 136 */ 137 public void assertPlaceholderMessageText(String message) throws UiObjectNotFoundException { 138 UiObject messageTextView = findPlaceholderMessageTextView(); 139 assertTrue(messageTextView.exists()); 140 141 String msg = String.valueOf(message); 142 assertEquals(msg, messageTextView.getText()); 143 144 } 145 146 private UiObject findHeaderMessageTextView() { 147 return findObject( 148 DIR_CONTAINER_ID, 149 "com.android.documentsui:id/message_textview"); 150 } 151 152 private UiObject findHeaderMessageButton() { 153 return findObject( 154 DIR_CONTAINER_ID, 155 "com.android.documentsui:id/button_dismiss"); 156 } 157 158 private UiObject findPlaceholderMessageTextView() { 159 return findObject( 160 DIR_CONTAINER_ID, 161 "com.android.documentsui:id/message"); 162 } 163 164 public void assertSnackbar(int id) { 165 assertNotNull(getSnackbar(mContext.getString(id))); 166 } 167 168 public void openDocument(String label) throws UiObjectNotFoundException { 169 int toolType = Configurator.getInstance().getToolType(); 170 Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); 171 UiObject doc = findDocument(label); 172 doc.click(); 173 Configurator.getInstance().setToolType(toolType); 174 } 175 176 public void selectDocument(String label) throws UiObjectNotFoundException { 177 waitForDocument(label); 178 UiObject2 selectionHotspot = findSelectionHotspot(label); 179 selectionHotspot.click(); 180 } 181 182 /** 183 * @param label The filename of the document 184 * @param number Which nth document it is. The number corresponding to "n selected" 185 */ 186 public void selectDocument(String label, int number) throws UiObjectNotFoundException { 187 selectDocument(label); 188 189 // wait until selection is fully done to avoid future click being registered as double 190 // clicking 191 assertSelection(number); 192 } 193 194 public UiObject2 findSelectionHotspot(String label) { 195 final BySelector list = By.res(DIR_LIST_ID); 196 197 BySelector selector = By.hasChild(By.text(label)); 198 UiObject2 parent = mDevice.findObject(list).findObject(selector); 199 if (parent.getClassName().equals("android.widget.LinearLayout")) { 200 // For list mode, the parent of the textView does not contain the selector icon, but the 201 // grandparent of the textView does 202 // Gotta go one more level up 203 selector = By.hasDescendant(By.text(label).depth(2)); 204 parent = mDevice.findObject(list).findObject(selector); 205 } 206 return parent.findObject(By.clazz(ImageView.class)); 207 } 208 209 public void copyFilesToClipboard(String...labels) throws UiObjectNotFoundException { 210 for (String label: labels) { 211 selectDocument(label); 212 } 213 mDevice.pressKeyCode(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON); 214 } 215 216 public void pasteFilesFromClipboard() { 217 mDevice.pressKeyCode(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON); 218 } 219 220 public UiObject2 getSnackbar(String message) { 221 return mDevice.wait(Until.findObject(By.text(message)), mTimeout); 222 } 223 224 public void waitForDeleteSnackbar() { 225 mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout); 226 } 227 228 public void waitForDeleteSnackbarGone() { 229 // wait a little longer for snackbar to go away, as it disappears after a timeout. 230 mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2); 231 } 232 233 public void waitForDocument(String label) throws UiObjectNotFoundException { 234 findDocument(label).waitForExists(mTimeout); 235 } 236 237 public UiObject findDocument(String label) throws UiObjectNotFoundException { 238 final UiSelector docList = new UiSelector().resourceId( 239 DIR_CONTAINER_ID).childSelector( 240 new UiSelector().resourceId(DIR_LIST_ID)); 241 242 // Wait for the first list item to appear 243 new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout); 244 245 // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label)); 246 return mDevice.findObject(docList.childSelector(new UiSelector().text(label))); 247 } 248 249 public boolean hasDocuments(String... labels) throws UiObjectNotFoundException { 250 for (String label : labels) { 251 if (!findDocument(label).exists()) { 252 return false; 253 } 254 } 255 return true; 256 } 257 258 public void assertFirstDocumentHasFocus() throws UiObjectNotFoundException { 259 final UiSelector docList = new UiSelector().resourceId( 260 DIR_CONTAINER_ID).childSelector( 261 new UiSelector().resourceId(DIR_LIST_ID)); 262 263 // Wait for the first list item to appear 264 UiObject doc = new UiObject(docList.childSelector(new UiSelector())); 265 doc.waitForExists(mTimeout); 266 267 assertTrue(doc.isFocused()); 268 } 269 270 public UiObject findDocumentsList() { 271 return findObject( 272 DIR_CONTAINER_ID, 273 DIR_LIST_ID); 274 } 275 276 public void assertHasFocus() { 277 assertHasFocus(DIR_LIST_ID); 278 } 279 280 public void assertSelection(int numSelected) { 281 String assertSelectionText = numSelected + " selected"; 282 UiObject2 selectionText = mDevice.wait( 283 Until.findObject(By.text(assertSelectionText)), mTimeout); 284 assertTrue(selectionText != null); 285 } 286 287 public void assertOrder(String[] dirs, String[] files) throws UiObjectNotFoundException { 288 for (int i = 0; i < dirs.length - 1; ++i) { 289 assertOrder(dirs[i], dirs[i + 1]); 290 } 291 292 if (dirs.length > 0 && files.length > 0) { 293 assertOrder(dirs[dirs.length - 1], files[0]); 294 } 295 296 for (int i = 0; i < files.length - 1; ++i) { 297 assertOrder(files[i], files[i + 1]); 298 } 299 } 300 301 public void rightClickDocument(String label) throws UiObjectNotFoundException { 302 Rect startCoord = findDocument(label).getBounds(); 303 rightClickDocument(new Point(startCoord.centerX(), startCoord.centerY())); 304 } 305 306 public void rightClickDocument(Point point) throws UiObjectNotFoundException { 307 //TODO: Use Espresso instead of doing the events mock ourselves 308 MotionEvent motionDown = getTestMotionEvent( 309 MotionEvent.ACTION_DOWN, 310 MotionEvent.BUTTON_SECONDARY, 311 MotionEvent.TOOL_TYPE_MOUSE, 312 InputDevice.SOURCE_MOUSE, 313 point.x, 314 point.y); 315 mAutomation.injectInputEvent(motionDown, true); 316 SystemClock.sleep(100); 317 318 MotionEvent motionUp = getTestMotionEvent( 319 MotionEvent.ACTION_UP, 320 MotionEvent.BUTTON_SECONDARY, 321 MotionEvent.TOOL_TYPE_MOUSE, 322 InputDevice.SOURCE_MOUSE, 323 point.x, 324 point.y); 325 326 mAutomation.injectInputEvent(motionUp, true); 327 } 328 329 private MotionEvent getTestMotionEvent( 330 int action, int buttonState, int toolType, int source, int x, int y) { 331 long eventTime = SystemClock.uptimeMillis(); 332 333 MotionEvent.PointerProperties[] pp = {new MotionEvent.PointerProperties()}; 334 pp[0].clear(); 335 pp[0].id = 0; 336 pp[0].toolType = toolType; 337 338 MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()}; 339 pointerCoords[0].clear(); 340 pointerCoords[0].x = x; 341 pointerCoords[0].y = y; 342 pointerCoords[0].pressure = 0; 343 pointerCoords[0].size = 1; 344 345 return MotionEvent.obtain( 346 eventTime, 347 eventTime, 348 action, 349 1, 350 pp, 351 pointerCoords, 352 0, 353 buttonState, 354 1f, 355 1f, 356 0, 357 0, 358 source, 359 0); 360 } 361 362 private void assertOrder(String first, String second) throws UiObjectNotFoundException { 363 364 final UiObject firstObj = findDocument(first); 365 final UiObject secondObj = findDocument(second); 366 367 final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection(); 368 final Rect firstBound = firstObj.getVisibleBounds(); 369 final Rect secondBound = secondObj.getVisibleBounds(); 370 if (layoutDirection == View.LAYOUT_DIRECTION_LTR) { 371 assertTrue( 372 "\"" + first + "\" is not located above or to the left of \"" + second 373 + "\" in LTR", 374 firstBound.bottom < secondBound.top || firstBound.right < secondBound.left); 375 } else { 376 assertTrue( 377 "\"" + first + "\" is not located above or to the right of \"" + second + 378 "\" in RTL", 379 firstBound.bottom < secondBound.top || firstBound.left > secondBound.right); 380 } 381 } 382 } 383