• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
20 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
21 
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.os.Bundle;
25 import android.widget.TextView;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.test.uiautomator.By;
30 import androidx.test.uiautomator.BySelector;
31 import androidx.test.uiautomator.Direction;
32 import androidx.test.uiautomator.StaleObjectException;
33 import androidx.test.uiautomator.UiObject2;
34 
35 import com.android.launcher3.testing.shared.TestProtocol;
36 
37 import java.util.Collections;
38 import java.util.Comparator;
39 import java.util.List;
40 import java.util.stream.Collectors;
41 
42 /**
43  * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
44  */
45 public abstract class AllApps extends LauncherInstrumentation.VisibleContainer {
46     // Defer updates flag used to defer all apps updates by a test's request.
47     private static final int DEFER_UPDATES_TEST = 1 << 1;
48 
49     private static final int MAX_SCROLL_ATTEMPTS = 40;
50 
51     private final int mHeight;
52     private final int mIconHeight;
53 
AllApps(LauncherInstrumentation launcher)54     AllApps(LauncherInstrumentation launcher) {
55         super(launcher);
56         final UiObject2 allAppsContainer = verifyActiveContainer();
57         mHeight = mLauncher.getVisibleBounds(allAppsContainer).height();
58         final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
59         // Wait for the recycler to populate.
60         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
61         verifyNotFrozen("All apps freeze flags upon opening all apps");
62         mIconHeight = mLauncher.getTestInfo(TestProtocol.REQUEST_ICON_HEIGHT)
63                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
64     }
65 
hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler, BySelector appIconSelector, int displayBottom)66     private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
67             BySelector appIconSelector, int displayBottom) {
68         final UiObject2 icon;
69         try {
70             icon = appListRecycler.findObject(appIconSelector);
71         } catch (StaleObjectException e) {
72             mLauncher.fail("All apps recycler disappeared from screen");
73             return false;
74         }
75         if (icon == null) {
76             LauncherInstrumentation.log("hasClickableIcon: icon not visible");
77             return false;
78         }
79         final Rect iconBounds = mLauncher.getVisibleBounds(icon);
80         LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
81         if (iconBounds.height() < mIconHeight / 2) {
82             LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
83             return false;
84         }
85         if (hasSearchBox() && iconCenterInSearchBox(allAppsContainer, icon)) {
86             LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
87             return false;
88         }
89         if (iconCenterInRecyclerTopPadding(appListRecycler, icon)) {
90             LauncherInstrumentation.log(
91                     "hasClickableIcon: icon center is under the app list recycler's top padding.");
92             return false;
93         }
94         if (iconBounds.bottom > displayBottom) {
95             LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset");
96             return false;
97         }
98         LauncherInstrumentation.log("hasClickableIcon: icon is clickable");
99         return true;
100     }
101 
iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon)102     private boolean iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon) {
103         final Point iconCenter = icon.getVisibleCenter();
104         return mLauncher.getVisibleBounds(getSearchBox(allAppsContainer)).contains(
105                 iconCenter.x, iconCenter.y);
106     }
107 
iconCenterInRecyclerTopPadding(UiObject2 appsListRecycler, UiObject2 icon)108     private boolean iconCenterInRecyclerTopPadding(UiObject2 appsListRecycler, UiObject2 icon) {
109         final Point iconCenter = icon.getVisibleCenter();
110 
111         return iconCenter.y <= mLauncher.getVisibleBounds(appsListRecycler).top
112                 + getAppsListRecyclerTopPadding();
113     }
114 
115     /**
116      * Finds an icon. If the icon doesn't exist, return null.
117      * Scrolls the app list when needed to make sure the icon is visible.
118      *
119      * @param appName name of the app.
120      * @return The app if found, and null if not found.
121      */
122     @Nullable
tryGetAppIcon(String appName)123     public AppIcon tryGetAppIcon(String appName) {
124         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
125              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
126                      "getting app icon " + appName + " on all apps")) {
127             final UiObject2 allAppsContainer = verifyActiveContainer();
128             final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
129 
130             int deviceHeight = mLauncher.getRealDisplaySize().y;
131             int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
132             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
133             if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
134                     bottomGestureStartOnScreen)) {
135                 scrollBackToBeginning();
136                 int attempts = 0;
137                 int scroll = getAllAppsScroll();
138                 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
139                     while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
140                             bottomGestureStartOnScreen)) {
141                         mLauncher.scrollToLastVisibleRow(
142                                 allAppsContainer,
143                                 getBottomVisibleIconBounds(allAppsContainer),
144                                 mLauncher.getVisibleBounds(appListRecycler).top
145                                         + getAppsListRecyclerTopPadding()
146                                         - mLauncher.getVisibleBounds(allAppsContainer).top,
147                                 getAppsListRecyclerBottomPadding());
148                         verifyActiveContainer();
149                         final int newScroll = getAllAppsScroll();
150                         LauncherInstrumentation.log(
151                                 String.format("tryGetAppIcon: scrolled from %d to %d", scroll,
152                                         newScroll));
153                         mLauncher.assertTrue(
154                                 "Scrolled in a wrong direction in AllApps: from " + scroll + " to "
155                                         + newScroll, newScroll >= scroll);
156                         if (newScroll == scroll) break;
157 
158                         mLauncher.assertTrue(
159                                 "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
160                                 ++attempts <= MAX_SCROLL_ATTEMPTS);
161                         scroll = newScroll;
162                     }
163                 }
164                 verifyActiveContainer();
165             }
166             // Ignore bottom offset selection here as there might not be any scroll more scroll
167             // region available.
168             if (hasClickableIcon(
169                     allAppsContainer, appListRecycler, appIconSelector, deviceHeight)) {
170 
171                 final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
172                         appIconSelector);
173                 return createAppIcon(appIcon);
174             } else {
175                 return null;
176             }
177         }
178     }
179 
180     /** @return visible bounds of the top-most visible icon in the container. */
getTopVisibleIconBounds(UiObject2 allAppsContainer)181     protected Rect getTopVisibleIconBounds(UiObject2 allAppsContainer) {
182         return mLauncher.getVisibleBounds(Collections.min(getVisibleIcons(allAppsContainer),
183                 Comparator.comparingInt(i -> mLauncher.getVisibleBounds(i).top)));
184     }
185 
186     /** @return visible bounds of the bottom-most visible icon in the container. */
getBottomVisibleIconBounds(UiObject2 allAppsContainer)187     protected Rect getBottomVisibleIconBounds(UiObject2 allAppsContainer) {
188         return mLauncher.getVisibleBounds(Collections.max(getVisibleIcons(allAppsContainer),
189                 Comparator.comparingInt(i -> mLauncher.getVisibleBounds(i).top)));
190     }
191 
192     @NonNull
getVisibleIcons(UiObject2 allAppsContainer)193     private List<UiObject2> getVisibleIcons(UiObject2 allAppsContainer) {
194         return mLauncher.getObjectsInContainer(allAppsContainer, "icon")
195                 .stream()
196                 .filter(icon ->
197                         mLauncher.getVisibleBounds(icon).top
198                                 < mLauncher.getBottomGestureStartOnScreen())
199                 .collect(Collectors.toList());
200     }
201 
202     /**
203      * Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
204      * sure the icon is visible.
205      *
206      * @param appName name of the app.
207      * @return The app.
208      */
209     @NonNull
getAppIcon(String appName)210     public AppIcon getAppIcon(String appName) {
211         AppIcon appIcon = tryGetAppIcon(appName);
212         mLauncher.assertNotNull("Unable to scroll to a clickable icon: " + appName, appIcon);
213         // appIcon.getAppName() checks for content description, so it is possible that it can have
214         // trailing words. So check if the content description contains the appName.
215         mLauncher.assertTrue("Wrong app icon name.", appIcon.getAppName().contains(appName));
216         return appIcon;
217     }
218 
219     @NonNull
createAppIcon(UiObject2 icon)220     protected abstract AppIcon createAppIcon(UiObject2 icon);
221 
hasSearchBox()222     protected abstract boolean hasSearchBox();
223 
getAppsListRecyclerTopPadding()224     protected abstract int getAppsListRecyclerTopPadding();
225 
getAppsListRecyclerBottomPadding()226     protected int getAppsListRecyclerBottomPadding() {
227         return mLauncher.getTestInfo(TestProtocol.REQUEST_ALL_APPS_BOTTOM_PADDING)
228                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
229     }
230 
scrollBackToBeginning()231     private void scrollBackToBeginning() {
232         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
233                 "want to scroll back in all apps")) {
234             LauncherInstrumentation.log("Scrolling to the beginning");
235             final UiObject2 allAppsContainer = verifyActiveContainer();
236 
237             int attempts = 0;
238             final Rect margins = new Rect(
239                     /* left= */ 0,
240                     getTopVisibleIconBounds(allAppsContainer).bottom,
241                     /* right= */ 0,
242                     /* bottom= */ getAppsListRecyclerBottomPadding());
243 
244             for (int scroll = getAllAppsScroll();
245                     scroll != 0;
246                     scroll = getAllAppsScroll()) {
247                 mLauncher.assertTrue("Negative scroll position", scroll > 0);
248 
249                 mLauncher.assertTrue(
250                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
251                         ++attempts <= MAX_SCROLL_ATTEMPTS);
252 
253                 mLauncher.scroll(
254                         allAppsContainer,
255                         Direction.UP,
256                         margins,
257                         /* steps= */ 12,
258                         /* slowDown= */ false);
259             }
260 
261             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
262                 verifyActiveContainer();
263             }
264         }
265     }
266 
getAllAppsScroll()267     protected abstract int getAllAppsScroll();
268 
getAppListRecycler(UiObject2 allAppsContainer)269     protected UiObject2 getAppListRecycler(UiObject2 allAppsContainer) {
270         return mLauncher.waitForObjectInContainer(allAppsContainer, "apps_list_view");
271     }
272 
getSearchBox(UiObject2 allAppsContainer)273     protected UiObject2 getSearchBox(UiObject2 allAppsContainer) {
274         return mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
275     }
276 
277     /**
278      * Flings forward (down) and waits the fling's end.
279      */
flingForward()280     public void flingForward() {
281         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
282              LauncherInstrumentation.Closable c =
283                      mLauncher.addContextLayer("want to fling forward in all apps")) {
284             final UiObject2 allAppsContainer = verifyActiveContainer();
285             // Start the gesture in the center to avoid starting at elements near the top.
286             mLauncher.scroll(
287                     allAppsContainer,
288                     Direction.DOWN,
289                     new Rect(0, 0, 0, mHeight / 2),
290                     /* steps= */ 10,
291                     /* slowDown= */ false);
292             verifyActiveContainer();
293         }
294     }
295 
296     /**
297      * Flings backward (up) and waits the fling's end.
298      */
flingBackward()299     public void flingBackward() {
300         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
301              LauncherInstrumentation.Closable c =
302                      mLauncher.addContextLayer("want to fling backward in all apps")) {
303             final UiObject2 allAppsContainer = verifyActiveContainer();
304             // Start the gesture in the center, for symmetry with forward.
305             mLauncher.scroll(
306                     allAppsContainer,
307                     Direction.UP,
308                     new Rect(0, mHeight / 2, 0, 0),
309                     /* steps= */ 10,
310                     /*slowDown= */ false);
311             verifyActiveContainer();
312         }
313     }
314 
315     /**
316      * Freezes updating app list upon app install/uninstall/update.
317      */
freeze()318     public void freeze() {
319         mLauncher.getTestInfo(TestProtocol.REQUEST_FREEZE_APP_LIST);
320     }
321 
322     /**
323      * Resumes updating app list upon app install/uninstall/update.
324      */
unfreeze()325     public void unfreeze() {
326         mLauncher.getTestInfo(TestProtocol.REQUEST_UNFREEZE_APP_LIST);
327     }
328 
verifyNotFrozen(String message)329     private void verifyNotFrozen(String message) {
330         mLauncher.assertEquals(message, 0, getFreezeFlags() & DEFER_UPDATES_TEST);
331         mLauncher.assertTrue(message, mLauncher.waitAndGet(() -> getFreezeFlags() == 0,
332                 WAIT_TIME_MS, DEFAULT_POLL_INTERVAL));
333     }
334 
getFreezeFlags()335     private int getFreezeFlags() {
336         final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
337         return testInfo == null ? 0 : testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
338     }
339 
340     /**
341      * Return the QSB UI object on the AllApps screen.
342      * @return the QSB UI object.
343      */
344     @NonNull
getQsb()345     public abstract Qsb getQsb();
346 }