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