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