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.KeyEvent.ACTION_DOWN; 24 import static android.view.MotionEvent.ACTION_SCROLL; 25 import static android.view.MotionEvent.ACTION_UP; 26 import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT; 27 import static android.view.Surface.ROTATION_90; 28 29 import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID; 30 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; 31 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; 32 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE; 33 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS; 34 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD; 35 36 import android.app.ActivityManager; 37 import android.app.Instrumentation; 38 import android.app.UiAutomation; 39 import android.app.UiModeManager; 40 import android.content.ComponentName; 41 import android.content.ContentProviderClient; 42 import android.content.ContentResolver; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.pm.PackageManager; 46 import android.content.pm.ProviderInfo; 47 import android.content.res.Configuration; 48 import android.content.res.Resources; 49 import android.graphics.Insets; 50 import android.graphics.Point; 51 import android.graphics.Rect; 52 import android.net.Uri; 53 import android.os.Bundle; 54 import android.os.DeadObjectException; 55 import android.os.Parcelable; 56 import android.os.RemoteException; 57 import android.os.SystemClock; 58 import android.os.Trace; 59 import android.text.TextUtils; 60 import android.util.Log; 61 import android.view.InputDevice; 62 import android.view.InputEvent; 63 import android.view.KeyCharacterMap; 64 import android.view.KeyEvent; 65 import android.view.MotionEvent; 66 import android.view.ViewConfiguration; 67 import android.view.WindowManager; 68 import android.view.accessibility.AccessibilityEvent; 69 70 import androidx.annotation.NonNull; 71 import androidx.annotation.Nullable; 72 import androidx.test.InstrumentationRegistry; 73 import androidx.test.uiautomator.By; 74 import androidx.test.uiautomator.BySelector; 75 import androidx.test.uiautomator.Configurator; 76 import androidx.test.uiautomator.Direction; 77 import androidx.test.uiautomator.StaleObjectException; 78 import androidx.test.uiautomator.UiDevice; 79 import androidx.test.uiautomator.UiObject2; 80 import androidx.test.uiautomator.Until; 81 82 import com.android.launcher3.testing.shared.ResourceUtils; 83 import com.android.launcher3.testing.shared.TestProtocol; 84 import com.android.systemui.shared.system.QuickStepContract; 85 86 import org.junit.Assert; 87 88 import java.io.ByteArrayOutputStream; 89 import java.io.IOException; 90 import java.lang.ref.WeakReference; 91 import java.util.ArrayList; 92 import java.util.Arrays; 93 import java.util.Deque; 94 import java.util.LinkedList; 95 import java.util.List; 96 import java.util.Optional; 97 import java.util.concurrent.TimeoutException; 98 import java.util.function.BooleanSupplier; 99 import java.util.function.Function; 100 import java.util.function.Supplier; 101 import java.util.regex.Matcher; 102 import java.util.regex.Pattern; 103 import java.util.stream.Collectors; 104 105 /** 106 * The main tapl object. The only object that can be explicitly constructed by the using code. It 107 * produces all other objects. 108 */ 109 public final class LauncherInstrumentation { 110 111 private static final String TAG = "Tapl"; 112 private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 5; 113 private static final int GESTURE_STEP_MS = 16; 114 115 static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers"); 116 static final Pattern EVENT_START = Pattern.compile("start:"); 117 private static final Pattern EVENT_KEY_BACK_UP = 118 getKeyEventPattern("ACTION_UP", "KEYCODE_BACK"); 119 private static final Pattern EVENT_ON_BACK_INVOKED = Pattern.compile("onBackInvoked"); 120 121 private final String mLauncherPackage; 122 private Boolean mIsLauncher3; 123 private long mTestStartTime = -1; 124 125 // Types for launcher containers that the user is interacting with. "Background" is a 126 // pseudo-container corresponding to inactive launcher covered by another app. 127 public enum ContainerType { 128 WORKSPACE, HOME_ALL_APPS, OVERVIEW, SPLIT_SCREEN_SELECT, WIDGETS, FALLBACK_OVERVIEW, 129 LAUNCHED_APP, TASKBAR_ALL_APPS 130 } 131 132 public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON} 133 134 // Defines whether the gesture recognition triggers pilfer. 135 public enum GestureScope { 136 DONT_EXPECT_PILFER, 137 EXPECT_PILFER, 138 } 139 140 public enum TrackpadGestureType { 141 NONE, 142 TWO_FINGER, 143 THREE_FINGER, 144 FOUR_FINGER 145 } 146 147 // Base class for launcher containers. 148 abstract static class VisibleContainer { 149 protected final LauncherInstrumentation mLauncher; 150 VisibleContainer(LauncherInstrumentation launcher)151 protected VisibleContainer(LauncherInstrumentation launcher) { 152 mLauncher = launcher; 153 launcher.setActiveContainer(this); 154 } 155 getContainerType()156 protected abstract ContainerType getContainerType(); 157 158 /** 159 * Asserts that the launcher is in the mode matching 'this' object. 160 * 161 * @return UI object for the container. 162 */ verifyActiveContainer()163 final UiObject2 verifyActiveContainer() { 164 mLauncher.assertTrue("Attempt to use a stale container", 165 this == sActiveContainer.get()); 166 return mLauncher.verifyContainerType(getContainerType()); 167 } 168 } 169 170 public interface Closable extends AutoCloseable { close()171 void close(); 172 } 173 174 static final String WORKSPACE_RES_ID = "workspace"; 175 private static final String APPS_RES_ID = "apps_view"; 176 private static final String OVERVIEW_RES_ID = "overview_panel"; 177 private static final String WIDGETS_RES_ID = "primary_widgets_list_view"; 178 private static final String CONTEXT_MENU_RES_ID = "popup_container"; 179 private static final String OPEN_FOLDER_RES_ID = "folder_content"; 180 static final String TASKBAR_RES_ID = "taskbar_view"; 181 private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder"; 182 static final String KEYBOARD_QUICK_SWITCH_RES_ID = "keyboard_quick_switch_view"; 183 public static final int WAIT_TIME_MS = 30000; 184 static final long DEFAULT_POLL_INTERVAL = 1000; 185 private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; 186 private static final String ANDROID_PACKAGE = "android"; 187 private static final String ASSISTANT_PACKAGE = "com.google.android.googlequicksearchbox"; 188 private static final String ASSISTANT_GO_HOME_RES_ID = "home_icon"; 189 190 private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null); 191 192 private final UiDevice mDevice; 193 private final Instrumentation mInstrumentation; 194 private Integer mExpectedRotation = null; 195 private boolean mExpectedRotationCheckEnabled = true; 196 private final Uri mTestProviderUri; 197 private final Deque<String> mDiagnosticContext = new LinkedList<>(); 198 private Function<Long, String> mSystemHealthSupplier; 199 200 private boolean mIgnoreTaskbarVisibility = false; 201 202 private LogEventChecker mEventChecker; 203 204 // UI anomaly checker provided by the test. 205 private Runnable mTestAnomalyChecker; 206 207 private boolean mCheckEventsForSuccessfulGestures = false; 208 private Runnable mOnFailure; 209 private Runnable mOnLauncherCrashed; 210 211 private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE; 212 private int mPointerCount = 0; 213 214 private boolean mWaitingForMotionUpEvent; 215 getKeyEventPattern(String action, String keyCode)216 private static Pattern getKeyEventPattern(String action, String keyCode) { 217 return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode); 218 } 219 220 /** 221 * Constructs the root of TAPL hierarchy. You get all other objects from it. 222 */ LauncherInstrumentation()223 public LauncherInstrumentation() { 224 this(InstrumentationRegistry.getInstrumentation(), false); 225 } 226 227 /** 228 * Constructs the root of TAPL hierarchy. You get all other objects from it. 229 */ LauncherInstrumentation(boolean isLauncherTest)230 public LauncherInstrumentation(boolean isLauncherTest) { 231 this(InstrumentationRegistry.getInstrumentation(), isLauncherTest); 232 } 233 234 /** 235 * Constructs the root of TAPL hierarchy. You get all other objects from it. 236 * 237 * @deprecated use the constructor without Instrumentation parameter instead. 238 */ 239 @Deprecated LauncherInstrumentation(Instrumentation instrumentation)240 public LauncherInstrumentation(Instrumentation instrumentation) { 241 this(instrumentation, false); 242 } 243 244 /** 245 * Constructs the root of TAPL hierarchy. You get all other objects from it. 246 * 247 * @deprecated use the constructor without Instrumentation parameter instead. 248 */ 249 @Deprecated LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest)250 public LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest) { 251 mInstrumentation = instrumentation; 252 mDevice = UiDevice.getInstance(instrumentation); 253 254 // Launcher should run in test harness so that custom accessibility protocol between 255 // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call 256 // into Launcher. 257 assertTrue("Device must run in a test harness. " 258 + "Run `adb shell setprop ro.test_harness 1` to enable it.", 259 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness()); 260 261 final String testPackage = getContext().getPackageName(); 262 final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); 263 264 // Launcher package. As during inproc tests the tested launcher may not be selected as the 265 // current launcher, choosing target package for inproc. For out-of-proc, use the installed 266 // launcher package. 267 mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation() 268 ? getLauncherPackageName() 269 : targetPackage; 270 271 String testProviderAuthority = mLauncherPackage + ".TestInfo"; 272 mTestProviderUri = new Uri.Builder() 273 .scheme(ContentResolver.SCHEME_CONTENT) 274 .authority(testProviderAuthority) 275 .build(); 276 277 mInstrumentation.getUiAutomation().grantRuntimePermission( 278 testPackage, "android.permission.WRITE_SECURE_SETTINGS"); 279 280 PackageManager pm = getContext().getPackageManager(); 281 ProviderInfo pi = pm.resolveContentProvider( 282 testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS); 283 assertNotNull("Cannot find content provider for " + testProviderAuthority, pi); 284 ComponentName cn = new ComponentName(pi.packageName, pi.name); 285 286 final int iterations = isLauncherTest ? 300 : 100; 287 288 if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) { 289 if (TestHelpers.isInLauncherProcess()) { 290 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP); 291 } else { 292 try { 293 final int userId = getContext().getUserId(); 294 final String launcherPidCommand = "pidof " + pi.packageName; 295 final String initialPid = mDevice.executeShellCommand(launcherPidCommand); 296 297 mDevice.executeShellCommand( 298 "pm enable --user " + userId + " " + cn.flattenToString()); 299 300 // Wait for Launcher restart after enabling test provider. 301 for (int i = 0; i < iterations; ++i) { 302 final String currentPid = mDevice.executeShellCommand(launcherPidCommand) 303 .replaceAll("\\s", ""); 304 if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break; 305 if (i == iterations - 1) { 306 fail("Launcher didn't restart after enabling test provider"); 307 } 308 SystemClock.sleep(100); 309 } 310 } catch (IOException e) { 311 fail(e.toString()); 312 } 313 } 314 315 // Wait for Launcher content provider to become enabled. 316 for (int i = 0; i < iterations; ++i) { 317 final ContentProviderClient testProvider = getContext().getContentResolver() 318 .acquireContentProviderClient(mTestProviderUri); 319 if (testProvider != null) { 320 testProvider.close(); 321 break; 322 } 323 if (i == iterations - 1) { 324 fail("Launcher content provider is still not enabled"); 325 } 326 SystemClock.sleep(100); 327 } 328 } 329 } 330 331 /** 332 * Gradle only supports out of process instrumentation. The test package is automatically 333 * generated by appending `.test` to the target package. 334 */ isGradleInstrumentation()335 private boolean isGradleInstrumentation() { 336 final String testPackage = getContext().getPackageName(); 337 final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); 338 final String testSuffix = ".test"; 339 340 return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length() 341 && testPackage.substring(0, testPackage.length() - testSuffix.length()) 342 .equals(targetPackage); 343 } 344 enableCheckEventsForSuccessfulGestures()345 public void enableCheckEventsForSuccessfulGestures() { 346 mCheckEventsForSuccessfulGestures = true; 347 } 348 349 /** Sets a runnable that will be invoked upon assertion failures. */ setOnFailure(Runnable onFailure)350 public void setOnFailure(Runnable onFailure) { 351 mOnFailure = onFailure; 352 } 353 setOnLauncherCrashed(Runnable onLauncherCrashed)354 public void setOnLauncherCrashed(Runnable onLauncherCrashed) { 355 mOnLauncherCrashed = onLauncherCrashed; 356 } 357 getContext()358 Context getContext() { 359 return mInstrumentation.getContext(); 360 } 361 getTestInfo(String request)362 Bundle getTestInfo(String request) { 363 return getTestInfo(request, /*arg=*/ null); 364 } 365 getTestInfo(String request, String arg)366 Bundle getTestInfo(String request, String arg) { 367 return getTestInfo(request, arg, null); 368 } 369 getTestInfo(String request, String arg, Bundle extra)370 Bundle getTestInfo(String request, String arg, Bundle extra) { 371 try (ContentProviderClient client = getContext().getContentResolver() 372 .acquireContentProviderClient(mTestProviderUri)) { 373 return client.call(request, arg, extra); 374 } catch (DeadObjectException e) { 375 fail("Launcher crashed"); 376 return null; 377 } catch (RemoteException e) { 378 throw new RuntimeException(e); 379 } 380 } 381 getTestInfo(Intent request)382 Bundle getTestInfo(Intent request) { 383 return getTestInfo(request.getAction(), null, request.getExtras()); 384 } 385 getTargetInsets()386 Insets getTargetInsets() { 387 return getTestInfo(TestProtocol.REQUEST_TARGET_INSETS) 388 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); 389 } 390 getWindowInsets()391 Insets getWindowInsets() { 392 return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS) 393 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); 394 } 395 getSystemGestureRegion()396 Insets getSystemGestureRegion() { 397 return getTestInfo(TestProtocol.REQUEST_SYSTEM_GESTURE_REGION) 398 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); 399 } 400 getNumAllAppsColumns()401 public int getNumAllAppsColumns() { 402 return getTestInfo(REQUEST_NUM_ALL_APPS_COLUMNS).getInt( 403 TestProtocol.TEST_INFO_RESPONSE_FIELD); 404 } 405 isTablet()406 public boolean isTablet() { 407 return getTestInfo(TestProtocol.REQUEST_IS_TABLET) 408 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 409 } 410 isPredictiveBackSwipeEnabled()411 private boolean isPredictiveBackSwipeEnabled() { 412 return getTestInfo(TestProtocol.REQUEST_IS_PREDICTIVE_BACK_SWIPE_ENABLED) 413 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 414 } 415 isTaskbarNavbarUnificationEnabled()416 public boolean isTaskbarNavbarUnificationEnabled() { 417 return getTestInfo(TestProtocol.REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION) 418 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 419 } 420 isTwoPanels()421 public boolean isTwoPanels() { 422 return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS) 423 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 424 } 425 getCellLayoutBoarderHeight()426 int getCellLayoutBoarderHeight() { 427 return getTestInfo(TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT) 428 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 429 } 430 getOverviewTaskSize()431 Rect getOverviewTaskSize() { 432 return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_TASK_SIZE) 433 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, Rect.class); 434 } 435 getOverviewGridTaskSize()436 Rect getOverviewGridTaskSize() { 437 return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_GRID_TASK_SIZE) 438 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, Rect.class); 439 } 440 getOverviewPageSpacing()441 int getOverviewPageSpacing() { 442 return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_PAGE_SPACING) 443 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 444 } 445 getOverviewCurrentPageIndex()446 public int getOverviewCurrentPageIndex() { 447 return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX) 448 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 449 } 450 getExactScreenCenterX()451 float getExactScreenCenterX() { 452 return getRealDisplaySize().x / 2f; 453 } 454 setEnableRotation(boolean on)455 public void setEnableRotation(boolean on) { 456 getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on)); 457 } 458 hadNontestEvents()459 public boolean hadNontestEvents() { 460 return getTestInfo(TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS) 461 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 462 } 463 setActiveContainer(VisibleContainer container)464 void setActiveContainer(VisibleContainer container) { 465 sActiveContainer = new WeakReference<>(container); 466 } 467 468 /** 469 * Sets the accesibility interactive timeout to be effectively indefinite (UI using this 470 * accesibility timeout will not automatically dismiss if true). 471 */ setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout)472 void setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout) { 473 final String cmd = indefiniteTimeout 474 ? "settings put secure accessibility_interactive_ui_timeout_ms 10000" 475 : "settings delete secure accessibility_interactive_ui_timeout_ms"; 476 logShellCommand(cmd); 477 } 478 479 /** 480 * Retrieves a resource value from context that defines if nav bar can change position or if it 481 * is fixed position regardless of device orientation. 482 */ getNavBarCanMove()483 private boolean getNavBarCanMove() { 484 final Context baseContext = mInstrumentation.getTargetContext(); 485 try { 486 final Context ctx = getLauncherContext(baseContext); 487 return getNavBarCanMove(ctx); 488 } catch (Exception e) { 489 fail(e.toString()); 490 } 491 return false; 492 } 493 getNavigationModel()494 public NavigationModel getNavigationModel() { 495 final Context baseContext = mInstrumentation.getTargetContext(); 496 try { 497 final Context ctx = getLauncherContext(baseContext); 498 for (int i = 0; i < 100; ++i) { 499 final int currentInteractionMode = getCurrentInteractionMode(ctx); 500 final NavigationModel model = getNavigationModel(currentInteractionMode); 501 log("Interaction mode = " + currentInteractionMode + " (" + model + ")"); 502 if (model != null) return model; 503 Thread.sleep(100); 504 } 505 fail("Can't detect navigation mode"); 506 } catch (Exception e) { 507 fail(e.toString()); 508 } 509 return NavigationModel.THREE_BUTTON; 510 } 511 getNavigationModel(int currentInteractionMode)512 public static NavigationModel getNavigationModel(int currentInteractionMode) { 513 if (QuickStepContract.isGesturalMode(currentInteractionMode)) { 514 return NavigationModel.ZERO_BUTTON; 515 } else if (QuickStepContract.isLegacyMode(currentInteractionMode)) { 516 return NavigationModel.THREE_BUTTON; 517 } 518 return null; 519 } 520 log(String message)521 static void log(String message) { 522 Log.d(TAG, message); 523 } 524 addContextLayer(String piece)525 Closable addContextLayer(String piece) { 526 mDiagnosticContext.addLast(piece); 527 Trace.beginSection("Context: " + piece); 528 log("Entering context: " + piece); 529 return () -> { 530 Trace.endSection(); 531 log("Leaving context: " + piece); 532 mDiagnosticContext.removeLast(); 533 }; 534 } 535 dumpViewHierarchy()536 public void dumpViewHierarchy() { 537 try { 538 Trace.beginSection("dumpViewHierarchy"); 539 final ByteArrayOutputStream stream = new ByteArrayOutputStream(); 540 mDevice.dumpWindowHierarchy(stream); 541 stream.flush(); 542 stream.close(); 543 for (String line : stream.toString().split("\\r?\\n")) { 544 Log.e(TAG, line.trim()); 545 } 546 } catch (IOException e) { 547 Log.e(TAG, "error dumping XML to logcat", e); 548 } finally { 549 Trace.endSection(); 550 } 551 } 552 getSystemAnomalyMessage( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews)553 public String getSystemAnomalyMessage( 554 boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) { 555 try { 556 { 557 final StringBuilder sb = new StringBuilder(); 558 559 UiObject2 object = 560 mDevice.findObject(By.res("android", "alertTitle").pkg("android")); 561 if (object != null) { 562 sb.append("TITLE: ").append(object.getText()); 563 } 564 565 object = mDevice.findObject(By.res("android", "message").pkg("android")); 566 if (object != null) { 567 sb.append(" PACKAGE: ").append(object.getApplicationPackage()) 568 .append(" MESSAGE: ").append(object.getText()); 569 } 570 571 if (sb.length() != 0) { 572 return "System alert popup is visible: " + sb; 573 } 574 } 575 576 if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked"; 577 578 if (!ignoreOnlySystemUiViews) { 579 final String visibleApps = mDevice.findObjects(getAnyObjectSelector()) 580 .stream() 581 .map(LauncherInstrumentation::getApplicationPackageSafe) 582 .distinct() 583 .filter(pkg -> pkg != null) 584 .collect(Collectors.joining(",")); 585 if (SYSTEMUI_PACKAGE.equals(visibleApps)) return "Only System UI views are visible"; 586 } 587 if (!ignoreNavmodeChangeStates) { 588 if (!mDevice.wait(Until.hasObject(getAnyObjectSelector()), WAIT_TIME_MS)) { 589 return "Screen is empty"; 590 } 591 } 592 593 final String navigationModeError = getNavigationModeMismatchError(true); 594 if (navigationModeError != null) return navigationModeError; 595 } catch (Throwable e) { 596 Log.w(TAG, "getSystemAnomalyMessage failed", e); 597 } 598 599 return null; 600 } 601 checkForAnomaly()602 private void checkForAnomaly() { 603 checkForAnomaly(false, false); 604 } 605 606 /** 607 * Allows the test to provide a pluggable anomaly checker. It’s supposed to throw an exception 608 * if the check fails. The test may provide its own anomaly checker, for example, if it wants to 609 * check for an anomaly that’s recognized by the standard TAPL anomaly checker, but wants a 610 * custom error message, such as adding information whether the keyguard is seen for the first 611 * time during the shard execution. 612 */ setAnomalyChecker(Runnable anomalyChecker)613 public void setAnomalyChecker(Runnable anomalyChecker) { 614 mTestAnomalyChecker = anomalyChecker; 615 } 616 617 /** 618 * Verifies that there are no visible UI anomalies. An "anomaly" is a state of UI that should 619 * never happen during the text execution. Anomaly is something different from just “regular” 620 * unexpected state of the Launcher such as when we see Workspace after swiping up to All Apps. 621 * Workspace is a normal state. We can contrast this with an anomaly, when, for example, we see 622 * a lock screen. Launcher tests can never bring the lock screen, so the very presence of the 623 * lock screen is an indication that something went very wrong, and perhaps is caused by reasons 624 * outside of the Launcher and its tests, perhaps, by a crash in System UI. Diagnosing anomalies 625 * helps to understand faster whether the problem is in the Launcher or its tests, or outside. 626 */ checkForAnomaly( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews)627 public void checkForAnomaly( 628 boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) { 629 try { 630 Trace.beginSection("checkForAnomaly"); 631 if (mTestAnomalyChecker != null) mTestAnomalyChecker.run(); 632 633 final String systemAnomalyMessage = 634 getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews); 635 if (systemAnomalyMessage != null) { 636 if (mOnFailure != null) mOnFailure.run(); 637 Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( 638 "http://go/tapl : Tests are broken by a non-Launcher system error: " 639 + systemAnomalyMessage, false))); 640 } 641 } finally { 642 Trace.endSection(); 643 } 644 } 645 getVisiblePackages()646 private String getVisiblePackages() { 647 final String apps = mDevice.findObjects(getAnyObjectSelector()) 648 .stream() 649 .map(LauncherInstrumentation::getApplicationPackageSafe) 650 .distinct() 651 .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg)) 652 .collect(Collectors.joining(", ")); 653 return !apps.isEmpty() 654 ? "active app: " + apps 655 : "the test doesn't see views from any app, including Launcher"; 656 } 657 getApplicationPackageSafe(UiObject2 object)658 private static String getApplicationPackageSafe(UiObject2 object) { 659 try { 660 return object.getApplicationPackage(); 661 } catch (StaleObjectException e) { 662 // We are looking at all object in the system; external ones can suddenly go away. 663 return null; 664 } 665 } 666 getVisibleStateMessage()667 private String getVisibleStateMessage() { 668 if (hasLauncherObject(CONTEXT_MENU_RES_ID)) return "Context Menu"; 669 if (hasLauncherObject(OPEN_FOLDER_RES_ID)) return "Open Folder"; 670 if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets"; 671 if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview"; 672 if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace"; 673 if (hasLauncherObject(APPS_RES_ID)) return "AllApps"; 674 if (hasLauncherObject(TASKBAR_RES_ID)) return "Taskbar"; 675 if (hasLauncherObject("wallpaper_carousel")) return "Launcher Settings Popup"; 676 if (mDevice.hasObject(By.pkg(getLauncherPackageName()).depth(0))) { 677 return "<Launcher in invalid state>"; 678 } 679 return "LaunchedApp (" + getVisiblePackages() + ")"; 680 } 681 setSystemHealthSupplier(Function<Long, String> supplier)682 public void setSystemHealthSupplier(Function<Long, String> supplier) { 683 this.mSystemHealthSupplier = supplier; 684 } 685 onTestStart()686 public void onTestStart() { 687 mTestStartTime = System.currentTimeMillis(); 688 } 689 onTestFinish()690 public void onTestFinish() { 691 mTestStartTime = -1; 692 } 693 formatSystemHealthMessage(String message)694 private String formatSystemHealthMessage(String message) { 695 final String testPackage = getContext().getPackageName(); 696 697 mInstrumentation.getUiAutomation().grantRuntimePermission( 698 testPackage, "android.permission.READ_LOGS"); 699 mInstrumentation.getUiAutomation().grantRuntimePermission( 700 testPackage, "android.permission.PACKAGE_USAGE_STATS"); 701 702 if (mTestStartTime > 0) { 703 final String systemHealth = mSystemHealthSupplier != null 704 ? mSystemHealthSupplier.apply(mTestStartTime) 705 : TestHelpers.getSystemHealthMessage(getContext(), mTestStartTime); 706 707 if (systemHealth != null) { 708 message += ";\nPerhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n" 709 + systemHealth + "\n>>>>>>>>>>>>>>>>>>"; 710 } 711 } 712 Log.d(TAG, "About to throw the error: " + message, new Exception()); 713 return message; 714 } 715 formatErrorWithEvents(String message, boolean checkEvents)716 private String formatErrorWithEvents(String message, boolean checkEvents) { 717 if (mEventChecker != null) { 718 final LogEventChecker eventChecker = mEventChecker; 719 mEventChecker = null; 720 if (checkEvents) { 721 final String eventMismatch = eventChecker.verify(0); 722 if (eventMismatch != null) { 723 message = message + ";\n" + eventMismatch; 724 } 725 } else { 726 eventChecker.finishNoWait(); 727 } 728 } 729 730 dumpDiagnostics(message); 731 732 log("Hierarchy dump for: " + message); 733 dumpViewHierarchy(); 734 735 return message; 736 } 737 dumpDiagnostics(String message)738 private void dumpDiagnostics(String message) { 739 log("Diagnostics for failure: " + message); 740 log("Input:"); 741 logShellCommand("dumpsys input"); 742 log("TIS:"); 743 logShellCommand("dumpsys activity service TouchInteractionService"); 744 } 745 logShellCommand(String command)746 private void logShellCommand(String command) { 747 try { 748 for (String line : mDevice.executeShellCommand(command).split("\\n")) { 749 SystemClock.sleep(10); 750 log(line); 751 } 752 } catch (IOException e) { 753 log("Failed to execute " + command); 754 } 755 } 756 cleanUpInputStream()757 private void cleanUpInputStream() { 758 if (!mWaitingForMotionUpEvent) { 759 return; 760 } 761 long downTime = SystemClock.uptimeMillis(); 762 MotionEvent cleanUpEvent = getMotionEvent( 763 downTime, 764 downTime, 765 MotionEvent.ACTION_UP, 766 0, 767 0, 768 InputDevice.SOURCE_TOUCHSCREEN, 769 Configurator.getInstance().getToolType()); 770 log("Test failed while a ACTION_UP event was still pending. " 771 + "Cleaning up the input stream by sending an ACTION_UP event forcefully: " 772 + "event= " + cleanUpEvent); 773 774 injectEventUnchecked(cleanUpEvent); 775 mWaitingForMotionUpEvent = false; 776 } 777 fail(String message)778 void fail(String message) { 779 cleanUpInputStream(); 780 checkForAnomaly(); 781 if (mOnFailure != null) mOnFailure.run(); 782 Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( 783 "http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription() 784 + "; now visible state is " + getVisibleStateMessage(), true))); 785 } 786 getContextDescription()787 private String getContextDescription() { 788 return mDiagnosticContext.isEmpty() 789 ? "(no context)" : String.join(", ", mDiagnosticContext); 790 } 791 assertTrue(String message, boolean condition)792 void assertTrue(String message, boolean condition) { 793 if (!condition) { 794 fail(message); 795 } 796 } 797 assertNotNull(String message, Object object)798 void assertNotNull(String message, Object object) { 799 assertTrue(message, object != null); 800 } 801 failEquals(String message, Object actual)802 private void failEquals(String message, Object actual) { 803 fail(message + ". " + "Actual: " + actual); 804 } 805 assertEquals(String message, int expected, int actual)806 void assertEquals(String message, int expected, int actual) { 807 if (expected != actual) { 808 fail(message + " expected: " + expected + " but was: " + actual); 809 } 810 } 811 assertEquals(String message, String expected, String actual)812 void assertEquals(String message, String expected, String actual) { 813 if (!TextUtils.equals(expected, actual)) { 814 fail(message + " expected: '" + expected + "' but was: '" + actual + "'"); 815 } 816 } 817 assertEquals(String message, long expected, long actual)818 void assertEquals(String message, long expected, long actual) { 819 if (expected != actual) { 820 fail(message + " expected: " + expected + " but was: " + actual); 821 } 822 } 823 assertNotEquals(String message, int unexpected, int actual)824 void assertNotEquals(String message, int unexpected, int actual) { 825 if (unexpected == actual) { 826 failEquals(message, actual); 827 } 828 } 829 830 /** 831 * Whether to ignore verifying the task bar visibility during instrumenting. 832 * 833 * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly 834 * verifying the task bar visibility with 835 * {@link VisibleContainer#verifyActiveContainer}. 836 * {@code false} otherwise. 837 */ setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility)838 public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) { 839 mIgnoreTaskbarVisibility = ignoreTaskbarVisibility; 840 } 841 842 /** 843 * Set the trackpad gesture type of the interaction. 844 * 845 * @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or 846 * four-finger gesture. 847 */ setTrackpadGestureType(TrackpadGestureType trackpadGestureType)848 public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) { 849 mTrackpadGestureType = trackpadGestureType; 850 } 851 getTrackpadGestureType()852 TrackpadGestureType getTrackpadGestureType() { 853 return mTrackpadGestureType; 854 } 855 856 /** 857 * Sets expected rotation. 858 * TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one. 859 * Null parameter disables checks. The initial state is "no checks". 860 */ setExpectedRotation(Integer expectedRotation)861 public void setExpectedRotation(Integer expectedRotation) { 862 mExpectedRotation = expectedRotation; 863 } 864 setExpectedRotationCheckEnabled(boolean expectedRotationCheckEnabled)865 public void setExpectedRotationCheckEnabled(boolean expectedRotationCheckEnabled) { 866 mExpectedRotationCheckEnabled = expectedRotationCheckEnabled; 867 } 868 getExpectedRotationCheckEnabled()869 public boolean getExpectedRotationCheckEnabled() { 870 return mExpectedRotationCheckEnabled; 871 } 872 getNavigationModeMismatchError(boolean waitForCorrectState)873 public String getNavigationModeMismatchError(boolean waitForCorrectState) { 874 final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0; 875 final NavigationModel navigationModel = getNavigationModel(); 876 String resPackage = getNavigationButtonResPackage(); 877 if (navigationModel == NavigationModel.THREE_BUTTON) { 878 if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) { 879 return "Recents button not present in 3-button mode"; 880 } 881 } else { 882 if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) { 883 return "Recents button is present in non-3-button mode"; 884 } 885 } 886 887 if (navigationModel == NavigationModel.ZERO_BUTTON) { 888 if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) { 889 return "Home button is present in gestural mode"; 890 } 891 } else { 892 if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) { 893 return "Home button not present in non-gestural mode"; 894 } 895 } 896 return null; 897 } 898 getNavigationButtonResPackage()899 private String getNavigationButtonResPackage() { 900 return isTablet() || isTaskbarNavbarUnificationEnabled() 901 ? getLauncherPackageName() : SYSTEMUI_PACKAGE; 902 } 903 verifyContainerType(ContainerType containerType)904 UiObject2 verifyContainerType(ContainerType containerType) { 905 waitForLauncherInitialized(); 906 907 if (mExpectedRotationCheckEnabled && mExpectedRotation != null) { 908 assertEquals("Unexpected display rotation", 909 mExpectedRotation, mDevice.getDisplayRotation()); 910 } 911 912 final String error = getNavigationModeMismatchError(true); 913 assertTrue(error, error == null); 914 915 log("verifyContainerType: " + containerType); 916 917 final UiObject2 container = verifyVisibleObjects(containerType); 918 919 return container; 920 } 921 verifyVisibleObjects(ContainerType containerType)922 private UiObject2 verifyVisibleObjects(ContainerType containerType) { 923 try (Closable c = addContextLayer( 924 "but the current state is not " + containerType.name())) { 925 switch (containerType) { 926 case WORKSPACE: { 927 waitUntilLauncherObjectGone(APPS_RES_ID); 928 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 929 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 930 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 931 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 932 if (isTaskbarShownOnHome()) { 933 waitForSystemLauncherObject(TASKBAR_RES_ID); 934 } else { 935 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 936 } 937 938 return waitForLauncherObject(WORKSPACE_RES_ID); 939 } 940 case WIDGETS: { 941 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 942 waitUntilLauncherObjectGone(APPS_RES_ID); 943 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 944 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 945 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 946 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 947 948 return waitForLauncherObject(WIDGETS_RES_ID); 949 } 950 case TASKBAR_ALL_APPS: { 951 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 952 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 953 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 954 if (isTransientTaskbar()) { 955 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 956 } 957 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 958 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 959 960 return waitForLauncherObject(APPS_RES_ID); 961 } 962 case HOME_ALL_APPS: { 963 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 964 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 965 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 966 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 967 968 if ((is3PLauncher() && isTablet() && !isTransientTaskbar()) 969 || isTaskbarShownOnHome()) { 970 waitForSystemLauncherObject(TASKBAR_RES_ID); 971 } else { 972 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 973 } 974 975 boolean splitSelectionActive = getTestInfo(REQUEST_GET_SPLIT_SELECTION_ACTIVE) 976 .getBoolean(TEST_INFO_RESPONSE_FIELD); 977 if (!splitSelectionActive) { 978 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 979 } // do nothing, we expect that view 980 981 return waitForLauncherObject(APPS_RES_ID); 982 } 983 case OVERVIEW: 984 case FALLBACK_OVERVIEW: { 985 waitUntilLauncherObjectGone(APPS_RES_ID); 986 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 987 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 988 if (isTablet() && !is3PLauncher() && !isRecentsWindowEnabled()) { 989 waitForSystemLauncherObject(TASKBAR_RES_ID); 990 } else { 991 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 992 } 993 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 994 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 995 996 return waitForSystemLauncherObject(OVERVIEW_RES_ID); 997 } 998 case SPLIT_SCREEN_SELECT: { 999 waitUntilLauncherObjectGone(APPS_RES_ID); 1000 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 1001 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 1002 if (isTablet()) { 1003 waitForSystemLauncherObject(TASKBAR_RES_ID); 1004 } else { 1005 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 1006 } 1007 1008 waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID); 1009 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 1010 return waitForSystemLauncherObject(OVERVIEW_RES_ID); 1011 } 1012 case LAUNCHED_APP: { 1013 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 1014 waitUntilLauncherObjectGone(APPS_RES_ID); 1015 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 1016 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 1017 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 1018 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 1019 1020 if (mIgnoreTaskbarVisibility) { 1021 return null; 1022 } 1023 1024 if (isTablet()) { 1025 // Only check that Persistent Taskbar is visible, since Transient Taskbar 1026 // may or may not be visible by design. 1027 if (!isTransientTaskbar()) { 1028 waitForSystemLauncherObject(TASKBAR_RES_ID); 1029 } 1030 } else { 1031 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 1032 } 1033 return null; 1034 } 1035 default: 1036 fail("Invalid state: " + containerType); 1037 return null; 1038 } 1039 } 1040 } 1041 isRecentsWindowEnabled()1042 boolean isRecentsWindowEnabled() { 1043 return getTestInfo(TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED) 1044 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1045 } 1046 waitForModelQueueCleared()1047 public void waitForModelQueueCleared() { 1048 getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED); 1049 } 1050 waitForLauncherInitialized()1051 public void waitForLauncherInitialized() { 1052 try { 1053 Trace.beginSection("waitForLauncherInitialized"); 1054 for (int i = 0; i < 100; ++i) { 1055 if (getTestInfo(TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).getBoolean( 1056 TestProtocol.TEST_INFO_RESPONSE_FIELD)) { 1057 return; 1058 } 1059 SystemClock.sleep(100); 1060 } 1061 checkForAnomaly(); 1062 fail("Launcher didn't initialize"); 1063 } finally { 1064 Trace.endSection(); 1065 } 1066 } 1067 isLauncherActivityStarted()1068 public boolean isLauncherActivityStarted() { 1069 return getTestInfo( 1070 TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED). 1071 getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1072 } 1073 executeAndWaitForLauncherEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)1074 Parcelable executeAndWaitForLauncherEvent(Runnable command, 1075 UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, 1076 String actionName) { 1077 return executeAndWaitForEvent( 1078 command, 1079 e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e), 1080 message, actionName); 1081 } 1082 executeAndWaitForEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)1083 Parcelable executeAndWaitForEvent(Runnable command, 1084 UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, 1085 String actionName) { 1086 try (LauncherInstrumentation.Closable c = addContextLayer(actionName)) { 1087 try { 1088 final AccessibilityEvent event = 1089 mInstrumentation.getUiAutomation().executeAndWaitForEvent( 1090 command, eventFilter, WAIT_TIME_MS); 1091 assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event); 1092 final Parcelable parcelableData = event.getParcelableData(); 1093 event.recycle(); 1094 return parcelableData; 1095 } catch (TimeoutException e) { 1096 fail(message.get()); 1097 return null; 1098 } 1099 } 1100 } 1101 executeAndWaitForLauncherStop(Runnable command, String actionName)1102 void executeAndWaitForLauncherStop(Runnable command, String actionName) { 1103 executeAndWaitForLauncherEvent( 1104 () -> command.run(), 1105 event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE 1106 .equals(event.getClassName().toString()), 1107 () -> "Launcher activity didn't stop", actionName); 1108 } 1109 1110 /** 1111 * Get the resource ID of visible floating view. 1112 */ getFloatingResId()1113 private Optional<String> getFloatingResId() { 1114 if (hasLauncherObject(CONTEXT_MENU_RES_ID)) { 1115 return Optional.of(CONTEXT_MENU_RES_ID); 1116 } 1117 if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) { 1118 return Optional.of(FOLDER_CONTENT_RES_ID); 1119 } 1120 return Optional.empty(); 1121 } 1122 1123 /** 1124 * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content. 1125 */ swipeUpToCloseFloatingView()1126 private void swipeUpToCloseFloatingView() { 1127 final Point displaySize = getRealDisplaySize(); 1128 1129 final Optional<String> floatingRes = getFloatingResId(); 1130 1131 if (!floatingRes.isPresent()) { 1132 return; 1133 } 1134 1135 if (isLauncher3()) { 1136 gestureToDismissPopup(displaySize); 1137 } else { 1138 runToState(() -> gestureToDismissPopup(displaySize), NORMAL_STATE_ORDINAL, "swiping"); 1139 } 1140 1141 try (LauncherInstrumentation.Closable c1 = addContextLayer( 1142 String.format("Swiped up from floating view %s to home", floatingRes.get()))) { 1143 waitUntilLauncherObjectGone(floatingRes.get()); 1144 waitForLauncherObject(getAnyObjectSelector()); 1145 } 1146 } 1147 gestureToDismissPopup(Point displaySize)1148 private void gestureToDismissPopup(Point displaySize) { 1149 linearGesture( 1150 displaySize.x / 2, displaySize.y - 1, 1151 displaySize.x / 2, 0, 1152 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 1153 false, GestureScope.EXPECT_PILFER); 1154 } 1155 1156 /** 1157 * @return the Workspace object. 1158 * @deprecated use goHome(). 1159 * Presses nav bar home button. 1160 */ 1161 @Deprecated pressHome()1162 public Workspace pressHome() { 1163 return goHome(); 1164 } 1165 1166 /** 1167 * Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then 1168 * performing {@code goHome()} action. 1169 * 1170 * @return the Workspace object. 1171 */ goHomeFromImmersiveFullscreenApp()1172 public Workspace goHomeFromImmersiveFullscreenApp() { 1173 final boolean navBarCanMove = getNavBarCanMove(); 1174 if (getNavigationModel() == NavigationModel.ZERO_BUTTON || !navBarCanMove) { 1175 // in gesture nav we can swipe up at the bottom to bring the navbar handle 1176 final Point displaySize = getRealDisplaySize(); 1177 linearGesture( 1178 displaySize.x / 2, displaySize.y - 1, 1179 displaySize.x / 2, 0, 1180 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 1181 false, GestureScope.EXPECT_PILFER); 1182 } else { 1183 // in 3 button nav we swipe up on the side edge of the screen to bring the navbar 1184 final boolean rotated90degrees = mDevice.getDisplayRotation() == ROTATION_90; 1185 final Point displaySize = getRealDisplaySize(); 1186 final int startX = rotated90degrees ? displaySize.x : 0; 1187 final int endX = rotated90degrees ? 0 : displaySize.x; 1188 linearGesture( 1189 startX, displaySize.y / 2, 1190 endX, displaySize.y / 2, 1191 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 1192 false, GestureScope.EXPECT_PILFER); 1193 } 1194 return goHome(); 1195 } 1196 1197 /** 1198 * Goes to home by swiping up in zero-button mode or pressing Home button. 1199 * Calling it after another TAPL call is safe because all TAPL methods wait for the animations 1200 * to finish. 1201 * When calling it after a non-TAPL method, make sure that all animations have already 1202 * completed, otherwise it may detect the current state (for example "Application" or "Home") 1203 * incorrectly. 1204 * The method expects either app or Launcher to be active when it's called. Other states, such 1205 * as visible notification shade are not supported. 1206 * 1207 * @return the Workspace object. 1208 */ goHome()1209 public Workspace goHome() { 1210 try (LauncherInstrumentation.Closable e = eventsCheck(); 1211 LauncherInstrumentation.Closable c = addContextLayer("want to switch to home")) { 1212 waitForLauncherInitialized(); 1213 // Click home, then wait for any accessibility event, then wait until accessibility 1214 // events stop. 1215 // We need waiting for any accessibility event generated after pressing Home because 1216 // otherwise waitForIdle may return immediately in case when there was a big enough 1217 // pause in accessibility events prior to pressing Home. 1218 boolean isThreeFingerTrackpadGesture = 1219 mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 1220 final String action; 1221 if (getNavigationModel() == NavigationModel.ZERO_BUTTON 1222 || isThreeFingerTrackpadGesture) { 1223 checkForAnomaly(false, true); 1224 1225 final Point displaySize = getRealDisplaySize(); 1226 1227 // CLose floating views before going back to home. 1228 swipeUpToCloseFloatingView(); 1229 1230 if (hasLauncherObject(WORKSPACE_RES_ID)) { 1231 log(action = "already at home"); 1232 } else { 1233 action = "swiping up to home"; 1234 1235 int startY = isThreeFingerTrackpadGesture ? displaySize.y * 3 / 4 1236 : displaySize.y - 1; 1237 int endY = isThreeFingerTrackpadGesture ? displaySize.y / 4 : displaySize.y / 2; 1238 swipeToState( 1239 displaySize.x / 2, startY, 1240 displaySize.x / 2, endY, 1241 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL, 1242 GestureScope.EXPECT_PILFER); 1243 } 1244 } else { 1245 log("Hierarchy before clicking home:"); 1246 dumpViewHierarchy(); 1247 action = "clicking home button"; 1248 runToState( 1249 getHomeButton()::click, 1250 NORMAL_STATE_ORDINAL, 1251 !hasLauncherObject(WORKSPACE_RES_ID) 1252 && (hasLauncherObject(APPS_RES_ID) 1253 || hasSystemLauncherObject(OVERVIEW_RES_ID)), 1254 action); 1255 } 1256 try (LauncherInstrumentation.Closable c1 = addContextLayer( 1257 "performed action to switch to Home - " + action)) { 1258 return getWorkspace(); 1259 } 1260 } 1261 } 1262 1263 /** 1264 * Press navbar back button or swipe back if in gesture navigation mode. 1265 */ pressBack()1266 public void pressBack() { 1267 try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) { 1268 pressBackImpl(); 1269 } 1270 } 1271 pressBackImpl()1272 void pressBackImpl() { 1273 waitForLauncherInitialized(); 1274 final boolean launcherVisible = 1275 (isTablet() || isTaskbarNavbarUnificationEnabled()) ? isLauncherContainerVisible() 1276 : isLauncherVisible(); 1277 boolean isThreeFingerTrackpadGesture = 1278 mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 1279 if (getNavigationModel() == NavigationModel.ZERO_BUTTON 1280 || isThreeFingerTrackpadGesture) { 1281 final Point displaySize = getRealDisplaySize(); 1282 int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0; 1283 int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2; 1284 linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4, 1285 10, false, GestureScope.DONT_EXPECT_PILFER); 1286 } else { 1287 waitForNavigationUiObject("back").click(); 1288 } 1289 if (launcherVisible) { 1290 if (isPredictiveBackSwipeEnabled()) { 1291 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED); 1292 } else { 1293 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP); 1294 } 1295 } 1296 } 1297 getAnyObjectSelector()1298 private static BySelector getAnyObjectSelector() { 1299 return By.textStartsWith(""); 1300 } 1301 isLauncherVisible()1302 boolean isLauncherVisible() { 1303 try { 1304 Trace.beginSection("isLauncherVisible"); 1305 mDevice.waitForIdle(); 1306 return hasLauncherObject(getAnyObjectSelector()); 1307 } finally { 1308 Trace.endSection(); 1309 } 1310 } 1311 isLauncherContainerVisible()1312 boolean isLauncherContainerVisible() { 1313 final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID}; 1314 return Arrays.stream(containerResources).anyMatch( 1315 r -> r.equals(OVERVIEW_RES_ID) ? hasSystemLauncherObject(r) : hasLauncherObject(r)); 1316 } 1317 1318 /** 1319 * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the 1320 * launcher is not in that state. 1321 * 1322 * @return Workspace object. 1323 */ 1324 @NonNull getWorkspace()1325 public Workspace getWorkspace() { 1326 try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) { 1327 return new Workspace(this); 1328 } 1329 } 1330 1331 /** 1332 * Gets the LaunchedApp object if another app is active. Fails if the launcher is not in that 1333 * state. 1334 * 1335 * @return LaunchedApp object. 1336 */ 1337 @NonNull getLaunchedAppState()1338 public LaunchedAppState getLaunchedAppState() { 1339 return new LaunchedAppState(this); 1340 } 1341 1342 /** 1343 * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is 1344 * not in that state. 1345 * 1346 * @return Widgets object. 1347 */ 1348 @NonNull getAllWidgets()1349 public Widgets getAllWidgets() { 1350 try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) { 1351 return new Widgets(this); 1352 } 1353 } 1354 1355 @NonNull getAddToHomeScreenPrompt()1356 public AddToHomeScreenPrompt getAddToHomeScreenPrompt() { 1357 try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) { 1358 return new AddToHomeScreenPrompt(this); 1359 } 1360 } 1361 1362 /** 1363 * Gets the Overview object if the current state is showing the overview panel. Fails if the 1364 * launcher is not in that state. 1365 * 1366 * @return Overview object. 1367 */ 1368 @NonNull getOverview()1369 public Overview getOverview() { 1370 try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) { 1371 return new Overview(this); 1372 } 1373 } 1374 1375 /** 1376 * Gets the homescreen All Apps object if the current state is showing the all apps panel opened 1377 * by swiping from workspace. Fails if the launcher is not in that state. Please don't call this 1378 * method if App Apps was opened by swiping up from Overview, as it won't fail and will return 1379 * an incorrect object. 1380 * 1381 * @return Home All Apps object. 1382 */ 1383 @NonNull getAllApps()1384 public HomeAllApps getAllApps() { 1385 try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) { 1386 return new HomeAllApps(this); 1387 } 1388 } 1389 assertAppLaunched(@onNull String expectedPackageName)1390 LaunchedAppState assertAppLaunched(@NonNull String expectedPackageName) { 1391 BySelector packageSelector = By.pkg(expectedPackageName); 1392 assertTrue("App didn't start: (" + packageSelector + ")", 1393 mDevice.wait(Until.hasObject(packageSelector), 1394 LauncherInstrumentation.WAIT_TIME_MS)); 1395 return new LaunchedAppState(this); 1396 } 1397 waitUntilLauncherObjectGone(String resId)1398 void waitUntilLauncherObjectGone(String resId) { 1399 waitUntilGoneBySelector(getLauncherObjectSelector(resId)); 1400 } 1401 waitUntilOverviewObjectGone(String resId)1402 void waitUntilOverviewObjectGone(String resId) { 1403 waitUntilGoneBySelector(getOverviewObjectSelector(resId)); 1404 } 1405 waitUntilSystemLauncherObjectGone(String resId)1406 void waitUntilSystemLauncherObjectGone(String resId) { 1407 if (is3PLauncher()) { 1408 waitUntilOverviewObjectGone(resId); 1409 } else { 1410 waitUntilLauncherObjectGone(resId); 1411 } 1412 } 1413 waitUntilLauncherObjectGone(BySelector selector)1414 void waitUntilLauncherObjectGone(BySelector selector) { 1415 waitUntilGoneBySelector(makeLauncherSelector(selector)); 1416 } 1417 waitUntilGoneBySelector(BySelector launcherSelector)1418 private void waitUntilGoneBySelector(BySelector launcherSelector) { 1419 assertTrue("Unexpected launcher object visible: " + launcherSelector, 1420 mDevice.wait(Until.gone(launcherSelector), 1421 WAIT_TIME_MS)); 1422 } 1423 hasSystemUiObject(String resId)1424 private boolean hasSystemUiObject(String resId) { 1425 return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId)); 1426 } 1427 1428 @NonNull waitForSystemUiObject(String resId)1429 UiObject2 waitForSystemUiObject(String resId) { 1430 final UiObject2 object = mDevice.wait( 1431 Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS); 1432 assertNotNull("Can't find a systemui object with id: " + resId, object); 1433 return object; 1434 } 1435 1436 @NonNull waitForSystemUiObject(BySelector selector)1437 UiObject2 waitForSystemUiObject(BySelector selector) { 1438 final UiObject2 object = TestHelpers.wait( 1439 Until.findObject(selector), WAIT_TIME_MS); 1440 assertNotNull("Can't find a systemui object with selector: " + selector, object); 1441 return object; 1442 } 1443 1444 @NonNull getHomeButton()1445 private UiObject2 getHomeButton() { 1446 UiModeManager uiManager = 1447 (UiModeManager) getContext().getSystemService(Context.UI_MODE_SERVICE); 1448 if (uiManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { 1449 return waitForAssistantHomeButton(); 1450 } else { 1451 return waitForNavigationUiObject("home"); 1452 } 1453 } 1454 1455 /* Assistant Home button is present when system is in car mode. */ 1456 @NonNull waitForAssistantHomeButton()1457 UiObject2 waitForAssistantHomeButton() { 1458 final UiObject2 object = mDevice.wait( 1459 Until.findObject(By.res(ASSISTANT_PACKAGE, ASSISTANT_GO_HOME_RES_ID)), 1460 WAIT_TIME_MS); 1461 assertNotNull( 1462 "Can't find an assistant UI object with id: " + ASSISTANT_GO_HOME_RES_ID, object); 1463 return object; 1464 } 1465 1466 @NonNull waitForNavigationUiObject(String resId)1467 UiObject2 waitForNavigationUiObject(String resId) { 1468 String resPackage = getNavigationButtonResPackage(); 1469 final UiObject2 object = mDevice.wait( 1470 Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS); 1471 assertNotNull("Can't find a navigation UI object with id: " + resId, object); 1472 return object; 1473 } 1474 1475 @Nullable findObjectInContainer(UiObject2 container, String resName)1476 UiObject2 findObjectInContainer(UiObject2 container, String resName) { 1477 try { 1478 return container.findObject(getLauncherObjectSelector(resName)); 1479 } catch (StaleObjectException e) { 1480 fail("The container disappeared from screen"); 1481 return null; 1482 } 1483 } 1484 1485 @Nullable findObjectInContainer(UiObject2 container, BySelector selector)1486 UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) { 1487 try { 1488 return container.findObject(selector); 1489 } catch (StaleObjectException e) { 1490 fail("The container disappeared from screen"); 1491 return null; 1492 } 1493 } 1494 1495 @NonNull getObjectsInContainer(UiObject2 container, String resName)1496 List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) { 1497 try { 1498 return container.findObjects(getLauncherObjectSelector(resName)); 1499 } catch (StaleObjectException e) { 1500 fail("The container disappeared from screen"); 1501 return null; 1502 } 1503 } 1504 1505 @NonNull waitForObjectInContainer(UiObject2 container, String resName)1506 UiObject2 waitForObjectInContainer(UiObject2 container, String resName) { 1507 try { 1508 final UiObject2 object = container.wait( 1509 Until.findObject(getLauncherObjectSelector(resName)), 1510 WAIT_TIME_MS); 1511 assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: " 1512 + container.getResourceName(), object); 1513 return object; 1514 } catch (StaleObjectException e) { 1515 fail("The container disappeared from screen"); 1516 return null; 1517 } 1518 } 1519 waitForObjectEnabled(UiObject2 object, String waitReason)1520 void waitForObjectEnabled(UiObject2 object, String waitReason) { 1521 try { 1522 assertTrue("Timed out waiting for object to be enabled for " + waitReason + " " 1523 + object.getResourceName(), 1524 object.wait(Until.enabled(true), WAIT_TIME_MS)); 1525 } catch (StaleObjectException e) { 1526 fail("The object disappeared from screen"); 1527 } 1528 } 1529 waitForObjectFocused(UiObject2 object, String waitReason)1530 void waitForObjectFocused(UiObject2 object, String waitReason) { 1531 try { 1532 assertTrue("Timed out waiting for object to be focused for " + waitReason + " " 1533 + object.getResourceName(), 1534 object.wait(Until.focused(true), WAIT_TIME_MS)); 1535 } catch (StaleObjectException e) { 1536 fail("The object disappeared from screen"); 1537 } 1538 } 1539 1540 @NonNull waitForObjectInContainer(UiObject2 container, BySelector selector)1541 UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) { 1542 return waitForObjectsInContainer(container, selector).get(0); 1543 } 1544 1545 @NonNull waitForObjectsInContainer( UiObject2 container, BySelector selector)1546 List<UiObject2> waitForObjectsInContainer( 1547 UiObject2 container, BySelector selector) { 1548 try { 1549 final List<UiObject2> objects = container.wait( 1550 Until.findObjects(selector), 1551 WAIT_TIME_MS); 1552 assertNotNull("Can't find views in Launcher, id: " + selector + " in container: " 1553 + container.getResourceName(), objects); 1554 assertTrue("Can't find views in Launcher, id: " + selector + " in container: " 1555 + container.getResourceName(), objects.size() > 0); 1556 return objects; 1557 } catch (StaleObjectException e) { 1558 fail("The container disappeared from screen"); 1559 return null; 1560 } 1561 } 1562 getChildren(UiObject2 container)1563 List<UiObject2> getChildren(UiObject2 container) { 1564 try { 1565 return container.getChildren(); 1566 } catch (StaleObjectException e) { 1567 fail("The container disappeared from screen"); 1568 return null; 1569 } 1570 } 1571 hasLauncherObject(String resId)1572 private boolean hasLauncherObject(String resId) { 1573 return mDevice.hasObject(getLauncherObjectSelector(resId)); 1574 } 1575 hasSystemLauncherObject(String resId)1576 private boolean hasSystemLauncherObject(String resId) { 1577 return mDevice.hasObject(is3PLauncher() ? getOverviewObjectSelector(resId) 1578 : getLauncherObjectSelector(resId)); 1579 } 1580 hasLauncherObject(BySelector selector)1581 boolean hasLauncherObject(BySelector selector) { 1582 return mDevice.hasObject(makeLauncherSelector(selector)); 1583 } 1584 makeLauncherSelector(BySelector selector)1585 private BySelector makeLauncherSelector(BySelector selector) { 1586 return By.copy(selector).pkg(getLauncherPackageName()); 1587 } 1588 1589 @NonNull waitForOverviewObject(String resName)1590 UiObject2 waitForOverviewObject(String resName) { 1591 return waitForObjectBySelector(getOverviewObjectSelector(resName)); 1592 } 1593 1594 @NonNull waitForLauncherObject(String resName)1595 UiObject2 waitForLauncherObject(String resName) { 1596 return waitForObjectBySelector(getLauncherObjectSelector(resName)); 1597 } 1598 1599 @NonNull waitForSystemLauncherObject(String resName)1600 UiObject2 waitForSystemLauncherObject(String resName) { 1601 return is3PLauncher() ? waitForOverviewObject(resName) 1602 : waitForLauncherObject(resName); 1603 } 1604 1605 @NonNull waitForLauncherObject(BySelector selector)1606 UiObject2 waitForLauncherObject(BySelector selector) { 1607 return waitForObjectBySelector(makeLauncherSelector(selector)); 1608 } 1609 1610 @NonNull tryWaitForLauncherObject(BySelector selector, long timeout)1611 UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) { 1612 return tryWaitForObjectBySelector(makeLauncherSelector(selector), timeout); 1613 } 1614 1615 @NonNull waitForAndroidObject(String resId)1616 UiObject2 waitForAndroidObject(String resId) { 1617 final UiObject2 object = TestHelpers.wait( 1618 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS); 1619 assertNotNull("Can't find a android object with id: " + resId, object); 1620 return object; 1621 } 1622 1623 @NonNull waitForObjectsBySelector(BySelector selector)1624 List<UiObject2> waitForObjectsBySelector(BySelector selector) { 1625 final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS); 1626 assertNotNull("Can't find any view in Launcher, selector: " + selector, objects); 1627 return objects; 1628 } 1629 waitForObjectBySelector(BySelector selector)1630 UiObject2 waitForObjectBySelector(BySelector selector) { 1631 final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); 1632 assertNotNull("Can't find a view in Launcher, selector: " + selector, object); 1633 return object; 1634 } 1635 tryWaitForObjectBySelector(BySelector selector, long timeout)1636 private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) { 1637 return mDevice.wait(Until.findObject(selector), timeout); 1638 } 1639 getLauncherObjectSelector(String resName)1640 BySelector getLauncherObjectSelector(String resName) { 1641 return By.res(getLauncherPackageName(), resName); 1642 } 1643 getOverviewObjectSelector(String resName)1644 BySelector getOverviewObjectSelector(String resName) { 1645 return By.res(getOverviewPackageName(), resName); 1646 } 1647 getLauncherPackageName()1648 String getLauncherPackageName() { 1649 return mDevice.getLauncherPackageName(); 1650 } 1651 is3PLauncher()1652 boolean is3PLauncher() { 1653 return !getOverviewPackageName().equals(getLauncherPackageName()); 1654 } 1655 1656 @NonNull getDevice()1657 public UiDevice getDevice() { 1658 return mDevice; 1659 } 1660 eventListToString(List<Integer> actualEvents)1661 private static String eventListToString(List<Integer> actualEvents) { 1662 if (actualEvents.isEmpty()) return "no events"; 1663 1664 return "[" 1665 + actualEvents.stream() 1666 .map(state -> TestProtocol.stateOrdinalToString(state)) 1667 .collect(Collectors.joining(", ")) 1668 + "]"; 1669 } 1670 runToState(Runnable command, int expectedState, boolean requireEvent, String actionName)1671 void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) { 1672 if (requireEvent) { 1673 runToState(command, expectedState, actionName); 1674 } else { 1675 command.run(); 1676 } 1677 } 1678 1679 /** Run an action and wait for the specified Launcher state. */ runToState(Runnable command, int expectedState, String actionName)1680 public void runToState(Runnable command, int expectedState, String actionName) { 1681 final List<Integer> actualEvents = new ArrayList<>(); 1682 executeAndWaitForLauncherEvent( 1683 command, 1684 event -> isSwitchToStateEvent(event, expectedState, actualEvents), 1685 () -> "Failed to receive an event for the state change: expected [" 1686 + TestProtocol.stateOrdinalToString(expectedState) 1687 + "], actual: " + eventListToString(actualEvents), 1688 actionName); 1689 } 1690 isSwitchToStateEvent( AccessibilityEvent event, int expectedState, List<Integer> actualEvents)1691 private boolean isSwitchToStateEvent( 1692 AccessibilityEvent event, int expectedState, List<Integer> actualEvents) { 1693 if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false; 1694 1695 final Bundle parcel = (Bundle) event.getParcelableData(); 1696 final int actualState = parcel.getInt(TestProtocol.STATE_FIELD); 1697 actualEvents.add(actualState); 1698 return actualState == expectedState; 1699 } 1700 swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, GestureScope gestureScope)1701 void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, 1702 GestureScope gestureScope) { 1703 runToState( 1704 () -> linearGesture(startX, startY, endX, endY, steps, false, gestureScope), 1705 expectedState, 1706 "swiping"); 1707 } 1708 getBottomGestureSize()1709 int getBottomGestureSize() { 1710 return Math.max(getWindowInsets().bottom, ResourceUtils.getNavbarSize( 1711 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1; 1712 } 1713 getBottomGestureMarginInContainer(UiObject2 container)1714 int getBottomGestureMarginInContainer(UiObject2 container) { 1715 final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen(); 1716 return getVisibleBounds(container).bottom - bottomGestureStartOnScreen; 1717 } 1718 getRightGestureMarginInContainer(UiObject2 container)1719 int getRightGestureMarginInContainer(UiObject2 container) { 1720 final int rightGestureStartOnScreen = getRightGestureStartOnScreen(); 1721 return getVisibleBounds(container).right - rightGestureStartOnScreen; 1722 } 1723 getBottomGestureStartOnScreen()1724 int getBottomGestureStartOnScreen() { 1725 return getRealDisplaySize().y - getBottomGestureSize(); 1726 } 1727 getRightGestureStartOnScreen()1728 int getRightGestureStartOnScreen() { 1729 return getRealDisplaySize().x - getWindowInsets().right - 1; 1730 } 1731 1732 /** 1733 * Click on the ui object right away without waiting for animation. 1734 * 1735 * [UiObject2.click] would wait for all animations finished before clicking. Not waiting for 1736 * animations because in some scenarios there is a playing animations when the click is 1737 * attempted. 1738 */ clickObject(UiObject2 uiObject)1739 void clickObject(UiObject2 uiObject) { 1740 final long clickTime = SystemClock.uptimeMillis(); 1741 final Point center = uiObject.getVisibleCenter(); 1742 sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center, 1743 GestureScope.DONT_EXPECT_PILFER); 1744 sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center, 1745 GestureScope.DONT_EXPECT_PILFER); 1746 } 1747 clickLauncherObject(UiObject2 object)1748 void clickLauncherObject(UiObject2 object) { 1749 clickObject(object); 1750 } 1751 scrollToLastVisibleRow( UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, int appsListBottomPadding)1752 void scrollToLastVisibleRow( 1753 UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, 1754 int appsListBottomPadding) { 1755 final int itemRowCurrentTopOnScreen = bottomVisibleIconBounds.top; 1756 final Rect containerRect = getVisibleBounds(container); 1757 final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer; 1758 final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop(); 1759 1760 scrollDownByDistance(container, distance, appsListBottomPadding); 1761 } 1762 1763 /** Scrolls up by given distance within the container. */ scrollUpByDistance(UiObject2 container, int distance)1764 void scrollUpByDistance(UiObject2 container, int distance) { 1765 scrollUpByDistance(container, distance, 0); 1766 } 1767 1768 /** Scrolls up by given distance within the container considering the given bottom padding. */ scrollUpByDistance(UiObject2 container, int distance, int bottomPadding)1769 void scrollUpByDistance(UiObject2 container, int distance, int bottomPadding) { 1770 final Rect containerRect = getVisibleBounds(container); 1771 final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); 1772 scroll( 1773 container, 1774 Direction.UP, 1775 new Rect( 1776 0, 1777 containerRect.height() - bottomGestureMarginInContainer - distance, 1778 0, 1779 bottomGestureMarginInContainer + bottomPadding), 1780 /* steps= */ 10, 1781 /* slowDown= */ true); 1782 } 1783 scrollDownByDistance(UiObject2 container, int distance)1784 void scrollDownByDistance(UiObject2 container, int distance) { 1785 scrollDownByDistance(container, distance, 0); 1786 } 1787 scrollDownByDistance(UiObject2 container, int distance, int bottomPadding)1788 void scrollDownByDistance(UiObject2 container, int distance, int bottomPadding) { 1789 final Rect containerRect = getVisibleBounds(container); 1790 final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); 1791 scroll( 1792 container, 1793 Direction.DOWN, 1794 new Rect( 1795 0, 1796 containerRect.height() - distance - bottomGestureMarginInContainer, 1797 0, 1798 bottomGestureMarginInContainer + bottomPadding), 1799 /* steps= */ 10, 1800 /* slowDown= */ true); 1801 } 1802 scrollLeftByDistance(UiObject2 container, int distance)1803 void scrollLeftByDistance(UiObject2 container, int distance) { 1804 final Rect containerRect = getVisibleBounds(container); 1805 final int rightGestureMarginInContainer = getRightGestureMarginInContainer(container); 1806 final int leftGestureMargin = getTargetInsets().left + getEdgeSensitivityWidth(); 1807 scroll( 1808 container, 1809 Direction.LEFT, 1810 new Rect(leftGestureMargin, 1811 0, 1812 Math.max(containerRect.width() - distance - leftGestureMargin, 1813 rightGestureMarginInContainer), 1814 0), 1815 10, 1816 true); 1817 } 1818 scroll( UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown)1819 void scroll( 1820 UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) { 1821 final Rect rect = getVisibleBounds(container); 1822 if (margins != null) { 1823 rect.left += margins.left; 1824 rect.top += margins.top; 1825 rect.right -= margins.right; 1826 rect.bottom -= margins.bottom; 1827 } 1828 1829 final int startX; 1830 final int startY; 1831 final int endX; 1832 final int endY; 1833 1834 switch (direction) { 1835 case UP: { 1836 startX = endX = rect.centerX(); 1837 startY = rect.top; 1838 endY = rect.bottom - 1; 1839 } 1840 break; 1841 case DOWN: { 1842 startX = endX = rect.centerX(); 1843 startY = rect.bottom - 1; 1844 endY = rect.top; 1845 } 1846 break; 1847 case LEFT: { 1848 startY = endY = rect.centerY(); 1849 startX = rect.left; 1850 endX = rect.right - 1; 1851 } 1852 break; 1853 case RIGHT: { 1854 startY = endY = rect.centerY(); 1855 startX = rect.right - 1; 1856 endX = rect.left; 1857 } 1858 break; 1859 default: 1860 fail("Unsupported direction"); 1861 return; 1862 } 1863 1864 executeAndWaitForLauncherEvent( 1865 () -> linearGesture( 1866 startX, startY, endX, endY, steps, slowDown, 1867 GestureScope.DONT_EXPECT_PILFER), 1868 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), 1869 () -> "Didn't receive a scroll end message: " + startX + ", " + startY 1870 + ", " + endX + ", " + endY, 1871 "scrolling"); 1872 } 1873 pointerScroll(float pointerX, float pointerY, Direction direction)1874 void pointerScroll(float pointerX, float pointerY, Direction direction) { 1875 executeAndWaitForLauncherEvent( 1876 () -> injectEvent(getPointerMotionEvent( 1877 ACTION_SCROLL, pointerX, pointerY, direction)), 1878 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), 1879 () -> "Didn't receive a scroll end message: " + direction + " scroll from (" 1880 + pointerX + ", " + pointerY + ")", 1881 "scrolling"); 1882 } 1883 1884 // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a 1885 // fixed interval each time. linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown, GestureScope gestureScope)1886 public void linearGesture(int startX, int startY, int endX, int endY, int steps, 1887 boolean slowDown, GestureScope gestureScope) { 1888 log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY); 1889 final long downTime = SystemClock.uptimeMillis(); 1890 final Point start = new Point(startX, startY); 1891 final Point end = new Point(endX, endY); 1892 long endTime = downTime; 1893 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope); 1894 try { 1895 1896 if (mTrackpadGestureType != TrackpadGestureType.NONE) { 1897 sendPointer(downTime, downTime, 1898 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1), 1899 start, gestureScope); 1900 if (mTrackpadGestureType == TrackpadGestureType.THREE_FINGER 1901 || mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) { 1902 sendPointer(downTime, downTime, 1903 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2), 1904 start, gestureScope); 1905 if (mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) { 1906 sendPointer(downTime, downTime, 1907 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 3), 1908 start, gestureScope); 1909 } 1910 } 1911 } 1912 endTime = movePointer( 1913 start, end, steps, false, downTime, downTime, slowDown, gestureScope); 1914 } finally { 1915 if (mTrackpadGestureType != TrackpadGestureType.NONE) { 1916 for (int i = mPointerCount; i >= 2; i--) { 1917 sendPointer(downTime, downTime, 1918 getPointerAction(MotionEvent.ACTION_POINTER_UP, i - 1), 1919 start, gestureScope); 1920 } 1921 } 1922 sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope); 1923 } 1924 } 1925 getPointerAction(int action, int index)1926 private static int getPointerAction(int action, int index) { 1927 return action + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT); 1928 } 1929 movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, long startTime, boolean slowDown, GestureScope gestureScope)1930 long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, 1931 long startTime, boolean slowDown, GestureScope gestureScope) { 1932 long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS, 1933 isDecelerating, start, end, gestureScope); 1934 if (slowDown) { 1935 endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end, 1936 end, gestureScope); 1937 } 1938 return endTime; 1939 } 1940 waitForIdle()1941 void waitForIdle() { 1942 mDevice.waitForIdle(); 1943 } 1944 getTouchSlop()1945 int getTouchSlop() { 1946 return ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1947 } 1948 getResources()1949 public Resources getResources() { 1950 return getContext().getResources(); 1951 } 1952 getPointerMotionEvent( int action, float x, float y, Direction direction)1953 private static MotionEvent getPointerMotionEvent( 1954 int action, float x, float y, Direction direction) { 1955 MotionEvent.PointerCoords[] coordinates = new MotionEvent.PointerCoords[1]; 1956 coordinates[0] = new MotionEvent.PointerCoords(); 1957 coordinates[0].x = x; 1958 coordinates[0].y = y; 1959 boolean isVertical = direction == Direction.UP || direction == Direction.DOWN; 1960 boolean isForward = direction == Direction.RIGHT || direction == Direction.DOWN; 1961 coordinates[0].setAxisValue( 1962 isVertical ? MotionEvent.AXIS_VSCROLL : MotionEvent.AXIS_HSCROLL, 1963 isForward ? 1f : -1f); 1964 1965 MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1]; 1966 properties[0] = new MotionEvent.PointerProperties(); 1967 properties[0].id = 0; 1968 properties[0].toolType = MotionEvent.TOOL_TYPE_MOUSE; 1969 1970 final long downTime = SystemClock.uptimeMillis(); 1971 return MotionEvent.obtain( 1972 downTime, 1973 downTime, 1974 action, 1975 /* pointerCount= */ 1, 1976 properties, 1977 coordinates, 1978 /* metaState= */ 0, 1979 /* buttonState= */ 0, 1980 /* xPrecision= */ 1f, 1981 /* yPrecision= */ 1f, 1982 /* deviceId= */ 0, 1983 /* edgeFlags= */ 0, 1984 InputDevice.SOURCE_CLASS_POINTER, 1985 /* flags= */ 0); 1986 } 1987 getTrackpadMotionEvent(long downTime, long eventTime, int action, float x, float y, int pointerCount, TrackpadGestureType gestureType)1988 private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime, 1989 int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) { 1990 MotionEvent.PointerProperties[] pointerProperties = 1991 new MotionEvent.PointerProperties[pointerCount]; 1992 MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount]; 1993 boolean isMultiFingerGesture = gestureType != TrackpadGestureType.TWO_FINGER; 1994 for (int i = 0; i < pointerCount; i++) { 1995 pointerProperties[i] = getPointerProperties(i); 1996 pointerCoords[i] = getPointerCoords(x, y); 1997 if (isMultiFingerGesture) { 1998 pointerCoords[i].setAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT, 1999 gestureType == TrackpadGestureType.THREE_FINGER ? 3 : 4); 2000 } 2001 } 2002 return MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerProperties, 2003 pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, 2004 InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_CLASS_POINTER, 0, 0, 2005 isMultiFingerGesture ? MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE 2006 : MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE); 2007 } 2008 getMotionEvent(long downTime, long eventTime, int action, float x, float y, int source, int toolType)2009 private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, 2010 float x, float y, int source, int toolType) { 2011 return MotionEvent.obtain(downTime, eventTime, action, 1, 2012 new MotionEvent.PointerProperties[]{getPointerProperties(0, toolType)}, 2013 new MotionEvent.PointerCoords[]{getPointerCoords(x, y)}, 2014 0, 0, 1.0f, 1.0f, 0, 0, source, 0); 2015 } 2016 getPointerProperties(int pointerId)2017 private static MotionEvent.PointerProperties getPointerProperties(int pointerId) { 2018 return getPointerProperties(pointerId, Configurator.getInstance().getToolType()); 2019 } 2020 getPointerProperties(int pointerId, int toolType)2021 private static MotionEvent.PointerProperties getPointerProperties(int pointerId, int toolType) { 2022 MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties(); 2023 properties.id = pointerId; 2024 properties.toolType = toolType; 2025 return properties; 2026 } 2027 getPointerCoords(float x, float y)2028 private static MotionEvent.PointerCoords getPointerCoords(float x, float y) { 2029 MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); 2030 coords.pressure = 1; 2031 coords.size = 1; 2032 coords.x = x; 2033 coords.y = y; 2034 return coords; 2035 } 2036 hasTIS()2037 private boolean hasTIS() { 2038 return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean( 2039 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2040 } 2041 isGridOnlyOverviewEnabled()2042 public boolean isGridOnlyOverviewEnabled() { 2043 return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean( 2044 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2045 } 2046 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope)2047 public void sendPointer(long downTime, long currentTime, int action, Point point, 2048 GestureScope gestureScope) { 2049 sendPointer(downTime, currentTime, action, point, gestureScope, 2050 InputDevice.SOURCE_TOUCHSCREEN, false); 2051 } 2052 injectEvent(InputEvent event)2053 private void injectEvent(InputEvent event) { 2054 if (event instanceof MotionEvent motionEvent) { 2055 switch (motionEvent.getAction()) { 2056 case MotionEvent.ACTION_DOWN: 2057 assertTrue("Attempting to inject a second ACTION_DOWN event before a " 2058 + "ACTION_UP event: " + event, 2059 !mWaitingForMotionUpEvent); 2060 mWaitingForMotionUpEvent = true; 2061 break; 2062 case MotionEvent.ACTION_CANCEL: 2063 case MotionEvent.ACTION_UP: 2064 assertTrue("Attempting to inject an unexpected ACTION_UP event: " + event, 2065 mWaitingForMotionUpEvent); 2066 mWaitingForMotionUpEvent = false; 2067 2068 break; 2069 default: 2070 // Nothing to do 2071 break; 2072 } 2073 } 2074 assertTrue("injectInputEvent failed: event=" + event, injectEventUnchecked(event)); 2075 } 2076 injectEventUnchecked(InputEvent event)2077 private boolean injectEventUnchecked(InputEvent event) { 2078 boolean result = mInstrumentation.getUiAutomation().injectInputEvent(event, true, false); 2079 2080 // Only MotionEvents need to be recycled. 2081 if (event instanceof MotionEvent motionEvent) { 2082 motionEvent.recycle(); 2083 } 2084 2085 return result; 2086 } 2087 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source)2088 public void sendPointer(long downTime, long currentTime, int action, Point point, 2089 GestureScope gestureScope, int source) { 2090 sendPointer(downTime, currentTime, action, point, gestureScope, source, false); 2091 } 2092 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source, boolean isRightClick)2093 public void sendPointer(long downTime, long currentTime, int action, Point point, 2094 GestureScope gestureScope, int source, boolean isRightClick) { 2095 sendPointer( 2096 downTime, 2097 currentTime, 2098 action, 2099 point, 2100 gestureScope, 2101 source, 2102 isRightClick, 2103 Configurator.getInstance().getToolType()); 2104 } 2105 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source, boolean isRightClick, int toolType)2106 public void sendPointer(long downTime, long currentTime, int action, Point point, 2107 GestureScope gestureScope, int source, boolean isRightClick, int toolType) { 2108 final boolean hasTIS = hasTIS(); 2109 int pointerCount = mPointerCount; 2110 2111 boolean isTrackpadGesture = mTrackpadGestureType != TrackpadGestureType.NONE; 2112 switch (action & MotionEvent.ACTION_MASK) { 2113 case MotionEvent.ACTION_DOWN: 2114 if (isTrackpadGesture) { 2115 mPointerCount = 1; 2116 pointerCount = mPointerCount; 2117 } 2118 break; 2119 case MotionEvent.ACTION_UP: 2120 if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) { 2121 expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS); 2122 } 2123 break; 2124 case MotionEvent.ACTION_POINTER_DOWN: 2125 mPointerCount++; 2126 pointerCount = mPointerCount; 2127 break; 2128 case MotionEvent.ACTION_POINTER_UP: 2129 // When the gesture is handled outside, it's cancelled within launcher. 2130 mPointerCount--; 2131 break; 2132 } 2133 2134 final MotionEvent event = isTrackpadGesture 2135 ? getTrackpadMotionEvent( 2136 downTime, currentTime, action, point.x, point.y, pointerCount, 2137 mTrackpadGestureType) 2138 : getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType); 2139 int button = isRightClick ? MotionEvent.BUTTON_SECONDARY : MotionEvent.BUTTON_PRIMARY; 2140 if (action == MotionEvent.ACTION_BUTTON_PRESS) { 2141 event.setButtonState(event.getButtonState() | button); 2142 } 2143 if (action == MotionEvent.ACTION_BUTTON_PRESS 2144 || action == MotionEvent.ACTION_BUTTON_RELEASE) { 2145 event.setActionButton(button); 2146 } 2147 injectEvent(event); 2148 } 2149 createKeyEvent(int keyCode, int metaState, boolean actionDown)2150 private KeyEvent createKeyEvent(int keyCode, int metaState, boolean actionDown) { 2151 long eventTime = SystemClock.uptimeMillis(); 2152 return KeyEvent.obtain( 2153 eventTime, 2154 eventTime, 2155 actionDown ? ACTION_DOWN : ACTION_UP, 2156 keyCode, 2157 /* repeat= */ 0, 2158 metaState, 2159 KeyCharacterMap.VIRTUAL_KEYBOARD, 2160 /* scancode= */ 0, 2161 /* flags= */ 0, 2162 InputDevice.SOURCE_KEYBOARD, 2163 /* characters =*/ null); 2164 } 2165 2166 /** 2167 * Sends a {@link KeyEvent} with {@link ACTION_DOWN} for the given key codes without sending 2168 * a {@link KeyEvent} with {@link ACTION_UP}. 2169 */ pressAndHoldKeyCode(int keyCode, int metaState)2170 public void pressAndHoldKeyCode(int keyCode, int metaState) { 2171 injectEvent(createKeyEvent(keyCode, metaState, true)); 2172 } 2173 2174 2175 /** 2176 * Sends a {@link KeyEvent} with {@link ACTION_UP} for the given key codes. 2177 */ unpressKeyCode(int keyCode, int metaState)2178 public void unpressKeyCode(int keyCode, int metaState) { 2179 injectEvent(createKeyEvent(keyCode, metaState, false)); 2180 } 2181 movePointer(long downTime, long startTime, long duration, Point from, Point to, GestureScope gestureScope)2182 public long movePointer(long downTime, long startTime, long duration, Point from, Point to, 2183 GestureScope gestureScope) { 2184 return movePointer(downTime, startTime, duration, false, from, to, gestureScope); 2185 } 2186 movePointer(long downTime, long startTime, long duration, boolean isDecelerating, Point from, Point to, GestureScope gestureScope)2187 public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating, 2188 Point from, Point to, GestureScope gestureScope) { 2189 log("movePointer: " + from + " to " + to); 2190 final Point point = new Point(); 2191 long steps = duration / GESTURE_STEP_MS; 2192 2193 long currentTime = startTime; 2194 2195 if (isDecelerating) { 2196 // formula: V = V0 - D*T, assuming V = 0 when T = duration 2197 2198 // vx0: initial speed at the x-dimension, set as twice the avg speed 2199 // dx: the constant deceleration at the x-dimension 2200 double vx0 = 2.0 * (to.x - from.x) / duration; 2201 double dx = vx0 / duration; 2202 // vy0: initial speed at the y-dimension, set as twice the avg speed 2203 // dy: the constant deceleration at the y-dimension 2204 double vy0 = 2.0 * (to.y - from.y) / duration; 2205 double dy = vy0 / duration; 2206 2207 for (long i = 0; i < steps; ++i) { 2208 sleep(GESTURE_STEP_MS); 2209 currentTime += GESTURE_STEP_MS; 2210 2211 // formula: P = P0 + V0*T - (D*T^2/2) 2212 final double t = (i + 1) * GESTURE_STEP_MS; 2213 point.x = from.x + (int) (vx0 * t - 0.5 * dx * t * t); 2214 point.y = from.y + (int) (vy0 * t - 0.5 * dy * t * t); 2215 2216 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope); 2217 } 2218 } else { 2219 for (long i = 0; i < steps; ++i) { 2220 sleep(GESTURE_STEP_MS); 2221 currentTime += GESTURE_STEP_MS; 2222 2223 final float progress = (currentTime - startTime) / (float) duration; 2224 point.x = from.x + (int) (progress * (to.x - from.x)); 2225 point.y = from.y + (int) (progress * (to.y - from.y)); 2226 2227 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope); 2228 2229 } 2230 } 2231 2232 return currentTime; 2233 } 2234 getCurrentInteractionMode(Context context)2235 public static int getCurrentInteractionMode(Context context) { 2236 return getSystemIntegerRes(context, "config_navBarInteractionMode"); 2237 } 2238 2239 /** 2240 * Retrieve the resource value that defines if nav bar can moved or if it is fixed position. 2241 */ getNavBarCanMove(Context context)2242 private static boolean getNavBarCanMove(Context context) { 2243 return getSystemBooleanRes(context, "config_navBarCanMove"); 2244 } 2245 2246 @NonNull clickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent)2247 UiObject2 clickAndGet( 2248 @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) { 2249 final Point targetCenter = target.getVisibleCenter(); 2250 final long downTime = SystemClock.uptimeMillis(); 2251 // Use stylus secondary button press to prevent using the exteded long press timeout rule 2252 // unnecessarily 2253 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, 2254 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, 2255 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); 2256 sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter, 2257 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, 2258 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); 2259 try { 2260 expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent); 2261 final UiObject2 result = waitForLauncherObject(resName); 2262 return result; 2263 } finally { 2264 sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE, targetCenter, 2265 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, 2266 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); 2267 sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter, 2268 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, 2269 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); 2270 } 2271 } 2272 2273 @NonNull rightClickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent)2274 UiObject2 rightClickAndGet( 2275 @NonNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent) { 2276 final Point targetCenter = target.getVisibleCenter(); 2277 final long downTime = SystemClock.uptimeMillis(); 2278 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, 2279 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, 2280 /* isRightClick= */ true); 2281 sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter, 2282 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, 2283 /* isRightClick= */ true); 2284 try { 2285 expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent); 2286 final UiObject2 result = waitForLauncherObject(resName); 2287 return result; 2288 } finally { 2289 sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_BUTTON_RELEASE, 2290 targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, 2291 /* isRightClick= */ true); 2292 sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter, 2293 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, 2294 /* isRightClick= */ true); 2295 } 2296 } 2297 getSystemBooleanRes(Context context, String resName)2298 private static boolean getSystemBooleanRes(Context context, String resName) { 2299 Resources res = context.getResources(); 2300 int resId = res.getIdentifier(resName, "bool", "android"); 2301 2302 if (resId != 0) { 2303 return res.getBoolean(resId); 2304 } else { 2305 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 2306 return false; 2307 } 2308 } 2309 getSystemIntegerRes(Context context, String resName)2310 private static int getSystemIntegerRes(Context context, String resName) { 2311 Resources res = context.getResources(); 2312 int resId = res.getIdentifier(resName, "integer", "android"); 2313 2314 if (resId != 0) { 2315 return res.getInteger(resId); 2316 } else { 2317 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 2318 return -1; 2319 } 2320 } 2321 getSystemDimensionResId(Context context, String resName)2322 private static int getSystemDimensionResId(Context context, String resName) { 2323 Resources res = context.getResources(); 2324 int resId = res.getIdentifier(resName, "dimen", "android"); 2325 2326 if (resId != 0) { 2327 return resId; 2328 } else { 2329 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 2330 return -1; 2331 } 2332 } 2333 sleep(int duration)2334 static void sleep(int duration) { 2335 SystemClock.sleep(duration); 2336 } 2337 getEdgeSensitivityWidth()2338 int getEdgeSensitivityWidth() { 2339 try { 2340 final Context context = mInstrumentation.getTargetContext().createPackageContext( 2341 getLauncherPackageName(), 0); 2342 return context.getResources().getDimensionPixelSize( 2343 getSystemDimensionResId(context, "config_backGestureInset")) + 1; 2344 } catch (PackageManager.NameNotFoundException e) { 2345 fail("Can't get edge sensitivity: " + e); 2346 return 0; 2347 } 2348 } 2349 2350 /** Returns the bounds of the display as a Point where x is width and y is height. */ getRealDisplaySize()2351 Point getRealDisplaySize() { 2352 final Rect displayBounds = getContext().getSystemService(WindowManager.class) 2353 .getMaximumWindowMetrics() 2354 .getBounds(); 2355 return new Point(displayBounds.width(), displayBounds.height()); 2356 } 2357 enableDebugTracing()2358 public void enableDebugTracing() { 2359 getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING); 2360 } 2361 disableSensorRotation()2362 private void disableSensorRotation() { 2363 getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION); 2364 } 2365 disableDebugTracing()2366 public void disableDebugTracing() { 2367 getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING); 2368 } 2369 forceGc()2370 public void forceGc() { 2371 // GC the system & sysui first before gc'ing launcher 2372 logShellCommand("cmd statusbar run-gc"); 2373 getTestInfo(TestProtocol.REQUEST_FORCE_GC); 2374 } 2375 getPid()2376 public Integer getPid() { 2377 final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID); 2378 return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null; 2379 } 2380 getRecentTasks()2381 public ArrayList<ComponentName> getRecentTasks() { 2382 ArrayList<ComponentName> tasks = new ArrayList<>(); 2383 ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST) 2384 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2385 for (String s : components) { 2386 tasks.add(ComponentName.unflattenFromString(s)); 2387 } 2388 return tasks; 2389 } 2390 2391 /** Reinitializes the workspace to its default layout. */ reinitializeLauncherData()2392 public void reinitializeLauncherData() { 2393 getTestInfo(TestProtocol.REQUEST_REINITIALIZE_DATA); 2394 } 2395 2396 /** Clears the workspace, leaving it empty. */ clearLauncherData()2397 public void clearLauncherData() { 2398 getTestInfo(TestProtocol.REQUEST_CLEAR_DATA); 2399 } 2400 2401 /** Shows the taskbar if it is hidden, otherwise does nothing. */ showTaskbarIfHidden()2402 public void showTaskbarIfHidden() { 2403 getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED); 2404 } 2405 2406 /** Shows the bubble bar if it is stashed, otherwise this does nothing. */ showBubbleBarIfHidden()2407 public void showBubbleBarIfHidden() { 2408 getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED); 2409 } 2410 injectFakeTrackpad()2411 public void injectFakeTrackpad() { 2412 getTestInfo(TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD); 2413 } 2414 ejectFakeTrackpad()2415 public void ejectFakeTrackpad() { 2416 getTestInfo(TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD); 2417 } 2418 2419 /** Blocks the taskbar from automatically stashing based on time. */ enableBlockTimeout(boolean enable)2420 public void enableBlockTimeout(boolean enable) { 2421 getTestInfo(enable 2422 ? TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT 2423 : TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT); 2424 } 2425 isTransientTaskbar()2426 public boolean isTransientTaskbar() { 2427 return getTestInfo(TestProtocol.REQUEST_IS_TRANSIENT_TASKBAR) 2428 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2429 } 2430 2431 /** Whether taskbar will be shown on home for current default display. */ isTaskbarShownOnHome()2432 public boolean isTaskbarShownOnHome() { 2433 return getTestInfo(TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME).getBoolean( 2434 TEST_INFO_RESPONSE_FIELD); 2435 } 2436 isImeDocked()2437 public boolean isImeDocked() { 2438 return getTestInfo(TestProtocol.REQUEST_TASKBAR_IME_DOCKED).getBoolean( 2439 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2440 } 2441 2442 /** Enables transient taskbar for testing purposes only. */ enableTransientTaskbar(boolean enable)2443 public void enableTransientTaskbar(boolean enable) { 2444 getTestInfo(enable 2445 ? TestProtocol.REQUEST_ENABLE_TRANSIENT_TASKBAR 2446 : TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR); 2447 } 2448 2449 /** 2450 * Recreates the taskbar (outside of tests this is done for certain configuration changes). 2451 * The expected behavior is that the taskbar retains its current state after being recreated. 2452 * For example, if taskbar is currently stashed, it should still be stashed after recreating. 2453 */ recreateTaskbar()2454 public void recreateTaskbar() { 2455 getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR); 2456 } 2457 2458 // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup. 2459 2460 /** Refreshes the known overview target in TIS. */ refreshOverviewTarget()2461 public void refreshOverviewTarget() { 2462 getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET); 2463 } 2464 getHotseatIconNames()2465 public List<String> getHotseatIconNames() { 2466 return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES) 2467 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2468 } 2469 getActivities()2470 private String[] getActivities() { 2471 return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES) 2472 .getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2473 } 2474 getRootedActivitiesList()2475 public String getRootedActivitiesList() { 2476 return String.join(", ", getActivities()); 2477 } 2478 2479 /** Returns whether no leaked activities are detected. */ noLeakedActivities(boolean requireOneActiveActivity)2480 public boolean noLeakedActivities(boolean requireOneActiveActivity) { 2481 final String[] activities = getActivities(); 2482 2483 for (String activity : activities) { 2484 if (activity.contains("(destroyed)")) { 2485 return false; 2486 } 2487 } 2488 return activities.length <= (requireOneActiveActivity ? 1 : 2); 2489 } 2490 getActivitiesCreated()2491 public int getActivitiesCreated() { 2492 return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT) 2493 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2494 } 2495 eventsCheck()2496 public Closable eventsCheck() { 2497 Assert.assertTrue("Nested event checking", mEventChecker == null); 2498 disableSensorRotation(); 2499 final Integer initialPid = getPid(); 2500 final LogEventChecker eventChecker = new LogEventChecker(this); 2501 eventChecker.setLogExclusionRule(event -> { 2502 Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event); 2503 if (matcher.find()) { 2504 long keyEventFlags = Long.parseLong(matcher.group(1), 16); 2505 // ignore KeyEvents with FLAG_CANCELED 2506 return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0; 2507 } 2508 return false; 2509 }); 2510 2511 if (eventChecker.start()) mEventChecker = eventChecker; 2512 2513 return () -> { 2514 if (initialPid != null && initialPid.intValue() != getPid()) { 2515 if (mOnLauncherCrashed != null) mOnLauncherCrashed.run(); 2516 checkForAnomaly(); 2517 Assert.fail( 2518 formatSystemHealthMessage( 2519 formatErrorWithEvents("Launcher crashed", false))); 2520 } 2521 2522 if (mEventChecker != null) { 2523 mEventChecker = null; 2524 if (mCheckEventsForSuccessfulGestures) { 2525 final String message = eventChecker.verify(WAIT_TIME_MS); 2526 if (message != null) { 2527 dumpDiagnostics(message); 2528 checkForAnomaly(); 2529 Assert.fail(formatSystemHealthMessage( 2530 "http://go/tapl : successful gesture produced " + message)); 2531 } 2532 } else { 2533 eventChecker.finishNoWait(); 2534 } 2535 } 2536 }; 2537 } 2538 2539 /** Returns whether the Launcher is a Launcher3 one */ 2540 public boolean isLauncher3() { 2541 if (mIsLauncher3 == null) { 2542 mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName()); 2543 } 2544 return mIsLauncher3; 2545 } 2546 2547 void expectEvent(String sequence, Pattern expected) { 2548 if (mEventChecker != null) { 2549 mEventChecker.expectPattern(sequence, expected); 2550 } else { 2551 Log.d(TAG, "Expecting: " + sequence + " / " + expected); 2552 } 2553 } 2554 2555 Rect getVisibleBounds(UiObject2 object) { 2556 try { 2557 return object.getVisibleBounds(); 2558 } catch (StaleObjectException e) { 2559 fail("Object disappeared from screen"); 2560 return null; 2561 } catch (Throwable t) { 2562 fail(t.toString()); 2563 return null; 2564 } 2565 } 2566 2567 float getWindowCornerRadius() { 2568 // Return a larger corner radius to ensure gesture calculated from the radius are offset to 2569 // prevent overlapping 2570 final float tmpBuffer = 100f; 2571 final Resources resources = getResources(); 2572 if (!supportsRoundedCornersOnWindows(resources)) { 2573 Log.d(TAG, "No rounded corners"); 2574 return tmpBuffer; 2575 } 2576 2577 // Radius that should be used in case top or bottom aren't defined. 2578 float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0); 2579 2580 float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0); 2581 if (topRadius == 0f) { 2582 topRadius = defaultRadius; 2583 } 2584 float bottomRadius = ResourceUtils.getDimenByName( 2585 "rounded_corner_radius_bottom", resources, 0); 2586 if (bottomRadius == 0f) { 2587 bottomRadius = defaultRadius; 2588 } 2589 2590 // Always use the smallest radius to make sure the rounded corners will 2591 // completely cover the display. 2592 Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius); 2593 return Math.max(topRadius, bottomRadius) + tmpBuffer; 2594 } 2595 2596 private Context getLauncherContext(Context baseContext) 2597 throws PackageManager.NameNotFoundException { 2598 // Workaround, use constructed context because both the instrumentation context and the 2599 // app context are not constructed with resources that take overlays into account 2600 return baseContext.createPackageContext(getLauncherPackageName(), 0); 2601 } 2602 2603 private static boolean supportsRoundedCornersOnWindows(Resources resources) { 2604 return ResourceUtils.getBoolByName( 2605 "config_supportsRoundedCornersOnWindows", resources, false); 2606 } 2607 2608 /** 2609 * Taps outside container to dismiss, centered vertically and halfway to the edge of the screen. 2610 * 2611 * @param container container to be dismissed 2612 * @param tapRight tap on the right of the container if true, or left otherwise 2613 */ 2614 void touchOutsideContainer(UiObject2 container, boolean tapRight) { 2615 touchOutsideContainer(container, tapRight, true); 2616 } 2617 2618 /** 2619 * Taps outside the container, to the right or left, and centered vertically. 2620 * 2621 * @param tapRight if true touches to the right of the container, otherwise touches on left 2622 * @param halfwayToEdge if true touches halfway to the screen edge, if false touches 1 px from 2623 * container 2624 */ 2625 void touchOutsideContainer(UiObject2 container, boolean tapRight, boolean halfwayToEdge) { 2626 try (LauncherInstrumentation.Closable c = addContextLayer( 2627 "want to tap outside container on the " + (tapRight ? "right" : "left"))) { 2628 Rect containerBounds = getVisibleBounds(container); 2629 2630 int x; 2631 if (halfwayToEdge) { 2632 x = tapRight 2633 ? (containerBounds.right + getRealDisplaySize().x) / 2 2634 : containerBounds.left / 2; 2635 } else { 2636 x = tapRight 2637 ? containerBounds.right + 1 2638 : containerBounds.left - 1; 2639 } 2640 // If IME is visible and overlaps the container bounds, touch above it. 2641 final Insets systemGestureRegion = getSystemGestureRegion(); 2642 int bottomBound = Math.min( 2643 containerBounds.bottom, 2644 getRealDisplaySize().y - systemGestureRegion.bottom); 2645 int y = (bottomBound + containerBounds.top) / 2; 2646 // Do not tap in the status bar. 2647 y = Math.max(y, systemGestureRegion.top); 2648 2649 final long downTime = SystemClock.uptimeMillis(); 2650 final Point tapTarget = new Point(x, y); 2651 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget, 2652 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); 2653 sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget, 2654 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); 2655 } 2656 } 2657 2658 /** 2659 * Waits until a particular condition is true. Based on WaitMixin. 2660 */ 2661 boolean waitAndGet(BooleanSupplier condition, long timeout, long interval) { 2662 long startTime = SystemClock.uptimeMillis(); 2663 2664 boolean result = condition.getAsBoolean(); 2665 for (long elapsedTime = 0; !result; elapsedTime = SystemClock.uptimeMillis() - startTime) { 2666 if (elapsedTime >= timeout) { 2667 break; 2668 } 2669 SystemClock.sleep(interval); 2670 result = condition.getAsBoolean(); 2671 } 2672 return result; 2673 } 2674 2675 /** Executes a runnable and waits for the wallpaper-open animation completion. */ 2676 public void executeAndWaitForWallpaperAnimation(Runnable r, String actionName) { 2677 executeAndWaitForLauncherEvent( 2678 () -> r.run(), 2679 event -> TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE 2680 .equals(event.getClassName().toString()), 2681 () -> "Didn't detect finishing wallpaper-open animation", 2682 actionName); 2683 } 2684 } 2685