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