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