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