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.UiScrollable;
38 import android.support.test.uiautomator.UiSelector;
39 import android.support.test.uiautomator.Until;
40 import android.view.InputDevice;
41 import android.view.KeyEvent;
42 import android.view.MotionEvent;
43 import android.view.View;
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 
56     private static final int MAX_LAYOUT_LEVEL = 10;
57 
58     private static final BySelector SNACK_DELETE =
59             By.text(Pattern.compile("^Deleting [0-9]+ item.+"));
60 
61     private final String mDirContainerId;
62     private final String mDirListId;
63     private final String mItemRootId;
64     private final String mPreviewId;
65     private final String mIconId;
66 
67     private UiAutomation mAutomation;
68 
DirectoryListBot( UiDevice device, UiAutomation automation, Context context, int timeout)69     public DirectoryListBot(
70             UiDevice device, UiAutomation automation, Context context, int timeout) {
71         super(device, context, timeout);
72         mAutomation = automation;
73         mDirContainerId = mTargetPackage + ":id/container_directory";
74         mDirListId = mTargetPackage + ":id/dir_list";
75         mItemRootId = mTargetPackage + ":id/item_root";
76         mPreviewId = mTargetPackage + ":id/preview_icon";
77         mIconId = mTargetPackage + ":id/icon";
78     }
79 
assertDocumentsCount(int count)80     public void assertDocumentsCount(int count) throws UiObjectNotFoundException {
81         UiObject docsList = findDocumentsList();
82         assertEquals(count, docsList.getChildCount());
83     }
84 
assertDocumentsPresent(String... labels)85     public void assertDocumentsPresent(String... labels) throws UiObjectNotFoundException {
86         List<String> absent = new ArrayList<>();
87         for (String label : labels) {
88             if (!findDocument(label).exists()) {
89                 absent.add(label);
90             }
91         }
92         if (!absent.isEmpty()) {
93             fail("Expected documents " + Arrays.asList(labels)
94                     + ", but missing " + absent);
95         }
96     }
97 
assertDocumentsAbsent(String... labels)98     public void assertDocumentsAbsent(String... labels) throws UiObjectNotFoundException {
99         List<String> found = new ArrayList<>();
100         for (String label : labels) {
101             if (findDocument(label).exists()) {
102                 found.add(label);
103             }
104         }
105         if (!found.isEmpty()) {
106             fail("Expected documents not present" + Arrays.asList(labels)
107                     + ", but present " + found);
108         }
109     }
110 
assertDocumentsCountOnList(boolean exists, int count)111     public void assertDocumentsCountOnList(boolean exists, int count) throws UiObjectNotFoundException {
112         UiObject docsList = findDocumentsList();
113         assertEquals(exists, docsList.exists());
114         if(docsList.exists()) {
115             assertEquals(count, docsList.getChildCount());
116         }
117     }
118 
assertHasMessage(String expected)119     public void assertHasMessage(String expected) throws UiObjectNotFoundException {
120         UiObject messageTextView = findHeaderMessageTextView();
121         String msg = String.valueOf(expected);
122         assertEquals(msg, messageTextView.getText());
123     }
124 
assertHasMessage(boolean expected)125     public void assertHasMessage(boolean expected) throws UiObjectNotFoundException {
126         UiObject messageTextView = findHeaderMessageTextView();
127         if (expected) {
128             assertTrue(messageTextView.exists());
129         } else {
130             assertFalse(messageTextView.exists());
131         }
132     }
133 
assertHasMessageButtonText(String expected)134     public void assertHasMessageButtonText(String expected) throws UiObjectNotFoundException {
135         UiObject button = findHeaderMessageButton();
136         String msg = String.valueOf(expected);
137         assertEquals(msg.toUpperCase(), button.getText().toUpperCase());
138     }
139 
clickMessageButton()140     public void clickMessageButton() throws UiObjectNotFoundException {
141         UiObject button = findHeaderMessageButton();
142         button.click();
143     }
144 
145     /**
146      * Checks against placeholder text. Placeholder can be Empty page, No results page, or the
147      * "Hourglass" page (ie. something-went-wrong page).
148      */
assertPlaceholderMessageText(String message)149     public void assertPlaceholderMessageText(String message) throws UiObjectNotFoundException {
150         UiObject messageTextView = findPlaceholderMessageTextView();
151         assertTrue(messageTextView.exists());
152 
153         String msg = String.valueOf(message);
154         assertEquals(msg, messageTextView.getText());
155 
156     }
157 
findHeaderMessageTextView()158     private UiObject findHeaderMessageTextView() {
159         return findObject(
160                 mDirContainerId,
161                 mTargetPackage + ":id/message_textview");
162     }
163 
findHeaderMessageButton()164     private UiObject findHeaderMessageButton() {
165         return findObject(
166                 mDirContainerId,
167                 mTargetPackage + ":id/dismiss_button");
168     }
169 
findPlaceholderMessageTextView()170     private UiObject findPlaceholderMessageTextView() {
171         return findObject(
172                 mDirContainerId,
173                 mTargetPackage + ":id/message");
174     }
175 
waitForHolderMessage()176     public void waitForHolderMessage() {
177         findPlaceholderMessageTextView().waitForExists(mTimeout);
178     }
179 
assertSnackbar(int id)180     public void assertSnackbar(int id) {
181         assertNotNull(getSnackbar(mContext.getString(id)));
182     }
183 
openDocument(String label)184     public void openDocument(String label) throws UiObjectNotFoundException {
185         int toolType = Configurator.getInstance().getToolType();
186         Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
187         UiObject doc = findDocument(label, true);
188         doc.click();
189         Configurator.getInstance().setToolType(toolType);
190     }
191 
selectDocument(String label)192     public void selectDocument(String label) throws UiObjectNotFoundException {
193         waitForDocument(label);
194         UiObject2 selectionHotspot = findSelectionHotspot(label);
195         selectionHotspot.click();
196     }
197 
198     /**
199      * @param label The filename of the document
200      * @param number Which nth document it is. The number corresponding to "n selected"
201      */
selectDocument(String label, int number)202     public void selectDocument(String label, int number) throws UiObjectNotFoundException {
203         selectDocument(label);
204 
205         // wait until selection is fully done to avoid future click being registered as double
206         // clicking
207         assertSelection(number);
208     }
209 
isDocumentSelected(String label)210     public boolean isDocumentSelected(String label) throws UiObjectNotFoundException {
211         waitForDocument(label);
212         UiObject2 selectionHotspot = findSelectionHotspot(label);
213         return selectionHotspot.getResourceName()
214                 .equals(mTargetPackage + ":id/icon_check");
215     }
216 
findSelectionHotspot(String label)217     public UiObject2 findSelectionHotspot(String label) throws UiObjectNotFoundException {
218         final BySelector list = By.res(mDirListId);
219 
220         BySelector selector = By.hasChild(By.text(label));
221 
222         final UiSelector docList = findDocumentsListSelector();
223         new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
224 
225         UiObject2 parent = mDevice.findObject(list).findObject(selector);
226         for (int i = 1; i <= MAX_LAYOUT_LEVEL; i++) {
227             parent = parent.getParent();
228             if (mItemRootId.equals(parent.getResourceName())) {
229                 break;
230             }
231         }
232         return parent.findObject(By.res(mIconId));
233     }
234 
copyFilesToClipboard(String...labels)235     public void copyFilesToClipboard(String...labels) throws UiObjectNotFoundException {
236         for (String label: labels) {
237             selectDocument(label);
238         }
239         mDevice.pressKeyCode(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON);
240     }
241 
pasteFilesFromClipboard()242     public void pasteFilesFromClipboard() {
243         mDevice.pressKeyCode(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON);
244     }
245 
getSnackbar(String message)246     public UiObject2 getSnackbar(String message) {
247         return mDevice.wait(Until.findObject(By.text(message)), mTimeout);
248     }
249 
clickSnackbarAction()250     public void clickSnackbarAction() throws UiObjectNotFoundException {
251         UiObject snackbarAction =
252                 findObject(mTargetPackage + ":id/snackbar_action");
253         snackbarAction.click();
254     }
255 
waitForDeleteSnackbar()256     public void waitForDeleteSnackbar() {
257         mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
258     }
259 
waitForDeleteSnackbarGone()260     public void waitForDeleteSnackbarGone() {
261         // wait a little longer for snackbar to go away, as it disappears after a timeout.
262         mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
263     }
264 
waitForDocument(String label)265     public void waitForDocument(String label) throws UiObjectNotFoundException {
266         findDocument(label).waitForExists(mTimeout);
267     }
268 
findDocument(String label)269     public UiObject findDocument(String label) throws UiObjectNotFoundException {
270         return findDocument(label, false);
271     }
272 
findDocument(String label, boolean withScroll)273     public UiObject findDocument(String label, boolean withScroll)
274             throws UiObjectNotFoundException {
275         final UiSelector docList = findDocumentsListSelector();
276 
277         // Wait for the first list item to appear
278         new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
279 
280         if (withScroll) {
281             new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
282         }
283         return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
284     }
285 
hasDocuments(String... labels)286     public boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
287         for (String label : labels) {
288             if (!findDocument(label).exists()) {
289                 return false;
290             }
291         }
292         return true;
293     }
294 
hasDocumentPreview(String label)295     public boolean hasDocumentPreview(String label) {
296         final BySelector list = By.res(mDirListId);
297         final UiObject2 text = mDevice.findObject(list).findObject(By.text(label));
298 
299         UiObject2 parent = text;
300         for (int i = 1; i <= MAX_LAYOUT_LEVEL; i++) {
301             parent = parent.getParent();
302             if (mItemRootId.equals(parent.getResourceName())) {
303                 break;
304             }
305         }
306 
307         return parent.hasObject(By.res(mPreviewId));
308     }
309 
assertFirstDocumentHasFocus()310     public void assertFirstDocumentHasFocus() throws UiObjectNotFoundException {
311         final UiSelector docList = findDocumentsListSelector();
312 
313         // Wait for the first list item to appear
314         UiObject doc = new UiObject(docList.childSelector(new UiSelector()));
315         doc.waitForExists(mTimeout);
316 
317         assertTrue(doc.isFocused());
318     }
319 
findDocumentsList()320     public UiObject findDocumentsList() {
321         return findObject(
322                 mDirContainerId,
323                 mDirListId);
324     }
325 
findDocumentsListSelector()326     private UiSelector findDocumentsListSelector() {
327         return new UiSelector().resourceId(
328                 mDirContainerId).childSelector(
329                 new UiSelector().resourceId(mDirListId));
330     }
331 
assertHasFocus()332     public void assertHasFocus() {
333         assertHasFocus(mDirListId);
334     }
335 
assertSelection(int numSelected)336     public void assertSelection(int numSelected) {
337         String assertSelectionText = numSelected + " selected";
338         UiObject2 selectionText = mDevice.wait(
339                 Until.findObject(By.text(assertSelectionText)), mTimeout);
340         assertTrue(selectionText != null);
341     }
342 
assertOrder(String[] dirs, String[] files)343     public void assertOrder(String[] dirs, String[] files) throws UiObjectNotFoundException {
344         for (int i = 0; i < dirs.length - 1; ++i) {
345             assertOrder(dirs[i], dirs[i + 1]);
346         }
347 
348         if (dirs.length > 0 && files.length > 0) {
349             assertOrder(dirs[dirs.length - 1], files[0]);
350         }
351 
352         for (int i = 0; i < files.length - 1; ++i) {
353             assertOrder(files[i], files[i + 1]);
354         }
355     }
356 
rightClickDocument(String label)357     public void rightClickDocument(String label) throws UiObjectNotFoundException {
358         Rect startCoord = findDocument(label, true).getBounds();
359         rightClickDocument(new Point(startCoord.centerX(), startCoord.centerY()));
360     }
361 
rightClickDocument(Point point)362     public void rightClickDocument(Point point) throws UiObjectNotFoundException {
363         //TODO: Use Espresso instead of doing the events mock ourselves
364         MotionEvent motionDown = getTestMotionEvent(
365                 MotionEvent.ACTION_DOWN,
366                 MotionEvent.BUTTON_SECONDARY,
367                 MotionEvent.TOOL_TYPE_MOUSE,
368                 InputDevice.SOURCE_MOUSE,
369                 point.x,
370                 point.y);
371         mAutomation.injectInputEvent(motionDown, true);
372         SystemClock.sleep(100);
373 
374         MotionEvent motionUp = getTestMotionEvent(
375                 MotionEvent.ACTION_UP,
376                 MotionEvent.BUTTON_SECONDARY,
377                 MotionEvent.TOOL_TYPE_MOUSE,
378                 InputDevice.SOURCE_MOUSE,
379                 point.x,
380                 point.y);
381 
382         mAutomation.injectInputEvent(motionUp, true);
383     }
384 
getTestMotionEvent( int action, int buttonState, int toolType, int source, int x, int y)385     private MotionEvent getTestMotionEvent(
386             int action, int buttonState, int toolType, int source, int x, int y) {
387         long eventTime = SystemClock.uptimeMillis();
388 
389         MotionEvent.PointerProperties[] pp = {new MotionEvent.PointerProperties()};
390         pp[0].clear();
391         pp[0].id = 0;
392         pp[0].toolType = toolType;
393 
394         MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()};
395         pointerCoords[0].clear();
396         pointerCoords[0].x = x;
397         pointerCoords[0].y = y;
398         pointerCoords[0].pressure = 0;
399         pointerCoords[0].size = 1;
400 
401         return MotionEvent.obtain(
402                 eventTime,
403                 eventTime,
404                 action,
405                 1,
406                 pp,
407                 pointerCoords,
408                 0,
409                 buttonState,
410                 1f,
411                 1f,
412                 0,
413                 0,
414                 source,
415                 0);
416     }
417 
assertOrder(String first, String second)418     private void assertOrder(String first, String second) throws UiObjectNotFoundException {
419 
420         final UiObject firstObj = findDocument(first);
421         final UiObject secondObj = findDocument(second);
422 
423         final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection();
424         final Rect firstBound = firstObj.getVisibleBounds();
425         final Rect secondBound = secondObj.getVisibleBounds();
426         if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
427             assertTrue(
428                     "\"" + first + "\" is not located above or to the left of \"" + second
429                             + "\" in LTR",
430                     firstBound.bottom < secondBound.top || firstBound.right < secondBound.left);
431         } else {
432             assertTrue(
433                     "\"" + first + "\" is not located above or to the right of \"" + second +
434                             "\" in RTL",
435                     firstBound.bottom < secondBound.top || firstBound.left > secondBound.right);
436         }
437     }
438 }
439