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.server.wm.flicker.helpers;
18 
19 import static android.os.SystemClock.sleep;
20 import static android.view.Surface.ROTATION_0;
21 
22 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
23 
24 import static org.junit.Assert.assertNotNull;
25 
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.graphics.Point;
29 import android.graphics.Rect;
30 import android.os.RemoteException;
31 import android.support.test.uiautomator.By;
32 import android.support.test.uiautomator.BySelector;
33 import android.support.test.uiautomator.Configurator;
34 import android.support.test.uiautomator.UiDevice;
35 import android.support.test.uiautomator.UiObject2;
36 import android.support.test.uiautomator.Until;
37 import android.util.Log;
38 import android.util.Rational;
39 import android.view.Surface;
40 import android.view.View;
41 import android.view.ViewConfiguration;
42 
43 import androidx.test.InstrumentationRegistry;
44 
45 import com.android.server.wm.flicker.WindowUtils;
46 
47 import java.util.Locale;
48 
49 /** Collection of UI Automation helper functions. */
50 public class AutomationUtils {
51     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
52     private static final long FIND_TIMEOUT = 10000;
53     private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L;
54     private static final String TAG = "FLICKER";
55 
wakeUpAndGoToHomeScreen()56     public static void wakeUpAndGoToHomeScreen() {
57         UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
58         try {
59             device.wakeUp();
60         } catch (RemoteException e) {
61             throw new RuntimeException(e);
62         }
63         device.pressHome();
64     }
65 
66     /**
67      * Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing the
68      * {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly. This
69      * removes some delays when using the UIAutomator library required to create fast UI
70      * transitions.
71      */
setFastWait()72     public static void setFastWait() {
73         Configurator.getInstance().setWaitForIdleTimeout(0);
74     }
75 
76     /** Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior. */
setDefaultWait()77     public static void setDefaultWait() {
78         Configurator.getInstance().setWaitForIdleTimeout(10000);
79     }
80 
isQuickstepEnabled(UiDevice device)81     public static boolean isQuickstepEnabled(UiDevice device) {
82         boolean enabled = device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null;
83         Log.d(TAG, "Quickstep enabled: " + enabled);
84         return enabled;
85     }
86 
openQuickstep(UiDevice device)87     public static void openQuickstep(UiDevice device) {
88         if (isQuickstepEnabled(device)) {
89             int height = device.getDisplayHeight();
90             UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"));
91 
92             Rect navBarVisibleBounds;
93 
94             // TODO(vishnun) investigate why this object cannot be found.
95             if (navBar != null) {
96                 navBarVisibleBounds = navBar.getVisibleBounds();
97             } else {
98                 Log.e(TAG, "Could not find nav bar, infer location");
99                 navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0);
100             }
101 
102             // Swipe from nav bar to 2/3rd down the screen.
103             device.swipe(
104                     navBarVisibleBounds.centerX(),
105                     navBarVisibleBounds.centerY(),
106                     navBarVisibleBounds.centerX(),
107                     height * 2 / 3,
108                     (navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step
109         }
110 
111         // use a long timeout to wait until recents populated
112         BySelector recentsSysUISelector = By.res(device.getLauncherPackageName(), "overview_panel");
113         UiObject2 recents = device.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT);
114 
115         // Quickstep detection is flaky on AOSP, UIDevice doesn't always find SysUI elements
116         // If it couldn't find, try pressing 'recent items' button
117         if (recents == null) {
118             try {
119                 device.pressRecentApps();
120             } catch (RemoteException e) {
121                 throw new RuntimeException(e);
122             }
123             recents = device.wait(Until.findObject(recentsSysUISelector), FIND_TIMEOUT);
124         }
125 
126         assertNotNull("Recent items didn't appear", recents);
127         device.waitForIdle();
128     }
129 
clearRecents(UiDevice device)130     public static void clearRecents(UiDevice device) {
131         if (isQuickstepEnabled(device)) {
132             openQuickstep(device);
133 
134             for (int i = 0; i < 10; i++) {
135                 device.swipe(
136                         device.getDisplayWidth() / 2,
137                         device.getDisplayHeight() / 2,
138                         device.getDisplayWidth(),
139                         device.getDisplayHeight() / 2,
140                         5);
141 
142                 BySelector noRecentItemsSelector =
143                         getLauncherOverviewSelector(device).desc("No recent items");
144                 UiObject2 noRecentItems = device.wait(Until.findObject(noRecentItemsSelector), 100);
145 
146                 // If "No recent items"  is displayed, there're no apps to remove
147                 if (noRecentItems != null) {
148                     return;
149                 }
150 
151                 // If "Clear all"  button appears, use it
152                 BySelector clearAllSelector =
153                         By.res(device.getLauncherPackageName(), "clear_all_button");
154                 UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100);
155                 if (clearAllButton != null) {
156                     clearAllButton.click();
157                     return;
158                 }
159             }
160         }
161     }
162 
getLauncherOverviewSelector(UiDevice device)163     private static BySelector getLauncherOverviewSelector(UiDevice device) {
164         return By.res(device.getLauncherPackageName(), "overview_panel");
165     }
166 
longPressRecents(UiDevice device)167     private static void longPressRecents(UiDevice device) {
168         BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps");
169         UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT);
170         assertNotNull("Unable to find 'recent items' button", recentsButton);
171         recentsButton.click(LONG_PRESS_TIMEOUT);
172     }
173 
launchSplitScreen(UiDevice device)174     public static void launchSplitScreen(UiDevice device) {
175         if (isQuickstepEnabled(device)) {
176             // Quickstep enabled
177             openQuickstep(device);
178         } else {
179             try {
180                 device.pressRecentApps();
181             } catch (RemoteException e) {
182                 Log.e(TAG, "launchSplitScreen", e);
183             }
184         }
185 
186         BySelector overviewIconSelector =
187                 By.res(device.getLauncherPackageName(), "icon").clazz(View.class);
188         UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector), FIND_TIMEOUT);
189         assertNotNull("Unable to find app icon in Overview", overviewIcon);
190         overviewIcon.click();
191 
192         BySelector splitScreenButtonSelector = By.text("Split screen");
193         UiObject2 splitScreenButton =
194                 device.wait(Until.findObject(splitScreenButtonSelector), FIND_TIMEOUT);
195         assertNotNull("Unable to find Split screen button in Overview", splitScreenButton);
196         splitScreenButton.click();
197 
198         // Wait for animation to complete.
199         sleep(2000);
200 
201         UiObject2 divider =
202                 device.wait(Until.findObject(getSplitScreenDividerSelector()), FIND_TIMEOUT);
203         assertNotNull("Unable to find Split screen divider", divider);
204     }
205 
getSplitScreenDividerSelector()206     private static BySelector getSplitScreenDividerSelector() {
207         return By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
208     }
209 
exitSplitScreen(UiDevice device)210     public static void exitSplitScreen(UiDevice device) {
211         // Quickstep enabled
212         UiObject2 divider =
213                 device.wait(Until.findObject(getSplitScreenDividerSelector()), FIND_TIMEOUT);
214         assertNotNull("Unable to find Split screen divider", divider);
215 
216         // Drag the split screen divider to the top of the screen
217         int rotation = device.getDisplayRotation();
218         boolean isRotated = rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
219 
220         Point dstPoint;
221         if (isRotated) {
222             dstPoint = new Point(0, device.getDisplayWidth() / 2);
223         } else {
224             dstPoint = new Point(device.getDisplayWidth() / 2, 0);
225         }
226         divider.drag(dstPoint, 400);
227         // Wait for animation to complete.
228         sleep(2000);
229     }
230 
resizeSplitScreen(UiDevice device, Rational windowHeightRatio)231     public static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
232         BySelector dividerSelector = getSplitScreenDividerSelector();
233         UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
234         assertNotNull("Unable to find Split screen divider", divider);
235 
236         int destHeight =
237                 (int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue());
238 
239         // Drag the split screen divider to so that the ratio of top window height and bottom
240         // window height is windowHeightRatio
241         device.drag(
242                 divider.getVisibleBounds().centerX(),
243                 divider.getVisibleBounds().centerY(),
244                 device.getDisplayWidth() / 2,
245                 destHeight,
246                 10);
247         // divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400)
248         device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
249 
250         // Wait for animation to complete.
251         sleep(2000);
252     }
253 
getPipWindowSelector()254     public static BySelector getPipWindowSelector() {
255         return By.res(SYSTEMUI_PACKAGE, "background");
256     }
257 
closePipWindow(UiDevice device)258     public static void closePipWindow(UiDevice device) {
259         UiObject2 pipWindow = device.findObject(getPipWindowSelector());
260         assertNotNull("PIP window not found", pipWindow);
261 
262         pipWindow.click();
263 
264         UiObject2 exitPipObject = device.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"));
265         assertNotNull("PIP window dismiss button not found", pipWindow);
266 
267         exitPipObject.click();
268         // Wait for animation to complete.
269         sleep(2000);
270     }
271 
expandPipWindow(UiDevice device)272     public static void expandPipWindow(UiDevice device) {
273         UiObject2 pipWindow = device.findObject(getPipWindowSelector());
274         assertNotNull("PIP window not found", pipWindow);
275         pipWindow.click();
276         pipWindow.click();
277     }
278 
stopPackage(Context context, String packageName)279     public static void stopPackage(Context context, String packageName) {
280         runShellCommand("am force-stop " + packageName);
281         int packageUid;
282         try {
283             packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
284         } catch (PackageManager.NameNotFoundException e) {
285             return;
286         }
287         while (targetPackageIsRunning(packageUid)) {
288             try {
289                 Thread.sleep(100);
290             } catch (InterruptedException e) {
291                 // ignore
292             }
293         }
294     }
295 
targetPackageIsRunning(int uid)296     private static boolean targetPackageIsRunning(int uid) {
297         final String result =
298                 runShellCommand(
299                         String.format(Locale.getDefault(), "cmd activity get-uid-state %d", uid));
300         return !result.contains("(NONEXISTENT)");
301     }
302 }
303