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