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.support.test.uiautomator.By; 20 import android.support.test.uiautomator.BySelector; 21 import android.support.test.uiautomator.Direction; 22 import android.support.test.uiautomator.UiDevice; 23 import android.support.test.uiautomator.UiObject2; 24 import android.support.test.uiautomator.UiObjectNotFoundException; 25 import android.support.test.uiautomator.Until; 26 import android.util.Log; 27 28 /** 29 * A helper class for generic launcher interactions that can be abstracted across different types 30 * of launchers. 31 * 32 */ 33 public class CommonLauncherHelper { 34 35 private static final String LOG_TAG = CommonLauncherHelper.class.getSimpleName(); 36 private static final int MAX_SCROLL_ATTEMPTS = 20; 37 private static final int MIN_INTERACT_SIZE = 100; 38 private static final int APP_LAUNCH_TIMEOUT = 10000; 39 private static CommonLauncherHelper sInstance; 40 private UiDevice mDevice; 41 CommonLauncherHelper(UiDevice uiDevice)42 private CommonLauncherHelper(UiDevice uiDevice) { 43 mDevice = uiDevice; 44 } 45 46 /** 47 * Retrieves the singleton instance of {@link CommonLauncherHelper} 48 * @param uiDevice 49 * @return 50 */ getInstance(UiDevice uiDevice)51 public static CommonLauncherHelper getInstance(UiDevice uiDevice) { 52 if (sInstance == null) { 53 sInstance = new CommonLauncherHelper(uiDevice); 54 } 55 return sInstance; 56 } 57 58 /** 59 * Scrolls a container back to the beginning 60 * @param container 61 * @param backDirection 62 * @throws UiObjectNotFoundException 63 */ scrollBackToBeginning(UiObject2 container, Direction backDirection)64 public void scrollBackToBeginning(UiObject2 container, Direction backDirection) 65 throws UiObjectNotFoundException { 66 scrollBackToBeginning(container, backDirection, MAX_SCROLL_ATTEMPTS); 67 } 68 69 /** 70 * Scrolls a container back to the beginning 71 * @param container 72 * @param backDirection 73 * @param maxAttempts 74 * @throws UiObjectNotFoundException 75 */ scrollBackToBeginning(UiObject2 container, Direction backDirection, int maxAttempts)76 public void scrollBackToBeginning(UiObject2 container, Direction backDirection, int maxAttempts) 77 throws UiObjectNotFoundException { 78 int attempts = 0; 79 while (container.fling(backDirection)) { 80 attempts++; 81 if (attempts > maxAttempts) { 82 throw new RuntimeException( 83 "scrollBackToBeginning: exceeded max attampts: " + maxAttempts); 84 } 85 } 86 } 87 88 /** 89 * Ensures that the described widget has enough visible portion by scrolling its container if 90 * necessary 91 * @param app 92 * @param container 93 * @param dir 94 * @throws UiObjectNotFoundException 95 */ ensureIconVisible(BySelector app, UiObject2 container, Direction dir)96 private void ensureIconVisible(BySelector app, UiObject2 container, Direction dir) 97 throws UiObjectNotFoundException { 98 UiObject2 appIcon = mDevice.findObject(app); 99 Rect appR = appIcon.getVisibleBounds(); 100 Rect containerR = container.getVisibleBounds(); 101 int size = 0; 102 int containerSize = 0; 103 if (Direction.DOWN.equals(dir) || Direction.UP.equals(dir)) { 104 size = appR.height(); 105 containerSize = containerR.height(); 106 } else { 107 size = appR.width(); 108 containerSize = containerR.width(); 109 } 110 if (size < MIN_INTERACT_SIZE) { 111 // try to figure out how much percentage of the container needs to be scrolled in order 112 // to reveal the app icon to have the MIN_INTERACT_SIZE 113 float pct = ((float)(MIN_INTERACT_SIZE - size)) / containerSize; 114 if (pct < 0.2f) { 115 pct = 0.2f; 116 } 117 container.scroll(dir, pct); 118 } 119 } 120 121 /** 122 * Triggers app launch by interacting with its launcher icon as described, optionally verify 123 * that the frontend UI has the expected app package name 124 * @param launcherStrategy 125 * @param app 126 * @param packageName 127 * @return 128 * @throws UiObjectNotFoundException 129 */ launchApp(ILauncherStrategy launcherStrategy, BySelector app, String packageName)130 public boolean launchApp(ILauncherStrategy launcherStrategy, BySelector app, 131 String packageName) throws UiObjectNotFoundException { 132 return launchApp(launcherStrategy, app, packageName, MAX_SCROLL_ATTEMPTS); 133 } 134 135 /** 136 * Triggers app launch by interacting with its launcher icon as described, optionally verify 137 * that the frontend UI has the expected app package name 138 * @param launcherStrategy 139 * @param app 140 * @param packageName 141 * @param maxScrollAttempts 142 * @return 143 * @throws UiObjectNotFoundException 144 */ launchApp(ILauncherStrategy launcherStrategy, BySelector app, String packageName, int maxScrollAttempts)145 public boolean launchApp(ILauncherStrategy launcherStrategy, BySelector app, 146 String packageName, int maxScrollAttempts) 147 throws UiObjectNotFoundException { 148 Direction dir = launcherStrategy.getAllAppsScrollDirection(); 149 // attempt to find the app icon if it's not already on the screen 150 if (!mDevice.hasObject(app)) { 151 UiObject2 container = launcherStrategy.openAllApps(false); 152 153 if (!mDevice.hasObject(app)) { 154 scrollBackToBeginning(container, Direction.reverse(dir)); 155 int attempts = 0; 156 while (!mDevice.hasObject(app) && container.scroll(dir, 0.8f)) { 157 attempts++; 158 if (attempts > maxScrollAttempts) { 159 throw new RuntimeException( 160 "launchApp: exceeded max attampts to locate app icon: " 161 + maxScrollAttempts); 162 } 163 } 164 } 165 // HACK-ish: ensure icon has enough parts revealed for it to be clicked on 166 ensureIconVisible(app, container, dir); 167 } 168 169 if (!mDevice.findObject(app).clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT)) { 170 Log.w(LOG_TAG, "no new window detected after app launch attempt."); 171 return false; 172 } 173 mDevice.waitForIdle(); 174 if (packageName != null) { 175 Log.w(LOG_TAG, String.format( 176 "No UI element with package name %s detected.", packageName)); 177 return mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT); 178 } else { 179 return true; 180 } 181 } 182 } 183