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.launcher3.tapl; 18 19 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 20 import static android.content.pm.PackageManager.DONT_KILL_APP; 21 import static android.content.pm.PackageManager.MATCH_ALL; 22 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; 23 24 import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID; 25 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; 26 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; 27 28 import android.app.ActivityManager; 29 import android.app.Instrumentation; 30 import android.app.UiAutomation; 31 import android.content.ComponentName; 32 import android.content.ContentProviderClient; 33 import android.content.ContentResolver; 34 import android.content.Context; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ProviderInfo; 37 import android.content.res.Resources; 38 import android.graphics.Insets; 39 import android.graphics.Point; 40 import android.graphics.Rect; 41 import android.net.Uri; 42 import android.os.Bundle; 43 import android.os.DeadObjectException; 44 import android.os.Parcelable; 45 import android.os.RemoteException; 46 import android.os.SystemClock; 47 import android.text.TextUtils; 48 import android.util.Log; 49 import android.view.InputDevice; 50 import android.view.MotionEvent; 51 import android.view.ViewConfiguration; 52 import android.view.WindowManager; 53 import android.view.accessibility.AccessibilityEvent; 54 55 import androidx.annotation.NonNull; 56 import androidx.annotation.Nullable; 57 import androidx.test.InstrumentationRegistry; 58 import androidx.test.uiautomator.By; 59 import androidx.test.uiautomator.BySelector; 60 import androidx.test.uiautomator.Configurator; 61 import androidx.test.uiautomator.Direction; 62 import androidx.test.uiautomator.StaleObjectException; 63 import androidx.test.uiautomator.UiDevice; 64 import androidx.test.uiautomator.UiObject2; 65 import androidx.test.uiautomator.Until; 66 67 import com.android.launcher3.testing.shared.ResourceUtils; 68 import com.android.launcher3.testing.shared.TestInformationRequest; 69 import com.android.launcher3.testing.shared.TestProtocol; 70 import com.android.systemui.shared.system.QuickStepContract; 71 72 import org.junit.Assert; 73 74 import java.io.ByteArrayOutputStream; 75 import java.io.IOException; 76 import java.lang.ref.WeakReference; 77 import java.util.ArrayList; 78 import java.util.Arrays; 79 import java.util.Deque; 80 import java.util.LinkedList; 81 import java.util.List; 82 import java.util.Optional; 83 import java.util.concurrent.TimeoutException; 84 import java.util.function.BooleanSupplier; 85 import java.util.function.Consumer; 86 import java.util.function.Function; 87 import java.util.function.Supplier; 88 import java.util.regex.Pattern; 89 import java.util.stream.Collectors; 90 91 /** 92 * The main tapl object. The only object that can be explicitly constructed by the using code. It 93 * produces all other objects. 94 */ 95 public final class LauncherInstrumentation { 96 97 private static final String TAG = "Tapl"; 98 private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15; 99 private static final int GESTURE_STEP_MS = 16; 100 101 static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN"); 102 static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP"); 103 private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL"); 104 static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers"); 105 static final Pattern EVENT_START = Pattern.compile("start:"); 106 107 static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN"); 108 static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP"); 109 static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPatternTIS("ACTION_CANCEL"); 110 111 private static final Pattern EVENT_KEY_BACK_DOWN = 112 getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK"); 113 private static final Pattern EVENT_KEY_BACK_UP = 114 getKeyEventPattern("ACTION_UP", "KEYCODE_BACK"); 115 private static final Pattern EVENT_ON_BACK_INVOKED = Pattern.compile("onBackInvoked"); 116 117 private final String mLauncherPackage; 118 private Boolean mIsLauncher3; 119 private long mTestStartTime = -1; 120 121 // Types for launcher containers that the user is interacting with. "Background" is a 122 // pseudo-container corresponding to inactive launcher covered by another app. 123 public enum ContainerType { 124 WORKSPACE, HOME_ALL_APPS, OVERVIEW, SPLIT_SCREEN_SELECT, WIDGETS, FALLBACK_OVERVIEW, 125 LAUNCHED_APP, TASKBAR_ALL_APPS 126 } 127 128 public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON} 129 130 // Where the gesture happens: outside of Launcher, inside or from inside to outside and 131 // whether the gesture recognition triggers pilfer. 132 public enum GestureScope { 133 OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE, 134 INSIDE_TO_OUTSIDE_WITHOUT_PILFER, 135 INSIDE_TO_OUTSIDE_WITH_KEYCODE, // For gestures that will trigger a keycode from TIS. 136 OUTSIDE_WITH_KEYCODE, 137 } 138 139 /** 140 * Represents a point in the code at which a callback can run. 141 */ 142 public enum CALLBACK_RUN_POINT { 143 CALLBACK_HOLD_BEFORE_DROP 144 } 145 146 private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null; 147 148 // Base class for launcher containers. 149 abstract static class VisibleContainer { 150 protected final LauncherInstrumentation mLauncher; 151 VisibleContainer(LauncherInstrumentation launcher)152 protected VisibleContainer(LauncherInstrumentation launcher) { 153 mLauncher = launcher; 154 launcher.setActiveContainer(this); 155 } 156 getContainerType()157 protected abstract ContainerType getContainerType(); 158 159 /** 160 * Asserts that the launcher is in the mode matching 'this' object. 161 * 162 * @return UI object for the container. 163 */ verifyActiveContainer()164 final UiObject2 verifyActiveContainer() { 165 mLauncher.assertTrue("Attempt to use a stale container", 166 this == sActiveContainer.get()); 167 return mLauncher.verifyContainerType(getContainerType()); 168 } 169 } 170 171 public interface Closable extends AutoCloseable { close()172 void close(); 173 } 174 175 private static final String WORKSPACE_RES_ID = "workspace"; 176 private static final String APPS_RES_ID = "apps_view"; 177 private static final String OVERVIEW_RES_ID = "overview_panel"; 178 private static final String WIDGETS_RES_ID = "primary_widgets_list_view"; 179 private static final String CONTEXT_MENU_RES_ID = "popup_container"; 180 static final String TASKBAR_RES_ID = "taskbar_view"; 181 private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder"; 182 public static final int WAIT_TIME_MS = 30000; 183 static final long DEFAULT_POLL_INTERVAL = 1000; 184 private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; 185 private static final String ANDROID_PACKAGE = "android"; 186 187 private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null); 188 189 private final UiDevice mDevice; 190 private final Instrumentation mInstrumentation; 191 private Integer mExpectedRotation = null; 192 private final Uri mTestProviderUri; 193 private final Deque<String> mDiagnosticContext = new LinkedList<>(); 194 private Function<Long, String> mSystemHealthSupplier; 195 196 private boolean mIgnoreTaskbarVisibility = false; 197 198 private Consumer<ContainerType> mOnSettledStateAction; 199 200 private LogEventChecker mEventChecker; 201 202 private boolean mCheckEventsForSuccessfulGestures = false; 203 private Runnable mOnLauncherCrashed; 204 getTouchEventPattern(String prefix, String action)205 private static Pattern getTouchEventPattern(String prefix, String action) { 206 // The pattern includes checks that we don't get a multi-touch events or other surprises. 207 return Pattern.compile( 208 prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0" 209 + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1"); 210 } 211 getTouchEventPattern(String action)212 private static Pattern getTouchEventPattern(String action) { 213 return getTouchEventPattern("Touch event", action); 214 } 215 getTouchEventPatternTIS(String action)216 private static Pattern getTouchEventPatternTIS(String action) { 217 return getTouchEventPattern("TouchInteractionService.onInputEvent", action); 218 } 219 getKeyEventPattern(String action, String keyCode)220 private static Pattern getKeyEventPattern(String action, String keyCode) { 221 return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode); 222 } 223 224 /** 225 * Constructs the root of TAPL hierarchy. You get all other objects from it. 226 */ LauncherInstrumentation()227 public LauncherInstrumentation() { 228 this(InstrumentationRegistry.getInstrumentation()); 229 } 230 231 /** 232 * Constructs the root of TAPL hierarchy. You get all other objects from it. 233 * Deprecated: use the constructor without parameters instead. 234 */ 235 @Deprecated LauncherInstrumentation(Instrumentation instrumentation)236 public LauncherInstrumentation(Instrumentation instrumentation) { 237 mInstrumentation = instrumentation; 238 mDevice = UiDevice.getInstance(instrumentation); 239 240 // Launcher should run in test harness so that custom accessibility protocol between 241 // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call 242 // into Launcher. 243 assertTrue("Device must run in a test harness. " 244 + "Run `adb shell setprop ro.test_harness 1` to enable it.", 245 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness()); 246 247 final String testPackage = getContext().getPackageName(); 248 final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); 249 250 // Launcher package. As during inproc tests the tested launcher may not be selected as the 251 // current launcher, choosing target package for inproc. For out-of-proc, use the installed 252 // launcher package. 253 mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation() 254 ? getLauncherPackageName() 255 : targetPackage; 256 257 String testProviderAuthority = mLauncherPackage + ".TestInfo"; 258 mTestProviderUri = new Uri.Builder() 259 .scheme(ContentResolver.SCHEME_CONTENT) 260 .authority(testProviderAuthority) 261 .build(); 262 263 mInstrumentation.getUiAutomation().grantRuntimePermission( 264 testPackage, "android.permission.WRITE_SECURE_SETTINGS"); 265 266 PackageManager pm = getContext().getPackageManager(); 267 ProviderInfo pi = pm.resolveContentProvider( 268 testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS); 269 assertNotNull("Cannot find content provider for " + testProviderAuthority, pi); 270 ComponentName cn = new ComponentName(pi.packageName, pi.name); 271 272 if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) { 273 if (TestHelpers.isInLauncherProcess()) { 274 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP); 275 // b/195031154 276 SystemClock.sleep(5000); 277 } else { 278 try { 279 final int userId = getContext().getUserId(); 280 final String launcherPidCommand = "pidof " + pi.packageName; 281 final String initialPid = mDevice.executeShellCommand(launcherPidCommand) 282 .replaceAll("\\s", ""); 283 mDevice.executeShellCommand( 284 "pm enable --user " + userId + " " + cn.flattenToString()); 285 // Wait for Launcher restart after enabling test provider. 286 for (int i = 0; i < 100; ++i) { 287 final String currentPid = mDevice.executeShellCommand(launcherPidCommand) 288 .replaceAll("\\s", ""); 289 if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break; 290 if (i == 99) fail("Launcher didn't restart after enabling test provider"); 291 SystemClock.sleep(100); 292 } 293 } catch (IOException e) { 294 fail(e.toString()); 295 } 296 } 297 } 298 } 299 300 /** 301 * Gradle only supports out of process instrumentation. The test package is automatically 302 * generated by appending `.test` to the target package. 303 */ isGradleInstrumentation()304 private boolean isGradleInstrumentation() { 305 final String testPackage = getContext().getPackageName(); 306 final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); 307 final String testSuffix = ".test"; 308 309 return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length() 310 && testPackage.substring(0, testPackage.length() - testSuffix.length()) 311 .equals(targetPackage); 312 } 313 enableCheckEventsForSuccessfulGestures()314 public void enableCheckEventsForSuccessfulGestures() { 315 mCheckEventsForSuccessfulGestures = true; 316 } 317 setOnLauncherCrashed(Runnable onLauncherCrashed)318 public void setOnLauncherCrashed(Runnable onLauncherCrashed) { 319 mOnLauncherCrashed = onLauncherCrashed; 320 } 321 getContext()322 Context getContext() { 323 return mInstrumentation.getContext(); 324 } 325 getTestInfo(String request)326 Bundle getTestInfo(String request) { 327 return getTestInfo(request, /*arg=*/ null); 328 } 329 getTestInfo(String request, String arg)330 Bundle getTestInfo(String request, String arg) { 331 return getTestInfo(request, arg, null); 332 } 333 getTestInfo(String request, String arg, Bundle extra)334 Bundle getTestInfo(String request, String arg, Bundle extra) { 335 try (ContentProviderClient client = getContext().getContentResolver() 336 .acquireContentProviderClient(mTestProviderUri)) { 337 return client.call(request, arg, extra); 338 } catch (DeadObjectException e) { 339 fail("Launcher crashed"); 340 return null; 341 } catch (RemoteException e) { 342 throw new RuntimeException(e); 343 } 344 } 345 getTestInfo(TestInformationRequest request)346 Bundle getTestInfo(TestInformationRequest request) { 347 Bundle extra = new Bundle(); 348 extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request); 349 return getTestInfo(request.getRequestName(), null, extra); 350 } 351 getTargetInsets()352 Insets getTargetInsets() { 353 return getTestInfo(TestProtocol.REQUEST_TARGET_INSETS) 354 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); 355 } 356 getWindowInsets()357 Insets getWindowInsets() { 358 return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS) 359 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); 360 } 361 isTablet()362 public boolean isTablet() { 363 return getTestInfo(TestProtocol.REQUEST_IS_TABLET) 364 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 365 } 366 isTwoPanels()367 public boolean isTwoPanels() { 368 return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS) 369 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 370 } 371 getFocusedTaskHeightForTablet()372 int getFocusedTaskHeightForTablet() { 373 return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt( 374 TestProtocol.TEST_INFO_RESPONSE_FIELD); 375 } 376 getGridTaskRectForTablet()377 Rect getGridTaskRectForTablet() { 378 return ((Rect) getTestInfo(TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET) 379 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD)); 380 } 381 getOverviewPageSpacing()382 int getOverviewPageSpacing() { 383 return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_PAGE_SPACING) 384 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 385 } 386 getExactScreenCenterX()387 float getExactScreenCenterX() { 388 return getRealDisplaySize().x / 2f; 389 } 390 setEnableRotation(boolean on)391 public void setEnableRotation(boolean on) { 392 getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on)); 393 } 394 setEnableSuggestion(boolean enableSuggestion)395 public void setEnableSuggestion(boolean enableSuggestion) { 396 getTestInfo(TestProtocol.REQUEST_ENABLE_SUGGESTION, Boolean.toString(enableSuggestion)); 397 } 398 hadNontestEvents()399 public boolean hadNontestEvents() { 400 return getTestInfo(TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS) 401 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 402 } 403 setActiveContainer(VisibleContainer container)404 void setActiveContainer(VisibleContainer container) { 405 sActiveContainer = new WeakReference<>(container); 406 } 407 408 /** 409 * Sets the accesibility interactive timeout to be effectively indefinite (UI using this 410 * accesibility timeout will not automatically dismiss if true). 411 */ setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout)412 void setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout) { 413 final String cmd = indefiniteTimeout 414 ? "settings put secure accessibility_interactive_ui_timeout_ms 10000" 415 : "settings delete secure accessibility_interactive_ui_timeout_ms"; 416 logShellCommand(cmd); 417 } 418 getNavigationModel()419 public NavigationModel getNavigationModel() { 420 final Context baseContext = mInstrumentation.getTargetContext(); 421 try { 422 // Workaround, use constructed context because both the instrumentation context and the 423 // app context are not constructed with resources that take overlays into account 424 final Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0); 425 for (int i = 0; i < 100; ++i) { 426 final int currentInteractionMode = getCurrentInteractionMode(ctx); 427 final NavigationModel model = getNavigationModel(currentInteractionMode); 428 log("Interaction mode = " + currentInteractionMode + " (" + model + ")"); 429 if (model != null) return model; 430 Thread.sleep(100); 431 } 432 fail("Can't detect navigation mode"); 433 } catch (Exception e) { 434 fail(e.toString()); 435 } 436 return NavigationModel.THREE_BUTTON; 437 } 438 getNavigationModel(int currentInteractionMode)439 public static NavigationModel getNavigationModel(int currentInteractionMode) { 440 if (QuickStepContract.isGesturalMode(currentInteractionMode)) { 441 return NavigationModel.ZERO_BUTTON; 442 } else if (QuickStepContract.isLegacyMode(currentInteractionMode)) { 443 return NavigationModel.THREE_BUTTON; 444 } 445 return null; 446 } 447 log(String message)448 static void log(String message) { 449 Log.d(TAG, message); 450 } 451 addContextLayer(String piece)452 Closable addContextLayer(String piece) { 453 mDiagnosticContext.addLast(piece); 454 log("Entering context: " + piece); 455 return () -> { 456 log("Leaving context: " + piece); 457 mDiagnosticContext.removeLast(); 458 }; 459 } 460 dumpViewHierarchy()461 public void dumpViewHierarchy() { 462 final ByteArrayOutputStream stream = new ByteArrayOutputStream(); 463 try { 464 mDevice.dumpWindowHierarchy(stream); 465 stream.flush(); 466 stream.close(); 467 for (String line : stream.toString().split("\\r?\\n")) { 468 Log.e(TAG, line.trim()); 469 } 470 } catch (IOException e) { 471 Log.e(TAG, "error dumping XML to logcat", e); 472 } 473 } 474 getSystemAnomalyMessage( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews)475 public String getSystemAnomalyMessage( 476 boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) { 477 try { 478 { 479 final StringBuilder sb = new StringBuilder(); 480 481 UiObject2 object = 482 mDevice.findObject(By.res("android", "alertTitle").pkg("android")); 483 if (object != null) { 484 sb.append("TITLE: ").append(object.getText()); 485 } 486 487 object = mDevice.findObject(By.res("android", "message").pkg("android")); 488 if (object != null) { 489 sb.append(" PACKAGE: ").append(object.getApplicationPackage()) 490 .append(" MESSAGE: ").append(object.getText()); 491 } 492 493 if (sb.length() != 0) { 494 return "System alert popup is visible: " + sb; 495 } 496 } 497 498 if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked"; 499 500 if (!ignoreOnlySystemUiViews) { 501 final String visibleApps = mDevice.findObjects(getAnyObjectSelector()) 502 .stream() 503 .map(LauncherInstrumentation::getApplicationPackageSafe) 504 .distinct() 505 .filter(pkg -> pkg != null) 506 .collect(Collectors.joining(",")); 507 if (SYSTEMUI_PACKAGE.equals(visibleApps)) return "Only System UI views are visible"; 508 } 509 if (!ignoreNavmodeChangeStates) { 510 if (!mDevice.wait(Until.hasObject(getAnyObjectSelector()), WAIT_TIME_MS)) { 511 return "Screen is empty"; 512 } 513 } 514 515 final String navigationModeError = getNavigationModeMismatchError(true); 516 if (navigationModeError != null) return navigationModeError; 517 } catch (Throwable e) { 518 Log.w(TAG, "getSystemAnomalyMessage failed", e); 519 } 520 521 return null; 522 } 523 checkForAnomaly()524 private void checkForAnomaly() { 525 checkForAnomaly(false, false); 526 } 527 checkForAnomaly( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews)528 public void checkForAnomaly( 529 boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) { 530 final String systemAnomalyMessage = 531 getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews); 532 if (systemAnomalyMessage != null) { 533 Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( 534 "http://go/tapl : Tests are broken by a non-Launcher system error: " 535 + systemAnomalyMessage, false))); 536 } 537 } 538 getVisiblePackages()539 private String getVisiblePackages() { 540 final String apps = mDevice.findObjects(getAnyObjectSelector()) 541 .stream() 542 .map(LauncherInstrumentation::getApplicationPackageSafe) 543 .distinct() 544 .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg)) 545 .collect(Collectors.joining(", ")); 546 return !apps.isEmpty() 547 ? "active app: " + apps 548 : "the test doesn't see views from any app, including Launcher"; 549 } 550 getApplicationPackageSafe(UiObject2 object)551 private static String getApplicationPackageSafe(UiObject2 object) { 552 try { 553 return object.getApplicationPackage(); 554 } catch (StaleObjectException e) { 555 // We are looking at all object in the system; external ones can suddenly go away. 556 return null; 557 } 558 } 559 getVisibleStateMessage()560 private String getVisibleStateMessage() { 561 if (hasLauncherObject(CONTEXT_MENU_RES_ID)) return "Context Menu"; 562 if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets"; 563 if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview"; 564 if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace"; 565 if (hasLauncherObject(APPS_RES_ID)) return "AllApps"; 566 return "LaunchedApp (" + getVisiblePackages() + ")"; 567 } 568 setSystemHealthSupplier(Function<Long, String> supplier)569 public void setSystemHealthSupplier(Function<Long, String> supplier) { 570 this.mSystemHealthSupplier = supplier; 571 } 572 setOnSettledStateAction(Consumer<ContainerType> onSettledStateAction)573 public void setOnSettledStateAction(Consumer<ContainerType> onSettledStateAction) { 574 mOnSettledStateAction = onSettledStateAction; 575 } 576 onTestStart()577 public void onTestStart() { 578 mTestStartTime = System.currentTimeMillis(); 579 } 580 onTestFinish()581 public void onTestFinish() { 582 mTestStartTime = -1; 583 } 584 formatSystemHealthMessage(String message)585 private String formatSystemHealthMessage(String message) { 586 final String testPackage = getContext().getPackageName(); 587 588 mInstrumentation.getUiAutomation().grantRuntimePermission( 589 testPackage, "android.permission.READ_LOGS"); 590 mInstrumentation.getUiAutomation().grantRuntimePermission( 591 testPackage, "android.permission.PACKAGE_USAGE_STATS"); 592 593 if (mTestStartTime > 0) { 594 final String systemHealth = mSystemHealthSupplier != null 595 ? mSystemHealthSupplier.apply(mTestStartTime) 596 : TestHelpers.getSystemHealthMessage(getContext(), mTestStartTime); 597 598 if (systemHealth != null) { 599 message += ";\nPerhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n" 600 + systemHealth + "\n>>>>>>>>>>>>>>>>>>"; 601 } 602 } 603 Log.d(TAG, "About to throw the error: " + message, new Exception()); 604 return message; 605 } 606 formatErrorWithEvents(String message, boolean checkEvents)607 private String formatErrorWithEvents(String message, boolean checkEvents) { 608 if (mEventChecker != null) { 609 final LogEventChecker eventChecker = mEventChecker; 610 mEventChecker = null; 611 if (checkEvents) { 612 final String eventMismatch = eventChecker.verify(0, false); 613 if (eventMismatch != null) { 614 message = message + ";\n" + eventMismatch; 615 } 616 } else { 617 eventChecker.finishNoWait(); 618 } 619 } 620 621 dumpDiagnostics(message); 622 623 log("Hierarchy dump for: " + message); 624 dumpViewHierarchy(); 625 626 return message; 627 } 628 dumpDiagnostics(String message)629 private void dumpDiagnostics(String message) { 630 log("Diagnostics for failure: " + message); 631 log("Input:"); 632 logShellCommand("dumpsys input"); 633 log("TIS:"); 634 logShellCommand("dumpsys activity service TouchInteractionService"); 635 } 636 logShellCommand(String command)637 private void logShellCommand(String command) { 638 try { 639 for (String line : mDevice.executeShellCommand(command).split("\\n")) { 640 SystemClock.sleep(10); 641 log(line); 642 } 643 } catch (IOException e) { 644 log("Failed to execute " + command); 645 } 646 } 647 fail(String message)648 void fail(String message) { 649 checkForAnomaly(); 650 Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( 651 "http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription() 652 + "; now visible state is " + getVisibleStateMessage(), true))); 653 } 654 getContextDescription()655 private String getContextDescription() { 656 return mDiagnosticContext.isEmpty() 657 ? "(no context)" : String.join(", ", mDiagnosticContext); 658 } 659 assertTrue(String message, boolean condition)660 void assertTrue(String message, boolean condition) { 661 if (!condition) { 662 fail(message); 663 } 664 } 665 assertNotNull(String message, Object object)666 void assertNotNull(String message, Object object) { 667 assertTrue(message, object != null); 668 } 669 failEquals(String message, Object actual)670 private void failEquals(String message, Object actual) { 671 fail(message + ". " + "Actual: " + actual); 672 } 673 assertEquals(String message, int expected, int actual)674 private void assertEquals(String message, int expected, int actual) { 675 if (expected != actual) { 676 fail(message + " expected: " + expected + " but was: " + actual); 677 } 678 } 679 assertEquals(String message, String expected, String actual)680 void assertEquals(String message, String expected, String actual) { 681 if (!TextUtils.equals(expected, actual)) { 682 fail(message + " expected: '" + expected + "' but was: '" + actual + "'"); 683 } 684 } 685 assertEquals(String message, long expected, long actual)686 void assertEquals(String message, long expected, long actual) { 687 if (expected != actual) { 688 fail(message + " expected: " + expected + " but was: " + actual); 689 } 690 } 691 assertNotEquals(String message, int unexpected, int actual)692 void assertNotEquals(String message, int unexpected, int actual) { 693 if (unexpected == actual) { 694 failEquals(message, actual); 695 } 696 } 697 698 /** 699 * Whether to ignore verifying the task bar visibility during instrumenting. 700 * 701 * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly 702 * verifying the task bar visibility with 703 * {@link VisibleContainer#verifyActiveContainer}. 704 * {@code false} otherwise. 705 */ setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility)706 public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) { 707 mIgnoreTaskbarVisibility = ignoreTaskbarVisibility; 708 } 709 710 /** 711 * Sets expected rotation. 712 * TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one. 713 * Null parameter disables checks. The initial state is "no checks". 714 */ setExpectedRotation(Integer expectedRotation)715 public void setExpectedRotation(Integer expectedRotation) { 716 mExpectedRotation = expectedRotation; 717 } 718 getNavigationModeMismatchError(boolean waitForCorrectState)719 public String getNavigationModeMismatchError(boolean waitForCorrectState) { 720 final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0; 721 final NavigationModel navigationModel = getNavigationModel(); 722 String resPackage = getNavigationButtonResPackage(); 723 if (navigationModel == NavigationModel.THREE_BUTTON) { 724 if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) { 725 return "Recents button not present in 3-button mode"; 726 } 727 } else { 728 if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) { 729 return "Recents button is present in non-3-button mode"; 730 } 731 } 732 733 if (navigationModel == NavigationModel.ZERO_BUTTON) { 734 if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) { 735 return "Home button is present in gestural mode"; 736 } 737 } else { 738 if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) { 739 return "Home button not present in non-gestural mode"; 740 } 741 } 742 return null; 743 } 744 getNavigationButtonResPackage()745 private String getNavigationButtonResPackage() { 746 return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE; 747 } 748 verifyContainerType(ContainerType containerType)749 private UiObject2 verifyContainerType(ContainerType containerType) { 750 waitForLauncherInitialized(); 751 752 if (mExpectedRotation != null) { 753 assertEquals("Unexpected display rotation", 754 mExpectedRotation, mDevice.getDisplayRotation()); 755 } 756 757 final String error = getNavigationModeMismatchError(true); 758 assertTrue(error, error == null); 759 760 log("verifyContainerType: " + containerType); 761 762 final UiObject2 container = verifyVisibleObjects(containerType); 763 764 if (mOnSettledStateAction != null) mOnSettledStateAction.accept(containerType); 765 766 return container; 767 } 768 verifyVisibleObjects(ContainerType containerType)769 private UiObject2 verifyVisibleObjects(ContainerType containerType) { 770 try (Closable c = addContextLayer( 771 "but the current state is not " + containerType.name())) { 772 switch (containerType) { 773 case WORKSPACE: { 774 waitUntilLauncherObjectGone(APPS_RES_ID); 775 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 776 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 777 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 778 779 if (is3PLauncher() && isTablet()) { 780 waitForSystemLauncherObject(TASKBAR_RES_ID); 781 } else { 782 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 783 } 784 785 return waitForLauncherObject(WORKSPACE_RES_ID); 786 } 787 case WIDGETS: { 788 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 789 waitUntilLauncherObjectGone(APPS_RES_ID); 790 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 791 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 792 793 if (is3PLauncher() && isTablet()) { 794 waitForSystemLauncherObject(TASKBAR_RES_ID); 795 } else { 796 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 797 } 798 799 return waitForLauncherObject(WIDGETS_RES_ID); 800 } 801 case TASKBAR_ALL_APPS: { 802 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 803 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 804 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 805 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 806 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 807 808 return waitForLauncherObject(APPS_RES_ID); 809 } 810 case HOME_ALL_APPS: { 811 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 812 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 813 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 814 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 815 816 if (is3PLauncher() && isTablet()) { 817 waitForSystemLauncherObject(TASKBAR_RES_ID); 818 } else { 819 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 820 } 821 822 return waitForLauncherObject(APPS_RES_ID); 823 } 824 case OVERVIEW: 825 case FALLBACK_OVERVIEW: { 826 waitUntilLauncherObjectGone(APPS_RES_ID); 827 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 828 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 829 if (isTablet()) { 830 waitForSystemLauncherObject(TASKBAR_RES_ID); 831 } else { 832 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 833 } 834 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 835 836 return waitForSystemLauncherObject(OVERVIEW_RES_ID); 837 } 838 case SPLIT_SCREEN_SELECT: { 839 waitUntilLauncherObjectGone(APPS_RES_ID); 840 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 841 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 842 if (isTablet()) { 843 waitForSystemLauncherObject(TASKBAR_RES_ID); 844 } else { 845 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 846 } 847 848 waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID); 849 return waitForSystemLauncherObject(OVERVIEW_RES_ID); 850 } 851 case LAUNCHED_APP: { 852 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 853 waitUntilLauncherObjectGone(APPS_RES_ID); 854 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 855 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 856 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 857 858 if (mIgnoreTaskbarVisibility) { 859 return null; 860 } 861 862 if (isTablet()) { 863 waitForSystemLauncherObject(TASKBAR_RES_ID); 864 } else { 865 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 866 } 867 return null; 868 } 869 default: 870 fail("Invalid state: " + containerType); 871 return null; 872 } 873 } 874 } 875 waitForModelQueueCleared()876 public void waitForModelQueueCleared() { 877 getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED); 878 } 879 waitForLauncherInitialized()880 public void waitForLauncherInitialized() { 881 for (int i = 0; i < 100; ++i) { 882 if (getTestInfo( 883 TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED). 884 getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) { 885 return; 886 } 887 SystemClock.sleep(100); 888 } 889 checkForAnomaly(); 890 fail("Launcher didn't initialize"); 891 } 892 executeAndWaitForLauncherEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)893 Parcelable executeAndWaitForLauncherEvent(Runnable command, 894 UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, 895 String actionName) { 896 return executeAndWaitForEvent( 897 command, 898 e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e), 899 message, actionName); 900 } 901 executeAndWaitForEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)902 Parcelable executeAndWaitForEvent(Runnable command, 903 UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, 904 String actionName) { 905 try (LauncherInstrumentation.Closable c = addContextLayer(actionName)) { 906 try { 907 final AccessibilityEvent event = 908 mInstrumentation.getUiAutomation().executeAndWaitForEvent( 909 command, eventFilter, WAIT_TIME_MS); 910 assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event); 911 final Parcelable parcelableData = event.getParcelableData(); 912 event.recycle(); 913 return parcelableData; 914 } catch (TimeoutException e) { 915 fail(message.get()); 916 return null; 917 } 918 } 919 } 920 921 /** 922 * Get the resource ID of visible floating view. 923 */ getFloatingResId()924 private Optional<String> getFloatingResId() { 925 if (hasLauncherObject(CONTEXT_MENU_RES_ID)) { 926 return Optional.of(CONTEXT_MENU_RES_ID); 927 } 928 if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) { 929 return Optional.of(FOLDER_CONTENT_RES_ID); 930 } 931 return Optional.empty(); 932 } 933 934 /** 935 * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content. 936 */ swipeUpToCloseFloatingView(boolean gestureStartFromLauncher)937 private void swipeUpToCloseFloatingView(boolean gestureStartFromLauncher) { 938 final Point displaySize = getRealDisplaySize(); 939 940 final Optional<String> floatingRes = getFloatingResId(); 941 942 if (!floatingRes.isPresent()) { 943 return; 944 } 945 946 GestureScope gestureScope = gestureStartFromLauncher 947 // Without the navigation bar layer, the gesture scope on tablets remains inside the 948 // launcher process. 949 ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE) 950 : GestureScope.OUTSIDE_WITH_PILFER; 951 linearGesture( 952 displaySize.x / 2, displaySize.y - 1, 953 displaySize.x / 2, 0, 954 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 955 false, gestureScope); 956 957 try (LauncherInstrumentation.Closable c1 = addContextLayer( 958 String.format("Swiped up from floating view %s to home", floatingRes.get()))) { 959 waitUntilLauncherObjectGone(floatingRes.get()); 960 waitForLauncherObject(getAnyObjectSelector()); 961 } 962 } 963 964 /** 965 * @return the Workspace object. 966 * @deprecated use goHome(). 967 * Presses nav bar home button. 968 */ 969 @Deprecated pressHome()970 public Workspace pressHome() { 971 return goHome(); 972 } 973 974 /** 975 * Goes to home by swiping up in zero-button mode or pressing Home button. 976 * Calling it after another TAPL call is safe because all TAPL methods wait for the animations 977 * to finish. 978 * When calling it after a non-TAPL method, make sure that all animations have already 979 * completed, otherwise it may detect the current state (for example "Application" or "Home") 980 * incorrectly. 981 * The method expects either app or Launcher to be active when it's called. Other states, such 982 * as visible notification shade are not supported. 983 * 984 * @return the Workspace object. 985 */ goHome()986 public Workspace goHome() { 987 try (LauncherInstrumentation.Closable e = eventsCheck(); 988 LauncherInstrumentation.Closable c = addContextLayer("want to switch to home")) { 989 waitForLauncherInitialized(); 990 // Click home, then wait for any accessibility event, then wait until accessibility 991 // events stop. 992 // We need waiting for any accessibility event generated after pressing Home because 993 // otherwise waitForIdle may return immediately in case when there was a big enough 994 // pause in accessibility events prior to pressing Home. 995 final String action; 996 if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { 997 checkForAnomaly(false, true); 998 999 final Point displaySize = getRealDisplaySize(); 1000 1001 boolean gestureStartFromLauncher = 1002 isTablet() ? !isLauncher3() : isLauncherVisible(); 1003 1004 // CLose floating views before going back to home. 1005 swipeUpToCloseFloatingView(gestureStartFromLauncher); 1006 1007 if (hasLauncherObject(WORKSPACE_RES_ID)) { 1008 log(action = "already at home"); 1009 } else { 1010 log("Hierarchy before swiping up to home:"); 1011 dumpViewHierarchy(); 1012 action = "swiping up to home"; 1013 1014 swipeToState( 1015 displaySize.x / 2, displaySize.y - 1, 1016 displaySize.x / 2, displaySize.y / 2, 1017 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL, 1018 gestureStartFromLauncher ? GestureScope.INSIDE_TO_OUTSIDE 1019 : GestureScope.OUTSIDE_WITH_PILFER); 1020 } 1021 } else { 1022 log("Hierarchy before clicking home:"); 1023 dumpViewHierarchy(); 1024 action = "clicking home button"; 1025 if (isTablet()) { 1026 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN); 1027 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP); 1028 } 1029 1030 runToState( 1031 waitForNavigationUiObject("home")::click, 1032 NORMAL_STATE_ORDINAL, 1033 !hasLauncherObject(WORKSPACE_RES_ID) 1034 && (hasLauncherObject(APPS_RES_ID) 1035 || hasSystemLauncherObject(OVERVIEW_RES_ID)), 1036 action); 1037 } 1038 try (LauncherInstrumentation.Closable c1 = addContextLayer( 1039 "performed action to switch to Home - " + action)) { 1040 return getWorkspace(); 1041 } 1042 } 1043 } 1044 1045 /** 1046 * Press navbar back button or swipe back if in gesture navigation mode. 1047 */ pressBack()1048 public void pressBack() { 1049 try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) { 1050 waitForLauncherInitialized(); 1051 final boolean launcherVisible = 1052 isTablet() ? isLauncherContainerVisible() : isLauncherVisible(); 1053 if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { 1054 final Point displaySize = getRealDisplaySize(); 1055 final GestureScope gestureScope = 1056 launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE 1057 : GestureScope.OUTSIDE_WITH_KEYCODE; 1058 // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the 1059 // issue is solved. 1060 linearGesture(0, displaySize.y / 4, displaySize.x / 2, displaySize.y / 4, 1061 10, false, gestureScope); 1062 } else { 1063 waitForNavigationUiObject("back").click(); 1064 if (isTablet()) { 1065 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN); 1066 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP); 1067 } 1068 } 1069 if (launcherVisible) { 1070 if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) { 1071 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED); 1072 } else { 1073 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN); 1074 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP); 1075 } 1076 } 1077 } 1078 } 1079 getAnyObjectSelector()1080 private static BySelector getAnyObjectSelector() { 1081 return By.textStartsWith(""); 1082 } 1083 isLauncherVisible()1084 boolean isLauncherVisible() { 1085 mDevice.waitForIdle(); 1086 return hasLauncherObject(getAnyObjectSelector()); 1087 } 1088 isLauncherContainerVisible()1089 boolean isLauncherContainerVisible() { 1090 final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID}; 1091 return Arrays.stream(containerResources).anyMatch( 1092 r -> r.equals(OVERVIEW_RES_ID) ? hasSystemLauncherObject(r) : hasLauncherObject(r)); 1093 } 1094 1095 /** 1096 * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the 1097 * launcher is not in that state. 1098 * 1099 * @return Workspace object. 1100 */ 1101 @NonNull getWorkspace()1102 public Workspace getWorkspace() { 1103 try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) { 1104 return new Workspace(this); 1105 } 1106 } 1107 1108 /** 1109 * Gets the LaunchedApp object if another app is active. Fails if the launcher is not in that 1110 * state. 1111 * 1112 * @return LaunchedApp object. 1113 */ 1114 @NonNull getLaunchedAppState()1115 public LaunchedAppState getLaunchedAppState() { 1116 return new LaunchedAppState(this); 1117 } 1118 1119 /** 1120 * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is 1121 * not in that state. 1122 * 1123 * @return Widgets object. 1124 */ 1125 @NonNull getAllWidgets()1126 public Widgets getAllWidgets() { 1127 try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) { 1128 return new Widgets(this); 1129 } 1130 } 1131 1132 @NonNull getAddToHomeScreenPrompt()1133 public AddToHomeScreenPrompt getAddToHomeScreenPrompt() { 1134 try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) { 1135 return new AddToHomeScreenPrompt(this); 1136 } 1137 } 1138 1139 /** 1140 * Gets the Overview object if the current state is showing the overview panel. Fails if the 1141 * launcher is not in that state. 1142 * 1143 * @return Overview object. 1144 */ 1145 @NonNull getOverview()1146 public Overview getOverview() { 1147 try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) { 1148 return new Overview(this); 1149 } 1150 } 1151 1152 /** 1153 * Gets the homescreen All Apps object if the current state is showing the all apps panel opened 1154 * by swiping from workspace. Fails if the launcher is not in that state. Please don't call this 1155 * method if App Apps was opened by swiping up from Overview, as it won't fail and will return 1156 * an incorrect object. 1157 * 1158 * @return Home All Apps object. 1159 */ 1160 @NonNull getAllApps()1161 public HomeAllApps getAllApps() { 1162 try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) { 1163 return new HomeAllApps(this); 1164 } 1165 } 1166 waitUntilLauncherObjectGone(String resId)1167 void waitUntilLauncherObjectGone(String resId) { 1168 waitUntilGoneBySelector(getLauncherObjectSelector(resId)); 1169 } 1170 waitUntilOverviewObjectGone(String resId)1171 void waitUntilOverviewObjectGone(String resId) { 1172 waitUntilGoneBySelector(getOverviewObjectSelector(resId)); 1173 } 1174 waitUntilSystemLauncherObjectGone(String resId)1175 void waitUntilSystemLauncherObjectGone(String resId) { 1176 if (is3PLauncher()) { 1177 waitUntilOverviewObjectGone(resId); 1178 } else { 1179 waitUntilLauncherObjectGone(resId); 1180 } 1181 } 1182 waitUntilLauncherObjectGone(BySelector selector)1183 void waitUntilLauncherObjectGone(BySelector selector) { 1184 waitUntilGoneBySelector(makeLauncherSelector(selector)); 1185 } 1186 waitUntilGoneBySelector(BySelector launcherSelector)1187 private void waitUntilGoneBySelector(BySelector launcherSelector) { 1188 assertTrue("Unexpected launcher object visible: " + launcherSelector, 1189 mDevice.wait(Until.gone(launcherSelector), 1190 WAIT_TIME_MS)); 1191 } 1192 hasSystemUiObject(String resId)1193 private boolean hasSystemUiObject(String resId) { 1194 return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId)); 1195 } 1196 1197 @NonNull waitForSystemUiObject(String resId)1198 UiObject2 waitForSystemUiObject(String resId) { 1199 final UiObject2 object = mDevice.wait( 1200 Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS); 1201 assertNotNull("Can't find a systemui object with id: " + resId, object); 1202 return object; 1203 } 1204 1205 @NonNull waitForSystemUiObject(BySelector selector)1206 UiObject2 waitForSystemUiObject(BySelector selector) { 1207 final UiObject2 object = TestHelpers.wait( 1208 Until.findObject(selector), WAIT_TIME_MS); 1209 assertNotNull("Can't find a systemui object with selector: " + selector, object); 1210 return object; 1211 } 1212 1213 @NonNull waitForNavigationUiObject(String resId)1214 UiObject2 waitForNavigationUiObject(String resId) { 1215 String resPackage = getNavigationButtonResPackage(); 1216 final UiObject2 object = mDevice.wait( 1217 Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS); 1218 assertNotNull("Can't find a navigation UI object with id: " + resId, object); 1219 return object; 1220 } 1221 1222 @Nullable findObjectInContainer(UiObject2 container, String resName)1223 UiObject2 findObjectInContainer(UiObject2 container, String resName) { 1224 try { 1225 return container.findObject(getLauncherObjectSelector(resName)); 1226 } catch (StaleObjectException e) { 1227 fail("The container disappeared from screen"); 1228 return null; 1229 } 1230 } 1231 1232 @Nullable findObjectInContainer(UiObject2 container, BySelector selector)1233 UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) { 1234 try { 1235 return container.findObject(selector); 1236 } catch (StaleObjectException e) { 1237 fail("The container disappeared from screen"); 1238 return null; 1239 } 1240 } 1241 1242 @NonNull getObjectsInContainer(UiObject2 container, String resName)1243 List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) { 1244 try { 1245 return container.findObjects(getLauncherObjectSelector(resName)); 1246 } catch (StaleObjectException e) { 1247 fail("The container disappeared from screen"); 1248 return null; 1249 } 1250 } 1251 1252 @NonNull waitForObjectInContainer(UiObject2 container, String resName)1253 UiObject2 waitForObjectInContainer(UiObject2 container, String resName) { 1254 try { 1255 final UiObject2 object = container.wait( 1256 Until.findObject(getLauncherObjectSelector(resName)), 1257 WAIT_TIME_MS); 1258 assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: " 1259 + container.getResourceName(), object); 1260 return object; 1261 } catch (StaleObjectException e) { 1262 fail("The container disappeared from screen"); 1263 return null; 1264 } 1265 } 1266 waitForObjectEnabled(UiObject2 object, String waitReason)1267 void waitForObjectEnabled(UiObject2 object, String waitReason) { 1268 try { 1269 assertTrue("Timed out waiting for object to be enabled for " + waitReason + " " 1270 + object.getResourceName(), 1271 object.wait(Until.enabled(true), WAIT_TIME_MS)); 1272 } catch (StaleObjectException e) { 1273 fail("The object disappeared from screen"); 1274 } 1275 } 1276 1277 @NonNull waitForObjectInContainer(UiObject2 container, BySelector selector)1278 UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) { 1279 return waitForObjectsInContainer(container, selector).get(0); 1280 } 1281 1282 @NonNull waitForObjectsInContainer( UiObject2 container, BySelector selector)1283 List<UiObject2> waitForObjectsInContainer( 1284 UiObject2 container, BySelector selector) { 1285 try { 1286 final List<UiObject2> objects = container.wait( 1287 Until.findObjects(selector), 1288 WAIT_TIME_MS); 1289 assertNotNull("Can't find views in Launcher, id: " + selector + " in container: " 1290 + container.getResourceName(), objects); 1291 assertTrue("Can't find views in Launcher, id: " + selector + " in container: " 1292 + container.getResourceName(), objects.size() > 0); 1293 return objects; 1294 } catch (StaleObjectException e) { 1295 fail("The container disappeared from screen"); 1296 return null; 1297 } 1298 } 1299 getChildren(UiObject2 container)1300 List<UiObject2> getChildren(UiObject2 container) { 1301 try { 1302 return container.getChildren(); 1303 } catch (StaleObjectException e) { 1304 fail("The container disappeared from screen"); 1305 return null; 1306 } 1307 } 1308 hasLauncherObject(String resId)1309 private boolean hasLauncherObject(String resId) { 1310 return mDevice.hasObject(getLauncherObjectSelector(resId)); 1311 } 1312 hasSystemLauncherObject(String resId)1313 private boolean hasSystemLauncherObject(String resId) { 1314 return mDevice.hasObject(is3PLauncher() ? getOverviewObjectSelector(resId) 1315 : getLauncherObjectSelector(resId)); 1316 } 1317 hasLauncherObject(BySelector selector)1318 boolean hasLauncherObject(BySelector selector) { 1319 return mDevice.hasObject(makeLauncherSelector(selector)); 1320 } 1321 makeLauncherSelector(BySelector selector)1322 private BySelector makeLauncherSelector(BySelector selector) { 1323 return By.copy(selector).pkg(getLauncherPackageName()); 1324 } 1325 1326 @NonNull waitForOverviewObject(String resName)1327 UiObject2 waitForOverviewObject(String resName) { 1328 return waitForObjectBySelector(getOverviewObjectSelector(resName)); 1329 } 1330 1331 @NonNull waitForLauncherObject(String resName)1332 UiObject2 waitForLauncherObject(String resName) { 1333 return waitForObjectBySelector(getLauncherObjectSelector(resName)); 1334 } 1335 1336 @NonNull waitForSystemLauncherObject(String resName)1337 UiObject2 waitForSystemLauncherObject(String resName) { 1338 return is3PLauncher() ? waitForOverviewObject(resName) 1339 : waitForLauncherObject(resName); 1340 } 1341 1342 @NonNull waitForLauncherObject(BySelector selector)1343 UiObject2 waitForLauncherObject(BySelector selector) { 1344 return waitForObjectBySelector(makeLauncherSelector(selector)); 1345 } 1346 1347 @NonNull tryWaitForLauncherObject(BySelector selector, long timeout)1348 UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) { 1349 return tryWaitForObjectBySelector(makeLauncherSelector(selector), timeout); 1350 } 1351 1352 @NonNull waitForAndroidObject(String resId)1353 UiObject2 waitForAndroidObject(String resId) { 1354 final UiObject2 object = TestHelpers.wait( 1355 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS); 1356 assertNotNull("Can't find a android object with id: " + resId, object); 1357 return object; 1358 } 1359 1360 @NonNull waitForObjectsBySelector(BySelector selector)1361 List<UiObject2> waitForObjectsBySelector(BySelector selector) { 1362 final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS); 1363 assertNotNull("Can't find any view in Launcher, selector: " + selector, objects); 1364 return objects; 1365 } 1366 waitForObjectBySelector(BySelector selector)1367 private UiObject2 waitForObjectBySelector(BySelector selector) { 1368 final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); 1369 assertNotNull("Can't find a view in Launcher, selector: " + selector, object); 1370 return object; 1371 } 1372 tryWaitForObjectBySelector(BySelector selector, long timeout)1373 private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) { 1374 return mDevice.wait(Until.findObject(selector), timeout); 1375 } 1376 getLauncherObjectSelector(String resName)1377 BySelector getLauncherObjectSelector(String resName) { 1378 return By.res(getLauncherPackageName(), resName); 1379 } 1380 getOverviewObjectSelector(String resName)1381 BySelector getOverviewObjectSelector(String resName) { 1382 return By.res(getOverviewPackageName(), resName); 1383 } 1384 getLauncherPackageName()1385 String getLauncherPackageName() { 1386 return mDevice.getLauncherPackageName(); 1387 } 1388 is3PLauncher()1389 boolean is3PLauncher() { 1390 return !getOverviewPackageName().equals(getLauncherPackageName()); 1391 } 1392 1393 @NonNull getDevice()1394 public UiDevice getDevice() { 1395 return mDevice; 1396 } 1397 eventListToString(List<Integer> actualEvents)1398 private static String eventListToString(List<Integer> actualEvents) { 1399 if (actualEvents.isEmpty()) return "no events"; 1400 1401 return "[" 1402 + actualEvents.stream() 1403 .map(state -> TestProtocol.stateOrdinalToString(state)) 1404 .collect(Collectors.joining(", ")) 1405 + "]"; 1406 } 1407 runToState(Runnable command, int expectedState, boolean requireEvent, String actionName)1408 void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) { 1409 if (requireEvent) { 1410 runToState(command, expectedState, actionName); 1411 } else { 1412 command.run(); 1413 } 1414 } 1415 runToState(Runnable command, int expectedState, String actionName)1416 void runToState(Runnable command, int expectedState, String actionName) { 1417 final List<Integer> actualEvents = new ArrayList<>(); 1418 executeAndWaitForLauncherEvent( 1419 command, 1420 event -> isSwitchToStateEvent(event, expectedState, actualEvents), 1421 () -> "Failed to receive an event for the state change: expected [" 1422 + TestProtocol.stateOrdinalToString(expectedState) 1423 + "], actual: " + eventListToString(actualEvents), 1424 actionName); 1425 } 1426 isSwitchToStateEvent( AccessibilityEvent event, int expectedState, List<Integer> actualEvents)1427 private boolean isSwitchToStateEvent( 1428 AccessibilityEvent event, int expectedState, List<Integer> actualEvents) { 1429 if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false; 1430 1431 final Bundle parcel = (Bundle) event.getParcelableData(); 1432 final int actualState = parcel.getInt(TestProtocol.STATE_FIELD); 1433 actualEvents.add(actualState); 1434 return actualState == expectedState; 1435 } 1436 swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, GestureScope gestureScope)1437 void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, 1438 GestureScope gestureScope) { 1439 runToState( 1440 () -> linearGesture(startX, startY, endX, endY, steps, false, gestureScope), 1441 expectedState, 1442 "swiping"); 1443 } 1444 getBottomGestureSize()1445 int getBottomGestureSize() { 1446 return Math.max(getWindowInsets().bottom, ResourceUtils.getNavbarSize( 1447 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1; 1448 } 1449 getBottomGestureMarginInContainer(UiObject2 container)1450 int getBottomGestureMarginInContainer(UiObject2 container) { 1451 final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen(); 1452 return getVisibleBounds(container).bottom - bottomGestureStartOnScreen; 1453 } 1454 getRightGestureMarginInContainer(UiObject2 container)1455 int getRightGestureMarginInContainer(UiObject2 container) { 1456 final int rightGestureStartOnScreen = getRightGestureStartOnScreen(); 1457 return getVisibleBounds(container).right - rightGestureStartOnScreen; 1458 } 1459 getBottomGestureStartOnScreen()1460 int getBottomGestureStartOnScreen() { 1461 return getRealDisplaySize().y - getBottomGestureSize(); 1462 } 1463 getRightGestureStartOnScreen()1464 int getRightGestureStartOnScreen() { 1465 return getRealDisplaySize().x - getWindowInsets().right - 1; 1466 } 1467 1468 /** 1469 * Click on the ui object right away without waiting for animation. 1470 * 1471 * [UiObject2.click] would wait for all animations finished before clicking. Not waiting for 1472 * animations because in some scenarios there is a playing animations when the click is 1473 * attempted. 1474 */ clickObject(UiObject2 uiObject, GestureScope gestureScope)1475 void clickObject(UiObject2 uiObject, GestureScope gestureScope) { 1476 final long clickTime = SystemClock.uptimeMillis(); 1477 final Point center = uiObject.getVisibleCenter(); 1478 sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center, gestureScope); 1479 sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center, gestureScope); 1480 } 1481 clickLauncherObject(UiObject2 object)1482 void clickLauncherObject(UiObject2 object) { 1483 clickObject(object, GestureScope.INSIDE); 1484 } 1485 scrollToLastVisibleRow( UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, int appsListBottomPadding)1486 void scrollToLastVisibleRow( 1487 UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, 1488 int appsListBottomPadding) { 1489 final int itemRowCurrentTopOnScreen = bottomVisibleIconBounds.top; 1490 final Rect containerRect = getVisibleBounds(container); 1491 final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer; 1492 final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop(); 1493 1494 scrollDownByDistance(container, distance, appsListBottomPadding); 1495 } 1496 scrollDownByDistance(UiObject2 container, int distance)1497 void scrollDownByDistance(UiObject2 container, int distance) { 1498 scrollDownByDistance(container, distance, 0); 1499 } 1500 scrollDownByDistance(UiObject2 container, int distance, int bottomPadding)1501 void scrollDownByDistance(UiObject2 container, int distance, int bottomPadding) { 1502 final Rect containerRect = getVisibleBounds(container); 1503 final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); 1504 scroll( 1505 container, 1506 Direction.DOWN, 1507 new Rect( 1508 0, 1509 containerRect.height() - distance - bottomGestureMarginInContainer, 1510 0, 1511 bottomGestureMarginInContainer + bottomPadding), 1512 /* steps= */ 10, 1513 /* slowDown= */ true); 1514 } 1515 scrollLeftByDistance(UiObject2 container, int distance)1516 void scrollLeftByDistance(UiObject2 container, int distance) { 1517 final Rect containerRect = getVisibleBounds(container); 1518 final int rightGestureMarginInContainer = getRightGestureMarginInContainer(container); 1519 final int leftGestureMargin = getTargetInsets().left + getEdgeSensitivityWidth(); 1520 scroll( 1521 container, 1522 Direction.LEFT, 1523 new Rect(leftGestureMargin, 0, 1524 containerRect.width() - distance - rightGestureMarginInContainer, 0), 1525 10, 1526 true); 1527 } 1528 scroll( UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown)1529 void scroll( 1530 UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) { 1531 final Rect rect = getVisibleBounds(container); 1532 if (margins != null) { 1533 rect.left += margins.left; 1534 rect.top += margins.top; 1535 rect.right -= margins.right; 1536 rect.bottom -= margins.bottom; 1537 } 1538 1539 final int startX; 1540 final int startY; 1541 final int endX; 1542 final int endY; 1543 1544 switch (direction) { 1545 case UP: { 1546 startX = endX = rect.centerX(); 1547 startY = rect.top; 1548 endY = rect.bottom - 1; 1549 } 1550 break; 1551 case DOWN: { 1552 startX = endX = rect.centerX(); 1553 startY = rect.bottom - 1; 1554 endY = rect.top; 1555 } 1556 break; 1557 case LEFT: { 1558 startY = endY = rect.centerY(); 1559 startX = rect.left; 1560 endX = rect.right - 1; 1561 } 1562 break; 1563 case RIGHT: { 1564 startY = endY = rect.centerY(); 1565 startX = rect.right - 1; 1566 endX = rect.left; 1567 } 1568 break; 1569 default: 1570 fail("Unsupported direction"); 1571 return; 1572 } 1573 1574 executeAndWaitForLauncherEvent( 1575 () -> linearGesture( 1576 startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE), 1577 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), 1578 () -> "Didn't receive a scroll end message: " + startX + ", " + startY 1579 + ", " + endX + ", " + endY, 1580 "scrolling"); 1581 } 1582 1583 // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a 1584 // fixed interval each time. linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown, GestureScope gestureScope)1585 public void linearGesture(int startX, int startY, int endX, int endY, int steps, 1586 boolean slowDown, 1587 GestureScope gestureScope) { 1588 log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY); 1589 final long downTime = SystemClock.uptimeMillis(); 1590 final Point start = new Point(startX, startY); 1591 final Point end = new Point(endX, endY); 1592 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope); 1593 final long endTime = movePointer( 1594 start, end, steps, false, downTime, downTime, slowDown, gestureScope); 1595 sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope); 1596 } 1597 movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, long startTime, boolean slowDown, GestureScope gestureScope)1598 long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, 1599 long startTime, boolean slowDown, GestureScope gestureScope) { 1600 long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS, 1601 isDecelerating, start, end, gestureScope); 1602 if (slowDown) { 1603 endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end, 1604 end, gestureScope); 1605 } 1606 return endTime; 1607 } 1608 waitForIdle()1609 void waitForIdle() { 1610 mDevice.waitForIdle(); 1611 } 1612 getTouchSlop()1613 int getTouchSlop() { 1614 return ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1615 } 1616 getResources()1617 public Resources getResources() { 1618 return getContext().getResources(); 1619 } 1620 getMotionEvent(long downTime, long eventTime, int action, float x, float y)1621 private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, 1622 float x, float y) { 1623 MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties(); 1624 properties.id = 0; 1625 properties.toolType = Configurator.getInstance().getToolType(); 1626 1627 MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); 1628 coords.pressure = 1; 1629 coords.size = 1; 1630 coords.x = x; 1631 coords.y = y; 1632 1633 return MotionEvent.obtain(downTime, eventTime, action, 1, 1634 new MotionEvent.PointerProperties[]{properties}, 1635 new MotionEvent.PointerCoords[]{coords}, 1636 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); 1637 } 1638 hasTIS()1639 private boolean hasTIS() { 1640 return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean(TestProtocol.REQUEST_HAS_TIS); 1641 } 1642 1643 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope)1644 public void sendPointer(long downTime, long currentTime, int action, Point point, 1645 GestureScope gestureScope) { 1646 final boolean hasTIS = hasTIS(); 1647 switch (action) { 1648 case MotionEvent.ACTION_DOWN: 1649 if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER 1650 && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER 1651 && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) { 1652 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN); 1653 } 1654 if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) { 1655 expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS); 1656 } 1657 break; 1658 case MotionEvent.ACTION_UP: 1659 if (hasTIS && gestureScope != GestureScope.INSIDE 1660 && gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER 1661 && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER 1662 || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) { 1663 expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS); 1664 } 1665 if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER 1666 && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER 1667 && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) { 1668 expectEvent(TestProtocol.SEQUENCE_MAIN, 1669 gestureScope == GestureScope.INSIDE 1670 || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER 1671 ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL); 1672 } 1673 if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) { 1674 expectEvent(TestProtocol.SEQUENCE_TIS, 1675 gestureScope == GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE 1676 || gestureScope == GestureScope.OUTSIDE_WITH_KEYCODE 1677 ? EVENT_TOUCH_CANCEL_TIS : EVENT_TOUCH_UP_TIS); 1678 } 1679 break; 1680 } 1681 1682 final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y); 1683 assertTrue("injectInputEvent failed", 1684 mInstrumentation.getUiAutomation().injectInputEvent(event, true, false)); 1685 event.recycle(); 1686 } 1687 movePointer(long downTime, long startTime, long duration, Point from, Point to, GestureScope gestureScope)1688 public long movePointer(long downTime, long startTime, long duration, Point from, Point to, 1689 GestureScope gestureScope) { 1690 return movePointer( 1691 downTime, startTime, duration, false, from, to, gestureScope); 1692 } 1693 movePointer(long downTime, long startTime, long duration, boolean isDecelerating, Point from, Point to, GestureScope gestureScope)1694 public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating, 1695 Point from, Point to, GestureScope gestureScope) { 1696 log("movePointer: " + from + " to " + to); 1697 final Point point = new Point(); 1698 long steps = duration / GESTURE_STEP_MS; 1699 1700 long currentTime = startTime; 1701 1702 if (isDecelerating) { 1703 // formula: V = V0 - D*T, assuming V = 0 when T = duration 1704 1705 // vx0: initial speed at the x-dimension, set as twice the avg speed 1706 // dx: the constant deceleration at the x-dimension 1707 double vx0 = 2.0 * (to.x - from.x) / duration; 1708 double dx = vx0 / duration; 1709 // vy0: initial speed at the y-dimension, set as twice the avg speed 1710 // dy: the constant deceleration at the y-dimension 1711 double vy0 = 2.0 * (to.y - from.y) / duration; 1712 double dy = vy0 / duration; 1713 1714 for (long i = 0; i < steps; ++i) { 1715 sleep(GESTURE_STEP_MS); 1716 currentTime += GESTURE_STEP_MS; 1717 1718 // formula: P = P0 + V0*T - (D*T^2/2) 1719 final double t = (i + 1) * GESTURE_STEP_MS; 1720 point.x = from.x + (int) (vx0 * t - 0.5 * dx * t * t); 1721 point.y = from.y + (int) (vy0 * t - 0.5 * dy * t * t); 1722 1723 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope); 1724 } 1725 } else { 1726 for (long i = 0; i < steps; ++i) { 1727 sleep(GESTURE_STEP_MS); 1728 currentTime += GESTURE_STEP_MS; 1729 1730 final float progress = (currentTime - startTime) / (float) duration; 1731 point.x = from.x + (int) (progress * (to.x - from.x)); 1732 point.y = from.y + (int) (progress * (to.y - from.y)); 1733 1734 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope); 1735 1736 } 1737 } 1738 1739 return currentTime; 1740 } 1741 getCurrentInteractionMode(Context context)1742 public static int getCurrentInteractionMode(Context context) { 1743 return getSystemIntegerRes(context, "config_navBarInteractionMode"); 1744 } 1745 1746 @NonNull clickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent)1747 UiObject2 clickAndGet( 1748 @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) { 1749 final Point targetCenter = target.getVisibleCenter(); 1750 final long downTime = SystemClock.uptimeMillis(); 1751 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.INSIDE); 1752 expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent); 1753 final UiObject2 result = waitForLauncherObject(resName); 1754 sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter, 1755 GestureScope.INSIDE); 1756 return result; 1757 } 1758 getSystemIntegerRes(Context context, String resName)1759 private static int getSystemIntegerRes(Context context, String resName) { 1760 Resources res = context.getResources(); 1761 int resId = res.getIdentifier(resName, "integer", "android"); 1762 1763 if (resId != 0) { 1764 return res.getInteger(resId); 1765 } else { 1766 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 1767 return -1; 1768 } 1769 } 1770 getSystemDimensionResId(Context context, String resName)1771 private static int getSystemDimensionResId(Context context, String resName) { 1772 Resources res = context.getResources(); 1773 int resId = res.getIdentifier(resName, "dimen", "android"); 1774 1775 if (resId != 0) { 1776 return resId; 1777 } else { 1778 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 1779 return -1; 1780 } 1781 } 1782 sleep(int duration)1783 static void sleep(int duration) { 1784 SystemClock.sleep(duration); 1785 } 1786 getEdgeSensitivityWidth()1787 int getEdgeSensitivityWidth() { 1788 try { 1789 final Context context = mInstrumentation.getTargetContext().createPackageContext( 1790 getLauncherPackageName(), 0); 1791 return context.getResources().getDimensionPixelSize( 1792 getSystemDimensionResId(context, "config_backGestureInset")) + 1; 1793 } catch (PackageManager.NameNotFoundException e) { 1794 fail("Can't get edge sensitivity: " + e); 1795 return 0; 1796 } 1797 } 1798 getRealDisplaySize()1799 Point getRealDisplaySize() { 1800 final Rect displayBounds = getContext().getSystemService(WindowManager.class) 1801 .getMaximumWindowMetrics() 1802 .getBounds(); 1803 return new Point(displayBounds.width(), displayBounds.height()); 1804 } 1805 enableDebugTracing()1806 public void enableDebugTracing() { 1807 getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING); 1808 } 1809 disableSensorRotation()1810 private void disableSensorRotation() { 1811 getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION); 1812 } 1813 disableDebugTracing()1814 public void disableDebugTracing() { 1815 getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING); 1816 } 1817 forceGc()1818 public void forceGc() { 1819 // GC the system & sysui first before gc'ing launcher 1820 logShellCommand("cmd statusbar run-gc"); 1821 getTestInfo(TestProtocol.REQUEST_FORCE_GC); 1822 } 1823 getPid()1824 public Integer getPid() { 1825 final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID); 1826 return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null; 1827 } 1828 produceViewLeak()1829 public void produceViewLeak() { 1830 getTestInfo(TestProtocol.REQUEST_VIEW_LEAK); 1831 } 1832 printViewLeak()1833 public void printViewLeak() { 1834 getTestInfo(TestProtocol.PRINT_VIEW_LEAK); 1835 } 1836 getRecentTasks()1837 public ArrayList<ComponentName> getRecentTasks() { 1838 ArrayList<ComponentName> tasks = new ArrayList<>(); 1839 ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST) 1840 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1841 for (String s : components) { 1842 tasks.add(ComponentName.unflattenFromString(s)); 1843 } 1844 return tasks; 1845 } 1846 clearLauncherData()1847 public void clearLauncherData() { 1848 getTestInfo(TestProtocol.REQUEST_CLEAR_DATA); 1849 } 1850 1851 /** 1852 * Reloads the workspace with a test layout that includes the Test Activity app icon on the 1853 * hotseat. 1854 */ useTestWorkspaceLayoutOnReload()1855 public void useTestWorkspaceLayoutOnReload() { 1856 getTestInfo(TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT); 1857 } 1858 1859 /** 1860 * Reloads the workspace with a test layout that includes Maps/Play on workspace, and 1861 * Dialer/Messaging/Chrome/Camera on hotseat. 1862 */ useTest2WorkspaceLayoutOnReload()1863 public void useTest2WorkspaceLayoutOnReload() { 1864 getTestInfo(TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT); 1865 } 1866 1867 1868 /** 1869 * Reloads the workspace with a test layout that includes the chrome Activity app icon on the 1870 * hotseat. 1871 */ useTaplWorkspaceLayoutOnReload()1872 public void useTaplWorkspaceLayoutOnReload() { 1873 getTestInfo(TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT); 1874 } 1875 1876 /** Reloads the workspace with the default layout defined by the user's grid size selection. */ useDefaultWorkspaceLayoutOnReload()1877 public void useDefaultWorkspaceLayoutOnReload() { 1878 getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT); 1879 } 1880 1881 /** Shows the taskbar if it is hidden, otherwise does nothing. */ showTaskbarIfHidden()1882 public void showTaskbarIfHidden() { 1883 getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED); 1884 } 1885 1886 /** Blocks the taskbar from automatically stashing based on time. */ enableBlockTimeout(boolean enable)1887 public void enableBlockTimeout(boolean enable) { 1888 getTestInfo(enable 1889 ? TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT 1890 : TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT); 1891 } 1892 1893 /** Enables transient taskbar for testing purposes only. */ enableTransientTaskbar(boolean enable)1894 public void enableTransientTaskbar(boolean enable) { 1895 getTestInfo(enable 1896 ? TestProtocol.REQUEST_ENABLE_TRANSIENT_TASKBAR 1897 : TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR); 1898 } 1899 1900 /** 1901 * Recreates the taskbar (outside of tests this is done for certain configuration changes). 1902 * The expected behavior is that the taskbar retains its current state after being recreated. 1903 * For example, if taskbar is currently stashed, it should still be stashed after recreating. 1904 */ recreateTaskbar()1905 public void recreateTaskbar() { 1906 getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR); 1907 } 1908 getHotseatIconNames()1909 public List<String> getHotseatIconNames() { 1910 return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES) 1911 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1912 } 1913 getActivities()1914 private String[] getActivities() { 1915 return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES) 1916 .getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1917 } 1918 getRootedActivitiesList()1919 public String getRootedActivitiesList() { 1920 return String.join(", ", getActivities()); 1921 } 1922 noLeakedActivities()1923 public boolean noLeakedActivities() { 1924 final String[] activities = getActivities(); 1925 for (String activity : activities) { 1926 if (activity.contains("(destroyed)")) { 1927 return false; 1928 } 1929 } 1930 return activities.length <= 2; 1931 } 1932 getActivitiesCreated()1933 public int getActivitiesCreated() { 1934 return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT) 1935 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1936 } 1937 eventsCheck()1938 public Closable eventsCheck() { 1939 Assert.assertTrue("Nested event checking", mEventChecker == null); 1940 disableSensorRotation(); 1941 final Integer initialPid = getPid(); 1942 final LogEventChecker eventChecker = new LogEventChecker(this); 1943 if (eventChecker.start()) mEventChecker = eventChecker; 1944 1945 return () -> { 1946 if (initialPid != null && initialPid.intValue() != getPid()) { 1947 if (mOnLauncherCrashed != null) mOnLauncherCrashed.run(); 1948 checkForAnomaly(); 1949 Assert.fail( 1950 formatSystemHealthMessage( 1951 formatErrorWithEvents("Launcher crashed", false))); 1952 } 1953 1954 if (mEventChecker != null) { 1955 mEventChecker = null; 1956 if (mCheckEventsForSuccessfulGestures) { 1957 final String message = eventChecker.verify(WAIT_TIME_MS, true); 1958 if (message != null) { 1959 dumpDiagnostics(message); 1960 checkForAnomaly(); 1961 Assert.fail(formatSystemHealthMessage( 1962 "http://go/tapl : successful gesture produced " + message)); 1963 } 1964 } else { 1965 eventChecker.finishNoWait(); 1966 } 1967 } 1968 }; 1969 } 1970 1971 boolean isLauncher3() { 1972 if (mIsLauncher3 == null) { 1973 mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName()); 1974 } 1975 return mIsLauncher3; 1976 } 1977 1978 void expectEvent(String sequence, Pattern expected) { 1979 if (mEventChecker != null) { 1980 mEventChecker.expectPattern(sequence, expected); 1981 } else { 1982 Log.d(TAG, "Expecting: " + sequence + " / " + expected); 1983 } 1984 } 1985 1986 Rect getVisibleBounds(UiObject2 object) { 1987 try { 1988 return object.getVisibleBounds(); 1989 } catch (StaleObjectException e) { 1990 fail("Object disappeared from screen"); 1991 return null; 1992 } catch (Throwable t) { 1993 fail(t.toString()); 1994 return null; 1995 } 1996 } 1997 1998 float getWindowCornerRadius() { 1999 // TODO(b/197326121): Check if the touch is overlapping with the corners by offsetting 2000 final float tmpBuffer = 100f; 2001 final Resources resources = getResources(); 2002 if (!supportsRoundedCornersOnWindows(resources)) { 2003 Log.d(TAG, "No rounded corners"); 2004 return tmpBuffer; 2005 } 2006 2007 // Radius that should be used in case top or bottom aren't defined. 2008 float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0); 2009 2010 float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0); 2011 if (topRadius == 0f) { 2012 topRadius = defaultRadius; 2013 } 2014 float bottomRadius = ResourceUtils.getDimenByName( 2015 "rounded_corner_radius_bottom", resources, 0); 2016 if (bottomRadius == 0f) { 2017 bottomRadius = defaultRadius; 2018 } 2019 2020 // Always use the smallest radius to make sure the rounded corners will 2021 // completely cover the display. 2022 Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius); 2023 return Math.max(topRadius, bottomRadius) + tmpBuffer; 2024 } 2025 2026 private static boolean supportsRoundedCornersOnWindows(Resources resources) { 2027 return ResourceUtils.getBoolByName( 2028 "config_supportsRoundedCornersOnWindows", resources, false); 2029 } 2030 2031 /** 2032 * Taps outside container to dismiss. 2033 * 2034 * @param container container to be dismissed 2035 * @param tapRight tap on the right of the container if true, or left otherwise 2036 */ 2037 void touchOutsideContainer(UiObject2 container, boolean tapRight) { 2038 try (LauncherInstrumentation.Closable c = addContextLayer( 2039 "want to tap outside container on the " + (tapRight ? "right" : "left"))) { 2040 Rect containerBounds = getVisibleBounds(container); 2041 final long downTime = SystemClock.uptimeMillis(); 2042 final Point tapTarget = new Point( 2043 tapRight 2044 ? (containerBounds.right + getRealDisplaySize().x) / 2 2045 : containerBounds.left / 2, 2046 containerBounds.top + 1); 2047 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget, 2048 LauncherInstrumentation.GestureScope.INSIDE); 2049 sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget, 2050 LauncherInstrumentation.GestureScope.INSIDE); 2051 } 2052 } 2053 2054 /** 2055 * Sets the consumer to run callbacks at all run-points. 2056 */ 2057 public void setRunPointCallback(Consumer<CALLBACK_RUN_POINT> callback) { 2058 mCallbackAtRunPoint = callback; 2059 } 2060 2061 /** 2062 * Runs the callback at the specified point if it exists. 2063 */ 2064 void runCallbackIfActive(CALLBACK_RUN_POINT runPoint) { 2065 if (mCallbackAtRunPoint != null) { 2066 mCallbackAtRunPoint.accept(runPoint); 2067 } 2068 } 2069 2070 /** 2071 * Waits until a particular condition is true. Based on WaitMixin. 2072 */ 2073 boolean waitAndGet(BooleanSupplier condition, long timeout, long interval) { 2074 long startTime = SystemClock.uptimeMillis(); 2075 2076 boolean result = condition.getAsBoolean(); 2077 for (long elapsedTime = 0; !result; elapsedTime = SystemClock.uptimeMillis() - startTime) { 2078 if (elapsedTime >= timeout) { 2079 break; 2080 } 2081 SystemClock.sleep(interval); 2082 result = condition.getAsBoolean(); 2083 } 2084 return result; 2085 } 2086 } 2087