• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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