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.BySelector; 23 import androidx.test.uiautomator.Direction; 24 import androidx.test.uiautomator.UiObject2; 25 26 import java.util.Collections; 27 import java.util.List; 28 import java.util.stream.Collectors; 29 30 /** 31 * Common overview panel for both Launcher and fallback recents 32 */ 33 public class BaseOverview extends LauncherInstrumentation.VisibleContainer { 34 private static final int FLINGS_FOR_DISMISS_LIMIT = 40; 35 BaseOverview(LauncherInstrumentation launcher)36 BaseOverview(LauncherInstrumentation launcher) { 37 super(launcher); 38 verifyActiveContainer(); 39 verifyActionsViewVisibility(); 40 } 41 42 @Override getContainerType()43 protected LauncherInstrumentation.ContainerType getContainerType() { 44 return LauncherInstrumentation.ContainerType.FALLBACK_OVERVIEW; 45 } 46 47 /** 48 * Flings forward (left) and waits the fling's end. 49 */ flingForward()50 public void flingForward() { 51 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 52 flingForwardImpl(); 53 } 54 } 55 flingForwardImpl()56 private void flingForwardImpl() { 57 try (LauncherInstrumentation.Closable c = 58 mLauncher.addContextLayer("want to fling forward in overview")) { 59 LauncherInstrumentation.log("Overview.flingForward before fling"); 60 final UiObject2 overview = verifyActiveContainer(); 61 final int leftMargin = 62 mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth(); 63 mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, 64 false); 65 try (LauncherInstrumentation.Closable c2 = 66 mLauncher.addContextLayer("flung forwards")) { 67 verifyActiveContainer(); 68 verifyActionsViewVisibility(); 69 } 70 } 71 } 72 73 /** 74 * Dismissed all tasks by scrolling to Clear-all button and pressing it. 75 */ dismissAllTasks()76 public void dismissAllTasks() { 77 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 78 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 79 "dismissing all tasks")) { 80 final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all"); 81 for (int i = 0; 82 i < FLINGS_FOR_DISMISS_LIMIT 83 && !verifyActiveContainer().hasObject(clearAllSelector); 84 ++i) { 85 flingForwardImpl(); 86 } 87 88 mLauncher.clickLauncherObject( 89 mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector)); 90 91 mLauncher.waitUntilLauncherObjectGone(clearAllSelector); 92 } 93 } 94 95 /** 96 * Flings backward (right) and waits the fling's end. 97 */ flingBackward()98 public void flingBackward() { 99 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 100 LauncherInstrumentation.Closable c = 101 mLauncher.addContextLayer("want to fling backward in overview")) { 102 LauncherInstrumentation.log("Overview.flingBackward before fling"); 103 final UiObject2 overview = verifyActiveContainer(); 104 final int rightMargin = 105 mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth(); 106 mLauncher.scroll( 107 overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false); 108 try (LauncherInstrumentation.Closable c2 = 109 mLauncher.addContextLayer("flung backwards")) { 110 verifyActiveContainer(); 111 verifyActionsViewVisibility(); 112 } 113 } 114 } 115 116 /** 117 * Scrolls the current task via flinging forward until it is off screen. 118 * 119 * If only one task is present, it is only partially scrolled off screen and will still be 120 * the current task. 121 */ scrollCurrentTaskOffScreen()122 public void scrollCurrentTaskOffScreen() { 123 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 124 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 125 "want to scroll current task off screen in overview")) { 126 verifyActiveContainer(); 127 128 OverviewTask task = getCurrentTask(); 129 mLauncher.assertNotNull("current task is null", task); 130 mLauncher.scrollLeftByDistance(verifyActiveContainer(), task.getVisibleWidth()); 131 132 try (LauncherInstrumentation.Closable c2 = 133 mLauncher.addContextLayer("scrolled task off screen")) { 134 verifyActiveContainer(); 135 verifyActionsViewVisibility(); 136 137 if (getTaskCount() > 1) { 138 if (mLauncher.isTablet()) { 139 mLauncher.assertTrue("current task is not grid height", 140 getCurrentTask().getVisibleHeight() == mLauncher 141 .getGridTaskRectForTablet().height()); 142 } 143 mLauncher.assertTrue("Current task not scrolled off screen", 144 !getCurrentTask().equals(task)); 145 } 146 } 147 } 148 } 149 150 /** 151 * Gets the current task in the carousel, or fails if the carousel is empty. 152 * 153 * @return the task in the middle of the visible tasks list. 154 */ 155 @NonNull getCurrentTask()156 public OverviewTask getCurrentTask() { 157 final List<UiObject2> taskViews = getTasks(); 158 mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); 159 160 // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one 161 // in the center, and parts of its right and left siblings. Find the main task view by 162 // its width. 163 final UiObject2 widestTask = Collections.max(taskViews, 164 (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(), 165 mLauncher.getVisibleBounds(t2).width())); 166 167 return new OverviewTask(mLauncher, widestTask, this); 168 } 169 170 /** 171 * Returns a list of all tasks fully visible in the tablet grid overview. 172 */ 173 @NonNull getCurrentTasksForTablet()174 public List<OverviewTask> getCurrentTasksForTablet() { 175 final List<UiObject2> taskViews = getTasks(); 176 mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); 177 178 final int gridTaskWidth = mLauncher.getGridTaskRectForTablet().width(); 179 180 return taskViews.stream().filter(t -> t.getVisibleBounds().width() == gridTaskWidth).map( 181 t -> new OverviewTask(mLauncher, t, this)).collect(Collectors.toList()); 182 } 183 184 @NonNull getTasks()185 private List<UiObject2> getTasks() { 186 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 187 "want to get overview tasks")) { 188 verifyActiveContainer(); 189 return mLauncher.getDevice().findObjects( 190 mLauncher.getOverviewObjectSelector("snapshot")); 191 } 192 } 193 getTaskCount()194 int getTaskCount() { 195 return getTasks().size(); 196 } 197 198 /** 199 * Returns whether Overview has tasks. 200 */ hasTasks()201 public boolean hasTasks() { 202 return getTasks().size() > 0; 203 } 204 205 /** 206 * Gets Overview Actions. 207 * 208 * @return The Overview Actions 209 */ 210 @NonNull getOverviewActions()211 public OverviewActions getOverviewActions() { 212 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 213 "want to get overview actions")) { 214 verifyActiveContainer(); 215 UiObject2 overviewActions = mLauncher.waitForOverviewObject("action_buttons"); 216 return new OverviewActions(overviewActions, mLauncher); 217 } 218 } 219 220 /** 221 * Returns if clear all button is visible. 222 */ isClearAllVisible()223 public boolean isClearAllVisible() { 224 return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all")); 225 } 226 verifyActionsViewVisibility()227 private void verifyActionsViewVisibility() { 228 if (!hasTasks()) { 229 return; 230 } 231 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 232 "want to assert overview actions view visibility")) { 233 if (mLauncher.isTablet() && !isOverviewSnappedToFocusedTaskForTablet()) { 234 mLauncher.waitUntilOverviewObjectGone("action_buttons"); 235 } else { 236 mLauncher.waitForOverviewObject("action_buttons"); 237 } 238 } 239 } 240 241 /** 242 * Returns if focused task is currently snapped task in tablet grid overview. 243 */ isOverviewSnappedToFocusedTaskForTablet()244 private boolean isOverviewSnappedToFocusedTaskForTablet() { 245 UiObject2 focusedTask = getFocusedTaskForTablet(); 246 if (focusedTask == null) { 247 return false; 248 } 249 return Math.abs( 250 focusedTask.getVisibleBounds().exactCenterX() - mLauncher.getExactScreenCenterX()) 251 < 1; 252 } 253 254 /** 255 * Returns Overview focused task if it exists. 256 * 257 * @throws IllegalStateException if not run on a tablet device. 258 */ getFocusedTaskForTablet()259 UiObject2 getFocusedTaskForTablet() { 260 if (!mLauncher.isTablet()) { 261 throw new IllegalStateException("Must be run on tablet device."); 262 } 263 final List<UiObject2> taskViews = getTasks(); 264 if (taskViews.size() == 0) { 265 return null; 266 } 267 int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet(); 268 for (UiObject2 task : taskViews) { 269 if (task.getVisibleBounds().height() == focusedTaskHeight) { 270 return task; 271 } 272 } 273 return null; 274 } 275 }