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 com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; 20 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; 21 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL; 22 23 import static junit.framework.TestCase.assertTrue; 24 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.os.SystemClock; 28 import android.view.KeyEvent; 29 import android.view.MotionEvent; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 import androidx.test.uiautomator.By; 34 import androidx.test.uiautomator.Direction; 35 import androidx.test.uiautomator.UiObject2; 36 37 import com.android.launcher3.ResourceUtils; 38 import com.android.launcher3.testing.TestProtocol; 39 40 import java.util.regex.Pattern; 41 42 /** 43 * Operations on the workspace screen. 44 */ 45 public final class Workspace extends Home { 46 private static final int FLING_STEPS = 10; 47 48 static final Pattern EVENT_CTRL_W_DOWN = Pattern.compile( 49 "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_W" 50 + ".*?metaState=META_CTRL_ON"); 51 static final Pattern EVENT_CTRL_W_UP = Pattern.compile( 52 "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_W" 53 + ".*?metaState=META_CTRL_ON"); 54 private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onWorkspaceItemLongClick"); 55 56 private final UiObject2 mHotseat; 57 Workspace(LauncherInstrumentation launcher)58 Workspace(LauncherInstrumentation launcher) { 59 super(launcher); 60 mHotseat = launcher.waitForLauncherObject("hotseat"); 61 } 62 63 /** 64 * Swipes up to All Apps. 65 * 66 * @return the App Apps object. 67 */ 68 @NonNull switchToAllApps()69 public AllApps switchToAllApps() { 70 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 71 LauncherInstrumentation.Closable c = 72 mLauncher.addContextLayer("want to switch from workspace to all apps")) { 73 verifyActiveContainer(); 74 final int deviceHeight = mLauncher.getDevice().getDisplayHeight(); 75 final int bottomGestureMargin = ResourceUtils.getNavbarSize( 76 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()); 77 final int windowCornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius()); 78 final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1; 79 final int swipeHeight = mLauncher.getTestInfo( 80 TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT). 81 getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 82 LauncherInstrumentation.log( 83 "switchToAllApps: deviceHeight = " + deviceHeight + ", startY = " + startY 84 + ", swipeHeight = " + swipeHeight + ", slop = " 85 + mLauncher.getTouchSlop()); 86 87 mLauncher.swipeToState( 88 0, 89 startY, 90 0, 91 startY - swipeHeight - mLauncher.getTouchSlop(), 92 12, 93 ALL_APPS_STATE_ORDINAL, LauncherInstrumentation.GestureScope.INSIDE); 94 95 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 96 "swiped to all apps")) { 97 return new AllApps(mLauncher); 98 } 99 } 100 } 101 102 /** 103 * Returns an icon for the app, if currently visible. 104 * 105 * @param appName name of the app 106 * @return app icon, if found, null otherwise. 107 */ 108 @Nullable tryGetWorkspaceAppIcon(String appName)109 public AppIcon tryGetWorkspaceAppIcon(String appName) { 110 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 111 "want to get a workspace icon")) { 112 final UiObject2 workspace = verifyActiveContainer(); 113 final UiObject2 icon = workspace.findObject( 114 AppIcon.getAppIconSelector(appName, mLauncher)); 115 return icon != null ? new AppIcon(mLauncher, icon) : null; 116 } 117 } 118 119 120 /** 121 * Returns an icon for the app; fails if the icon doesn't exist. 122 * 123 * @param appName name of the app 124 * @return app icon. 125 */ 126 @NonNull getWorkspaceAppIcon(String appName)127 public AppIcon getWorkspaceAppIcon(String appName) { 128 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 129 "want to get a workspace icon")) { 130 return new AppIcon(mLauncher, 131 mLauncher.waitForObjectInContainer( 132 verifyActiveContainer(), 133 AppIcon.getAppIconSelector(appName, mLauncher))); 134 } 135 } 136 137 /** 138 * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the 139 * second screen. 140 */ ensureWorkspaceIsScrollable()141 public void ensureWorkspaceIsScrollable() { 142 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 143 final UiObject2 workspace = verifyActiveContainer(); 144 if (!isWorkspaceScrollable(workspace)) { 145 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 146 "dragging icon to a second page of workspace to make it scrollable")) { 147 dragIconToWorkspace( 148 mLauncher, 149 getHotseatAppIcon("Chrome"), 150 new Point(mLauncher.getDevice().getDisplayWidth(), 151 mLauncher.getVisibleBounds(workspace).centerY()), 152 "popup_container", 153 false, 154 false, 155 () -> mLauncher.expectEvent( 156 TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT)); 157 verifyActiveContainer(); 158 } 159 } 160 assertTrue("Home screen workspace didn't become scrollable", 161 isWorkspaceScrollable(workspace)); 162 } 163 } 164 isWorkspaceScrollable(UiObject2 workspace)165 private boolean isWorkspaceScrollable(UiObject2 workspace) { 166 return workspace.getChildCount() > 1; 167 } 168 169 @NonNull getHotseatAppIcon(String appName)170 public AppIcon getHotseatAppIcon(String appName) { 171 return new AppIcon(mLauncher, mLauncher.waitForObjectInContainer( 172 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher))); 173 } 174 dragIconToWorkspace( LauncherInstrumentation launcher, Launchable launchable, Point dest, String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut, Runnable expectLongClickEvents)175 static void dragIconToWorkspace( 176 LauncherInstrumentation launcher, Launchable launchable, Point dest, 177 String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut, 178 Runnable expectLongClickEvents) { 179 LauncherInstrumentation.log("dragIconToWorkspace: begin"); 180 final Point launchableCenter = launchable.getObject().getVisibleCenter(); 181 final long downTime = SystemClock.uptimeMillis(); 182 launcher.runToState( 183 () -> { 184 launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, 185 launchableCenter, LauncherInstrumentation.GestureScope.INSIDE); 186 LauncherInstrumentation.log("dragIconToWorkspace: sent down"); 187 expectLongClickEvents.run(); 188 launcher.waitForLauncherObject(longPressIndicator); 189 LauncherInstrumentation.log("dragIconToWorkspace: indicator"); 190 launcher.movePointer(launchableCenter, dest, 10, downTime, true, 191 LauncherInstrumentation.GestureScope.INSIDE); 192 }, 193 SPRING_LOADED_STATE_ORDINAL, 194 "long-pressing and moving"); 195 LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); 196 launcher.runToState( 197 () -> launcher.sendPointer( 198 downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest, 199 LauncherInstrumentation.GestureScope.INSIDE), 200 NORMAL_STATE_ORDINAL, 201 "sending UP event"); 202 if (startsActivity || isWidgetShortcut) { 203 launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START); 204 } 205 LauncherInstrumentation.log("dragIconToWorkspace: end"); 206 launcher.waitUntilLauncherObjectGone("drop_target_bar"); 207 } 208 209 /** 210 * Flings to get to screens on the right. Waits for scrolling and a possible overscroll 211 * recoil to complete. 212 */ flingForward()213 public void flingForward() { 214 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 215 final UiObject2 workspace = verifyActiveContainer(); 216 mLauncher.scroll(workspace, Direction.RIGHT, 217 new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0), 218 FLING_STEPS, false); 219 verifyActiveContainer(); 220 } 221 } 222 223 /** 224 * Flings to get to screens on the left. Waits for scrolling and a possible overscroll 225 * recoil to complete. 226 */ flingBackward()227 public void flingBackward() { 228 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 229 final UiObject2 workspace = verifyActiveContainer(); 230 mLauncher.scroll(workspace, Direction.LEFT, 231 new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0), 232 FLING_STEPS, false); 233 verifyActiveContainer(); 234 } 235 } 236 237 /** 238 * Opens widgets container by pressing Ctrl+W. 239 * 240 * @return the widgets container. 241 */ 242 @NonNull openAllWidgets()243 public Widgets openAllWidgets() { 244 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 245 verifyActiveContainer(); 246 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_DOWN); 247 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_UP); 248 mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON); 249 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) { 250 return new Widgets(mLauncher); 251 } 252 } 253 } 254 255 @Override getSwipeHeightRequestName()256 protected String getSwipeHeightRequestName() { 257 return TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT; 258 } 259 260 @Override getSwipeStartY()261 protected int getSwipeStartY() { 262 return mLauncher.getRealDisplaySize().y - 1; 263 } 264 265 @Nullable tryGetWidget(String label, long timeout)266 public Widget tryGetWidget(String label, long timeout) { 267 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 268 "getting widget " + label + " on workspace with timeout " + timeout)) { 269 final UiObject2 widget = mLauncher.tryWaitForLauncherObject( 270 By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label), 271 timeout); 272 return widget != null ? new Widget(mLauncher, widget) : null; 273 } 274 } 275 276 @Nullable tryGetPendingWidget(long timeout)277 public Widget tryGetPendingWidget(long timeout) { 278 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 279 "getting pending widget on workspace with timeout " + timeout)) { 280 final UiObject2 widget = mLauncher.tryWaitForLauncherObject( 281 By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout); 282 return widget != null ? new Widget(mLauncher, widget) : null; 283 } 284 } 285 }