Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 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; |
Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 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 | |
Brett Chabot | 502ec7a | 2019-03-01 14:43:20 -0800 | [diff] [blame] | 45 | import androidx.test.InstrumentationRegistry; |
| 46 | |
Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 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 | |
| 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 | */ |
| 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 | */ |
| 80 | static void setDefaultWait() { |
| 81 | Configurator.getInstance().setWaitForIdleTimeout(10000); |
| 82 | } |
| 83 | |
| 84 | public static boolean isQuickstepEnabled(UiDevice device) { |
| 85 | return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null; |
| 86 | } |
| 87 | |
| 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 | |
| 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 | |
| 148 | private static BySelector getLauncherOverviewSelector(UiDevice device) { |
| 149 | return By.res(device.getLauncherPackageName(), "overview_panel"); |
| 150 | } |
| 151 | |
| 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 | |
| 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); |
Nataniel Borges | 7b3ec11 | 2019-01-30 17:29:13 -0800 | [diff] [blame] | 177 | assertNotNull("Unable to find Split screen button in Overview", splitscreenButton); |
Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | } |