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