1 /* 2 * Copyright (C) 2015 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 package android.support.test.launcherhelper; 17 18 import android.graphics.Rect; 19 import android.os.RemoteException; 20 import android.os.SystemClock; 21 import android.support.test.uiautomator.By; 22 import android.support.test.uiautomator.BySelector; 23 import android.support.test.uiautomator.Direction; 24 import android.support.test.uiautomator.UiDevice; 25 import android.support.test.uiautomator.UiObject2; 26 import android.support.test.uiautomator.Until; 27 import android.util.Log; 28 29 import com.android.launcher3.tapl.AppIcon; 30 import com.android.launcher3.tapl.LauncherInstrumentation; 31 import com.android.launcher3.tapl.Workspace; 32 33 /** 34 * A helper class for generic launcher interactions that can be abstracted across different types 35 * of launchers. 36 * 37 */ 38 public class CommonLauncherHelper { 39 40 private static final String LOG_TAG = CommonLauncherHelper.class.getSimpleName(); 41 private static final int MAX_SCROLL_ATTEMPTS = 40; 42 private static final int MIN_INTERACT_SIZE = 100; 43 private static final int APP_LAUNCH_TIMEOUT = 10000; 44 private static CommonLauncherHelper sInstance; 45 private UiDevice mDevice; 46 CommonLauncherHelper(UiDevice uiDevice)47 private CommonLauncherHelper(UiDevice uiDevice) { 48 mDevice = uiDevice; 49 } 50 51 /** 52 * Retrieves the singleton instance of {@link CommonLauncherHelper} 53 * @param uiDevice 54 * @return 55 */ getInstance(UiDevice uiDevice)56 public static CommonLauncherHelper getInstance(UiDevice uiDevice) { 57 if (sInstance == null) { 58 sInstance = new CommonLauncherHelper(uiDevice); 59 } 60 return sInstance; 61 } 62 63 /** 64 * Scrolls a container back to the beginning 65 * @param container 66 * @param backDirection 67 */ scrollBackToBeginning(UiObject2 container, Direction backDirection)68 public void scrollBackToBeginning(UiObject2 container, Direction backDirection) { 69 scrollBackToBeginning(container, backDirection, MAX_SCROLL_ATTEMPTS); 70 } 71 72 /** 73 * Scrolls a container back to the beginning 74 * @param container 75 * @param backDirection 76 * @param maxAttempts 77 */ scrollBackToBeginning(UiObject2 container, Direction backDirection, int maxAttempts)78 public void scrollBackToBeginning(UiObject2 container, Direction backDirection, int maxAttempts) { 79 int attempts = 0; 80 while (container.scroll(backDirection, 0.25f)) { 81 attempts++; 82 if (attempts > maxAttempts) { 83 throw new RuntimeException( 84 "scrollBackToBeginning: exceeded max attempts: " + maxAttempts); 85 } 86 } 87 } 88 89 /** 90 * Ensures that the described widget has enough visible portion by scrolling its container if 91 * necessary 92 * @param app 93 * @param container 94 * @param dir 95 */ ensureIconVisible(BySelector app, UiObject2 container, Direction dir)96 private void ensureIconVisible(BySelector app, UiObject2 container, Direction dir) { 97 UiObject2 appIcon = mDevice.findObject(app); 98 if (appIcon == null) { 99 throw new RuntimeException("App icon was not visible."); 100 } 101 102 Rect appR = appIcon.getVisibleBounds(); 103 Rect containerR = container.getVisibleBounds(); 104 int size = 0; 105 int containerSize = 0; 106 if (Direction.DOWN.equals(dir) || Direction.UP.equals(dir)) { 107 size = appR.height(); 108 containerSize = containerR.height(); 109 } else { 110 size = appR.width(); 111 containerSize = containerR.width(); 112 } 113 if (size < MIN_INTERACT_SIZE) { 114 // try to figure out how much percentage of the container needs to be scrolled in order 115 // to reveal the app icon to have the MIN_INTERACT_SIZE 116 float pct = ((float)(MIN_INTERACT_SIZE - size)) / containerSize; 117 if (pct < 0.2f) { 118 pct = 0.2f; 119 } 120 container.scroll(dir, pct); 121 } 122 } 123 124 /** 125 * Triggers app launch by interacting with its launcher icon as described, optionally verify 126 * that the frontend UI has the expected app package name 127 * @param launcherStrategy 128 * @param app 129 * @param packageName 130 * @return 131 */ launchApp(ILauncherStrategy launcherStrategy, BySelector app, String packageName)132 public long launchApp(ILauncherStrategy launcherStrategy, BySelector app, 133 String packageName) { 134 return launchApp(launcherStrategy, app, packageName, MAX_SCROLL_ATTEMPTS); 135 } 136 137 /** 138 * Triggers app launch by interacting with its launcher icon as described, optionally verify 139 * that the frontend UI has the expected app package name 140 * @param launcherStrategy 141 * @param app 142 * @param packageName 143 * @param maxScrollAttempts 144 * @return the SystemClock#uptimeMillis timestamp just before launching the application. 145 */ launchApp(ILauncherStrategy launcherStrategy, BySelector app, String packageName, int maxScrollAttempts)146 public long launchApp(ILauncherStrategy launcherStrategy, BySelector app, 147 String packageName, int maxScrollAttempts) { 148 unlockDeviceIfAsleep(); 149 150 if (isAppOpen(packageName)) { 151 // Application is already open 152 return 0; 153 } 154 155 // Go to the home page 156 launcherStrategy.open(); 157 // attempt to find the app icon if it's not already on the screen 158 if (!mDevice.hasObject(app)) { 159 UiObject2 container = launcherStrategy.openAllApps(false); 160 Direction dir = launcherStrategy.getAllAppsScrollDirection(); 161 162 if (!mDevice.hasObject(app)) { 163 scrollBackToBeginning(container, Direction.reverse(dir)); 164 int attempts = 0; 165 while (!mDevice.hasObject(app) && container.scroll(dir, 0.8f)) { 166 attempts++; 167 if (attempts > maxScrollAttempts) { 168 throw new RuntimeException( 169 "launchApp: exceeded max attempts to locate app icon: " 170 + maxScrollAttempts); 171 } 172 } 173 } 174 // HACK-ish: ensure icon has enough parts revealed for it to be clicked on 175 ensureIconVisible(app, container, dir); 176 } 177 178 long ready = SystemClock.uptimeMillis(); 179 if (!mDevice.findObject(app).clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT)) { 180 Log.w(LOG_TAG, "no new window detected after app launch attempt."); 181 return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP; 182 } 183 return verifyAppStart(packageName, ready); 184 } 185 186 /** 187 * Triggers app launch by interacting with its launcher icon as described, optionally verify 188 * that the frontend UI has the expected app package name 189 * 190 * @param launcher root TAPL object 191 * @param appName 192 * @param packageName 193 * @return the SystemClock#uptimeMillis timestamp just before launching the application. 194 */ launchApp(LauncherInstrumentation launcher, String appName, String packageName)195 public long launchApp(LauncherInstrumentation launcher, String appName, String packageName) { 196 unlockDeviceIfAsleep(); 197 198 if (isAppOpen(packageName)) { 199 // Application is already open 200 return 0; 201 } 202 203 // Go to the home page 204 final Workspace workspace = launcher.pressHome(); 205 AppIcon icon = workspace.tryGetWorkspaceAppIcon(appName); 206 if (icon == null) { 207 icon = workspace.switchToAllApps().getAppIcon(appName); 208 } 209 210 final long ready = SystemClock.uptimeMillis(); 211 icon.launch(packageName); 212 return ready; 213 } 214 verifyAppStart(String packageName, long appStartTime)215 private long verifyAppStart(String packageName, long appStartTime) { 216 mDevice.waitForIdle(); 217 if (packageName != null) { 218 Log.w(LOG_TAG, String.format( 219 "No UI element with package name %s detected.", packageName)); 220 boolean success = mDevice.wait(Until.hasObject( 221 By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT); 222 if (success) { 223 return appStartTime; 224 } else { 225 return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP; 226 } 227 } else { 228 return appStartTime; 229 } 230 } 231 isAppOpen(String appPackage)232 private boolean isAppOpen (String appPackage) { 233 return mDevice.hasObject(By.pkg(appPackage).depth(0)); 234 } 235 unlockDeviceIfAsleep()236 private void unlockDeviceIfAsleep () { 237 // Turn screen on if necessary 238 try { 239 if (!mDevice.isScreenOn()) { 240 mDevice.wakeUp(); 241 } 242 } catch (RemoteException e) { 243 Log.e(LOG_TAG, "Failed to unlock the screen-off device.", e); 244 } 245 // Check for lock screen element 246 if (mDevice.hasObject(By.res("com.android.systemui", "keyguard_bottom_area"))) { 247 mDevice.pressMenu(); 248 } 249 } 250 } 251