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