• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.launcher3.tapl;
18 
19 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
20 
21 import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOLD_BEFORE_DROP;
22 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
23 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
24 
25 import static junit.framework.TestCase.assertNotNull;
26 import static junit.framework.TestCase.assertTrue;
27 
28 import android.graphics.Point;
29 import android.graphics.Rect;
30 import android.os.SystemClock;
31 import android.view.KeyEvent;
32 import android.view.MotionEvent;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.test.uiautomator.By;
37 import androidx.test.uiautomator.BySelector;
38 import androidx.test.uiautomator.Direction;
39 import androidx.test.uiautomator.UiDevice;
40 import androidx.test.uiautomator.UiObject2;
41 import androidx.test.uiautomator.Until;
42 
43 import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
44 import com.android.launcher3.testing.shared.TestProtocol;
45 import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
46 
47 import java.util.List;
48 import java.util.Map;
49 import java.util.function.Supplier;
50 import java.util.regex.Pattern;
51 import java.util.stream.Collectors;
52 
53 /**
54  * Operations on the workspace screen.
55  */
56 public final class Workspace extends Home {
57     private static final int FLING_STEPS = 10;
58     private static final int DEFAULT_DRAG_STEPS = 10;
59     private static final String DROP_BAR_RES_ID = "drop_target_bar";
60     private static final String DELETE_TARGET_TEXT_ID = "delete_target_text";
61     private static final String UNINSTALL_TARGET_TEXT_ID = "uninstall_target_text";
62 
63     static final Pattern EVENT_CTRL_W_DOWN = Pattern.compile(
64             "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_W"
65                     + ".*?metaState=META_CTRL_ON");
66     static final Pattern EVENT_CTRL_W_UP = Pattern.compile(
67             "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_W"
68                     + ".*?metaState=META_CTRL_ON");
69     static final Pattern LONG_CLICK_EVENT = Pattern.compile("onWorkspaceItemLongClick");
70     public static final int MAX_WORKSPACE_DRAG_TRIES = 100;
71 
72     private final UiObject2 mHotseat;
73 
Workspace(LauncherInstrumentation launcher)74     Workspace(LauncherInstrumentation launcher) {
75         super(launcher);
76         mHotseat = launcher.waitForLauncherObject("hotseat");
77     }
78 
79     /**
80      * Swipes up to All Apps.
81      *
82      * @return the All Apps object.
83      */
84     @NonNull
switchToAllApps()85     public HomeAllApps switchToAllApps() {
86         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
87              LauncherInstrumentation.Closable c =
88                      mLauncher.addContextLayer("want to switch from workspace to all apps")) {
89             verifyActiveContainer();
90             final int deviceHeight = mLauncher.getDevice().getDisplayHeight();
91             final int bottomGestureMargin = mLauncher.getBottomGestureSize();
92             final int windowCornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
93             final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
94             final int swipeHeight = mLauncher.getTestInfo(
95                             TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
96                     .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
97             LauncherInstrumentation.log(
98                     "switchToAllApps: deviceHeight = " + deviceHeight + ", startY = " + startY
99                             + ", swipeHeight = " + swipeHeight + ", slop = "
100                             + mLauncher.getTouchSlop());
101 
102             mLauncher.swipeToState(
103                     windowCornerRadius,
104                     startY,
105                     windowCornerRadius,
106                     startY - swipeHeight - mLauncher.getTouchSlop(),
107                     12,
108                     ALL_APPS_STATE_ORDINAL, LauncherInstrumentation.GestureScope.INSIDE);
109 
110             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
111                     "swiped to all apps")) {
112                 return new HomeAllApps(mLauncher);
113             }
114         }
115     }
116 
117     /**
118      * Returns the home qsb.
119      *
120      * The qsb must already be visible when calling this method.
121      */
122     @NonNull
getQsb()123     public Qsb getQsb() {
124         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
125                 "want to get the home qsb")) {
126             return new HomeQsb(mLauncher, mHotseat);
127         }
128     }
129 
130     /**
131      * Returns an icon for the app, if currently visible.
132      *
133      * @param appName name of the app
134      * @return app icon, if found, null otherwise.
135      */
136     @Nullable
tryGetWorkspaceAppIcon(String appName)137     public HomeAppIcon tryGetWorkspaceAppIcon(String appName) {
138         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
139                 "want to get a workspace icon")) {
140             final UiObject2 workspace = verifyActiveContainer();
141             final UiObject2 icon = workspace.findObject(
142                     AppIcon.getAppIconSelector(appName, mLauncher));
143             return icon != null ? new WorkspaceAppIcon(mLauncher, icon) : null;
144         }
145     }
146 
147     /**
148      * Waits for an app icon to be gone (e.g. after uninstall). Fails if it remains.
149      *
150      * @param errorMessage error message thrown then the icon doesn't disappear.
151      * @param appName      app that should be gone.
152      */
verifyWorkspaceAppIconIsGone(String errorMessage, String appName)153     public void verifyWorkspaceAppIconIsGone(String errorMessage, String appName) {
154         final UiObject2 workspace = verifyActiveContainer();
155         assertTrue(errorMessage,
156                 workspace.wait(
157                         Until.gone(AppIcon.getAppIconSelector(appName, mLauncher)),
158                         LauncherInstrumentation.WAIT_TIME_MS));
159     }
160 
161 
162     /**
163      * Returns an icon for the app; fails if the icon doesn't exist.
164      *
165      * @param appName name of the app
166      * @return app icon.
167      */
168     @NonNull
getWorkspaceAppIcon(String appName)169     public HomeAppIcon getWorkspaceAppIcon(String appName) {
170         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
171                 "want to get a workspace icon")) {
172             return new WorkspaceAppIcon(mLauncher,
173                     mLauncher.waitForObjectInContainer(
174                             verifyActiveContainer(),
175                             AppIcon.getAppIconSelector(appName, mLauncher)));
176         }
177     }
178 
179     /**
180      * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
181      * second screen.
182      */
ensureWorkspaceIsScrollable()183     public void ensureWorkspaceIsScrollable() {
184         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
185             final UiObject2 workspace = verifyActiveContainer();
186             if (!isWorkspaceScrollable(workspace)) {
187                 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
188                         "dragging icon to a second page of workspace to make it scrollable")) {
189                     dragIcon(workspace, getHotseatAppIcon("Chrome"), pagesPerScreen());
190                     verifyActiveContainer();
191                 }
192             }
193             assertTrue("Home screen workspace didn't become scrollable",
194                     isWorkspaceScrollable(workspace));
195         }
196     }
197 
198     /** Returns the number of pages. */
getPageCount()199     public int getPageCount() {
200         final UiObject2 workspace = verifyActiveContainer();
201         return workspace.getChildCount();
202     }
203 
204     /**
205      * Returns the number of pages that are visible on the screen simultaneously.
206      */
pagesPerScreen()207     public int pagesPerScreen() {
208         return mLauncher.isTwoPanels() ? 2 : 1;
209     }
210 
211     /**
212      * Drags an icon to the (currentPage + pageDelta) page.
213      * If the target page doesn't exist yet, a new page will be created.
214      * In case the target page can't be created (e.g. existing pages are 0, 1, current: 0,
215      * pageDelta: 3, the latest page that can be created is 2) the icon will be dragged onto the
216      * page that can be created and is closest to the target page.
217      *
218      * @param homeAppIcon - icon to drag.
219      * @param pageDelta   - how many pages should the icon be dragged from the current page.
220      *                    It can be a negative value. currentPage + pageDelta should be greater
221      *                    than or equal to 0.
222      */
dragIcon(HomeAppIcon homeAppIcon, int pageDelta)223     public void dragIcon(HomeAppIcon homeAppIcon, int pageDelta) {
224         if (mHotseat.getVisibleBounds().height() > mHotseat.getVisibleBounds().width()) {
225             throw new UnsupportedOperationException(
226                     "dragIcon does NOT support dragging when the hotseat is on the side.");
227         }
228         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
229             final UiObject2 workspace = verifyActiveContainer();
230             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
231                     "dragging icon to page with delta: " + pageDelta)) {
232                 dragIcon(workspace, homeAppIcon, pageDelta);
233                 verifyActiveContainer();
234             }
235         }
236     }
237 
dragIcon(UiObject2 workspace, HomeAppIcon homeAppIcon, int pageDelta)238     private void dragIcon(UiObject2 workspace, HomeAppIcon homeAppIcon, int pageDelta) {
239         int pageWidth = mLauncher.getDevice().getDisplayWidth() / pagesPerScreen();
240         int targetX = (pageWidth / 2) + pageWidth * pageDelta;
241         int targetY = mLauncher.getVisibleBounds(workspace).centerY();
242         dragIconToWorkspace(
243                 mLauncher,
244                 homeAppIcon,
245                 () -> new Point(targetX, targetY),
246                 false,
247                 false,
248                 () -> mLauncher.expectEvent(
249                         TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
250         verifyActiveContainer();
251     }
252 
isWorkspaceScrollable(UiObject2 workspace)253     private boolean isWorkspaceScrollable(UiObject2 workspace) {
254         return workspace.getChildCount() > (mLauncher.isTwoPanels() ? 2 : 1);
255     }
256 
257     @NonNull
getHotseatAppIcon(String appName)258     public HomeAppIcon getHotseatAppIcon(String appName) {
259         return new WorkspaceAppIcon(mLauncher, mLauncher.waitForObjectInContainer(
260                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
261     }
262 
263     /**
264      * Returns an icon for the given cell; fails if the icon doesn't exist.
265      *
266      * @param cellInd zero based index number of the hotseat cells.
267      * @return app icon.
268      */
269     @NonNull
getHotseatAppIcon(int cellInd)270     public HomeAppIcon getHotseatAppIcon(int cellInd) {
271         List<UiObject2> icons = mHotseat.findObjects(AppIcon.getAnyAppIconSelector());
272         final Point center = getHotseatCellCenter(mLauncher, cellInd);
273         return icons.stream()
274                 .filter(icon -> icon.getVisibleBounds().contains(center.x, center.y))
275                 .findFirst()
276                 .map(icon -> new WorkspaceAppIcon(mLauncher, icon))
277                 .orElseThrow(() ->
278                         new AssertionError("Unable to get a hotseat icon on " + cellInd));
279     }
280 
281     /**
282      * @return map of text -> center of the view. In case of icons with the same name, the one with
283      * lower x coordinate is selected.
284      */
getWorkspaceIconsPositions()285     public Map<String, Point> getWorkspaceIconsPositions() {
286         final UiObject2 workspace = verifyActiveContainer();
287         List<UiObject2> workspaceIcons =
288                 mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
289         return workspaceIcons.stream()
290                 .collect(
291                         Collectors.toMap(
292                                 /* keyMapper= */ UiObject2::getText,
293                                 /* valueMapper= */ UiObject2::getVisibleCenter,
294                                 /* mergeFunction= */ (p1, p2) -> p1.x < p2.x ? p1 : p2));
295     }
296 
297     /*
298      * Get the center point of the delete/uninstall icon in the drop target bar.
299      */
getDropPointFromDropTargetBar( LauncherInstrumentation launcher, String targetId)300     private static Point getDropPointFromDropTargetBar(
301             LauncherInstrumentation launcher, String targetId) {
302         return launcher.waitForObjectInContainer(
303                 launcher.waitForLauncherObject(DROP_BAR_RES_ID),
304                 targetId).getVisibleCenter();
305     }
306 
307     /**
308      * Drag the appIcon from the workspace and cancel by dragging icon to corner of screen where no
309      * drop point exists.
310      *
311      * @param homeAppIcon to be dragged.
312      */
313     @NonNull
dragAndCancelAppIcon(HomeAppIcon homeAppIcon)314     public Workspace dragAndCancelAppIcon(HomeAppIcon homeAppIcon) {
315         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
316              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
317                      "dragging app icon across workspace")) {
318             dragIconToWorkspace(
319                     mLauncher,
320                     homeAppIcon,
321                     () -> new Point(0, 0),
322                     () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
323                     null);
324 
325             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
326                     "dragged the app across workspace")) {
327                 return new Workspace(mLauncher);
328             }
329         }
330     }
331 
332     /**
333      * Delete the appIcon from the workspace.
334      *
335      * @param homeAppIcon to be deleted.
336      * @return validated workspace after the existing appIcon being deleted.
337      */
deleteAppIcon(HomeAppIcon homeAppIcon)338     public Workspace deleteAppIcon(HomeAppIcon homeAppIcon) {
339         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
340              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
341                      "removing app icon from workspace")) {
342             dragIconToWorkspace(
343                     mLauncher,
344                     homeAppIcon,
345                     () -> getDropPointFromDropTargetBar(mLauncher, DELETE_TARGET_TEXT_ID),
346                     () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
347                     /* expectDropEvents= */ null);
348 
349             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
350                     "dragged the app to the drop bar")) {
351                 return new Workspace(mLauncher);
352             }
353         }
354     }
355 
356 
357     /**
358      * Uninstall the appIcon by dragging it to the 'uninstall' drop point of the drop_target_bar.
359      *
360      * @param launcher              the root TAPL instrumentation object of {@link
361      *                              LauncherInstrumentation} type.
362      * @param homeAppIcon           to be uninstalled.
363      * @param launcher              the root TAPL instrumentation object of {@link
364      *                              LauncherInstrumentation} type.
365      * @param homeAppIcon           to be uninstalled.
366      * @param expectLongClickEvents the runnable to be executed to verify expected longclick event.
367      * @return validated workspace after the existing appIcon being uninstalled.
368      */
uninstallAppIcon(LauncherInstrumentation launcher, HomeAppIcon homeAppIcon, Runnable expectLongClickEvents)369     static Workspace uninstallAppIcon(LauncherInstrumentation launcher, HomeAppIcon homeAppIcon,
370             Runnable expectLongClickEvents) {
371         try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
372                 "uninstalling app icon")) {
373             dragIconToWorkspace(
374                     launcher,
375                     homeAppIcon,
376                     () -> getDropPointFromDropTargetBar(launcher, UNINSTALL_TARGET_TEXT_ID),
377                     expectLongClickEvents,
378                     /* expectDropEvents= */null);
379 
380             launcher.waitUntilLauncherObjectGone(DROP_BAR_RES_ID);
381 
382             final BySelector installerAlert = By.text(Pattern.compile(
383                     "Do you want to uninstall this app\\?",
384                     Pattern.DOTALL | Pattern.MULTILINE));
385             final UiDevice device = launcher.getDevice();
386             assertTrue("uninstall alert is not shown", device.wait(
387                     Until.hasObject(installerAlert), LauncherInstrumentation.WAIT_TIME_MS));
388             final UiObject2 ok = device.findObject(By.text("OK"));
389             assertNotNull("OK button is not shown", ok);
390             launcher.clickObject(ok, LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER);
391             assertTrue("Uninstall alert is not dismissed after clicking OK", device.wait(
392                     Until.gone(installerAlert), LauncherInstrumentation.WAIT_TIME_MS));
393 
394             try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
395                     "uninstalled app by dragging to the drop bar")) {
396                 return new Workspace(launcher);
397             }
398         }
399     }
400 
401     /**
402      * Get cell layout's grids size. The return point's x and y values are the cell counts in X and
403      * Y directions respectively, not the values in pixels.
404      */
getIconGridDimensions()405     public Point getIconGridDimensions() {
406         int[] countXY = mLauncher.getTestInfo(
407                 TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE).getIntArray(
408                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
409         return new Point(countXY[0], countXY[1]);
410     }
411 
getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY)412     static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY) {
413         return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX).setCellY(
414                 cellY).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
415     }
416 
getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY, int spanX, int spanY)417     static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY, int spanX,
418             int spanY) {
419         return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX)
420                 .setCellY(cellY).setSpanX(spanX).setSpanY(spanY).build())
421                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
422     }
423 
getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd)424     static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd) {
425         return launcher.getTestInfo(HotseatCellCenterRequest.builder()
426                 .setCellInd(cellInd).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
427     }
428 
429     /** Returns the number of rows and columns in the workspace */
getRowsAndCols()430     public Point getRowsAndCols() {
431         return mLauncher.getTestInfo(TestProtocol.REQUEST_WORKSPACE_COLUMNS_ROWS).getParcelable(
432                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
433     }
434 
435     /** Returns the index of the current page */
geCurrentPage(LauncherInstrumentation launcher)436     static int geCurrentPage(LauncherInstrumentation launcher) {
437         return launcher.getTestInfo(TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX).getInt(
438                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
439     }
440 
441     /**
442      * Finds folder icons in the current workspace.
443      *
444      * @return a list of folder icons.
445      */
getFolderIcons()446     List<FolderIcon> getFolderIcons() {
447         final UiObject2 workspace = verifyActiveContainer();
448         return mLauncher.getObjectsInContainer(workspace, "folder_icon_name").stream().map(
449                 o -> new FolderIcon(mLauncher, o)).collect(Collectors.toList());
450     }
451 
dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime, @Nullable Runnable expectedEvents)452     private static void dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime,
453             @Nullable Runnable expectedEvents) {
454         launcher.runToState(
455                 () -> launcher.sendPointer(
456                         downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
457                         LauncherInstrumentation.GestureScope.INSIDE),
458                 NORMAL_STATE_ORDINAL,
459                 "sending UP event");
460         if (expectedEvents != null) {
461             expectedEvents.run();
462         }
463         LauncherInstrumentation.log("dropIcon: end");
464         launcher.waitUntilLauncherObjectGone("drop_target_bar");
465     }
466 
dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest, boolean startsActivity, boolean isWidgetShortcut, Runnable expectLongClickEvents)467     static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
468             Supplier<Point> dest, boolean startsActivity, boolean isWidgetShortcut,
469             Runnable expectLongClickEvents) {
470         Runnable expectDropEvents = null;
471         if (startsActivity || isWidgetShortcut) {
472             expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
473                     LauncherInstrumentation.EVENT_START);
474         }
475         dragIconToWorkspace(
476                 launcher, launchable, dest, expectLongClickEvents, expectDropEvents);
477     }
478 
dragIconToWorkspaceCellPosition(LauncherInstrumentation launcher, Launchable launchable, int cellX, int cellY, int spanX, int spanY, boolean startsActivity, boolean isWidgetShortcut, Runnable expectLongClickEvents)479     static void dragIconToWorkspaceCellPosition(LauncherInstrumentation launcher,
480             Launchable launchable, int cellX, int cellY, int spanX, int spanY,
481             boolean startsActivity, boolean isWidgetShortcut, Runnable expectLongClickEvents) {
482         Runnable expectDropEvents = null;
483         if (startsActivity || isWidgetShortcut) {
484             expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
485                     LauncherInstrumentation.EVENT_START);
486         }
487         dragIconToWorkspaceCellPosition(
488                 launcher, launchable, cellX, cellY, spanX, spanY, true, expectLongClickEvents,
489                 expectDropEvents);
490     }
491 
492     /**
493      * Drag icon in workspace to else where and drop it immediately.
494      * (There is no slow down time before drop event)
495      * This function expects the launchable is inside the workspace and there is no drop event.
496      */
dragIconToWorkspace( LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> destSupplier)497     static void dragIconToWorkspace(
498             LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> destSupplier) {
499         dragIconToWorkspace(
500                 launcher,
501                 launchable,
502                 destSupplier,
503                 /* isDecelerating= */ false,
504                 () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
505                 /* expectDropEvents= */ null);
506     }
507 
dragIconToWorkspace( LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest, Runnable expectLongClickEvents, @Nullable Runnable expectDropEvents)508     static void dragIconToWorkspace(
509             LauncherInstrumentation launcher,
510             Launchable launchable,
511             Supplier<Point> dest,
512             Runnable expectLongClickEvents,
513             @Nullable Runnable expectDropEvents) {
514         dragIconToWorkspace(launcher, launchable, dest, /* isDecelerating */ true,
515                 expectLongClickEvents, expectDropEvents);
516     }
517 
dragIconToWorkspace( LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest, boolean isDecelerating, Runnable expectLongClickEvents, @Nullable Runnable expectDropEvents)518     static void dragIconToWorkspace(
519             LauncherInstrumentation launcher,
520             Launchable launchable,
521             Supplier<Point> dest,
522             boolean isDecelerating,
523             Runnable expectLongClickEvents,
524             @Nullable Runnable expectDropEvents) {
525         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
526                 "want to drag icon to workspace")) {
527             final long downTime = SystemClock.uptimeMillis();
528             Point dragStart = launchable.startDrag(
529                     downTime,
530                     expectLongClickEvents,
531                     /* runToSpringLoadedState= */ true);
532             Point targetDest = dest.get();
533             int displayX = launcher.getRealDisplaySize().x;
534 
535             // Since the destination can be on another page, we need to drag to the edge first
536             // until we reach the target page
537             while (targetDest.x > displayX || targetDest.x < 0) {
538                 // Don't drag all the way to the edge to prevent touch events from getting out of
539                 //screen bounds.
540                 int edgeX = targetDest.x > 0 ? displayX - 1 : 1;
541                 Point screenEdge = new Point(edgeX, targetDest.y);
542                 Point finalDragStart = dragStart;
543                 executeAndWaitForPageScroll(launcher,
544                         () -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
545                                 true, downTime, downTime, true,
546                                 LauncherInstrumentation.GestureScope.INSIDE));
547                 targetDest.x += displayX * (targetDest.x > 0 ? -1 : 1);
548                 dragStart = screenEdge;
549             }
550 
551             // targetDest.x is now between 0 and displayX so we found the target page,
552             // we just have to put move the icon to the destination and drop it
553             launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
554                     downTime, SystemClock.uptimeMillis(), false,
555                     LauncherInstrumentation.GestureScope.INSIDE);
556             launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
557             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
558         }
559     }
560 
dragIconToWorkspaceCellPosition( LauncherInstrumentation launcher, Launchable launchable, int cellX, int cellY, int spanX, int spanY, boolean isDecelerating, Runnable expectLongClickEvents, @Nullable Runnable expectDropEvents)561     static void dragIconToWorkspaceCellPosition(
562             LauncherInstrumentation launcher,
563             Launchable launchable,
564             int cellX, int cellY, int spanX, int spanY,
565             boolean isDecelerating,
566             Runnable expectLongClickEvents,
567             @Nullable Runnable expectDropEvents) {
568         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
569                 "want to drag icon to workspace")) {
570             Point rowsAndCols = launcher.getWorkspace().getRowsAndCols();
571             int destinationWorkspace = cellX / rowsAndCols.x;
572             cellX = cellX % rowsAndCols.x;
573 
574             final long downTime = SystemClock.uptimeMillis();
575             Point dragStart = launchable.startDrag(
576                     downTime,
577                     expectLongClickEvents,
578                     /* runToSpringLoadedState= */ true);
579             Point targetDest = getCellCenter(launcher, cellX, cellY, spanX, spanY);
580             // Since the destination can be on another page, we need to drag to the edge first
581             // until we reach the target page
582             dragStart = dragToGivenWorkspace(launcher, dragStart, destinationWorkspace,
583                     targetDest.y);
584 
585             // targetDest.x is now between 0 and displayX so we found the target page,
586             // we just have to put move the icon to the destination and drop it
587             launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
588                     downTime, SystemClock.uptimeMillis(), false,
589                     LauncherInstrumentation.GestureScope.INSIDE);
590             launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
591             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
592         }
593     }
594 
595     /**
596      * Given a drag that already started at currentPosition, drag the item to the given destination
597      * index defined by destinationWorkspaceIndex.
598      *
599      * @param launcher
600      * @param currentPosition
601      * @param destinationWorkspaceIndex
602      * @param y
603      * @return the finishing position of the drag.
604      */
dragToGivenWorkspace(LauncherInstrumentation launcher, Point currentPosition, int destinationWorkspaceIndex, int y)605     static Point dragToGivenWorkspace(LauncherInstrumentation launcher, Point currentPosition,
606             int destinationWorkspaceIndex, int y) {
607         final long downTime = SystemClock.uptimeMillis();
608         int displayX = launcher.getRealDisplaySize().x;
609         int currentPage = Workspace.geCurrentPage(launcher);
610         int counter = 0;
611         while (currentPage != destinationWorkspaceIndex) {
612             counter++;
613             if (counter > MAX_WORKSPACE_DRAG_TRIES) {
614                 throw new RuntimeException(
615                         "Wrong destination workspace index " + destinationWorkspaceIndex
616                                 + ", desired workspace was never reached");
617             }
618             // if the destination is greater than current page, set the display edge to be the
619             // right edge. Don't drag all the way to the edge to prevent touch events from
620             // getting out of screen bounds.
621             int displayEdge = destinationWorkspaceIndex > currentPage ? displayX - 1 : 1;
622             Point screenEdge = new Point(displayEdge, y);
623             Point finalDragStart = currentPosition;
624             executeAndWaitForPageScroll(launcher,
625                     () -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
626                             true, downTime, downTime, true,
627                             LauncherInstrumentation.GestureScope.INSIDE));
628             currentPage = Workspace.geCurrentPage(launcher);
629             currentPosition = screenEdge;
630         }
631         return currentPosition;
632     }
633 
executeAndWaitForPageScroll(LauncherInstrumentation launcher, Runnable command)634     private static void executeAndWaitForPageScroll(LauncherInstrumentation launcher,
635             Runnable command) {
636         launcher.executeAndWaitForEvent(command,
637                 event -> event.getEventType() == TYPE_VIEW_SCROLLED,
638                 () -> "Page scroll didn't happen", "Scrolling page");
639     }
640 
dragIconToHotseat( LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest, Runnable expectLongClickEvents, @Nullable Runnable expectDropEvents)641     static void dragIconToHotseat(
642             LauncherInstrumentation launcher,
643             Launchable launchable,
644             Supplier<Point> dest,
645             Runnable expectLongClickEvents,
646             @Nullable Runnable expectDropEvents) {
647         final long downTime = SystemClock.uptimeMillis();
648         Point dragStart = launchable.startDrag(
649                 downTime,
650                 expectLongClickEvents,
651                 /* runToSpringLoadedState= */ true);
652         Point targetDest = dest.get();
653 
654         launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, true,
655                 downTime, SystemClock.uptimeMillis(), false,
656                 LauncherInstrumentation.GestureScope.INSIDE);
657         dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
658     }
659 
660     /**
661      * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
662      * recoil to complete.
663      */
flingForward()664     public void flingForward() {
665         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
666             final UiObject2 workspace = verifyActiveContainer();
667             mLauncher.scroll(workspace, Direction.RIGHT,
668                     new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0),
669                     FLING_STEPS, false);
670             verifyActiveContainer();
671         }
672     }
673 
674     /**
675      * Flings to get to screens on the left.  Waits for scrolling and a possible overscroll
676      * recoil to complete.
677      */
flingBackward()678     public void flingBackward() {
679         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
680             final UiObject2 workspace = verifyActiveContainer();
681             mLauncher.scroll(workspace, Direction.LEFT,
682                     new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0),
683                     FLING_STEPS, false);
684             verifyActiveContainer();
685         }
686     }
687 
688     /**
689      * Opens widgets container by pressing Ctrl+W.
690      *
691      * @return the widgets container.
692      */
693     @NonNull
openAllWidgets()694     public Widgets openAllWidgets() {
695         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
696             verifyActiveContainer();
697             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_DOWN);
698             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_UP);
699             mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
700             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {
701                 return new Widgets(mLauncher);
702             }
703         }
704     }
705 
706     @Override
getSwipeHeightRequestName()707     protected String getSwipeHeightRequestName() {
708         return TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT;
709     }
710 
711     @Override
getSwipeStartY()712     protected int getSwipeStartY() {
713         return mLauncher.getRealDisplaySize().y - 1;
714     }
715 
716     @Nullable
tryGetWidget(String label, long timeout)717     public Widget tryGetWidget(String label, long timeout) {
718         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
719                 "getting widget " + label + " on workspace with timeout " + timeout)) {
720             final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
721                     By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
722                     timeout);
723             return widget != null ? new Widget(mLauncher, widget) : null;
724         }
725     }
726 
727     /**
728      * @param cellX X position of the widget trying to get.
729      * @param cellY Y position of the widget trying to get.
730      * @return returns the Widget in the given position in the Launcher or an Exception if no such
731      * widget is in that position.
732      */
733     @NonNull
getWidgetAtCell(int cellX, int cellY)734     public Widget getWidgetAtCell(int cellX, int cellY) {
735         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
736                 "getting widget at cell position " + cellX + "," + cellY)) {
737             final List<UiObject2> widgets = mLauncher.waitForObjectsBySelector(
738                     By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView"));
739             Point coordinateInScreen = Workspace.getCellCenter(mLauncher, cellX, cellY);
740             for (UiObject2 widget : widgets) {
741                 if (widget.getVisibleBounds().contains(coordinateInScreen.x,
742                         coordinateInScreen.y)) {
743                     return new Widget(mLauncher, widget);
744                 }
745             }
746         }
747         mLauncher.fail("Unable to find widget at cell " + cellX + "," + cellY);
748         // This statement is unreachable because mLauncher.fail throws an exception
749         // but is needed for compiling
750         return null;
751     }
752 
753     @Nullable
tryGetPendingWidget(long timeout)754     public Widget tryGetPendingWidget(long timeout) {
755         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
756                 "getting pending widget on workspace with timeout " + timeout)) {
757             final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
758                     By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
759             return widget != null ? new Widget(mLauncher, widget) : null;
760         }
761     }
762 }
763