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.pm.PackageManager; 45 import android.content.pm.ProviderInfo; 46 import android.content.res.Configuration; 47 import android.content.res.Resources; 48 import android.graphics.Insets; 49 import android.graphics.Point; 50 import android.graphics.Rect; 51 import android.net.Uri; 52 import android.os.Bundle; 53 import android.os.DeadObjectException; 54 import android.os.Parcelable; 55 import android.os.RemoteException; 56 import android.os.SystemClock; 57 import android.os.Trace; 58 import android.text.TextUtils; 59 import android.util.Log; 60 import android.view.InputDevice; 61 import android.view.InputEvent; 62 import android.view.KeyCharacterMap; 63 import android.view.KeyEvent; 64 import android.view.MotionEvent; 65 import android.view.ViewConfiguration; 66 import android.view.WindowManager; 67 import android.view.accessibility.AccessibilityEvent; 68 69 import androidx.annotation.NonNull; 70 import androidx.annotation.Nullable; 71 import androidx.test.InstrumentationRegistry; 72 import androidx.test.uiautomator.By; 73 import androidx.test.uiautomator.BySelector; 74 import androidx.test.uiautomator.Configurator; 75 import androidx.test.uiautomator.Direction; 76 import androidx.test.uiautomator.StaleObjectException; 77 import androidx.test.uiautomator.UiDevice; 78 import androidx.test.uiautomator.UiObject2; 79 import androidx.test.uiautomator.Until; 80 81 import com.android.launcher3.testing.shared.ResourceUtils; 82 import com.android.launcher3.testing.shared.TestInformationRequest; 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 getKeyEventPattern(String action, String keyCode)214 private static Pattern getKeyEventPattern(String action, String keyCode) { 215 return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode); 216 } 217 218 /** 219 * Constructs the root of TAPL hierarchy. You get all other objects from it. 220 */ LauncherInstrumentation()221 public LauncherInstrumentation() { 222 this(InstrumentationRegistry.getInstrumentation(), false); 223 } 224 225 /** 226 * Constructs the root of TAPL hierarchy. You get all other objects from it. 227 */ LauncherInstrumentation(boolean isLauncherTest)228 public LauncherInstrumentation(boolean isLauncherTest) { 229 this(InstrumentationRegistry.getInstrumentation(), isLauncherTest); 230 } 231 232 /** 233 * Constructs the root of TAPL hierarchy. You get all other objects from it. 234 * 235 * @deprecated use the constructor without Instrumentation parameter instead. 236 */ 237 @Deprecated LauncherInstrumentation(Instrumentation instrumentation)238 public LauncherInstrumentation(Instrumentation instrumentation) { 239 this(instrumentation, false); 240 } 241 242 /** 243 * Constructs the root of TAPL hierarchy. You get all other objects from it. 244 * 245 * @deprecated use the constructor without Instrumentation parameter instead. 246 */ 247 @Deprecated LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest)248 public LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest) { 249 mInstrumentation = instrumentation; 250 mDevice = UiDevice.getInstance(instrumentation); 251 252 // Launcher should run in test harness so that custom accessibility protocol between 253 // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call 254 // into Launcher. 255 assertTrue("Device must run in a test harness. " 256 + "Run `adb shell setprop ro.test_harness 1` to enable it.", 257 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness()); 258 259 final String testPackage = getContext().getPackageName(); 260 final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); 261 262 // Launcher package. As during inproc tests the tested launcher may not be selected as the 263 // current launcher, choosing target package for inproc. For out-of-proc, use the installed 264 // launcher package. 265 mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation() 266 ? getLauncherPackageName() 267 : targetPackage; 268 269 String testProviderAuthority = mLauncherPackage + ".TestInfo"; 270 mTestProviderUri = new Uri.Builder() 271 .scheme(ContentResolver.SCHEME_CONTENT) 272 .authority(testProviderAuthority) 273 .build(); 274 275 mInstrumentation.getUiAutomation().grantRuntimePermission( 276 testPackage, "android.permission.WRITE_SECURE_SETTINGS"); 277 278 PackageManager pm = getContext().getPackageManager(); 279 ProviderInfo pi = pm.resolveContentProvider( 280 testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS); 281 assertNotNull("Cannot find content provider for " + testProviderAuthority, pi); 282 ComponentName cn = new ComponentName(pi.packageName, pi.name); 283 284 final int iterations = isLauncherTest ? 300 : 100; 285 286 if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) { 287 if (TestHelpers.isInLauncherProcess()) { 288 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP); 289 } else { 290 try { 291 final int userId = getContext().getUserId(); 292 final String launcherPidCommand = "pidof " + pi.packageName; 293 final String initialPid = mDevice.executeShellCommand(launcherPidCommand); 294 295 mDevice.executeShellCommand( 296 "pm enable --user " + userId + " " + cn.flattenToString()); 297 298 // Wait for Launcher restart after enabling test provider. 299 for (int i = 0; i < iterations; ++i) { 300 final String currentPid = mDevice.executeShellCommand(launcherPidCommand) 301 .replaceAll("\\s", ""); 302 if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break; 303 if (i == iterations - 1) { 304 fail("Launcher didn't restart after enabling test provider"); 305 } 306 SystemClock.sleep(100); 307 } 308 } catch (IOException e) { 309 fail(e.toString()); 310 } 311 } 312 313 // Wait for Launcher content provider to become enabled. 314 for (int i = 0; i < iterations; ++i) { 315 final ContentProviderClient testProvider = getContext().getContentResolver() 316 .acquireContentProviderClient(mTestProviderUri); 317 if (testProvider != null) { 318 testProvider.close(); 319 break; 320 } 321 if (i == iterations - 1) { 322 fail("Launcher content provider is still not enabled"); 323 } 324 SystemClock.sleep(100); 325 } 326 } 327 } 328 329 /** 330 * Gradle only supports out of process instrumentation. The test package is automatically 331 * generated by appending `.test` to the target package. 332 */ isGradleInstrumentation()333 private boolean isGradleInstrumentation() { 334 final String testPackage = getContext().getPackageName(); 335 final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); 336 final String testSuffix = ".test"; 337 338 return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length() 339 && testPackage.substring(0, testPackage.length() - testSuffix.length()) 340 .equals(targetPackage); 341 } 342 enableCheckEventsForSuccessfulGestures()343 public void enableCheckEventsForSuccessfulGestures() { 344 mCheckEventsForSuccessfulGestures = true; 345 } 346 347 /** Sets a runnable that will be invoked upon assertion failures. */ setOnFailure(Runnable onFailure)348 public void setOnFailure(Runnable onFailure) { 349 mOnFailure = onFailure; 350 } 351 setOnLauncherCrashed(Runnable onLauncherCrashed)352 public void setOnLauncherCrashed(Runnable onLauncherCrashed) { 353 mOnLauncherCrashed = onLauncherCrashed; 354 } 355 getContext()356 Context getContext() { 357 return mInstrumentation.getContext(); 358 } 359 getTestInfo(String request)360 Bundle getTestInfo(String request) { 361 return getTestInfo(request, /*arg=*/ null); 362 } 363 getTestInfo(String request, String arg)364 Bundle getTestInfo(String request, String arg) { 365 return getTestInfo(request, arg, null); 366 } 367 getTestInfo(String request, String arg, Bundle extra)368 Bundle getTestInfo(String request, String arg, Bundle extra) { 369 try (ContentProviderClient client = getContext().getContentResolver() 370 .acquireContentProviderClient(mTestProviderUri)) { 371 return client.call(request, arg, extra); 372 } catch (DeadObjectException e) { 373 fail("Launcher crashed"); 374 return null; 375 } catch (RemoteException e) { 376 throw new RuntimeException(e); 377 } 378 } 379 getTestInfo(TestInformationRequest request)380 Bundle getTestInfo(TestInformationRequest request) { 381 Bundle extra = new Bundle(); 382 extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request); 383 return getTestInfo(request.getRequestName(), null, extra); 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 fail(String message)757 void fail(String message) { 758 checkForAnomaly(); 759 if (mOnFailure != null) mOnFailure.run(); 760 Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( 761 "http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription() 762 + "; now visible state is " + getVisibleStateMessage(), true))); 763 } 764 getContextDescription()765 private String getContextDescription() { 766 return mDiagnosticContext.isEmpty() 767 ? "(no context)" : String.join(", ", mDiagnosticContext); 768 } 769 assertTrue(String message, boolean condition)770 void assertTrue(String message, boolean condition) { 771 if (!condition) { 772 fail(message); 773 } 774 } 775 assertNotNull(String message, Object object)776 void assertNotNull(String message, Object object) { 777 assertTrue(message, object != null); 778 } 779 failEquals(String message, Object actual)780 private void failEquals(String message, Object actual) { 781 fail(message + ". " + "Actual: " + actual); 782 } 783 assertEquals(String message, int expected, int actual)784 void assertEquals(String message, int expected, int actual) { 785 if (expected != actual) { 786 fail(message + " expected: " + expected + " but was: " + actual); 787 } 788 } 789 assertEquals(String message, String expected, String actual)790 void assertEquals(String message, String expected, String actual) { 791 if (!TextUtils.equals(expected, actual)) { 792 fail(message + " expected: '" + expected + "' but was: '" + actual + "'"); 793 } 794 } 795 assertEquals(String message, long expected, long actual)796 void assertEquals(String message, long expected, long actual) { 797 if (expected != actual) { 798 fail(message + " expected: " + expected + " but was: " + actual); 799 } 800 } 801 assertNotEquals(String message, int unexpected, int actual)802 void assertNotEquals(String message, int unexpected, int actual) { 803 if (unexpected == actual) { 804 failEquals(message, actual); 805 } 806 } 807 808 /** 809 * Whether to ignore verifying the task bar visibility during instrumenting. 810 * 811 * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly 812 * verifying the task bar visibility with 813 * {@link VisibleContainer#verifyActiveContainer}. 814 * {@code false} otherwise. 815 */ setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility)816 public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) { 817 mIgnoreTaskbarVisibility = ignoreTaskbarVisibility; 818 } 819 820 /** 821 * Set the trackpad gesture type of the interaction. 822 * 823 * @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or 824 * four-finger gesture. 825 */ setTrackpadGestureType(TrackpadGestureType trackpadGestureType)826 public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) { 827 mTrackpadGestureType = trackpadGestureType; 828 } 829 getTrackpadGestureType()830 TrackpadGestureType getTrackpadGestureType() { 831 return mTrackpadGestureType; 832 } 833 834 /** 835 * Sets expected rotation. 836 * TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one. 837 * Null parameter disables checks. The initial state is "no checks". 838 */ setExpectedRotation(Integer expectedRotation)839 public void setExpectedRotation(Integer expectedRotation) { 840 mExpectedRotation = expectedRotation; 841 } 842 setExpectedRotationCheckEnabled(boolean expectedRotationCheckEnabled)843 public void setExpectedRotationCheckEnabled(boolean expectedRotationCheckEnabled) { 844 mExpectedRotationCheckEnabled = expectedRotationCheckEnabled; 845 } 846 getExpectedRotationCheckEnabled()847 public boolean getExpectedRotationCheckEnabled() { 848 return mExpectedRotationCheckEnabled; 849 } 850 getNavigationModeMismatchError(boolean waitForCorrectState)851 public String getNavigationModeMismatchError(boolean waitForCorrectState) { 852 final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0; 853 final NavigationModel navigationModel = getNavigationModel(); 854 String resPackage = getNavigationButtonResPackage(); 855 if (navigationModel == NavigationModel.THREE_BUTTON) { 856 if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) { 857 return "Recents button not present in 3-button mode"; 858 } 859 } else { 860 if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) { 861 return "Recents button is present in non-3-button mode"; 862 } 863 } 864 865 if (navigationModel == NavigationModel.ZERO_BUTTON) { 866 if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) { 867 return "Home button is present in gestural mode"; 868 } 869 } else { 870 if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) { 871 return "Home button not present in non-gestural mode"; 872 } 873 } 874 return null; 875 } 876 getNavigationButtonResPackage()877 private String getNavigationButtonResPackage() { 878 return isTablet() || isTaskbarNavbarUnificationEnabled() 879 ? getLauncherPackageName() : SYSTEMUI_PACKAGE; 880 } 881 verifyContainerType(ContainerType containerType)882 UiObject2 verifyContainerType(ContainerType containerType) { 883 waitForLauncherInitialized(); 884 885 if (mExpectedRotationCheckEnabled && mExpectedRotation != null) { 886 assertEquals("Unexpected display rotation", 887 mExpectedRotation, mDevice.getDisplayRotation()); 888 } 889 890 final String error = getNavigationModeMismatchError(true); 891 assertTrue(error, error == null); 892 893 log("verifyContainerType: " + containerType); 894 895 final UiObject2 container = verifyVisibleObjects(containerType); 896 897 return container; 898 } 899 verifyVisibleObjects(ContainerType containerType)900 private UiObject2 verifyVisibleObjects(ContainerType containerType) { 901 try (Closable c = addContextLayer( 902 "but the current state is not " + containerType.name())) { 903 switch (containerType) { 904 case WORKSPACE: { 905 waitUntilLauncherObjectGone(APPS_RES_ID); 906 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 907 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 908 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 909 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 910 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 911 912 return waitForLauncherObject(WORKSPACE_RES_ID); 913 } 914 case WIDGETS: { 915 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 916 waitUntilLauncherObjectGone(APPS_RES_ID); 917 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 918 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 919 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 920 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 921 922 return waitForLauncherObject(WIDGETS_RES_ID); 923 } 924 case TASKBAR_ALL_APPS: { 925 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 926 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 927 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 928 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 929 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 930 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 931 932 return waitForLauncherObject(APPS_RES_ID); 933 } 934 case HOME_ALL_APPS: { 935 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 936 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 937 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 938 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 939 940 if (is3PLauncher() && isTablet() && !isTransientTaskbar()) { 941 waitForSystemLauncherObject(TASKBAR_RES_ID); 942 } else { 943 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 944 } 945 946 boolean splitSelectionActive = getTestInfo(REQUEST_GET_SPLIT_SELECTION_ACTIVE) 947 .getBoolean(TEST_INFO_RESPONSE_FIELD); 948 if (!splitSelectionActive) { 949 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 950 } // do nothing, we expect that view 951 952 return waitForLauncherObject(APPS_RES_ID); 953 } 954 case OVERVIEW: 955 case FALLBACK_OVERVIEW: { 956 waitUntilLauncherObjectGone(APPS_RES_ID); 957 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 958 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 959 if (isTablet() && !is3PLauncher()) { 960 waitForSystemLauncherObject(TASKBAR_RES_ID); 961 } else { 962 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 963 } 964 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 965 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 966 967 return waitForSystemLauncherObject(OVERVIEW_RES_ID); 968 } 969 case SPLIT_SCREEN_SELECT: { 970 waitUntilLauncherObjectGone(APPS_RES_ID); 971 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 972 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 973 if (isTablet()) { 974 waitForSystemLauncherObject(TASKBAR_RES_ID); 975 } else { 976 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 977 } 978 979 waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID); 980 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 981 return waitForSystemLauncherObject(OVERVIEW_RES_ID); 982 } 983 case LAUNCHED_APP: { 984 waitUntilLauncherObjectGone(WORKSPACE_RES_ID); 985 waitUntilLauncherObjectGone(APPS_RES_ID); 986 waitUntilLauncherObjectGone(WIDGETS_RES_ID); 987 waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); 988 waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); 989 waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 990 991 if (mIgnoreTaskbarVisibility) { 992 return null; 993 } 994 995 if (isTablet()) { 996 // Only check that Persistent Taskbar is visible, since Transient Taskbar 997 // may or may not be visible by design. 998 if (!isTransientTaskbar()) { 999 waitForSystemLauncherObject(TASKBAR_RES_ID); 1000 } 1001 } else { 1002 waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); 1003 } 1004 return null; 1005 } 1006 default: 1007 fail("Invalid state: " + containerType); 1008 return null; 1009 } 1010 } 1011 } 1012 waitForModelQueueCleared()1013 public void waitForModelQueueCleared() { 1014 getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED); 1015 } 1016 waitForLauncherInitialized()1017 public void waitForLauncherInitialized() { 1018 try { 1019 Trace.beginSection("waitForLauncherInitialized"); 1020 for (int i = 0; i < 100; ++i) { 1021 if (getTestInfo(TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).getBoolean( 1022 TestProtocol.TEST_INFO_RESPONSE_FIELD)) { 1023 return; 1024 } 1025 SystemClock.sleep(100); 1026 } 1027 checkForAnomaly(); 1028 fail("Launcher didn't initialize"); 1029 } finally { 1030 Trace.endSection(); 1031 } 1032 } 1033 isLauncherActivityStarted()1034 public boolean isLauncherActivityStarted() { 1035 return getTestInfo( 1036 TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED). 1037 getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 1038 } 1039 executeAndWaitForLauncherEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)1040 Parcelable executeAndWaitForLauncherEvent(Runnable command, 1041 UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, 1042 String actionName) { 1043 return executeAndWaitForEvent( 1044 command, 1045 e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e), 1046 message, actionName); 1047 } 1048 executeAndWaitForEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)1049 Parcelable executeAndWaitForEvent(Runnable command, 1050 UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, 1051 String actionName) { 1052 try (LauncherInstrumentation.Closable c = addContextLayer(actionName)) { 1053 try { 1054 final AccessibilityEvent event = 1055 mInstrumentation.getUiAutomation().executeAndWaitForEvent( 1056 command, eventFilter, WAIT_TIME_MS); 1057 assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event); 1058 final Parcelable parcelableData = event.getParcelableData(); 1059 event.recycle(); 1060 return parcelableData; 1061 } catch (TimeoutException e) { 1062 fail(message.get()); 1063 return null; 1064 } 1065 } 1066 } 1067 executeAndWaitForLauncherStop(Runnable command, String actionName)1068 void executeAndWaitForLauncherStop(Runnable command, String actionName) { 1069 executeAndWaitForLauncherEvent( 1070 () -> command.run(), 1071 event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE 1072 .equals(event.getClassName().toString()), 1073 () -> "Launcher activity didn't stop", actionName); 1074 } 1075 1076 /** 1077 * Get the resource ID of visible floating view. 1078 */ getFloatingResId()1079 private Optional<String> getFloatingResId() { 1080 if (hasLauncherObject(CONTEXT_MENU_RES_ID)) { 1081 return Optional.of(CONTEXT_MENU_RES_ID); 1082 } 1083 if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) { 1084 return Optional.of(FOLDER_CONTENT_RES_ID); 1085 } 1086 return Optional.empty(); 1087 } 1088 1089 /** 1090 * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content. 1091 */ swipeUpToCloseFloatingView()1092 private void swipeUpToCloseFloatingView() { 1093 final Point displaySize = getRealDisplaySize(); 1094 1095 final Optional<String> floatingRes = getFloatingResId(); 1096 1097 if (!floatingRes.isPresent()) { 1098 return; 1099 } 1100 1101 if (isLauncher3()) { 1102 gestureToDismissPopup(displaySize); 1103 } else { 1104 runToState(() -> gestureToDismissPopup(displaySize), NORMAL_STATE_ORDINAL, "swiping"); 1105 } 1106 1107 try (LauncherInstrumentation.Closable c1 = addContextLayer( 1108 String.format("Swiped up from floating view %s to home", floatingRes.get()))) { 1109 waitUntilLauncherObjectGone(floatingRes.get()); 1110 waitForLauncherObject(getAnyObjectSelector()); 1111 } 1112 } 1113 gestureToDismissPopup(Point displaySize)1114 private void gestureToDismissPopup(Point displaySize) { 1115 linearGesture( 1116 displaySize.x / 2, displaySize.y - 1, 1117 displaySize.x / 2, 0, 1118 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 1119 false, GestureScope.EXPECT_PILFER); 1120 } 1121 1122 /** 1123 * @return the Workspace object. 1124 * @deprecated use goHome(). 1125 * Presses nav bar home button. 1126 */ 1127 @Deprecated pressHome()1128 public Workspace pressHome() { 1129 return goHome(); 1130 } 1131 1132 /** 1133 * Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then 1134 * performing {@code goHome()} action. 1135 * 1136 * @return the Workspace object. 1137 */ goHomeFromImmersiveFullscreenApp()1138 public Workspace goHomeFromImmersiveFullscreenApp() { 1139 final boolean navBarCanMove = getNavBarCanMove(); 1140 if (getNavigationModel() == NavigationModel.ZERO_BUTTON || !navBarCanMove) { 1141 // in gesture nav we can swipe up at the bottom to bring the navbar handle 1142 final Point displaySize = getRealDisplaySize(); 1143 linearGesture( 1144 displaySize.x / 2, displaySize.y - 1, 1145 displaySize.x / 2, 0, 1146 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 1147 false, GestureScope.EXPECT_PILFER); 1148 } else { 1149 // in 3 button nav we swipe up on the side edge of the screen to bring the navbar 1150 final boolean rotated90degrees = mDevice.getDisplayRotation() == ROTATION_90; 1151 final Point displaySize = getRealDisplaySize(); 1152 final int startX = rotated90degrees ? displaySize.x : 0; 1153 final int endX = rotated90degrees ? 0 : displaySize.x; 1154 linearGesture( 1155 startX, displaySize.y / 2, 1156 endX, displaySize.y / 2, 1157 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, 1158 false, GestureScope.EXPECT_PILFER); 1159 } 1160 return goHome(); 1161 } 1162 1163 /** 1164 * Goes to home by swiping up in zero-button mode or pressing Home button. 1165 * Calling it after another TAPL call is safe because all TAPL methods wait for the animations 1166 * to finish. 1167 * When calling it after a non-TAPL method, make sure that all animations have already 1168 * completed, otherwise it may detect the current state (for example "Application" or "Home") 1169 * incorrectly. 1170 * The method expects either app or Launcher to be active when it's called. Other states, such 1171 * as visible notification shade are not supported. 1172 * 1173 * @return the Workspace object. 1174 */ goHome()1175 public Workspace goHome() { 1176 try (LauncherInstrumentation.Closable e = eventsCheck(); 1177 LauncherInstrumentation.Closable c = addContextLayer("want to switch to home")) { 1178 waitForLauncherInitialized(); 1179 // Click home, then wait for any accessibility event, then wait until accessibility 1180 // events stop. 1181 // We need waiting for any accessibility event generated after pressing Home because 1182 // otherwise waitForIdle may return immediately in case when there was a big enough 1183 // pause in accessibility events prior to pressing Home. 1184 boolean isThreeFingerTrackpadGesture = 1185 mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 1186 final String action; 1187 if (getNavigationModel() == NavigationModel.ZERO_BUTTON 1188 || isThreeFingerTrackpadGesture) { 1189 checkForAnomaly(false, true); 1190 1191 final Point displaySize = getRealDisplaySize(); 1192 1193 // CLose floating views before going back to home. 1194 swipeUpToCloseFloatingView(); 1195 1196 if (hasLauncherObject(WORKSPACE_RES_ID)) { 1197 log(action = "already at home"); 1198 } else { 1199 action = "swiping up to home"; 1200 1201 int startY = isThreeFingerTrackpadGesture ? displaySize.y * 3 / 4 1202 : displaySize.y - 1; 1203 int endY = isThreeFingerTrackpadGesture ? displaySize.y / 4 : displaySize.y / 2; 1204 swipeToState( 1205 displaySize.x / 2, startY, 1206 displaySize.x / 2, endY, 1207 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL, 1208 GestureScope.EXPECT_PILFER); 1209 } 1210 } else { 1211 log("Hierarchy before clicking home:"); 1212 dumpViewHierarchy(); 1213 action = "clicking home button"; 1214 runToState( 1215 getHomeButton()::click, 1216 NORMAL_STATE_ORDINAL, 1217 !hasLauncherObject(WORKSPACE_RES_ID) 1218 && (hasLauncherObject(APPS_RES_ID) 1219 || hasSystemLauncherObject(OVERVIEW_RES_ID)), 1220 action); 1221 } 1222 try (LauncherInstrumentation.Closable c1 = addContextLayer( 1223 "performed action to switch to Home - " + action)) { 1224 return getWorkspace(); 1225 } 1226 } 1227 } 1228 1229 /** 1230 * Press navbar back button or swipe back if in gesture navigation mode. 1231 */ pressBack()1232 public void pressBack() { 1233 try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) { 1234 pressBackImpl(); 1235 } 1236 } 1237 pressBackImpl()1238 void pressBackImpl() { 1239 waitForLauncherInitialized(); 1240 final boolean launcherVisible = 1241 (isTablet() || isTaskbarNavbarUnificationEnabled()) ? isLauncherContainerVisible() 1242 : isLauncherVisible(); 1243 boolean isThreeFingerTrackpadGesture = 1244 mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 1245 if (getNavigationModel() == NavigationModel.ZERO_BUTTON 1246 || isThreeFingerTrackpadGesture) { 1247 final Point displaySize = getRealDisplaySize(); 1248 // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the 1249 // issue is solved. 1250 int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0; 1251 int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2; 1252 linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4, 1253 10, false, GestureScope.DONT_EXPECT_PILFER); 1254 } else { 1255 waitForNavigationUiObject("back").click(); 1256 } 1257 if (launcherVisible) { 1258 if (isPredictiveBackSwipeEnabled()) { 1259 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED); 1260 } else { 1261 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP); 1262 } 1263 } 1264 } 1265 getAnyObjectSelector()1266 private static BySelector getAnyObjectSelector() { 1267 return By.textStartsWith(""); 1268 } 1269 isLauncherVisible()1270 boolean isLauncherVisible() { 1271 try { 1272 Trace.beginSection("isLauncherVisible"); 1273 mDevice.waitForIdle(); 1274 return hasLauncherObject(getAnyObjectSelector()); 1275 } finally { 1276 Trace.endSection(); 1277 } 1278 } 1279 isLauncherContainerVisible()1280 boolean isLauncherContainerVisible() { 1281 final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID}; 1282 return Arrays.stream(containerResources).anyMatch( 1283 r -> r.equals(OVERVIEW_RES_ID) ? hasSystemLauncherObject(r) : hasLauncherObject(r)); 1284 } 1285 1286 /** 1287 * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the 1288 * launcher is not in that state. 1289 * 1290 * @return Workspace object. 1291 */ 1292 @NonNull getWorkspace()1293 public Workspace getWorkspace() { 1294 try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) { 1295 return new Workspace(this); 1296 } 1297 } 1298 1299 /** 1300 * Gets the LaunchedApp object if another app is active. Fails if the launcher is not in that 1301 * state. 1302 * 1303 * @return LaunchedApp object. 1304 */ 1305 @NonNull getLaunchedAppState()1306 public LaunchedAppState getLaunchedAppState() { 1307 return new LaunchedAppState(this); 1308 } 1309 1310 /** 1311 * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is 1312 * not in that state. 1313 * 1314 * @return Widgets object. 1315 */ 1316 @NonNull getAllWidgets()1317 public Widgets getAllWidgets() { 1318 try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) { 1319 return new Widgets(this); 1320 } 1321 } 1322 1323 @NonNull getAddToHomeScreenPrompt()1324 public AddToHomeScreenPrompt getAddToHomeScreenPrompt() { 1325 try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) { 1326 return new AddToHomeScreenPrompt(this); 1327 } 1328 } 1329 1330 /** 1331 * Gets the Overview object if the current state is showing the overview panel. Fails if the 1332 * launcher is not in that state. 1333 * 1334 * @return Overview object. 1335 */ 1336 @NonNull getOverview()1337 public Overview getOverview() { 1338 try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) { 1339 return new Overview(this); 1340 } 1341 } 1342 1343 /** 1344 * Gets the homescreen All Apps object if the current state is showing the all apps panel opened 1345 * by swiping from workspace. Fails if the launcher is not in that state. Please don't call this 1346 * method if App Apps was opened by swiping up from Overview, as it won't fail and will return 1347 * an incorrect object. 1348 * 1349 * @return Home All Apps object. 1350 */ 1351 @NonNull getAllApps()1352 public HomeAllApps getAllApps() { 1353 try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) { 1354 return new HomeAllApps(this); 1355 } 1356 } 1357 assertAppLaunched(@onNull String expectedPackageName)1358 LaunchedAppState assertAppLaunched(@NonNull String expectedPackageName) { 1359 BySelector packageSelector = By.pkg(expectedPackageName); 1360 assertTrue("App didn't start: (" + packageSelector + ")", 1361 mDevice.wait(Until.hasObject(packageSelector), 1362 LauncherInstrumentation.WAIT_TIME_MS)); 1363 return new LaunchedAppState(this); 1364 } 1365 waitUntilLauncherObjectGone(String resId)1366 void waitUntilLauncherObjectGone(String resId) { 1367 waitUntilGoneBySelector(getLauncherObjectSelector(resId)); 1368 } 1369 waitUntilOverviewObjectGone(String resId)1370 void waitUntilOverviewObjectGone(String resId) { 1371 waitUntilGoneBySelector(getOverviewObjectSelector(resId)); 1372 } 1373 waitUntilSystemLauncherObjectGone(String resId)1374 void waitUntilSystemLauncherObjectGone(String resId) { 1375 if (is3PLauncher()) { 1376 waitUntilOverviewObjectGone(resId); 1377 } else { 1378 waitUntilLauncherObjectGone(resId); 1379 } 1380 } 1381 waitUntilLauncherObjectGone(BySelector selector)1382 void waitUntilLauncherObjectGone(BySelector selector) { 1383 waitUntilGoneBySelector(makeLauncherSelector(selector)); 1384 } 1385 waitUntilGoneBySelector(BySelector launcherSelector)1386 private void waitUntilGoneBySelector(BySelector launcherSelector) { 1387 assertTrue("Unexpected launcher object visible: " + launcherSelector, 1388 mDevice.wait(Until.gone(launcherSelector), 1389 WAIT_TIME_MS)); 1390 } 1391 hasSystemUiObject(String resId)1392 private boolean hasSystemUiObject(String resId) { 1393 return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId)); 1394 } 1395 1396 @NonNull waitForSystemUiObject(String resId)1397 UiObject2 waitForSystemUiObject(String resId) { 1398 final UiObject2 object = mDevice.wait( 1399 Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS); 1400 assertNotNull("Can't find a systemui object with id: " + resId, object); 1401 return object; 1402 } 1403 1404 @NonNull waitForSystemUiObject(BySelector selector)1405 UiObject2 waitForSystemUiObject(BySelector selector) { 1406 final UiObject2 object = TestHelpers.wait( 1407 Until.findObject(selector), WAIT_TIME_MS); 1408 assertNotNull("Can't find a systemui object with selector: " + selector, object); 1409 return object; 1410 } 1411 1412 @NonNull getHomeButton()1413 private UiObject2 getHomeButton() { 1414 UiModeManager uiManager = 1415 (UiModeManager) getContext().getSystemService(Context.UI_MODE_SERVICE); 1416 if (uiManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { 1417 return waitForAssistantHomeButton(); 1418 } else { 1419 return waitForNavigationUiObject("home"); 1420 } 1421 } 1422 1423 /* Assistant Home button is present when system is in car mode. */ 1424 @NonNull waitForAssistantHomeButton()1425 UiObject2 waitForAssistantHomeButton() { 1426 final UiObject2 object = mDevice.wait( 1427 Until.findObject(By.res(ASSISTANT_PACKAGE, ASSISTANT_GO_HOME_RES_ID)), 1428 WAIT_TIME_MS); 1429 assertNotNull( 1430 "Can't find an assistant UI object with id: " + ASSISTANT_GO_HOME_RES_ID, object); 1431 return object; 1432 } 1433 1434 @NonNull waitForNavigationUiObject(String resId)1435 UiObject2 waitForNavigationUiObject(String resId) { 1436 String resPackage = getNavigationButtonResPackage(); 1437 final UiObject2 object = mDevice.wait( 1438 Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS); 1439 assertNotNull("Can't find a navigation UI object with id: " + resId, object); 1440 return object; 1441 } 1442 1443 @Nullable findObjectInContainer(UiObject2 container, String resName)1444 UiObject2 findObjectInContainer(UiObject2 container, String resName) { 1445 try { 1446 return container.findObject(getLauncherObjectSelector(resName)); 1447 } catch (StaleObjectException e) { 1448 fail("The container disappeared from screen"); 1449 return null; 1450 } 1451 } 1452 1453 @Nullable findObjectInContainer(UiObject2 container, BySelector selector)1454 UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) { 1455 try { 1456 return container.findObject(selector); 1457 } catch (StaleObjectException e) { 1458 fail("The container disappeared from screen"); 1459 return null; 1460 } 1461 } 1462 1463 @NonNull getObjectsInContainer(UiObject2 container, String resName)1464 List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) { 1465 try { 1466 return container.findObjects(getLauncherObjectSelector(resName)); 1467 } catch (StaleObjectException e) { 1468 fail("The container disappeared from screen"); 1469 return null; 1470 } 1471 } 1472 1473 @NonNull waitForObjectInContainer(UiObject2 container, String resName)1474 UiObject2 waitForObjectInContainer(UiObject2 container, String resName) { 1475 try { 1476 final UiObject2 object = container.wait( 1477 Until.findObject(getLauncherObjectSelector(resName)), 1478 WAIT_TIME_MS); 1479 assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: " 1480 + container.getResourceName(), object); 1481 return object; 1482 } catch (StaleObjectException e) { 1483 fail("The container disappeared from screen"); 1484 return null; 1485 } 1486 } 1487 waitForObjectEnabled(UiObject2 object, String waitReason)1488 void waitForObjectEnabled(UiObject2 object, String waitReason) { 1489 try { 1490 assertTrue("Timed out waiting for object to be enabled for " + waitReason + " " 1491 + object.getResourceName(), 1492 object.wait(Until.enabled(true), WAIT_TIME_MS)); 1493 } catch (StaleObjectException e) { 1494 fail("The object disappeared from screen"); 1495 } 1496 } 1497 waitForObjectFocused(UiObject2 object, String waitReason)1498 void waitForObjectFocused(UiObject2 object, String waitReason) { 1499 try { 1500 assertTrue("Timed out waiting for object to be focused for " + waitReason + " " 1501 + object.getResourceName(), 1502 object.wait(Until.focused(true), WAIT_TIME_MS)); 1503 } catch (StaleObjectException e) { 1504 fail("The object disappeared from screen"); 1505 } 1506 } 1507 1508 @NonNull waitForObjectInContainer(UiObject2 container, BySelector selector)1509 UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) { 1510 return waitForObjectsInContainer(container, selector).get(0); 1511 } 1512 1513 @NonNull waitForObjectsInContainer( UiObject2 container, BySelector selector)1514 List<UiObject2> waitForObjectsInContainer( 1515 UiObject2 container, BySelector selector) { 1516 try { 1517 final List<UiObject2> objects = container.wait( 1518 Until.findObjects(selector), 1519 WAIT_TIME_MS); 1520 assertNotNull("Can't find views in Launcher, id: " + selector + " in container: " 1521 + container.getResourceName(), objects); 1522 assertTrue("Can't find views in Launcher, id: " + selector + " in container: " 1523 + container.getResourceName(), objects.size() > 0); 1524 return objects; 1525 } catch (StaleObjectException e) { 1526 fail("The container disappeared from screen"); 1527 return null; 1528 } 1529 } 1530 getChildren(UiObject2 container)1531 List<UiObject2> getChildren(UiObject2 container) { 1532 try { 1533 return container.getChildren(); 1534 } catch (StaleObjectException e) { 1535 fail("The container disappeared from screen"); 1536 return null; 1537 } 1538 } 1539 hasLauncherObject(String resId)1540 private boolean hasLauncherObject(String resId) { 1541 return mDevice.hasObject(getLauncherObjectSelector(resId)); 1542 } 1543 hasSystemLauncherObject(String resId)1544 private boolean hasSystemLauncherObject(String resId) { 1545 return mDevice.hasObject(is3PLauncher() ? getOverviewObjectSelector(resId) 1546 : getLauncherObjectSelector(resId)); 1547 } 1548 hasLauncherObject(BySelector selector)1549 boolean hasLauncherObject(BySelector selector) { 1550 return mDevice.hasObject(makeLauncherSelector(selector)); 1551 } 1552 makeLauncherSelector(BySelector selector)1553 private BySelector makeLauncherSelector(BySelector selector) { 1554 return By.copy(selector).pkg(getLauncherPackageName()); 1555 } 1556 1557 @NonNull waitForOverviewObject(String resName)1558 UiObject2 waitForOverviewObject(String resName) { 1559 return waitForObjectBySelector(getOverviewObjectSelector(resName)); 1560 } 1561 1562 @NonNull waitForLauncherObject(String resName)1563 UiObject2 waitForLauncherObject(String resName) { 1564 return waitForObjectBySelector(getLauncherObjectSelector(resName)); 1565 } 1566 1567 @NonNull waitForSystemLauncherObject(String resName)1568 UiObject2 waitForSystemLauncherObject(String resName) { 1569 return is3PLauncher() ? waitForOverviewObject(resName) 1570 : waitForLauncherObject(resName); 1571 } 1572 1573 @NonNull waitForLauncherObject(BySelector selector)1574 UiObject2 waitForLauncherObject(BySelector selector) { 1575 return waitForObjectBySelector(makeLauncherSelector(selector)); 1576 } 1577 1578 @NonNull tryWaitForLauncherObject(BySelector selector, long timeout)1579 UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) { 1580 return tryWaitForObjectBySelector(makeLauncherSelector(selector), timeout); 1581 } 1582 1583 @NonNull waitForAndroidObject(String resId)1584 UiObject2 waitForAndroidObject(String resId) { 1585 final UiObject2 object = TestHelpers.wait( 1586 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS); 1587 assertNotNull("Can't find a android object with id: " + resId, object); 1588 return object; 1589 } 1590 1591 @NonNull waitForObjectsBySelector(BySelector selector)1592 List<UiObject2> waitForObjectsBySelector(BySelector selector) { 1593 final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS); 1594 assertNotNull("Can't find any view in Launcher, selector: " + selector, objects); 1595 return objects; 1596 } 1597 waitForObjectBySelector(BySelector selector)1598 UiObject2 waitForObjectBySelector(BySelector selector) { 1599 final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); 1600 assertNotNull("Can't find a view in Launcher, selector: " + selector, object); 1601 return object; 1602 } 1603 tryWaitForObjectBySelector(BySelector selector, long timeout)1604 private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) { 1605 return mDevice.wait(Until.findObject(selector), timeout); 1606 } 1607 getLauncherObjectSelector(String resName)1608 BySelector getLauncherObjectSelector(String resName) { 1609 return By.res(getLauncherPackageName(), resName); 1610 } 1611 getOverviewObjectSelector(String resName)1612 BySelector getOverviewObjectSelector(String resName) { 1613 return By.res(getOverviewPackageName(), resName); 1614 } 1615 getLauncherPackageName()1616 String getLauncherPackageName() { 1617 return mDevice.getLauncherPackageName(); 1618 } 1619 is3PLauncher()1620 boolean is3PLauncher() { 1621 return !getOverviewPackageName().equals(getLauncherPackageName()); 1622 } 1623 1624 @NonNull getDevice()1625 public UiDevice getDevice() { 1626 return mDevice; 1627 } 1628 eventListToString(List<Integer> actualEvents)1629 private static String eventListToString(List<Integer> actualEvents) { 1630 if (actualEvents.isEmpty()) return "no events"; 1631 1632 return "[" 1633 + actualEvents.stream() 1634 .map(state -> TestProtocol.stateOrdinalToString(state)) 1635 .collect(Collectors.joining(", ")) 1636 + "]"; 1637 } 1638 runToState(Runnable command, int expectedState, boolean requireEvent, String actionName)1639 void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) { 1640 if (requireEvent) { 1641 runToState(command, expectedState, actionName); 1642 } else { 1643 command.run(); 1644 } 1645 } 1646 1647 /** Run an action and wait for the specified Launcher state. */ runToState(Runnable command, int expectedState, String actionName)1648 public void runToState(Runnable command, int expectedState, String actionName) { 1649 final List<Integer> actualEvents = new ArrayList<>(); 1650 executeAndWaitForLauncherEvent( 1651 command, 1652 event -> isSwitchToStateEvent(event, expectedState, actualEvents), 1653 () -> "Failed to receive an event for the state change: expected [" 1654 + TestProtocol.stateOrdinalToString(expectedState) 1655 + "], actual: " + eventListToString(actualEvents), 1656 actionName); 1657 } 1658 isSwitchToStateEvent( AccessibilityEvent event, int expectedState, List<Integer> actualEvents)1659 private boolean isSwitchToStateEvent( 1660 AccessibilityEvent event, int expectedState, List<Integer> actualEvents) { 1661 if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false; 1662 1663 final Bundle parcel = (Bundle) event.getParcelableData(); 1664 final int actualState = parcel.getInt(TestProtocol.STATE_FIELD); 1665 actualEvents.add(actualState); 1666 return actualState == expectedState; 1667 } 1668 swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, GestureScope gestureScope)1669 void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, 1670 GestureScope gestureScope) { 1671 runToState( 1672 () -> linearGesture(startX, startY, endX, endY, steps, false, gestureScope), 1673 expectedState, 1674 "swiping"); 1675 } 1676 getBottomGestureSize()1677 int getBottomGestureSize() { 1678 return Math.max(getWindowInsets().bottom, ResourceUtils.getNavbarSize( 1679 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1; 1680 } 1681 getBottomGestureMarginInContainer(UiObject2 container)1682 int getBottomGestureMarginInContainer(UiObject2 container) { 1683 final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen(); 1684 return getVisibleBounds(container).bottom - bottomGestureStartOnScreen; 1685 } 1686 getRightGestureMarginInContainer(UiObject2 container)1687 int getRightGestureMarginInContainer(UiObject2 container) { 1688 final int rightGestureStartOnScreen = getRightGestureStartOnScreen(); 1689 return getVisibleBounds(container).right - rightGestureStartOnScreen; 1690 } 1691 getBottomGestureStartOnScreen()1692 int getBottomGestureStartOnScreen() { 1693 return getRealDisplaySize().y - getBottomGestureSize(); 1694 } 1695 getRightGestureStartOnScreen()1696 int getRightGestureStartOnScreen() { 1697 return getRealDisplaySize().x - getWindowInsets().right - 1; 1698 } 1699 1700 /** 1701 * Click on the ui object right away without waiting for animation. 1702 * 1703 * [UiObject2.click] would wait for all animations finished before clicking. Not waiting for 1704 * animations because in some scenarios there is a playing animations when the click is 1705 * attempted. 1706 */ clickObject(UiObject2 uiObject)1707 void clickObject(UiObject2 uiObject) { 1708 final long clickTime = SystemClock.uptimeMillis(); 1709 final Point center = uiObject.getVisibleCenter(); 1710 sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center, 1711 GestureScope.DONT_EXPECT_PILFER); 1712 sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center, 1713 GestureScope.DONT_EXPECT_PILFER); 1714 } 1715 clickLauncherObject(UiObject2 object)1716 void clickLauncherObject(UiObject2 object) { 1717 clickObject(object); 1718 } 1719 scrollToLastVisibleRow( UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, int appsListBottomPadding)1720 void scrollToLastVisibleRow( 1721 UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, 1722 int appsListBottomPadding) { 1723 final int itemRowCurrentTopOnScreen = bottomVisibleIconBounds.top; 1724 final Rect containerRect = getVisibleBounds(container); 1725 final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer; 1726 final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop(); 1727 1728 scrollDownByDistance(container, distance, appsListBottomPadding); 1729 } 1730 1731 /** Scrolls up by given distance within the container. */ scrollUpByDistance(UiObject2 container, int distance)1732 void scrollUpByDistance(UiObject2 container, int distance) { 1733 scrollUpByDistance(container, distance, 0); 1734 } 1735 1736 /** Scrolls up by given distance within the container considering the given bottom padding. */ scrollUpByDistance(UiObject2 container, int distance, int bottomPadding)1737 void scrollUpByDistance(UiObject2 container, int distance, int bottomPadding) { 1738 final Rect containerRect = getVisibleBounds(container); 1739 final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); 1740 scroll( 1741 container, 1742 Direction.UP, 1743 new Rect( 1744 0, 1745 containerRect.height() - bottomGestureMarginInContainer - distance, 1746 0, 1747 bottomGestureMarginInContainer + bottomPadding), 1748 /* steps= */ 10, 1749 /* slowDown= */ true); 1750 } 1751 scrollDownByDistance(UiObject2 container, int distance)1752 void scrollDownByDistance(UiObject2 container, int distance) { 1753 scrollDownByDistance(container, distance, 0); 1754 } 1755 scrollDownByDistance(UiObject2 container, int distance, int bottomPadding)1756 void scrollDownByDistance(UiObject2 container, int distance, int bottomPadding) { 1757 final Rect containerRect = getVisibleBounds(container); 1758 final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); 1759 scroll( 1760 container, 1761 Direction.DOWN, 1762 new Rect( 1763 0, 1764 containerRect.height() - distance - bottomGestureMarginInContainer, 1765 0, 1766 bottomGestureMarginInContainer + bottomPadding), 1767 /* steps= */ 10, 1768 /* slowDown= */ true); 1769 } 1770 scrollLeftByDistance(UiObject2 container, int distance)1771 void scrollLeftByDistance(UiObject2 container, int distance) { 1772 final Rect containerRect = getVisibleBounds(container); 1773 final int rightGestureMarginInContainer = getRightGestureMarginInContainer(container); 1774 final int leftGestureMargin = getTargetInsets().left + getEdgeSensitivityWidth(); 1775 scroll( 1776 container, 1777 Direction.LEFT, 1778 new Rect(leftGestureMargin, 1779 0, 1780 Math.max(containerRect.width() - distance - leftGestureMargin, 1781 rightGestureMarginInContainer), 1782 0), 1783 10, 1784 true); 1785 } 1786 scroll( UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown)1787 void scroll( 1788 UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) { 1789 final Rect rect = getVisibleBounds(container); 1790 if (margins != null) { 1791 rect.left += margins.left; 1792 rect.top += margins.top; 1793 rect.right -= margins.right; 1794 rect.bottom -= margins.bottom; 1795 } 1796 1797 final int startX; 1798 final int startY; 1799 final int endX; 1800 final int endY; 1801 1802 switch (direction) { 1803 case UP: { 1804 startX = endX = rect.centerX(); 1805 startY = rect.top; 1806 endY = rect.bottom - 1; 1807 } 1808 break; 1809 case DOWN: { 1810 startX = endX = rect.centerX(); 1811 startY = rect.bottom - 1; 1812 endY = rect.top; 1813 } 1814 break; 1815 case LEFT: { 1816 startY = endY = rect.centerY(); 1817 startX = rect.left; 1818 endX = rect.right - 1; 1819 } 1820 break; 1821 case RIGHT: { 1822 startY = endY = rect.centerY(); 1823 startX = rect.right - 1; 1824 endX = rect.left; 1825 } 1826 break; 1827 default: 1828 fail("Unsupported direction"); 1829 return; 1830 } 1831 1832 executeAndWaitForLauncherEvent( 1833 () -> linearGesture( 1834 startX, startY, endX, endY, steps, slowDown, 1835 GestureScope.DONT_EXPECT_PILFER), 1836 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), 1837 () -> "Didn't receive a scroll end message: " + startX + ", " + startY 1838 + ", " + endX + ", " + endY, 1839 "scrolling"); 1840 } 1841 pointerScroll(float pointerX, float pointerY, Direction direction)1842 void pointerScroll(float pointerX, float pointerY, Direction direction) { 1843 executeAndWaitForLauncherEvent( 1844 () -> injectEvent(getPointerMotionEvent( 1845 ACTION_SCROLL, pointerX, pointerY, direction)), 1846 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), 1847 () -> "Didn't receive a scroll end message: " + direction + " scroll from (" 1848 + pointerX + ", " + pointerY + ")", 1849 "scrolling"); 1850 } 1851 1852 // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a 1853 // fixed interval each time. linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown, GestureScope gestureScope)1854 public void linearGesture(int startX, int startY, int endX, int endY, int steps, 1855 boolean slowDown, GestureScope gestureScope) { 1856 log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY); 1857 final long downTime = SystemClock.uptimeMillis(); 1858 final Point start = new Point(startX, startY); 1859 final Point end = new Point(endX, endY); 1860 long endTime = downTime; 1861 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope); 1862 try { 1863 1864 if (mTrackpadGestureType != TrackpadGestureType.NONE) { 1865 sendPointer(downTime, downTime, 1866 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1), 1867 start, gestureScope); 1868 if (mTrackpadGestureType == TrackpadGestureType.THREE_FINGER 1869 || mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) { 1870 sendPointer(downTime, downTime, 1871 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2), 1872 start, gestureScope); 1873 if (mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) { 1874 sendPointer(downTime, downTime, 1875 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 3), 1876 start, gestureScope); 1877 } 1878 } 1879 } 1880 endTime = movePointer( 1881 start, end, steps, false, downTime, downTime, slowDown, gestureScope); 1882 } finally { 1883 if (mTrackpadGestureType != TrackpadGestureType.NONE) { 1884 for (int i = mPointerCount; i >= 2; i--) { 1885 sendPointer(downTime, downTime, 1886 getPointerAction(MotionEvent.ACTION_POINTER_UP, i - 1), 1887 start, gestureScope); 1888 } 1889 } 1890 sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope); 1891 } 1892 } 1893 getPointerAction(int action, int index)1894 private static int getPointerAction(int action, int index) { 1895 return action + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT); 1896 } 1897 movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, long startTime, boolean slowDown, GestureScope gestureScope)1898 long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, 1899 long startTime, boolean slowDown, GestureScope gestureScope) { 1900 long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS, 1901 isDecelerating, start, end, gestureScope); 1902 if (slowDown) { 1903 endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end, 1904 end, gestureScope); 1905 } 1906 return endTime; 1907 } 1908 waitForIdle()1909 void waitForIdle() { 1910 mDevice.waitForIdle(); 1911 } 1912 getTouchSlop()1913 int getTouchSlop() { 1914 return ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1915 } 1916 getResources()1917 public Resources getResources() { 1918 return getContext().getResources(); 1919 } 1920 getPointerMotionEvent( int action, float x, float y, Direction direction)1921 private static MotionEvent getPointerMotionEvent( 1922 int action, float x, float y, Direction direction) { 1923 MotionEvent.PointerCoords[] coordinates = new MotionEvent.PointerCoords[1]; 1924 coordinates[0] = new MotionEvent.PointerCoords(); 1925 coordinates[0].x = x; 1926 coordinates[0].y = y; 1927 boolean isVertical = direction == Direction.UP || direction == Direction.DOWN; 1928 boolean isForward = direction == Direction.RIGHT || direction == Direction.DOWN; 1929 coordinates[0].setAxisValue( 1930 isVertical ? MotionEvent.AXIS_VSCROLL : MotionEvent.AXIS_HSCROLL, 1931 isForward ? 1f : -1f); 1932 1933 MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1]; 1934 properties[0] = new MotionEvent.PointerProperties(); 1935 properties[0].id = 0; 1936 properties[0].toolType = MotionEvent.TOOL_TYPE_MOUSE; 1937 1938 final long downTime = SystemClock.uptimeMillis(); 1939 return MotionEvent.obtain( 1940 downTime, 1941 downTime, 1942 action, 1943 /* pointerCount= */ 1, 1944 properties, 1945 coordinates, 1946 /* metaState= */ 0, 1947 /* buttonState= */ 0, 1948 /* xPrecision= */ 1f, 1949 /* yPrecision= */ 1f, 1950 /* deviceId= */ 0, 1951 /* edgeFlags= */ 0, 1952 InputDevice.SOURCE_CLASS_POINTER, 1953 /* flags= */ 0); 1954 } 1955 getTrackpadMotionEvent(long downTime, long eventTime, int action, float x, float y, int pointerCount, TrackpadGestureType gestureType)1956 private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime, 1957 int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) { 1958 MotionEvent.PointerProperties[] pointerProperties = 1959 new MotionEvent.PointerProperties[pointerCount]; 1960 MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount]; 1961 boolean isMultiFingerGesture = gestureType != TrackpadGestureType.TWO_FINGER; 1962 for (int i = 0; i < pointerCount; i++) { 1963 pointerProperties[i] = getPointerProperties(i); 1964 pointerCoords[i] = getPointerCoords(x, y); 1965 if (isMultiFingerGesture) { 1966 pointerCoords[i].setAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT, 1967 gestureType == TrackpadGestureType.THREE_FINGER ? 3 : 4); 1968 } 1969 } 1970 return MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerProperties, 1971 pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, 1972 InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_CLASS_POINTER, 0, 0, 1973 isMultiFingerGesture ? MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE 1974 : MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE); 1975 } 1976 getMotionEvent(long downTime, long eventTime, int action, float x, float y, int source, int toolType)1977 private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, 1978 float x, float y, int source, int toolType) { 1979 return MotionEvent.obtain(downTime, eventTime, action, 1, 1980 new MotionEvent.PointerProperties[]{getPointerProperties(0, toolType)}, 1981 new MotionEvent.PointerCoords[]{getPointerCoords(x, y)}, 1982 0, 0, 1.0f, 1.0f, 0, 0, source, 0); 1983 } 1984 getPointerProperties(int pointerId)1985 private static MotionEvent.PointerProperties getPointerProperties(int pointerId) { 1986 return getPointerProperties(pointerId, Configurator.getInstance().getToolType()); 1987 } 1988 getPointerProperties(int pointerId, int toolType)1989 private static MotionEvent.PointerProperties getPointerProperties(int pointerId, int toolType) { 1990 MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties(); 1991 properties.id = pointerId; 1992 properties.toolType = toolType; 1993 return properties; 1994 } 1995 getPointerCoords(float x, float y)1996 private static MotionEvent.PointerCoords getPointerCoords(float x, float y) { 1997 MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); 1998 coords.pressure = 1; 1999 coords.size = 1; 2000 coords.x = x; 2001 coords.y = y; 2002 return coords; 2003 } 2004 hasTIS()2005 private boolean hasTIS() { 2006 return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean( 2007 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2008 } 2009 isGridOnlyOverviewEnabled()2010 public boolean isGridOnlyOverviewEnabled() { 2011 return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean( 2012 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2013 } 2014 isAppPairsEnabled()2015 boolean isAppPairsEnabled() { 2016 return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean( 2017 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2018 } 2019 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope)2020 public void sendPointer(long downTime, long currentTime, int action, Point point, 2021 GestureScope gestureScope) { 2022 sendPointer(downTime, currentTime, action, point, gestureScope, 2023 InputDevice.SOURCE_TOUCHSCREEN, false); 2024 } 2025 injectEvent(InputEvent event)2026 private void injectEvent(InputEvent event) { 2027 assertTrue("injectInputEvent failed: event=" + event, 2028 mInstrumentation.getUiAutomation().injectInputEvent(event, true, false)); 2029 } 2030 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source)2031 public void sendPointer(long downTime, long currentTime, int action, Point point, 2032 GestureScope gestureScope, int source) { 2033 sendPointer(downTime, currentTime, action, point, gestureScope, source, false); 2034 } 2035 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source, boolean isRightClick)2036 public void sendPointer(long downTime, long currentTime, int action, Point point, 2037 GestureScope gestureScope, int source, boolean isRightClick) { 2038 sendPointer( 2039 downTime, 2040 currentTime, 2041 action, 2042 point, 2043 gestureScope, 2044 source, 2045 isRightClick, 2046 Configurator.getInstance().getToolType()); 2047 } 2048 sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source, boolean isRightClick, int toolType)2049 public void sendPointer(long downTime, long currentTime, int action, Point point, 2050 GestureScope gestureScope, int source, boolean isRightClick, int toolType) { 2051 final boolean hasTIS = hasTIS(); 2052 int pointerCount = mPointerCount; 2053 2054 boolean isTrackpadGesture = mTrackpadGestureType != TrackpadGestureType.NONE; 2055 switch (action & MotionEvent.ACTION_MASK) { 2056 case MotionEvent.ACTION_DOWN: 2057 if (isTrackpadGesture) { 2058 mPointerCount = 1; 2059 pointerCount = mPointerCount; 2060 } 2061 break; 2062 case MotionEvent.ACTION_UP: 2063 if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) { 2064 expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS); 2065 } 2066 break; 2067 case MotionEvent.ACTION_POINTER_DOWN: 2068 mPointerCount++; 2069 pointerCount = mPointerCount; 2070 break; 2071 case MotionEvent.ACTION_POINTER_UP: 2072 // When the gesture is handled outside, it's cancelled within launcher. 2073 mPointerCount--; 2074 break; 2075 } 2076 2077 final MotionEvent event = isTrackpadGesture 2078 ? getTrackpadMotionEvent( 2079 downTime, currentTime, action, point.x, point.y, pointerCount, 2080 mTrackpadGestureType) 2081 : getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType); 2082 if (action == MotionEvent.ACTION_BUTTON_PRESS 2083 || action == MotionEvent.ACTION_BUTTON_RELEASE) { 2084 event.setActionButton(MotionEvent.BUTTON_PRIMARY); 2085 } 2086 if (isRightClick) { 2087 event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY); 2088 } 2089 injectEvent(event); 2090 } 2091 createKeyEvent(int keyCode, int metaState, boolean actionDown)2092 private KeyEvent createKeyEvent(int keyCode, int metaState, boolean actionDown) { 2093 long eventTime = SystemClock.uptimeMillis(); 2094 return KeyEvent.obtain( 2095 eventTime, 2096 eventTime, 2097 actionDown ? ACTION_DOWN : ACTION_UP, 2098 keyCode, 2099 /* repeat= */ 0, 2100 metaState, 2101 KeyCharacterMap.VIRTUAL_KEYBOARD, 2102 /* scancode= */ 0, 2103 /* flags= */ 0, 2104 InputDevice.SOURCE_KEYBOARD, 2105 /* characters =*/ null); 2106 } 2107 2108 /** 2109 * Sends a {@link KeyEvent} with {@link ACTION_DOWN} for the given key codes without sending 2110 * a {@link KeyEvent} with {@link ACTION_UP}. 2111 */ pressAndHoldKeyCode(int keyCode, int metaState)2112 public void pressAndHoldKeyCode(int keyCode, int metaState) { 2113 injectEvent(createKeyEvent(keyCode, metaState, true)); 2114 } 2115 2116 2117 /** 2118 * Sends a {@link KeyEvent} with {@link ACTION_UP} for the given key codes. 2119 */ unpressKeyCode(int keyCode, int metaState)2120 public void unpressKeyCode(int keyCode, int metaState) { 2121 injectEvent(createKeyEvent(keyCode, metaState, false)); 2122 } 2123 movePointer(long downTime, long startTime, long duration, Point from, Point to, GestureScope gestureScope)2124 public long movePointer(long downTime, long startTime, long duration, Point from, Point to, 2125 GestureScope gestureScope) { 2126 return movePointer(downTime, startTime, duration, false, from, to, gestureScope); 2127 } 2128 movePointer(long downTime, long startTime, long duration, boolean isDecelerating, Point from, Point to, GestureScope gestureScope)2129 public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating, 2130 Point from, Point to, GestureScope gestureScope) { 2131 log("movePointer: " + from + " to " + to); 2132 final Point point = new Point(); 2133 long steps = duration / GESTURE_STEP_MS; 2134 2135 long currentTime = startTime; 2136 2137 if (isDecelerating) { 2138 // formula: V = V0 - D*T, assuming V = 0 when T = duration 2139 2140 // vx0: initial speed at the x-dimension, set as twice the avg speed 2141 // dx: the constant deceleration at the x-dimension 2142 double vx0 = 2.0 * (to.x - from.x) / duration; 2143 double dx = vx0 / duration; 2144 // vy0: initial speed at the y-dimension, set as twice the avg speed 2145 // dy: the constant deceleration at the y-dimension 2146 double vy0 = 2.0 * (to.y - from.y) / duration; 2147 double dy = vy0 / duration; 2148 2149 for (long i = 0; i < steps; ++i) { 2150 sleep(GESTURE_STEP_MS); 2151 currentTime += GESTURE_STEP_MS; 2152 2153 // formula: P = P0 + V0*T - (D*T^2/2) 2154 final double t = (i + 1) * GESTURE_STEP_MS; 2155 point.x = from.x + (int) (vx0 * t - 0.5 * dx * t * t); 2156 point.y = from.y + (int) (vy0 * t - 0.5 * dy * t * t); 2157 2158 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope); 2159 } 2160 } else { 2161 for (long i = 0; i < steps; ++i) { 2162 sleep(GESTURE_STEP_MS); 2163 currentTime += GESTURE_STEP_MS; 2164 2165 final float progress = (currentTime - startTime) / (float) duration; 2166 point.x = from.x + (int) (progress * (to.x - from.x)); 2167 point.y = from.y + (int) (progress * (to.y - from.y)); 2168 2169 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope); 2170 2171 } 2172 } 2173 2174 return currentTime; 2175 } 2176 getCurrentInteractionMode(Context context)2177 public static int getCurrentInteractionMode(Context context) { 2178 return getSystemIntegerRes(context, "config_navBarInteractionMode"); 2179 } 2180 2181 /** 2182 * Retrieve the resource value that defines if nav bar can moved or if it is fixed position. 2183 */ getNavBarCanMove(Context context)2184 private static boolean getNavBarCanMove(Context context) { 2185 return getSystemBooleanRes(context, "config_navBarCanMove"); 2186 } 2187 2188 @NonNull clickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent)2189 UiObject2 clickAndGet( 2190 @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) { 2191 final Point targetCenter = target.getVisibleCenter(); 2192 final long downTime = SystemClock.uptimeMillis(); 2193 // Use stylus secondary button press to prevent using the exteded long press timeout rule 2194 // unnecessarily 2195 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, 2196 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, 2197 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); 2198 try { 2199 expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent); 2200 final UiObject2 result = waitForLauncherObject(resName); 2201 return result; 2202 } finally { 2203 sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter, 2204 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN, 2205 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS); 2206 } 2207 } 2208 2209 @NonNull rightClickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent)2210 UiObject2 rightClickAndGet( 2211 @NonNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent) { 2212 final Point targetCenter = target.getVisibleCenter(); 2213 final long downTime = SystemClock.uptimeMillis(); 2214 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, 2215 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, 2216 /* isRightClick= */ true); 2217 try { 2218 expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent); 2219 final UiObject2 result = waitForLauncherObject(resName); 2220 return result; 2221 } finally { 2222 sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter, 2223 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, 2224 /* isRightClick= */ true); 2225 } 2226 } 2227 getSystemBooleanRes(Context context, String resName)2228 private static boolean getSystemBooleanRes(Context context, String resName) { 2229 Resources res = context.getResources(); 2230 int resId = res.getIdentifier(resName, "bool", "android"); 2231 2232 if (resId != 0) { 2233 return res.getBoolean(resId); 2234 } else { 2235 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 2236 return false; 2237 } 2238 } 2239 getSystemIntegerRes(Context context, String resName)2240 private static int getSystemIntegerRes(Context context, String resName) { 2241 Resources res = context.getResources(); 2242 int resId = res.getIdentifier(resName, "integer", "android"); 2243 2244 if (resId != 0) { 2245 return res.getInteger(resId); 2246 } else { 2247 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 2248 return -1; 2249 } 2250 } 2251 getSystemDimensionResId(Context context, String resName)2252 private static int getSystemDimensionResId(Context context, String resName) { 2253 Resources res = context.getResources(); 2254 int resId = res.getIdentifier(resName, "dimen", "android"); 2255 2256 if (resId != 0) { 2257 return resId; 2258 } else { 2259 Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?"); 2260 return -1; 2261 } 2262 } 2263 sleep(int duration)2264 static void sleep(int duration) { 2265 SystemClock.sleep(duration); 2266 } 2267 getEdgeSensitivityWidth()2268 int getEdgeSensitivityWidth() { 2269 try { 2270 final Context context = mInstrumentation.getTargetContext().createPackageContext( 2271 getLauncherPackageName(), 0); 2272 return context.getResources().getDimensionPixelSize( 2273 getSystemDimensionResId(context, "config_backGestureInset")) + 1; 2274 } catch (PackageManager.NameNotFoundException e) { 2275 fail("Can't get edge sensitivity: " + e); 2276 return 0; 2277 } 2278 } 2279 2280 /** Returns the bounds of the display as a Point where x is width and y is height. */ getRealDisplaySize()2281 Point getRealDisplaySize() { 2282 final Rect displayBounds = getContext().getSystemService(WindowManager.class) 2283 .getMaximumWindowMetrics() 2284 .getBounds(); 2285 return new Point(displayBounds.width(), displayBounds.height()); 2286 } 2287 enableDebugTracing()2288 public void enableDebugTracing() { 2289 getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING); 2290 } 2291 disableSensorRotation()2292 private void disableSensorRotation() { 2293 getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION); 2294 } 2295 disableDebugTracing()2296 public void disableDebugTracing() { 2297 getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING); 2298 } 2299 forceGc()2300 public void forceGc() { 2301 // GC the system & sysui first before gc'ing launcher 2302 logShellCommand("cmd statusbar run-gc"); 2303 getTestInfo(TestProtocol.REQUEST_FORCE_GC); 2304 } 2305 getPid()2306 public Integer getPid() { 2307 final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID); 2308 return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null; 2309 } 2310 getRecentTasks()2311 public ArrayList<ComponentName> getRecentTasks() { 2312 ArrayList<ComponentName> tasks = new ArrayList<>(); 2313 ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST) 2314 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2315 for (String s : components) { 2316 tasks.add(ComponentName.unflattenFromString(s)); 2317 } 2318 return tasks; 2319 } 2320 2321 /** Reinitializes the workspace to its default layout. */ reinitializeLauncherData()2322 public void reinitializeLauncherData() { 2323 getTestInfo(TestProtocol.REQUEST_REINITIALIZE_DATA); 2324 } 2325 2326 /** Clears the workspace, leaving it empty. */ clearLauncherData()2327 public void clearLauncherData() { 2328 getTestInfo(TestProtocol.REQUEST_CLEAR_DATA); 2329 } 2330 2331 /** Shows the taskbar if it is hidden, otherwise does nothing. */ showTaskbarIfHidden()2332 public void showTaskbarIfHidden() { 2333 getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED); 2334 } 2335 2336 /** Shows the bubble bar if it is stashed, otherwise this does nothing. */ showBubbleBarIfHidden()2337 public void showBubbleBarIfHidden() { 2338 getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED); 2339 } 2340 injectFakeTrackpad()2341 public void injectFakeTrackpad() { 2342 getTestInfo(TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD); 2343 } 2344 ejectFakeTrackpad()2345 public void ejectFakeTrackpad() { 2346 getTestInfo(TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD); 2347 } 2348 2349 /** Blocks the taskbar from automatically stashing based on time. */ enableBlockTimeout(boolean enable)2350 public void enableBlockTimeout(boolean enable) { 2351 getTestInfo(enable 2352 ? TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT 2353 : TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT); 2354 } 2355 isTransientTaskbar()2356 public boolean isTransientTaskbar() { 2357 return getTestInfo(TestProtocol.REQUEST_IS_TRANSIENT_TASKBAR) 2358 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2359 } 2360 isImeDocked()2361 public boolean isImeDocked() { 2362 return getTestInfo(TestProtocol.REQUEST_TASKBAR_IME_DOCKED).getBoolean( 2363 TestProtocol.TEST_INFO_RESPONSE_FIELD); 2364 } 2365 2366 /** Enables transient taskbar for testing purposes only. */ enableTransientTaskbar(boolean enable)2367 public void enableTransientTaskbar(boolean enable) { 2368 getTestInfo(enable 2369 ? TestProtocol.REQUEST_ENABLE_TRANSIENT_TASKBAR 2370 : TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR); 2371 } 2372 2373 /** 2374 * Recreates the taskbar (outside of tests this is done for certain configuration changes). 2375 * The expected behavior is that the taskbar retains its current state after being recreated. 2376 * For example, if taskbar is currently stashed, it should still be stashed after recreating. 2377 */ recreateTaskbar()2378 public void recreateTaskbar() { 2379 getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR); 2380 } 2381 2382 // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup. 2383 2384 /** Refreshes the known overview target in TIS. */ refreshOverviewTarget()2385 public void refreshOverviewTarget() { 2386 getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET); 2387 } 2388 getHotseatIconNames()2389 public List<String> getHotseatIconNames() { 2390 return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES) 2391 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2392 } 2393 getActivities()2394 private String[] getActivities() { 2395 return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES) 2396 .getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2397 } 2398 getRootedActivitiesList()2399 public String getRootedActivitiesList() { 2400 return String.join(", ", getActivities()); 2401 } 2402 2403 /** Returns whether no leaked activities are detected. */ noLeakedActivities(boolean requireOneActiveActivity)2404 public boolean noLeakedActivities(boolean requireOneActiveActivity) { 2405 final String[] activities = getActivities(); 2406 2407 for (String activity : activities) { 2408 if (activity.contains("(destroyed)")) { 2409 return false; 2410 } 2411 } 2412 return activities.length <= (requireOneActiveActivity ? 1 : 2); 2413 } 2414 getActivitiesCreated()2415 public int getActivitiesCreated() { 2416 return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT) 2417 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); 2418 } 2419 eventsCheck()2420 public Closable eventsCheck() { 2421 Assert.assertTrue("Nested event checking", mEventChecker == null); 2422 disableSensorRotation(); 2423 final Integer initialPid = getPid(); 2424 final LogEventChecker eventChecker = new LogEventChecker(this); 2425 eventChecker.setLogExclusionRule(event -> { 2426 Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event); 2427 if (matcher.find()) { 2428 long keyEventFlags = Long.parseLong(matcher.group(1), 16); 2429 // ignore KeyEvents with FLAG_CANCELED 2430 return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0; 2431 } 2432 return false; 2433 }); 2434 2435 if (eventChecker.start()) mEventChecker = eventChecker; 2436 2437 return () -> { 2438 if (initialPid != null && initialPid.intValue() != getPid()) { 2439 if (mOnLauncherCrashed != null) mOnLauncherCrashed.run(); 2440 checkForAnomaly(); 2441 Assert.fail( 2442 formatSystemHealthMessage( 2443 formatErrorWithEvents("Launcher crashed", false))); 2444 } 2445 2446 if (mEventChecker != null) { 2447 mEventChecker = null; 2448 if (mCheckEventsForSuccessfulGestures) { 2449 final String message = eventChecker.verify(WAIT_TIME_MS); 2450 if (message != null) { 2451 dumpDiagnostics(message); 2452 checkForAnomaly(); 2453 Assert.fail(formatSystemHealthMessage( 2454 "http://go/tapl : successful gesture produced " + message)); 2455 } 2456 } else { 2457 eventChecker.finishNoWait(); 2458 } 2459 } 2460 }; 2461 } 2462 2463 /** Returns whether the Launcher is a Launcher3 one */ 2464 public boolean isLauncher3() { 2465 if (mIsLauncher3 == null) { 2466 mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName()); 2467 } 2468 return mIsLauncher3; 2469 } 2470 2471 void expectEvent(String sequence, Pattern expected) { 2472 if (mEventChecker != null) { 2473 mEventChecker.expectPattern(sequence, expected); 2474 } else { 2475 Log.d(TAG, "Expecting: " + sequence + " / " + expected); 2476 } 2477 } 2478 2479 Rect getVisibleBounds(UiObject2 object) { 2480 try { 2481 return object.getVisibleBounds(); 2482 } catch (StaleObjectException e) { 2483 fail("Object disappeared from screen"); 2484 return null; 2485 } catch (Throwable t) { 2486 fail(t.toString()); 2487 return null; 2488 } 2489 } 2490 2491 float getWindowCornerRadius() { 2492 // Return a larger corner radius to ensure gesture calculated from the radius are offset to 2493 // prevent overlapping 2494 final float tmpBuffer = 100f; 2495 final Resources resources = getResources(); 2496 if (!supportsRoundedCornersOnWindows(resources)) { 2497 Log.d(TAG, "No rounded corners"); 2498 return tmpBuffer; 2499 } 2500 2501 // Radius that should be used in case top or bottom aren't defined. 2502 float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0); 2503 2504 float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0); 2505 if (topRadius == 0f) { 2506 topRadius = defaultRadius; 2507 } 2508 float bottomRadius = ResourceUtils.getDimenByName( 2509 "rounded_corner_radius_bottom", resources, 0); 2510 if (bottomRadius == 0f) { 2511 bottomRadius = defaultRadius; 2512 } 2513 2514 // Always use the smallest radius to make sure the rounded corners will 2515 // completely cover the display. 2516 Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius); 2517 return Math.max(topRadius, bottomRadius) + tmpBuffer; 2518 } 2519 2520 private Context getLauncherContext(Context baseContext) 2521 throws PackageManager.NameNotFoundException { 2522 // Workaround, use constructed context because both the instrumentation context and the 2523 // app context are not constructed with resources that take overlays into account 2524 return baseContext.createPackageContext(getLauncherPackageName(), 0); 2525 } 2526 2527 private static boolean supportsRoundedCornersOnWindows(Resources resources) { 2528 return ResourceUtils.getBoolByName( 2529 "config_supportsRoundedCornersOnWindows", resources, false); 2530 } 2531 2532 /** 2533 * Taps outside container to dismiss, centered vertically and halfway to the edge of the screen. 2534 * 2535 * @param container container to be dismissed 2536 * @param tapRight tap on the right of the container if true, or left otherwise 2537 */ 2538 void touchOutsideContainer(UiObject2 container, boolean tapRight) { 2539 touchOutsideContainer(container, tapRight, true); 2540 } 2541 2542 /** 2543 * Taps outside the container, to the right or left, and centered vertically. 2544 * 2545 * @param tapRight if true touches to the right of the container, otherwise touches on left 2546 * @param halfwayToEdge if true touches halfway to the screen edge, if false touches 1 px from 2547 * container 2548 */ 2549 void touchOutsideContainer(UiObject2 container, boolean tapRight, boolean halfwayToEdge) { 2550 try (LauncherInstrumentation.Closable c = addContextLayer( 2551 "want to tap outside container on the " + (tapRight ? "right" : "left"))) { 2552 Rect containerBounds = getVisibleBounds(container); 2553 2554 int x; 2555 if (halfwayToEdge) { 2556 x = tapRight 2557 ? (containerBounds.right + getRealDisplaySize().x) / 2 2558 : containerBounds.left / 2; 2559 } else { 2560 x = tapRight 2561 ? containerBounds.right + 1 2562 : containerBounds.left - 1; 2563 } 2564 // If IME is visible and overlaps the container bounds, touch above it. 2565 final Insets systemGestureRegion = getSystemGestureRegion(); 2566 int bottomBound = Math.min( 2567 containerBounds.bottom, 2568 getRealDisplaySize().y - systemGestureRegion.bottom); 2569 int y = (bottomBound + containerBounds.top) / 2; 2570 // Do not tap in the status bar. 2571 y = Math.max(y, systemGestureRegion.top); 2572 2573 final long downTime = SystemClock.uptimeMillis(); 2574 final Point tapTarget = new Point(x, y); 2575 sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget, 2576 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); 2577 sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget, 2578 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); 2579 } 2580 } 2581 2582 /** 2583 * Waits until a particular condition is true. Based on WaitMixin. 2584 */ 2585 boolean waitAndGet(BooleanSupplier condition, long timeout, long interval) { 2586 long startTime = SystemClock.uptimeMillis(); 2587 2588 boolean result = condition.getAsBoolean(); 2589 for (long elapsedTime = 0; !result; elapsedTime = SystemClock.uptimeMillis() - startTime) { 2590 if (elapsedTime >= timeout) { 2591 break; 2592 } 2593 SystemClock.sleep(interval); 2594 result = condition.getAsBoolean(); 2595 } 2596 return result; 2597 } 2598 2599 /** Executes a runnable and waits for the wallpaper-open animation completion. */ 2600 public void executeAndWaitForWallpaperAnimation(Runnable r, String actionName) { 2601 executeAndWaitForLauncherEvent( 2602 () -> r.run(), 2603 event -> TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE 2604 .equals(event.getClassName().toString()), 2605 () -> "Didn't detect finishing wallpaper-open animation", 2606 actionName); 2607 } 2608 } 2609