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