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