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 }