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