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