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