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.OverviewTask.OverviewSplitTask.DEFAULT; 20 import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT; 21 import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT; 22 23 import android.graphics.Rect; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.test.uiautomator.By; 28 import androidx.test.uiautomator.BySelector; 29 import androidx.test.uiautomator.UiObject2; 30 31 import com.android.launcher3.testing.shared.TestProtocol; 32 33 import java.util.List; 34 import java.util.regex.Pattern; 35 import java.util.stream.Collectors; 36 37 /** 38 * A recent task in the overview panel carousel. 39 */ 40 public final class OverviewTask { 41 private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; 42 static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync"); 43 static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect"); 44 static final Pattern SPLIT_START_EVENT = Pattern.compile("launchSplitTasks"); 45 private final LauncherInstrumentation mLauncher; 46 private final UiObject2 mTask; 47 private final BaseOverview mOverview; 48 OverviewTask(LauncherInstrumentation launcher, UiObject2 task, BaseOverview overview)49 OverviewTask(LauncherInstrumentation launcher, UiObject2 task, BaseOverview overview) { 50 mLauncher = launcher; 51 mTask = task; 52 mOverview = overview; 53 verifyActiveContainer(); 54 } 55 verifyActiveContainer()56 private void verifyActiveContainer() { 57 mOverview.verifyActiveContainer(); 58 } 59 60 /** 61 * Returns the height of the visible task, or the combined height of two tasks in split with a 62 * divider between. 63 */ getVisibleHeight()64 int getVisibleHeight() { 65 if (isTaskSplit()) { 66 return getCombinedSplitTaskHeight(); 67 } 68 69 UiObject2 taskSnapshot1 = findObjectInTask(DEFAULT.snapshotRes); 70 return taskSnapshot1.getVisibleBounds().height(); 71 } 72 73 /** 74 * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider. 75 */ getCombinedSplitTaskHeight()76 private int getCombinedSplitTaskHeight() { 77 UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes); 78 UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes); 79 80 // If the split task is partly off screen, taskSnapshot1 can be invisible. 81 if (taskSnapshot1 == null) { 82 return taskSnapshot2.getVisibleBounds().height(); 83 } 84 85 int top = Math.min( 86 taskSnapshot1.getVisibleBounds().top, taskSnapshot2.getVisibleBounds().top); 87 int bottom = Math.max( 88 taskSnapshot1.getVisibleBounds().bottom, taskSnapshot2.getVisibleBounds().bottom); 89 90 return bottom - top; 91 } 92 93 /** 94 * Returns the width of the visible task, or the combined width of two tasks in split with a 95 * divider between. 96 */ getVisibleWidth()97 int getVisibleWidth() { 98 if (isTaskSplit()) { 99 return getCombinedSplitTaskWidth(); 100 } 101 102 UiObject2 taskSnapshot1 = findObjectInTask(DEFAULT.snapshotRes); 103 return taskSnapshot1.getVisibleBounds().width(); 104 } 105 106 /** 107 * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider. 108 */ getCombinedSplitTaskWidth()109 private int getCombinedSplitTaskWidth() { 110 UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes); 111 UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes); 112 113 int left = Math.min( 114 taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left); 115 int right = Math.max( 116 taskSnapshot1.getVisibleBounds().right, taskSnapshot2.getVisibleBounds().right); 117 118 return right - left; 119 } 120 getTaskCenterX()121 int getTaskCenterX() { 122 return mTask.getVisibleCenter().x; 123 } 124 getTaskCenterY()125 int getTaskCenterY() { 126 return mTask.getVisibleCenter().y; 127 } 128 getExactCenterX()129 float getExactCenterX() { 130 return mTask.getVisibleBounds().exactCenterX(); 131 } 132 getUiObject()133 UiObject2 getUiObject() { 134 return mTask; 135 } 136 137 /** 138 * Dismisses the task by swiping up. 139 */ dismiss()140 public void dismiss() { 141 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 142 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 143 "want to dismiss an overview task")) { 144 verifyActiveContainer(); 145 int taskCountBeforeDismiss = mOverview.getTaskCount(); 146 mLauncher.assertNotEquals("Unable to find a task", 0, taskCountBeforeDismiss); 147 if (taskCountBeforeDismiss == 1) { 148 dismissBySwipingUp(); 149 return; 150 } 151 152 boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher 153 .getFocusedTaskHeightForTablet(); 154 List<Integer> originalTasksCenterX = 155 getCurrentTasksCenterXList().stream().sorted().toList(); 156 boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible(); 157 158 dismissBySwipingUp(); 159 160 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("dismissed")) { 161 if (taskWasFocused) { 162 mLauncher.assertNotNull("No task became focused", 163 mOverview.getFocusedTaskForTablet()); 164 } 165 if (!isClearAllVisibleBeforeDismiss) { 166 List<Integer> currentTasksCenterX = 167 getCurrentTasksCenterXList().stream().sorted().toList(); 168 if (originalTasksCenterX.size() == currentTasksCenterX.size()) { 169 // Check for the same number of visible tasks before and after to 170 // avoid asserting on cases of shifting all tasks to close the distance 171 // between clear all and tasks at the end of the grid. 172 mLauncher.assertTrue("Task centers not aligned", 173 originalTasksCenterX.equals(currentTasksCenterX)); 174 } 175 } 176 } 177 } 178 } 179 dismissBySwipingUp()180 private void dismissBySwipingUp() { 181 verifyActiveContainer(); 182 // Dismiss the task via flinging it up. 183 final Rect taskBounds = mLauncher.getVisibleBounds(mTask); 184 final int centerX = taskBounds.centerX(); 185 final int centerY = taskBounds.bottom - 1; 186 mLauncher.executeAndWaitForLauncherEvent( 187 () -> mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false, 188 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER), 189 event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals(event.getClassName()), 190 () -> "Didn't receive a dismiss animation ends message: " + centerX + ", " 191 + centerY, "swiping to dismiss"); 192 } 193 getCurrentTasksCenterXList()194 private List<Integer> getCurrentTasksCenterXList() { 195 return mLauncher.isTablet() 196 ? mOverview.getCurrentTasksForTablet().stream() 197 .map(OverviewTask::getTaskCenterX) 198 .collect(Collectors.toList()) 199 : List.of(mOverview.getCurrentTask().getTaskCenterX()); 200 } 201 202 /** 203 * Clicks the task. 204 */ open()205 public LaunchedAppState open() { 206 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 207 verifyActiveContainer(); 208 mLauncher.executeAndWaitForLauncherStop( 209 () -> mLauncher.clickLauncherObject(mTask), 210 "clicking an overview task"); 211 if (mOverview.getContainerType() 212 == LauncherInstrumentation.ContainerType.SPLIT_SCREEN_SELECT) { 213 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SPLIT_START_EVENT); 214 215 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 216 "launched splitscreen")) { 217 218 BySelector divider = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle"); 219 mLauncher.waitForSystemUiObject(divider); 220 return new LaunchedAppState(mLauncher); 221 } 222 } else { 223 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT); 224 return new LaunchedAppState(mLauncher); 225 } 226 } 227 } 228 229 /** Taps the task menu. Returns the task menu object. */ 230 @NonNull tapMenu()231 public OverviewTaskMenu tapMenu() { 232 return tapMenu(DEFAULT); 233 } 234 235 /** Taps the task menu of the split task. Returns the split task's menu object. */ 236 @NonNull tapMenu(OverviewSplitTask task)237 public OverviewTaskMenu tapMenu(OverviewSplitTask task) { 238 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); 239 LauncherInstrumentation.Closable c = mLauncher.addContextLayer( 240 "want to tap the task menu")) { 241 mLauncher.clickLauncherObject( 242 mLauncher.waitForObjectInContainer(mTask, task.iconAppRes)); 243 244 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 245 "tapped the task menu")) { 246 return new OverviewTaskMenu(mLauncher); 247 } 248 } 249 } 250 isTaskSplit()251 boolean isTaskSplit() { 252 return findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes) != null; 253 } 254 findObjectInTask(String resName)255 private UiObject2 findObjectInTask(String resName) { 256 return mTask.findObject(mLauncher.getOverviewObjectSelector(resName)); 257 } 258 259 /** 260 * Returns whether the given String is contained in this Task's contentDescription. Also returns 261 * true if both Strings are null. 262 * 263 * TODO(b/326565120): remove Nullable support once the bug causing it to be null is fixed. 264 */ containsContentDescription(@ullable String expected)265 public boolean containsContentDescription(@Nullable String expected) { 266 String actual = mTask.getContentDescription(); 267 if (actual == null && expected == null) { 268 return true; 269 } 270 if (actual == null || expected == null) { 271 return false; 272 } 273 return actual.contains(expected); 274 } 275 276 /** 277 * Enum used to specify which task is retrieved when it is a split task. 278 */ 279 public enum OverviewSplitTask { 280 // The main task when the task is not split. 281 DEFAULT("snapshot", "icon"), 282 // The first task in split task. 283 SPLIT_TOP_OR_LEFT("snapshot", "icon"), 284 // The second task in split task. 285 SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"); 286 287 public final String snapshotRes; 288 public final String iconAppRes; 289 OverviewSplitTask(String snapshotRes, String iconAppRes)290 OverviewSplitTask(String snapshotRes, String iconAppRes) { 291 this.snapshotRes = snapshotRes; 292 this.iconAppRes = iconAppRes; 293 } 294 } 295 } 296