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.tapl.BaseOverview.TASK_SELECTOR; 20 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT; 21 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; 22 23 import android.graphics.Point; 24 import android.os.SystemClock; 25 import android.view.MotionEvent; 26 27 import androidx.annotation.NonNull; 28 import androidx.test.uiautomator.UiObject2; 29 30 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel; 31 import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType; 32 import com.android.launcher3.tapl.OverviewTask.TaskViewType; 33 import com.android.launcher3.testing.shared.TestProtocol; 34 35 import java.util.List; 36 import java.util.regex.Pattern; 37 38 /** 39 * Indicates the base state with a UI other than Overview running as foreground. It can also 40 * indicate Launcher as long as Launcher is not in Overview state. 41 */ 42 public abstract class Background extends LauncherInstrumentation.VisibleContainer 43 implements KeyboardQuickSwitchSource { 44 private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500; 45 private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle"); 46 Background(LauncherInstrumentation launcher)47 Background(LauncherInstrumentation launcher) { 48 super(launcher); 49 } 50 51 @Override getLauncher()52 public LauncherInstrumentation getLauncher() { 53 return mLauncher; 54 } 55 56 @Override getStartingContainerType()57 public LauncherInstrumentation.ContainerType getStartingContainerType() { 58 return getContainerType(); 59 } 60 61 /** 62 * Swipes up or presses the square button to switch to Overview. 63 * Returns the base overview, which can be either in Launcher or the fallback recents. 64 * 65 * @return the Overview panel object. 66 */ 67 @NonNull switchToOverview()68 public BaseOverview switchToOverview() { 69 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 70 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 71 "want to switch from background to overview")) { 72 verifyActiveContainer(); 73 goToOverviewUnchecked(); 74 return mLauncher.is3PLauncher() 75 ? new BaseOverview(mLauncher) : new Overview(mLauncher); 76 } 77 } 78 79 zeroButtonToOverviewGestureStateTransitionWhileHolding()80 protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() { 81 return false; 82 } 83 goToOverviewUnchecked()84 protected void goToOverviewUnchecked() { 85 if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON 86 || mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER) { 87 final long downTime = SystemClock.uptimeMillis(); 88 sendDownPointerToEnterOverviewToLauncher(downTime); 89 String swipeAndHoldToEnterOverviewActionName = 90 "swiping and holding to enter overview"; 91 // If swiping from an app (e.g. Overview is in Background), we pause and hold on 92 // swipe up to make overview appear, or else swiping without holding would take 93 // us to the Home state. If swiping up from Home (e.g. Overview in Home or 94 // Workspace state where the below condition is true), there is no need to pause, 95 // and we will not test for an intermediate carousel as one will not exist. 96 if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) { 97 mLauncher.runToState( 98 () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime), 99 OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName); 100 sendUpPointerToEnterOverviewToLauncher(downTime); 101 } else { 102 // If swiping up from an app to overview, pause on intermediate carousel 103 // until snapshots are visible. No intermediate carousel when swiping from 104 // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If 105 // only a single task exists, no snapshots will be available during swipe up. 106 mLauncher.executeAndWaitForLauncherEvent( 107 () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime), 108 event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals( 109 event.getClassName().toString()), 110 () -> "Pause wasn't detected", 111 swipeAndHoldToEnterOverviewActionName); 112 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 113 "paused on swipe up to overview")) { 114 if (mLauncher.getRecentTasks().size() > 1) { 115 // When swiping up to grid-overview for tablets, the swiped tab will be 116 // in the middle of the screen (TaskViewSimulator, not a snapshot), and 117 // all remaining snapshots will be to the left of that task. In 118 // non-tablet overview, snapshots can be on either side of the swiped 119 // task, but we still check that they become visible after swiping and 120 // pausing. 121 mLauncher.waitForObjectBySelector(TASK_SELECTOR); 122 if (mLauncher.isTablet()) { 123 List<UiObject2> tasks = mLauncher.getDevice().findObjects( 124 TASK_SELECTOR); 125 126 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; 127 UiObject2 centerTask = tasks.stream() 128 .filter(t -> t.getVisibleCenter().x == centerX) 129 .findFirst() 130 .orElse(null); 131 132 if (centerTask != null) { 133 mLauncher.assertTrue( 134 "Task(s) found to the right of the swiped task", 135 tasks.stream() 136 .filter(t -> t != centerTask 137 && OverviewTask.getType(t) 138 != TaskViewType.DESKTOP) 139 .allMatch(t -> t.getVisibleBounds().right 140 < centerTask.getVisibleBounds().left)); 141 mLauncher.assertTrue( 142 "DesktopTask(s) found to the left of the swiped task", 143 tasks.stream() 144 .filter(t -> t != centerTask 145 && OverviewTask.getType(t) 146 == TaskViewType.DESKTOP) 147 .allMatch(t -> t.getVisibleBounds().left 148 > centerTask.getVisibleBounds().right)); 149 } 150 } 151 152 } 153 String upPointerToEnterOverviewActionName = 154 "sending UP pointer to enter overview"; 155 mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime), 156 OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName); 157 } 158 } 159 } else { 160 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT); 161 mLauncher.runToState( 162 () -> mLauncher.waitForNavigationUiObject("recent_apps").click(), 163 OVERVIEW_STATE_ORDINAL, "clicking Recents button"); 164 } 165 expectSwitchToOverviewEvents(); 166 } 167 expectSwitchToOverviewEvents()168 private void expectSwitchToOverviewEvents() { 169 } 170 sendDownPointerToEnterOverviewToLauncher(long downTime)171 private void sendDownPointerToEnterOverviewToLauncher(long downTime) { 172 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; 173 final int startY = getSwipeStartY(); 174 final Point start = new Point(centerX, startY); 175 176 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, 177 LauncherInstrumentation.GestureScope.EXPECT_PILFER); 178 179 if (!mLauncher.isLauncher3()) { 180 mLauncher.expectEvent(TestProtocol.SEQUENCE_PILFER, 181 LauncherInstrumentation.EVENT_PILFER_POINTERS); 182 } 183 } 184 sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(long downTime)185 private void sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(long downTime) { 186 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; 187 final int startY = getSwipeStartY(); 188 final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt( 189 TestProtocol.TEST_INFO_RESPONSE_FIELD); 190 final Point start = new Point(centerX, startY); 191 final Point end = 192 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop()); 193 194 mLauncher.movePointer( 195 downTime, 196 downTime, 197 ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION, 198 start, 199 end, 200 LauncherInstrumentation.GestureScope.EXPECT_PILFER); 201 } 202 sendUpPointerToEnterOverviewToLauncher(long downTime)203 private void sendUpPointerToEnterOverviewToLauncher(long downTime) { 204 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; 205 final int startY = getSwipeStartY(); 206 final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt( 207 TestProtocol.TEST_INFO_RESPONSE_FIELD); 208 final Point end = 209 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop()); 210 211 mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(), 212 MotionEvent.ACTION_UP, end, 213 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); 214 } 215 216 /** 217 * Quick switching to the app with swiping to right. 218 */ 219 @NonNull quickSwitchToPreviousApp()220 public LaunchedAppState quickSwitchToPreviousApp() { 221 quickSwitch(true /* toRight */); 222 return new LaunchedAppState(mLauncher); 223 } 224 225 /** 226 * Quick switching to the app with swiping to left. 227 */ 228 @NonNull quickSwitchToPreviousAppSwipeLeft()229 public LaunchedAppState quickSwitchToPreviousAppSwipeLeft() { 230 quickSwitch(false /* toRight */); 231 return new LaunchedAppState(mLauncher); 232 } 233 234 /** 235 * Making swipe gesture to quick-switch app tasks. 236 * 237 * @param toRight {@code true} means swiping right, {@code false} means swiping left. 238 * @throws {@link AssertionError} when failing to verify the visible UI in the container. 239 */ quickSwitch(boolean toRight)240 private void quickSwitch(boolean toRight) { 241 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 242 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 243 "want to quick switch to the previous app")) { 244 verifyActiveContainer(); 245 if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON 246 || mLauncher.getTrackpadGestureType() == TrackpadGestureType.FOUR_FINGER) { 247 final int startX; 248 final int startY; 249 final int endX; 250 final int endY; 251 final int cornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius()); 252 if (toRight) { 253 // Swipe from the bottom left to the bottom right of the screen. 254 startX = cornerRadius; 255 startY = getSwipeStartY(); 256 endX = mLauncher.getDevice().getDisplayWidth() - cornerRadius; 257 endY = startY; 258 } else { 259 // Swipe from the bottom right to the bottom left of the screen. 260 startX = mLauncher.getDevice().getDisplayWidth() - cornerRadius; 261 startY = getSwipeStartY(); 262 endX = cornerRadius; 263 endY = startY; 264 } 265 266 mLauncher.executeAndWaitForLauncherStop( 267 () -> mLauncher.linearGesture( 268 startX, startY, endX, endY, 20, false, 269 LauncherInstrumentation.GestureScope.EXPECT_PILFER), 270 "swiping"); 271 } else { 272 // Double press the recents button. 273 UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps"); 274 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT); 275 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL, 276 "clicking Recents button for the first time"); 277 mLauncher.getOverview(); 278 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT); 279 mLauncher.executeAndWaitForLauncherStop( 280 () -> recentsButton.click(), 281 "clicking Recents button for the second time"); 282 } 283 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT); 284 } 285 } 286 getSwipeHeightRequestName()287 protected String getSwipeHeightRequestName() { 288 return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT; 289 } 290 getSwipeStartX()291 protected int getSwipeStartX() { 292 return mLauncher.getRealDisplaySize().x - 1; 293 } 294 getSwipeStartY()295 protected int getSwipeStartY() { 296 return mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER 297 ? mLauncher.getDevice().getDisplayHeight() * 3 / 4 298 : mLauncher.getRealDisplaySize().y - 1; 299 } 300 } 301