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.Point;
20 import android.graphics.Rect;
21 import android.os.Bundle;
22 import android.widget.TextView;
23 
24 import androidx.annotation.NonNull;
25 import androidx.test.uiautomator.By;
26 import androidx.test.uiautomator.BySelector;
27 import androidx.test.uiautomator.Direction;
28 import androidx.test.uiautomator.StaleObjectException;
29 import androidx.test.uiautomator.UiObject2;
30 
31 import com.android.launcher3.testing.TestProtocol;
32 
33 import java.util.stream.Collectors;
34 
35 /**
36  * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
37  */
38 public class AllApps extends LauncherInstrumentation.VisibleContainer {
39     private static final int MAX_SCROLL_ATTEMPTS = 40;
40 
41     private final int mHeight;
42     private final int mIconHeight;
43 
AllApps(LauncherInstrumentation launcher)44     AllApps(LauncherInstrumentation launcher) {
45         super(launcher);
46         final UiObject2 allAppsContainer = verifyActiveContainer();
47         mHeight = mLauncher.getVisibleBounds(allAppsContainer).height();
48         final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
49                 "apps_list_view");
50         // Wait for the recycler to populate.
51         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
52         verifyNotFrozen("All apps freeze flags upon opening all apps");
53         mIconHeight = mLauncher.getTestInfo(
54                 TestProtocol.REQUEST_ICON_HEIGHT).
55                 getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
56     }
57 
58     @Override
getContainerType()59     protected LauncherInstrumentation.ContainerType getContainerType() {
60         return LauncherInstrumentation.ContainerType.ALL_APPS;
61     }
62 
hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler, BySelector appIconSelector, int displayBottom)63     private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
64             BySelector appIconSelector, int displayBottom) {
65         final UiObject2 icon;
66         try {
67             icon = appListRecycler.findObject(appIconSelector);
68         } catch (StaleObjectException e) {
69             mLauncher.fail("All apps recycler disappeared from screen");
70             return false;
71         }
72         if (icon == null) {
73             LauncherInstrumentation.log("hasClickableIcon: icon not visible");
74             return false;
75         }
76         final Rect iconBounds = mLauncher.getVisibleBounds(icon);
77         LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
78         if (iconBounds.height() < mIconHeight / 2) {
79             LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
80             return false;
81         }
82         if (iconCenterInSearchBox(allAppsContainer, icon)) {
83             LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
84             return false;
85         }
86         if (iconBounds.bottom > displayBottom) {
87             LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset");
88             return false;
89         }
90         LauncherInstrumentation.log("hasClickableIcon: icon is clickable");
91         return true;
92     }
93 
iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon)94     private boolean iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon) {
95         final Point iconCenter = icon.getVisibleCenter();
96         return mLauncher.getVisibleBounds(getSearchBox(allAppsContainer)).contains(
97                 iconCenter.x, iconCenter.y);
98     }
99 
100     /**
101      * Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
102      * sure the icon is visible.
103      *
104      * @param appName name of the app.
105      * @return The app.
106      */
107     @NonNull
getAppIcon(String appName)108     public AppIcon getAppIcon(String appName) {
109         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
110              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
111                      "getting app icon " + appName + " on all apps")) {
112             final UiObject2 allAppsContainer = verifyActiveContainer();
113             final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
114                     "apps_list_view");
115             final UiObject2 searchBox = getSearchBox(allAppsContainer);
116 
117             int deviceHeight = mLauncher.getRealDisplaySize().y;
118             int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
119             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
120             if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
121                     bottomGestureStartOnScreen)) {
122                 scrollBackToBeginning();
123                 int attempts = 0;
124                 int scroll = getAllAppsScroll();
125                 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
126                     while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
127                             bottomGestureStartOnScreen)) {
128                         mLauncher.scrollToLastVisibleRow(
129                                 allAppsContainer,
130                                 mLauncher.getObjectsInContainer(allAppsContainer, "icon")
131                                         .stream()
132                                         .filter(icon ->
133                                                 mLauncher.getVisibleBounds(icon).top
134                                                         < bottomGestureStartOnScreen)
135                                         .collect(Collectors.toList()),
136                                 mLauncher.getVisibleBounds(searchBox).bottom
137                                         - mLauncher.getVisibleBounds(allAppsContainer).top);
138                         verifyActiveContainer();
139                         final int newScroll = getAllAppsScroll();
140                         mLauncher.assertTrue(
141                                 "Scrolled in a wrong direction in AllApps: from " + scroll + " to "
142                                         + newScroll, newScroll >= scroll);
143                         if (newScroll == scroll) break;
144 
145                         mLauncher.assertTrue(
146                                 "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
147                                 ++attempts <= MAX_SCROLL_ATTEMPTS);
148                         scroll = newScroll;
149                     }
150                 }
151                 verifyActiveContainer();
152             }
153 
154             // Ignore bottom offset selection here as there might not be any scroll more scroll
155             // region available.
156             mLauncher.assertTrue("Unable to scroll to a clickable icon: " + appName,
157                     hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
158                             deviceHeight));
159 
160             final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
161                     appIconSelector);
162             return new AppIcon(mLauncher, appIcon);
163         }
164     }
165 
scrollBackToBeginning()166     private void scrollBackToBeginning() {
167         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
168                 "want to scroll back in all apps")) {
169             LauncherInstrumentation.log("Scrolling to the beginning");
170             final UiObject2 allAppsContainer = verifyActiveContainer();
171             final UiObject2 searchBox = getSearchBox(allAppsContainer);
172 
173             int attempts = 0;
174             final Rect margins =
175                     new Rect(0, mLauncher.getVisibleBounds(searchBox).bottom + 1, 0, 5);
176 
177             for (int scroll = getAllAppsScroll();
178                     scroll != 0;
179                     scroll = getAllAppsScroll()) {
180                 mLauncher.assertTrue("Negative scroll position", scroll > 0);
181 
182                 mLauncher.assertTrue(
183                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
184                         ++attempts <= MAX_SCROLL_ATTEMPTS);
185 
186                 mLauncher.scroll(
187                         allAppsContainer, Direction.UP, margins, 12, false);
188             }
189 
190             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
191                 verifyActiveContainer();
192             }
193         }
194     }
195 
getAllAppsScroll()196     private int getAllAppsScroll() {
197         return mLauncher.getTestInfo(
198                 TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
199                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
200     }
201 
getSearchBox(UiObject2 allAppsContainer)202     private UiObject2 getSearchBox(UiObject2 allAppsContainer) {
203         return mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
204     }
205 
206     /**
207      * Flings forward (down) and waits the fling's end.
208      */
flingForward()209     public void flingForward() {
210         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
211              LauncherInstrumentation.Closable c =
212                      mLauncher.addContextLayer("want to fling forward in all apps")) {
213             final UiObject2 allAppsContainer = verifyActiveContainer();
214             // Start the gesture in the center to avoid starting at elements near the top.
215             mLauncher.scroll(
216                     allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10, false);
217             verifyActiveContainer();
218         }
219     }
220 
221     /**
222      * Flings backward (up) and waits the fling's end.
223      */
flingBackward()224     public void flingBackward() {
225         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
226              LauncherInstrumentation.Closable c =
227                      mLauncher.addContextLayer("want to fling backward in all apps")) {
228             final UiObject2 allAppsContainer = verifyActiveContainer();
229             // Start the gesture in the center, for symmetry with forward.
230             mLauncher.scroll(
231                     allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10, false);
232             verifyActiveContainer();
233         }
234     }
235 
236     /**
237      * Freezes updating app list upon app install/uninstall/update.
238      */
freeze()239     public void freeze() {
240         mLauncher.getTestInfo(TestProtocol.REQUEST_FREEZE_APP_LIST);
241     }
242 
243     /**
244      * Resumes updating app list upon app install/uninstall/update.
245      */
unfreeze()246     public void unfreeze() {
247         mLauncher.getTestInfo(TestProtocol.REQUEST_UNFREEZE_APP_LIST);
248         verifyNotFrozen("All apps freeze flags upon unfreezing");
249     }
250 
verifyNotFrozen(String message)251     private void verifyNotFrozen(String message) {
252         final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
253         if (testInfo == null) return;
254         mLauncher.assertEquals(message, 0, testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
255     }
256 }
257