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