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 android.graphics.Rect; 20 21 import androidx.annotation.NonNull; 22 import androidx.test.uiautomator.By; 23 import androidx.test.uiautomator.BySelector; 24 import androidx.test.uiautomator.Direction; 25 import androidx.test.uiautomator.UiObject2; 26 27 import java.util.Collections; 28 import java.util.List; 29 import java.util.stream.Collectors; 30 31 /** 32 * Common overview panel for both Launcher and fallback recents 33 */ 34 public class BaseOverview extends LauncherInstrumentation.VisibleContainer { 35 private static final int FLINGS_FOR_DISMISS_LIMIT = 40; 36 BaseOverview(LauncherInstrumentation launcher)37 BaseOverview(LauncherInstrumentation launcher) { 38 super(launcher); 39 verifyActiveContainer(); 40 verifyActionsViewVisibility(); 41 } 42 43 @Override getContainerType()44 protected LauncherInstrumentation.ContainerType getContainerType() { 45 return LauncherInstrumentation.ContainerType.FALLBACK_OVERVIEW; 46 } 47 48 /** 49 * Flings forward (left) and waits the fling's end. 50 */ flingForward()51 public void flingForward() { 52 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 53 flingForwardImpl(); 54 } 55 } 56 flingForwardImpl()57 private void flingForwardImpl() { 58 try (LauncherInstrumentation.Closable c = 59 mLauncher.addContextLayer("want to fling forward in overview")) { 60 LauncherInstrumentation.log("Overview.flingForward before fling"); 61 final UiObject2 overview = verifyActiveContainer(); 62 final int leftMargin = 63 mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth(); 64 mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, 65 false); 66 try (LauncherInstrumentation.Closable c2 = 67 mLauncher.addContextLayer("flung forwards")) { 68 verifyActiveContainer(); 69 verifyActionsViewVisibility(); 70 } 71 } 72 } 73 74 /** 75 * Flings backward (right) and waits the fling's end. 76 */ flingBackward()77 public void flingBackward() { 78 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 79 flingBackwardImpl(); 80 } 81 } 82 flingBackwardImpl()83 private void flingBackwardImpl() { 84 try (LauncherInstrumentation.Closable c = 85 mLauncher.addContextLayer("want to fling backward in overview")) { 86 LauncherInstrumentation.log("Overview.flingBackward before fling"); 87 final UiObject2 overview = verifyActiveContainer(); 88 final int rightMargin = 89 mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth(); 90 mLauncher.scroll( 91 overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false); 92 try (LauncherInstrumentation.Closable c2 = 93 mLauncher.addContextLayer("flung backwards")) { 94 verifyActiveContainer(); 95 verifyActionsViewVisibility(); 96 } 97 } 98 } 99 flingToFirstTask()100 private OverviewTask flingToFirstTask() { 101 OverviewTask currentTask = getCurrentTask(); 102 103 while (mLauncher.getRealDisplaySize().x - currentTask.getUiObject().getVisibleBounds().right 104 <= mLauncher.getOverviewPageSpacing()) { 105 flingBackwardImpl(); 106 currentTask = getCurrentTask(); 107 } 108 109 return currentTask; 110 } 111 112 /** 113 * Dismissed all tasks by scrolling to Clear-all button and pressing it. 114 */ dismissAllTasks()115 public void dismissAllTasks() { 116 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 117 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 118 "dismissing all tasks")) { 119 final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all"); 120 for (int i = 0; 121 i < FLINGS_FOR_DISMISS_LIMIT 122 && !verifyActiveContainer().hasObject(clearAllSelector); 123 ++i) { 124 flingForwardImpl(); 125 } 126 127 mLauncher.clickLauncherObject( 128 mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector)); 129 130 mLauncher.waitUntilLauncherObjectGone(clearAllSelector); 131 } 132 } 133 134 /** 135 * Touch to the right of current task. This should dismiss overview and go back to Workspace. 136 */ touchOutsideFirstTask()137 public Workspace touchOutsideFirstTask() { 138 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 139 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 140 "touching outside the focused task")) { 141 142 if (getTaskCount() < 2) { 143 throw new IllegalStateException( 144 "Need to have at least 2 tasks"); 145 } 146 147 OverviewTask currentTask = flingToFirstTask(); 148 149 mLauncher.touchOutsideContainer(currentTask.getUiObject(), 150 /* tapRight= */ true, 151 /* halfwayToEdge= */ false); 152 153 return new Workspace(mLauncher); 154 } 155 } 156 157 /** 158 * Touch between two tasks 159 */ touchBetweenTasks()160 public void touchBetweenTasks() { 161 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 162 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 163 "touching outside the focused task")) { 164 if (getTaskCount() < 2) { 165 throw new IllegalStateException( 166 "Need to have at least 2 tasks"); 167 } 168 169 OverviewTask currentTask = flingToFirstTask(); 170 171 mLauncher.touchOutsideContainer(currentTask.getUiObject(), 172 /* tapRight= */ false, 173 /* halfwayToEdge= */ false); 174 } 175 } 176 177 /** 178 * Touch either on the right or the left corner of the screen, 1 pixel from the bottom and 179 * from the sides. 180 */ touchTaskbarBottomCorner(boolean tapRight)181 public void touchTaskbarBottomCorner(boolean tapRight) { 182 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 183 Taskbar taskbar = new Taskbar(mLauncher); 184 taskbar.touchBottomCorner(tapRight); 185 verifyActiveContainer(); 186 } 187 } 188 189 /** 190 * Scrolls the current task via flinging forward until it is off screen. 191 * 192 * If only one task is present, it is only partially scrolled off screen and will still be 193 * the current task. 194 */ scrollCurrentTaskOffScreen()195 public void scrollCurrentTaskOffScreen() { 196 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 197 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 198 "want to scroll current task off screen in overview")) { 199 verifyActiveContainer(); 200 201 OverviewTask task = getCurrentTask(); 202 mLauncher.assertNotNull("current task is null", task); 203 mLauncher.scrollLeftByDistance(verifyActiveContainer(), 204 task.getVisibleWidth() + mLauncher.getOverviewPageSpacing()); 205 206 try (LauncherInstrumentation.Closable c2 = 207 mLauncher.addContextLayer("scrolled task off screen")) { 208 verifyActiveContainer(); 209 verifyActionsViewVisibility(); 210 211 if (getTaskCount() > 1) { 212 if (mLauncher.isTablet()) { 213 mLauncher.assertTrue("current task is not grid height", 214 getCurrentTask().getVisibleHeight() == mLauncher 215 .getGridTaskRectForTablet().height()); 216 } 217 mLauncher.assertTrue("Current task not scrolled off screen", 218 !getCurrentTask().equals(task)); 219 } 220 } 221 } 222 } 223 224 /** 225 * Gets the current task in the carousel, or fails if the carousel is empty. 226 * 227 * @return the task in the middle of the visible tasks list. 228 */ 229 @NonNull getCurrentTask()230 public OverviewTask getCurrentTask() { 231 final List<UiObject2> taskViews = getTasks(); 232 mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); 233 234 // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one 235 // in the center, and parts of its right and left siblings. Find the main task view by 236 // its width. 237 final UiObject2 widestTask = Collections.max(taskViews, 238 (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(), 239 mLauncher.getVisibleBounds(t2).width())); 240 241 return new OverviewTask(mLauncher, widestTask, this); 242 } 243 244 /** Returns an overview task matching TestActivity {@param activityNumber}. */ 245 @NonNull getTestActivityTask(int activityNumber)246 public OverviewTask getTestActivityTask(int activityNumber) { 247 final List<UiObject2> taskViews = getTasks(); 248 mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); 249 250 final String activityName = "TestActivity" + activityNumber; 251 UiObject2 task = null; 252 for (UiObject2 taskView : taskViews) { 253 // TODO(b/239452415): Use equals instead of descEndsWith 254 if (taskView.getParent().hasObject(By.descEndsWith(activityName))) { 255 task = taskView; 256 break; 257 } 258 } 259 mLauncher.assertNotNull( 260 "Unable to find a task with " + activityName + " from the task list", task); 261 262 return new OverviewTask(mLauncher, task, this); 263 } 264 265 /** 266 * Returns a list of all tasks fully visible in the tablet grid overview. 267 */ 268 @NonNull getCurrentTasksForTablet()269 public List<OverviewTask> getCurrentTasksForTablet() { 270 final List<UiObject2> taskViews = getTasks(); 271 mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); 272 273 final int gridTaskWidth = mLauncher.getGridTaskRectForTablet().width(); 274 275 return taskViews.stream().filter(t -> t.getVisibleBounds().width() == gridTaskWidth).map( 276 t -> new OverviewTask(mLauncher, t, this)).collect(Collectors.toList()); 277 } 278 279 @NonNull getTasks()280 private List<UiObject2> getTasks() { 281 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 282 "want to get overview tasks")) { 283 verifyActiveContainer(); 284 return mLauncher.getDevice().findObjects( 285 mLauncher.getOverviewObjectSelector("snapshot")); 286 } 287 } 288 getTaskCount()289 int getTaskCount() { 290 return getTasks().size(); 291 } 292 293 /** 294 * Returns whether Overview has tasks. 295 */ hasTasks()296 public boolean hasTasks() { 297 return getTasks().size() > 0; 298 } 299 300 /** 301 * Gets Overview Actions. 302 * 303 * @return The Overview Actions 304 */ 305 @NonNull getOverviewActions()306 public OverviewActions getOverviewActions() { 307 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 308 "want to get overview actions")) { 309 verifyActiveContainer(); 310 UiObject2 overviewActions = mLauncher.waitForOverviewObject("action_buttons"); 311 return new OverviewActions(overviewActions, mLauncher); 312 } 313 } 314 315 /** 316 * Returns if clear all button is visible. 317 */ isClearAllVisible()318 public boolean isClearAllVisible() { 319 return verifyActiveContainer().hasObject( 320 mLauncher.getOverviewObjectSelector("clear_all")); 321 } 322 isActionsViewVisible()323 protected boolean isActionsViewVisible() { 324 if (!hasTasks() || isClearAllVisible()) { 325 return false; 326 } 327 boolean isTablet = mLauncher.isTablet(); 328 if (isTablet && mLauncher.isGridOnlyOverviewEnabled()) { 329 return false; 330 } 331 OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask(); 332 if (task == null) { 333 return false; 334 } 335 // In tablets, if focused task is not in center, overview actions aren't visible. 336 if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) { 337 return false; 338 } 339 // Overview actions aren't visible for split screen tasks. 340 return !task.isTaskSplit(); 341 } 342 verifyActionsViewVisibility()343 private void verifyActionsViewVisibility() { 344 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 345 "want to assert overview actions view visibility")) { 346 if (isActionsViewVisible()) { 347 mLauncher.waitForOverviewObject("action_buttons"); 348 } else { 349 mLauncher.waitUntilOverviewObjectGone("action_buttons"); 350 } 351 } 352 } 353 354 /** 355 * Returns Overview focused task if it exists. 356 * 357 * @throws IllegalStateException if not run on a tablet device. 358 */ getFocusedTaskForTablet()359 OverviewTask getFocusedTaskForTablet() { 360 if (!mLauncher.isTablet()) { 361 throw new IllegalStateException("Must be run on tablet device."); 362 } 363 final List<UiObject2> taskViews = getTasks(); 364 if (taskViews.size() == 0) { 365 return null; 366 } 367 int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet(); 368 for (UiObject2 task : taskViews) { 369 if (task.getVisibleBounds().height() == focusedTaskHeight) { 370 return new OverviewTask(mLauncher, task, this); 371 } 372 } 373 return null; 374 } 375 } 376