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