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.pm.PackageManager;
45 import android.content.pm.ProviderInfo;
46 import android.content.res.Configuration;
47 import android.content.res.Resources;
48 import android.graphics.Insets;
49 import android.graphics.Point;
50 import android.graphics.Rect;
51 import android.net.Uri;
52 import android.os.Bundle;
53 import android.os.DeadObjectException;
54 import android.os.Parcelable;
55 import android.os.RemoteException;
56 import android.os.SystemClock;
57 import android.os.Trace;
58 import android.text.TextUtils;
59 import android.util.Log;
60 import android.view.InputDevice;
61 import android.view.InputEvent;
62 import android.view.KeyCharacterMap;
63 import android.view.KeyEvent;
64 import android.view.MotionEvent;
65 import android.view.ViewConfiguration;
66 import android.view.WindowManager;
67 import android.view.accessibility.AccessibilityEvent;
68 
69 import androidx.annotation.NonNull;
70 import androidx.annotation.Nullable;
71 import androidx.test.InstrumentationRegistry;
72 import androidx.test.uiautomator.By;
73 import androidx.test.uiautomator.BySelector;
74 import androidx.test.uiautomator.Configurator;
75 import androidx.test.uiautomator.Direction;
76 import androidx.test.uiautomator.StaleObjectException;
77 import androidx.test.uiautomator.UiDevice;
78 import androidx.test.uiautomator.UiObject2;
79 import androidx.test.uiautomator.Until;
80 
81 import com.android.launcher3.testing.shared.ResourceUtils;
82 import com.android.launcher3.testing.shared.TestInformationRequest;
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 
getKeyEventPattern(String action, String keyCode)214     private static Pattern getKeyEventPattern(String action, String keyCode) {
215         return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
216     }
217 
218     /**
219      * Constructs the root of TAPL hierarchy. You get all other objects from it.
220      */
LauncherInstrumentation()221     public LauncherInstrumentation() {
222         this(InstrumentationRegistry.getInstrumentation(), false);
223     }
224 
225     /**
226      * Constructs the root of TAPL hierarchy. You get all other objects from it.
227      */
LauncherInstrumentation(boolean isLauncherTest)228     public LauncherInstrumentation(boolean isLauncherTest) {
229         this(InstrumentationRegistry.getInstrumentation(), isLauncherTest);
230     }
231 
232     /**
233      * Constructs the root of TAPL hierarchy. You get all other objects from it.
234      *
235      * @deprecated use the constructor without Instrumentation parameter instead.
236      */
237     @Deprecated
LauncherInstrumentation(Instrumentation instrumentation)238     public LauncherInstrumentation(Instrumentation instrumentation) {
239         this(instrumentation, false);
240     }
241 
242     /**
243      * Constructs the root of TAPL hierarchy. You get all other objects from it.
244      *
245      * @deprecated use the constructor without Instrumentation parameter instead.
246      */
247     @Deprecated
LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest)248     public LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest) {
249         mInstrumentation = instrumentation;
250         mDevice = UiDevice.getInstance(instrumentation);
251 
252         // Launcher should run in test harness so that custom accessibility protocol between
253         // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
254         // into Launcher.
255         assertTrue("Device must run in a test harness. "
256                         + "Run `adb shell setprop ro.test_harness 1` to enable it.",
257                 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
258 
259         final String testPackage = getContext().getPackageName();
260         final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
261 
262         // Launcher package. As during inproc tests the tested launcher may not be selected as the
263         // current launcher, choosing target package for inproc. For out-of-proc, use the installed
264         // launcher package.
265         mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation()
266                 ? getLauncherPackageName()
267                 : targetPackage;
268 
269         String testProviderAuthority = mLauncherPackage + ".TestInfo";
270         mTestProviderUri = new Uri.Builder()
271                 .scheme(ContentResolver.SCHEME_CONTENT)
272                 .authority(testProviderAuthority)
273                 .build();
274 
275         mInstrumentation.getUiAutomation().grantRuntimePermission(
276                 testPackage, "android.permission.WRITE_SECURE_SETTINGS");
277 
278         PackageManager pm = getContext().getPackageManager();
279         ProviderInfo pi = pm.resolveContentProvider(
280                 testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS);
281         assertNotNull("Cannot find content provider for " + testProviderAuthority, pi);
282         ComponentName cn = new ComponentName(pi.packageName, pi.name);
283 
284         final int iterations = isLauncherTest ? 300 : 100;
285 
286         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
287             if (TestHelpers.isInLauncherProcess()) {
288                 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
289             } else {
290                 try {
291                     final int userId = getContext().getUserId();
292                     final String launcherPidCommand = "pidof " + pi.packageName;
293                     final String initialPid = mDevice.executeShellCommand(launcherPidCommand);
294 
295                     mDevice.executeShellCommand(
296                             "pm enable --user " + userId + " " + cn.flattenToString());
297 
298                     // Wait for Launcher restart after enabling test provider.
299                     for (int i = 0; i < iterations; ++i) {
300                         final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
301                                 .replaceAll("\\s", "");
302                         if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
303                         if (i == iterations - 1) {
304                             fail("Launcher didn't restart after enabling test provider");
305                         }
306                         SystemClock.sleep(100);
307                     }
308                 } catch (IOException e) {
309                     fail(e.toString());
310                 }
311             }
312 
313             // Wait for Launcher content provider to become enabled.
314             for (int i = 0; i < iterations; ++i) {
315                 final ContentProviderClient testProvider = getContext().getContentResolver()
316                         .acquireContentProviderClient(mTestProviderUri);
317                 if (testProvider != null) {
318                     testProvider.close();
319                     break;
320                 }
321                 if (i == iterations - 1) {
322                     fail("Launcher content provider is still not enabled");
323                 }
324                 SystemClock.sleep(100);
325             }
326         }
327     }
328 
329     /**
330      * Gradle only supports out of process instrumentation. The test package is automatically
331      * generated by appending `.test` to the target package.
332      */
isGradleInstrumentation()333     private boolean isGradleInstrumentation() {
334         final String testPackage = getContext().getPackageName();
335         final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
336         final String testSuffix = ".test";
337 
338         return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length()
339                 && testPackage.substring(0, testPackage.length() - testSuffix.length())
340                 .equals(targetPackage);
341     }
342 
enableCheckEventsForSuccessfulGestures()343     public void enableCheckEventsForSuccessfulGestures() {
344         mCheckEventsForSuccessfulGestures = true;
345     }
346 
347     /** Sets a runnable that will be invoked upon assertion failures. */
setOnFailure(Runnable onFailure)348     public void setOnFailure(Runnable onFailure) {
349         mOnFailure = onFailure;
350     }
351 
setOnLauncherCrashed(Runnable onLauncherCrashed)352     public void setOnLauncherCrashed(Runnable onLauncherCrashed) {
353         mOnLauncherCrashed = onLauncherCrashed;
354     }
355 
getContext()356     Context getContext() {
357         return mInstrumentation.getContext();
358     }
359 
getTestInfo(String request)360     Bundle getTestInfo(String request) {
361         return getTestInfo(request, /*arg=*/ null);
362     }
363 
getTestInfo(String request, String arg)364     Bundle getTestInfo(String request, String arg) {
365         return getTestInfo(request, arg, null);
366     }
367 
getTestInfo(String request, String arg, Bundle extra)368     Bundle getTestInfo(String request, String arg, Bundle extra) {
369         try (ContentProviderClient client = getContext().getContentResolver()
370                 .acquireContentProviderClient(mTestProviderUri)) {
371             return client.call(request, arg, extra);
372         } catch (DeadObjectException e) {
373             fail("Launcher crashed");
374             return null;
375         } catch (RemoteException e) {
376             throw new RuntimeException(e);
377         }
378     }
379 
getTestInfo(TestInformationRequest request)380     Bundle getTestInfo(TestInformationRequest request) {
381         Bundle extra = new Bundle();
382         extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request);
383         return getTestInfo(request.getRequestName(), null, extra);
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 
fail(String message)757     void fail(String message) {
758         checkForAnomaly();
759         if (mOnFailure != null) mOnFailure.run();
760         Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
761                 "http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription()
762                         + "; now visible state is " + getVisibleStateMessage(), true)));
763     }
764 
getContextDescription()765     private String getContextDescription() {
766         return mDiagnosticContext.isEmpty()
767                 ? "(no context)" : String.join(", ", mDiagnosticContext);
768     }
769 
assertTrue(String message, boolean condition)770     void assertTrue(String message, boolean condition) {
771         if (!condition) {
772             fail(message);
773         }
774     }
775 
assertNotNull(String message, Object object)776     void assertNotNull(String message, Object object) {
777         assertTrue(message, object != null);
778     }
779 
failEquals(String message, Object actual)780     private void failEquals(String message, Object actual) {
781         fail(message + ". " + "Actual: " + actual);
782     }
783 
assertEquals(String message, int expected, int actual)784     void assertEquals(String message, int expected, int actual) {
785         if (expected != actual) {
786             fail(message + " expected: " + expected + " but was: " + actual);
787         }
788     }
789 
assertEquals(String message, String expected, String actual)790     void assertEquals(String message, String expected, String actual) {
791         if (!TextUtils.equals(expected, actual)) {
792             fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
793         }
794     }
795 
assertEquals(String message, long expected, long actual)796     void assertEquals(String message, long expected, long actual) {
797         if (expected != actual) {
798             fail(message + " expected: " + expected + " but was: " + actual);
799         }
800     }
801 
assertNotEquals(String message, int unexpected, int actual)802     void assertNotEquals(String message, int unexpected, int actual) {
803         if (unexpected == actual) {
804             failEquals(message, actual);
805         }
806     }
807 
808     /**
809      * Whether to ignore verifying the task bar visibility during instrumenting.
810      *
811      * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly
812      *                                verifying the task bar visibility with
813      *                                {@link VisibleContainer#verifyActiveContainer}.
814      *                                {@code false} otherwise.
815      */
setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility)816     public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) {
817         mIgnoreTaskbarVisibility = ignoreTaskbarVisibility;
818     }
819 
820     /**
821      * Set the trackpad gesture type of the interaction.
822      *
823      * @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or
824      *                            four-finger gesture.
825      */
setTrackpadGestureType(TrackpadGestureType trackpadGestureType)826     public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
827         mTrackpadGestureType = trackpadGestureType;
828     }
829 
getTrackpadGestureType()830     TrackpadGestureType getTrackpadGestureType() {
831         return mTrackpadGestureType;
832     }
833 
834     /**
835      * Sets expected rotation.
836      * TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one.
837      * Null parameter disables checks. The initial state is "no checks".
838      */
setExpectedRotation(Integer expectedRotation)839     public void setExpectedRotation(Integer expectedRotation) {
840         mExpectedRotation = expectedRotation;
841     }
842 
setExpectedRotationCheckEnabled(boolean expectedRotationCheckEnabled)843     public void setExpectedRotationCheckEnabled(boolean expectedRotationCheckEnabled) {
844         mExpectedRotationCheckEnabled = expectedRotationCheckEnabled;
845     }
846 
getExpectedRotationCheckEnabled()847     public boolean getExpectedRotationCheckEnabled() {
848         return mExpectedRotationCheckEnabled;
849     }
850 
getNavigationModeMismatchError(boolean waitForCorrectState)851     public String getNavigationModeMismatchError(boolean waitForCorrectState) {
852         final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0;
853         final NavigationModel navigationModel = getNavigationModel();
854         String resPackage = getNavigationButtonResPackage();
855         if (navigationModel == NavigationModel.THREE_BUTTON) {
856             if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) {
857                 return "Recents button not present in 3-button mode";
858             }
859         } else {
860             if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) {
861                 return "Recents button is present in non-3-button mode";
862             }
863         }
864 
865         if (navigationModel == NavigationModel.ZERO_BUTTON) {
866             if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) {
867                 return "Home button is present in gestural mode";
868             }
869         } else {
870             if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) {
871                 return "Home button not present in non-gestural mode";
872             }
873         }
874         return null;
875     }
876 
getNavigationButtonResPackage()877     private String getNavigationButtonResPackage() {
878         return isTablet() || isTaskbarNavbarUnificationEnabled()
879                 ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
880     }
881 
verifyContainerType(ContainerType containerType)882     UiObject2 verifyContainerType(ContainerType containerType) {
883         waitForLauncherInitialized();
884 
885         if (mExpectedRotationCheckEnabled && mExpectedRotation != null) {
886             assertEquals("Unexpected display rotation",
887                     mExpectedRotation, mDevice.getDisplayRotation());
888         }
889 
890         final String error = getNavigationModeMismatchError(true);
891         assertTrue(error, error == null);
892 
893         log("verifyContainerType: " + containerType);
894 
895         final UiObject2 container = verifyVisibleObjects(containerType);
896 
897         return container;
898     }
899 
verifyVisibleObjects(ContainerType containerType)900     private UiObject2 verifyVisibleObjects(ContainerType containerType) {
901         try (Closable c = addContextLayer(
902                 "but the current state is not " + containerType.name())) {
903             switch (containerType) {
904                 case WORKSPACE: {
905                     waitUntilLauncherObjectGone(APPS_RES_ID);
906                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
907                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
908                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
909                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
910                     waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
911 
912                     return waitForLauncherObject(WORKSPACE_RES_ID);
913                 }
914                 case WIDGETS: {
915                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
916                     waitUntilLauncherObjectGone(APPS_RES_ID);
917                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
918                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
919                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
920                     waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
921 
922                     return waitForLauncherObject(WIDGETS_RES_ID);
923                 }
924                 case TASKBAR_ALL_APPS: {
925                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
926                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
927                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
928                     waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
929                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
930                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
931 
932                     return waitForLauncherObject(APPS_RES_ID);
933                 }
934                 case HOME_ALL_APPS: {
935                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
936                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
937                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
938                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
939 
940                     if (is3PLauncher() && isTablet() && !isTransientTaskbar()) {
941                         waitForSystemLauncherObject(TASKBAR_RES_ID);
942                     } else {
943                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
944                     }
945 
946                     boolean splitSelectionActive = getTestInfo(REQUEST_GET_SPLIT_SELECTION_ACTIVE)
947                             .getBoolean(TEST_INFO_RESPONSE_FIELD);
948                     if (!splitSelectionActive) {
949                         waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
950                     } // do nothing, we expect that view
951 
952                     return waitForLauncherObject(APPS_RES_ID);
953                 }
954                 case OVERVIEW:
955                 case FALLBACK_OVERVIEW: {
956                     waitUntilLauncherObjectGone(APPS_RES_ID);
957                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
958                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
959                     if (isTablet() && !is3PLauncher()) {
960                         waitForSystemLauncherObject(TASKBAR_RES_ID);
961                     } else {
962                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
963                     }
964                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
965                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
966 
967                     return waitForSystemLauncherObject(OVERVIEW_RES_ID);
968                 }
969                 case SPLIT_SCREEN_SELECT: {
970                     waitUntilLauncherObjectGone(APPS_RES_ID);
971                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
972                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
973                     if (isTablet()) {
974                         waitForSystemLauncherObject(TASKBAR_RES_ID);
975                     } else {
976                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
977                     }
978 
979                     waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
980                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
981                     return waitForSystemLauncherObject(OVERVIEW_RES_ID);
982                 }
983                 case LAUNCHED_APP: {
984                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
985                     waitUntilLauncherObjectGone(APPS_RES_ID);
986                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
987                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
988                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
989                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
990 
991                     if (mIgnoreTaskbarVisibility) {
992                         return null;
993                     }
994 
995                     if (isTablet()) {
996                         // Only check that Persistent Taskbar is visible, since Transient Taskbar
997                         // may or may not be visible by design.
998                         if (!isTransientTaskbar()) {
999                             waitForSystemLauncherObject(TASKBAR_RES_ID);
1000                         }
1001                     } else {
1002                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
1003                     }
1004                     return null;
1005                 }
1006                 default:
1007                     fail("Invalid state: " + containerType);
1008                     return null;
1009             }
1010         }
1011     }
1012 
waitForModelQueueCleared()1013     public void waitForModelQueueCleared() {
1014         getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED);
1015     }
1016 
waitForLauncherInitialized()1017     public void waitForLauncherInitialized() {
1018         try {
1019             Trace.beginSection("waitForLauncherInitialized");
1020             for (int i = 0; i < 100; ++i) {
1021                 if (getTestInfo(TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).getBoolean(
1022                         TestProtocol.TEST_INFO_RESPONSE_FIELD)) {
1023                     return;
1024                 }
1025                 SystemClock.sleep(100);
1026             }
1027             checkForAnomaly();
1028             fail("Launcher didn't initialize");
1029         } finally {
1030             Trace.endSection();
1031         }
1032     }
1033 
isLauncherActivityStarted()1034     public boolean isLauncherActivityStarted() {
1035         return getTestInfo(
1036                 TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED).
1037                 getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
1038     }
1039 
executeAndWaitForLauncherEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)1040     Parcelable executeAndWaitForLauncherEvent(Runnable command,
1041             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message,
1042             String actionName) {
1043         return executeAndWaitForEvent(
1044                 command,
1045                 e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e),
1046                 message, actionName);
1047     }
1048 
executeAndWaitForEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)1049     Parcelable executeAndWaitForEvent(Runnable command,
1050             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message,
1051             String actionName) {
1052         try (LauncherInstrumentation.Closable c = addContextLayer(actionName)) {
1053             try {
1054                 final AccessibilityEvent event =
1055                         mInstrumentation.getUiAutomation().executeAndWaitForEvent(
1056                                 command, eventFilter, WAIT_TIME_MS);
1057                 assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
1058                 final Parcelable parcelableData = event.getParcelableData();
1059                 event.recycle();
1060                 return parcelableData;
1061             } catch (TimeoutException e) {
1062                 fail(message.get());
1063                 return null;
1064             }
1065         }
1066     }
1067 
executeAndWaitForLauncherStop(Runnable command, String actionName)1068     void executeAndWaitForLauncherStop(Runnable command, String actionName) {
1069         executeAndWaitForLauncherEvent(
1070                 () -> command.run(),
1071                 event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE
1072                         .equals(event.getClassName().toString()),
1073                 () -> "Launcher activity didn't stop", actionName);
1074     }
1075 
1076     /**
1077      * Get the resource ID of visible floating view.
1078      */
getFloatingResId()1079     private Optional<String> getFloatingResId() {
1080         if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
1081             return Optional.of(CONTEXT_MENU_RES_ID);
1082         }
1083         if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) {
1084             return Optional.of(FOLDER_CONTENT_RES_ID);
1085         }
1086         return Optional.empty();
1087     }
1088 
1089     /**
1090      * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content.
1091      */
swipeUpToCloseFloatingView()1092     private void swipeUpToCloseFloatingView() {
1093         final Point displaySize = getRealDisplaySize();
1094 
1095         final Optional<String> floatingRes = getFloatingResId();
1096 
1097         if (!floatingRes.isPresent()) {
1098             return;
1099         }
1100 
1101         if (isLauncher3()) {
1102             gestureToDismissPopup(displaySize);
1103         } else {
1104             runToState(() -> gestureToDismissPopup(displaySize), NORMAL_STATE_ORDINAL, "swiping");
1105         }
1106 
1107         try (LauncherInstrumentation.Closable c1 = addContextLayer(
1108                 String.format("Swiped up from floating view %s to home", floatingRes.get()))) {
1109             waitUntilLauncherObjectGone(floatingRes.get());
1110             waitForLauncherObject(getAnyObjectSelector());
1111         }
1112     }
1113 
gestureToDismissPopup(Point displaySize)1114     private void gestureToDismissPopup(Point displaySize) {
1115         linearGesture(
1116                 displaySize.x / 2, displaySize.y - 1,
1117                 displaySize.x / 2, 0,
1118                 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
1119                 false, GestureScope.EXPECT_PILFER);
1120     }
1121 
1122     /**
1123      * @return the Workspace object.
1124      * @deprecated use goHome().
1125      * Presses nav bar home button.
1126      */
1127     @Deprecated
pressHome()1128     public Workspace pressHome() {
1129         return goHome();
1130     }
1131 
1132     /**
1133      * Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then
1134      * performing {@code goHome()} action.
1135      *
1136      * @return the Workspace object.
1137      */
goHomeFromImmersiveFullscreenApp()1138     public Workspace goHomeFromImmersiveFullscreenApp() {
1139         final boolean navBarCanMove = getNavBarCanMove();
1140         if (getNavigationModel() == NavigationModel.ZERO_BUTTON || !navBarCanMove) {
1141             // in gesture nav we can swipe up at the bottom to bring the navbar handle
1142             final Point displaySize = getRealDisplaySize();
1143             linearGesture(
1144                     displaySize.x / 2, displaySize.y - 1,
1145                     displaySize.x / 2, 0,
1146                     ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
1147                     false, GestureScope.EXPECT_PILFER);
1148         } else {
1149             // in 3 button nav we swipe up on the side edge of the screen to bring the navbar
1150             final boolean rotated90degrees = mDevice.getDisplayRotation() == ROTATION_90;
1151             final Point displaySize = getRealDisplaySize();
1152             final int startX = rotated90degrees ? displaySize.x : 0;
1153             final int endX = rotated90degrees ? 0 : displaySize.x;
1154             linearGesture(
1155                     startX, displaySize.y / 2,
1156                     endX, displaySize.y / 2,
1157                     ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
1158                     false, GestureScope.EXPECT_PILFER);
1159         }
1160         return goHome();
1161     }
1162 
1163     /**
1164      * Goes to home by swiping up in zero-button mode or pressing Home button.
1165      * Calling it after another TAPL call is safe because all TAPL methods wait for the animations
1166      * to finish.
1167      * When calling it after a non-TAPL method, make sure that all animations have already
1168      * completed, otherwise it may detect the current state (for example "Application" or "Home")
1169      * incorrectly.
1170      * The method expects either app or Launcher to be active when it's called. Other states, such
1171      * as visible notification shade are not supported.
1172      *
1173      * @return the Workspace object.
1174      */
goHome()1175     public Workspace goHome() {
1176         try (LauncherInstrumentation.Closable e = eventsCheck();
1177              LauncherInstrumentation.Closable c = addContextLayer("want to switch to home")) {
1178             waitForLauncherInitialized();
1179             // Click home, then wait for any accessibility event, then wait until accessibility
1180             // events stop.
1181             // We need waiting for any accessibility event generated after pressing Home because
1182             // otherwise waitForIdle may return immediately in case when there was a big enough
1183             // pause in accessibility events prior to pressing Home.
1184             boolean isThreeFingerTrackpadGesture =
1185                     mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
1186             final String action;
1187             if (getNavigationModel() == NavigationModel.ZERO_BUTTON
1188                     || isThreeFingerTrackpadGesture) {
1189                 checkForAnomaly(false, true);
1190 
1191                 final Point displaySize = getRealDisplaySize();
1192 
1193                 // CLose floating views before going back to home.
1194                 swipeUpToCloseFloatingView();
1195 
1196                 if (hasLauncherObject(WORKSPACE_RES_ID)) {
1197                     log(action = "already at home");
1198                 } else {
1199                     action = "swiping up to home";
1200 
1201                     int startY = isThreeFingerTrackpadGesture ? displaySize.y * 3 / 4
1202                             : displaySize.y - 1;
1203                     int endY = isThreeFingerTrackpadGesture ? displaySize.y / 4 : displaySize.y / 2;
1204                     swipeToState(
1205                             displaySize.x / 2, startY,
1206                             displaySize.x / 2, endY,
1207                             ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
1208                             GestureScope.EXPECT_PILFER);
1209                 }
1210             } else {
1211                 log("Hierarchy before clicking home:");
1212                 dumpViewHierarchy();
1213                 action = "clicking home button";
1214                 runToState(
1215                         getHomeButton()::click,
1216                         NORMAL_STATE_ORDINAL,
1217                         !hasLauncherObject(WORKSPACE_RES_ID)
1218                                 && (hasLauncherObject(APPS_RES_ID)
1219                                 || hasSystemLauncherObject(OVERVIEW_RES_ID)),
1220                         action);
1221             }
1222             try (LauncherInstrumentation.Closable c1 = addContextLayer(
1223                     "performed action to switch to Home - " + action)) {
1224                 return getWorkspace();
1225             }
1226         }
1227     }
1228 
1229     /**
1230      * Press navbar back button or swipe back if in gesture navigation mode.
1231      */
pressBack()1232     public void pressBack() {
1233         try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) {
1234             pressBackImpl();
1235         }
1236     }
1237 
pressBackImpl()1238     void pressBackImpl() {
1239         waitForLauncherInitialized();
1240         final boolean launcherVisible =
1241                 (isTablet() || isTaskbarNavbarUnificationEnabled()) ? isLauncherContainerVisible()
1242                         : isLauncherVisible();
1243         boolean isThreeFingerTrackpadGesture =
1244                 mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
1245         if (getNavigationModel() == NavigationModel.ZERO_BUTTON
1246                 || isThreeFingerTrackpadGesture) {
1247             final Point displaySize = getRealDisplaySize();
1248             // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the
1249             //  issue is solved.
1250             int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0;
1251             int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2;
1252             linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4,
1253                     10, false, GestureScope.DONT_EXPECT_PILFER);
1254         } else {
1255             waitForNavigationUiObject("back").click();
1256         }
1257         if (launcherVisible) {
1258             if (isPredictiveBackSwipeEnabled()) {
1259                 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED);
1260             } else {
1261                 expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
1262             }
1263         }
1264     }
1265 
getAnyObjectSelector()1266     private static BySelector getAnyObjectSelector() {
1267         return By.textStartsWith("");
1268     }
1269 
isLauncherVisible()1270     boolean isLauncherVisible() {
1271         try {
1272             Trace.beginSection("isLauncherVisible");
1273             mDevice.waitForIdle();
1274             return hasLauncherObject(getAnyObjectSelector());
1275         } finally {
1276             Trace.endSection();
1277         }
1278     }
1279 
isLauncherContainerVisible()1280     boolean isLauncherContainerVisible() {
1281         final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID};
1282         return Arrays.stream(containerResources).anyMatch(
1283                 r -> r.equals(OVERVIEW_RES_ID) ? hasSystemLauncherObject(r) : hasLauncherObject(r));
1284     }
1285 
1286     /**
1287      * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
1288      * launcher is not in that state.
1289      *
1290      * @return Workspace object.
1291      */
1292     @NonNull
getWorkspace()1293     public Workspace getWorkspace() {
1294         try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) {
1295             return new Workspace(this);
1296         }
1297     }
1298 
1299     /**
1300      * Gets the LaunchedApp object if another app is active. Fails if the launcher is not in that
1301      * state.
1302      *
1303      * @return LaunchedApp object.
1304      */
1305     @NonNull
getLaunchedAppState()1306     public LaunchedAppState getLaunchedAppState() {
1307         return new LaunchedAppState(this);
1308     }
1309 
1310     /**
1311      * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
1312      * not in that state.
1313      *
1314      * @return Widgets object.
1315      */
1316     @NonNull
getAllWidgets()1317     public Widgets getAllWidgets() {
1318         try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) {
1319             return new Widgets(this);
1320         }
1321     }
1322 
1323     @NonNull
getAddToHomeScreenPrompt()1324     public AddToHomeScreenPrompt getAddToHomeScreenPrompt() {
1325         try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) {
1326             return new AddToHomeScreenPrompt(this);
1327         }
1328     }
1329 
1330     /**
1331      * Gets the Overview object if the current state is showing the overview panel. Fails if the
1332      * launcher is not in that state.
1333      *
1334      * @return Overview object.
1335      */
1336     @NonNull
getOverview()1337     public Overview getOverview() {
1338         try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) {
1339             return new Overview(this);
1340         }
1341     }
1342 
1343     /**
1344      * Gets the homescreen All Apps object if the current state is showing the all apps panel opened
1345      * by swiping from workspace. Fails if the launcher is not in that state. Please don't call this
1346      * method if App Apps was opened by swiping up from Overview, as it won't fail and will return
1347      * an incorrect object.
1348      *
1349      * @return Home All Apps object.
1350      */
1351     @NonNull
getAllApps()1352     public HomeAllApps getAllApps() {
1353         try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
1354             return new HomeAllApps(this);
1355         }
1356     }
1357 
assertAppLaunched(@onNull String expectedPackageName)1358     LaunchedAppState assertAppLaunched(@NonNull String expectedPackageName) {
1359         BySelector packageSelector = By.pkg(expectedPackageName);
1360         assertTrue("App didn't start: (" + packageSelector + ")",
1361                 mDevice.wait(Until.hasObject(packageSelector),
1362                         LauncherInstrumentation.WAIT_TIME_MS));
1363         return new LaunchedAppState(this);
1364     }
1365 
waitUntilLauncherObjectGone(String resId)1366     void waitUntilLauncherObjectGone(String resId) {
1367         waitUntilGoneBySelector(getLauncherObjectSelector(resId));
1368     }
1369 
waitUntilOverviewObjectGone(String resId)1370     void waitUntilOverviewObjectGone(String resId) {
1371         waitUntilGoneBySelector(getOverviewObjectSelector(resId));
1372     }
1373 
waitUntilSystemLauncherObjectGone(String resId)1374     void waitUntilSystemLauncherObjectGone(String resId) {
1375         if (is3PLauncher()) {
1376             waitUntilOverviewObjectGone(resId);
1377         } else {
1378             waitUntilLauncherObjectGone(resId);
1379         }
1380     }
1381 
waitUntilLauncherObjectGone(BySelector selector)1382     void waitUntilLauncherObjectGone(BySelector selector) {
1383         waitUntilGoneBySelector(makeLauncherSelector(selector));
1384     }
1385 
waitUntilGoneBySelector(BySelector launcherSelector)1386     private void waitUntilGoneBySelector(BySelector launcherSelector) {
1387         assertTrue("Unexpected launcher object visible: " + launcherSelector,
1388                 mDevice.wait(Until.gone(launcherSelector),
1389                         WAIT_TIME_MS));
1390     }
1391 
hasSystemUiObject(String resId)1392     private boolean hasSystemUiObject(String resId) {
1393         return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
1394     }
1395 
1396     @NonNull
waitForSystemUiObject(String resId)1397     UiObject2 waitForSystemUiObject(String resId) {
1398         final UiObject2 object = mDevice.wait(
1399                 Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS);
1400         assertNotNull("Can't find a systemui object with id: " + resId, object);
1401         return object;
1402     }
1403 
1404     @NonNull
waitForSystemUiObject(BySelector selector)1405     UiObject2 waitForSystemUiObject(BySelector selector) {
1406         final UiObject2 object = TestHelpers.wait(
1407                 Until.findObject(selector), WAIT_TIME_MS);
1408         assertNotNull("Can't find a systemui object with selector: " + selector, object);
1409         return object;
1410     }
1411 
1412     @NonNull
getHomeButton()1413     private UiObject2 getHomeButton() {
1414         UiModeManager uiManager =
1415                 (UiModeManager) getContext().getSystemService(Context.UI_MODE_SERVICE);
1416         if (uiManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
1417             return waitForAssistantHomeButton();
1418         } else {
1419             return waitForNavigationUiObject("home");
1420         }
1421     }
1422 
1423     /* Assistant Home button is present when system is in car mode. */
1424     @NonNull
waitForAssistantHomeButton()1425     UiObject2 waitForAssistantHomeButton() {
1426         final UiObject2 object = mDevice.wait(
1427                 Until.findObject(By.res(ASSISTANT_PACKAGE, ASSISTANT_GO_HOME_RES_ID)),
1428                 WAIT_TIME_MS);
1429         assertNotNull(
1430                 "Can't find an assistant UI object with id: " + ASSISTANT_GO_HOME_RES_ID, object);
1431         return object;
1432     }
1433 
1434     @NonNull
waitForNavigationUiObject(String resId)1435     UiObject2 waitForNavigationUiObject(String resId) {
1436         String resPackage = getNavigationButtonResPackage();
1437         final UiObject2 object = mDevice.wait(
1438                 Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS);
1439         assertNotNull("Can't find a navigation UI object with id: " + resId, object);
1440         return object;
1441     }
1442 
1443     @Nullable
findObjectInContainer(UiObject2 container, String resName)1444     UiObject2 findObjectInContainer(UiObject2 container, String resName) {
1445         try {
1446             return container.findObject(getLauncherObjectSelector(resName));
1447         } catch (StaleObjectException e) {
1448             fail("The container disappeared from screen");
1449             return null;
1450         }
1451     }
1452 
1453     @Nullable
findObjectInContainer(UiObject2 container, BySelector selector)1454     UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
1455         try {
1456             return container.findObject(selector);
1457         } catch (StaleObjectException e) {
1458             fail("The container disappeared from screen");
1459             return null;
1460         }
1461     }
1462 
1463     @NonNull
getObjectsInContainer(UiObject2 container, String resName)1464     List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
1465         try {
1466             return container.findObjects(getLauncherObjectSelector(resName));
1467         } catch (StaleObjectException e) {
1468             fail("The container disappeared from screen");
1469             return null;
1470         }
1471     }
1472 
1473     @NonNull
waitForObjectInContainer(UiObject2 container, String resName)1474     UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
1475         try {
1476             final UiObject2 object = container.wait(
1477                     Until.findObject(getLauncherObjectSelector(resName)),
1478                     WAIT_TIME_MS);
1479             assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
1480                     + container.getResourceName(), object);
1481             return object;
1482         } catch (StaleObjectException e) {
1483             fail("The container disappeared from screen");
1484             return null;
1485         }
1486     }
1487 
waitForObjectEnabled(UiObject2 object, String waitReason)1488     void waitForObjectEnabled(UiObject2 object, String waitReason) {
1489         try {
1490             assertTrue("Timed out waiting for object to be enabled for " + waitReason + " "
1491                             + object.getResourceName(),
1492                     object.wait(Until.enabled(true), WAIT_TIME_MS));
1493         } catch (StaleObjectException e) {
1494             fail("The object disappeared from screen");
1495         }
1496     }
1497 
waitForObjectFocused(UiObject2 object, String waitReason)1498     void waitForObjectFocused(UiObject2 object, String waitReason) {
1499         try {
1500             assertTrue("Timed out waiting for object to be focused for " + waitReason + " "
1501                             + object.getResourceName(),
1502                     object.wait(Until.focused(true), WAIT_TIME_MS));
1503         } catch (StaleObjectException e) {
1504             fail("The object disappeared from screen");
1505         }
1506     }
1507 
1508     @NonNull
waitForObjectInContainer(UiObject2 container, BySelector selector)1509     UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
1510         return waitForObjectsInContainer(container, selector).get(0);
1511     }
1512 
1513     @NonNull
waitForObjectsInContainer( UiObject2 container, BySelector selector)1514     List<UiObject2> waitForObjectsInContainer(
1515             UiObject2 container, BySelector selector) {
1516         try {
1517             final List<UiObject2> objects = container.wait(
1518                     Until.findObjects(selector),
1519                     WAIT_TIME_MS);
1520             assertNotNull("Can't find views in Launcher, id: " + selector + " in container: "
1521                     + container.getResourceName(), objects);
1522             assertTrue("Can't find views in Launcher, id: " + selector + " in container: "
1523                     + container.getResourceName(), objects.size() > 0);
1524             return objects;
1525         } catch (StaleObjectException e) {
1526             fail("The container disappeared from screen");
1527             return null;
1528         }
1529     }
1530 
getChildren(UiObject2 container)1531     List<UiObject2> getChildren(UiObject2 container) {
1532         try {
1533             return container.getChildren();
1534         } catch (StaleObjectException e) {
1535             fail("The container disappeared from screen");
1536             return null;
1537         }
1538     }
1539 
hasLauncherObject(String resId)1540     private boolean hasLauncherObject(String resId) {
1541         return mDevice.hasObject(getLauncherObjectSelector(resId));
1542     }
1543 
hasSystemLauncherObject(String resId)1544     private boolean hasSystemLauncherObject(String resId) {
1545         return mDevice.hasObject(is3PLauncher() ? getOverviewObjectSelector(resId)
1546                 : getLauncherObjectSelector(resId));
1547     }
1548 
hasLauncherObject(BySelector selector)1549     boolean hasLauncherObject(BySelector selector) {
1550         return mDevice.hasObject(makeLauncherSelector(selector));
1551     }
1552 
makeLauncherSelector(BySelector selector)1553     private BySelector makeLauncherSelector(BySelector selector) {
1554         return By.copy(selector).pkg(getLauncherPackageName());
1555     }
1556 
1557     @NonNull
waitForOverviewObject(String resName)1558     UiObject2 waitForOverviewObject(String resName) {
1559         return waitForObjectBySelector(getOverviewObjectSelector(resName));
1560     }
1561 
1562     @NonNull
waitForLauncherObject(String resName)1563     UiObject2 waitForLauncherObject(String resName) {
1564         return waitForObjectBySelector(getLauncherObjectSelector(resName));
1565     }
1566 
1567     @NonNull
waitForSystemLauncherObject(String resName)1568     UiObject2 waitForSystemLauncherObject(String resName) {
1569         return is3PLauncher() ? waitForOverviewObject(resName)
1570                 : waitForLauncherObject(resName);
1571     }
1572 
1573     @NonNull
waitForLauncherObject(BySelector selector)1574     UiObject2 waitForLauncherObject(BySelector selector) {
1575         return waitForObjectBySelector(makeLauncherSelector(selector));
1576     }
1577 
1578     @NonNull
tryWaitForLauncherObject(BySelector selector, long timeout)1579     UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
1580         return tryWaitForObjectBySelector(makeLauncherSelector(selector), timeout);
1581     }
1582 
1583     @NonNull
waitForAndroidObject(String resId)1584     UiObject2 waitForAndroidObject(String resId) {
1585         final UiObject2 object = TestHelpers.wait(
1586                 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS);
1587         assertNotNull("Can't find a android object with id: " + resId, object);
1588         return object;
1589     }
1590 
1591     @NonNull
waitForObjectsBySelector(BySelector selector)1592     List<UiObject2> waitForObjectsBySelector(BySelector selector) {
1593         final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS);
1594         assertNotNull("Can't find any view in Launcher, selector: " + selector, objects);
1595         return objects;
1596     }
1597 
waitForObjectBySelector(BySelector selector)1598     UiObject2 waitForObjectBySelector(BySelector selector) {
1599         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
1600         assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
1601         return object;
1602     }
1603 
tryWaitForObjectBySelector(BySelector selector, long timeout)1604     private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) {
1605         return mDevice.wait(Until.findObject(selector), timeout);
1606     }
1607 
getLauncherObjectSelector(String resName)1608     BySelector getLauncherObjectSelector(String resName) {
1609         return By.res(getLauncherPackageName(), resName);
1610     }
1611 
getOverviewObjectSelector(String resName)1612     BySelector getOverviewObjectSelector(String resName) {
1613         return By.res(getOverviewPackageName(), resName);
1614     }
1615 
getLauncherPackageName()1616     String getLauncherPackageName() {
1617         return mDevice.getLauncherPackageName();
1618     }
1619 
is3PLauncher()1620     boolean is3PLauncher() {
1621         return !getOverviewPackageName().equals(getLauncherPackageName());
1622     }
1623 
1624     @NonNull
getDevice()1625     public UiDevice getDevice() {
1626         return mDevice;
1627     }
1628 
eventListToString(List<Integer> actualEvents)1629     private static String eventListToString(List<Integer> actualEvents) {
1630         if (actualEvents.isEmpty()) return "no events";
1631 
1632         return "["
1633                 + actualEvents.stream()
1634                 .map(state -> TestProtocol.stateOrdinalToString(state))
1635                 .collect(Collectors.joining(", "))
1636                 + "]";
1637     }
1638 
runToState(Runnable command, int expectedState, boolean requireEvent, String actionName)1639     void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) {
1640         if (requireEvent) {
1641             runToState(command, expectedState, actionName);
1642         } else {
1643             command.run();
1644         }
1645     }
1646 
1647     /** Run an action and wait for the specified Launcher state. */
runToState(Runnable command, int expectedState, String actionName)1648     public void runToState(Runnable command, int expectedState, String actionName) {
1649         final List<Integer> actualEvents = new ArrayList<>();
1650         executeAndWaitForLauncherEvent(
1651                 command,
1652                 event -> isSwitchToStateEvent(event, expectedState, actualEvents),
1653                 () -> "Failed to receive an event for the state change: expected ["
1654                         + TestProtocol.stateOrdinalToString(expectedState)
1655                         + "], actual: " + eventListToString(actualEvents),
1656                 actionName);
1657     }
1658 
isSwitchToStateEvent( AccessibilityEvent event, int expectedState, List<Integer> actualEvents)1659     private boolean isSwitchToStateEvent(
1660             AccessibilityEvent event, int expectedState, List<Integer> actualEvents) {
1661         if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false;
1662 
1663         final Bundle parcel = (Bundle) event.getParcelableData();
1664         final int actualState = parcel.getInt(TestProtocol.STATE_FIELD);
1665         actualEvents.add(actualState);
1666         return actualState == expectedState;
1667     }
1668 
swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, GestureScope gestureScope)1669     void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState,
1670             GestureScope gestureScope) {
1671         runToState(
1672                 () -> linearGesture(startX, startY, endX, endY, steps, false, gestureScope),
1673                 expectedState,
1674                 "swiping");
1675     }
1676 
getBottomGestureSize()1677     int getBottomGestureSize() {
1678         return Math.max(getWindowInsets().bottom, ResourceUtils.getNavbarSize(
1679                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1;
1680     }
1681 
getBottomGestureMarginInContainer(UiObject2 container)1682     int getBottomGestureMarginInContainer(UiObject2 container) {
1683         final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen();
1684         return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
1685     }
1686 
getRightGestureMarginInContainer(UiObject2 container)1687     int getRightGestureMarginInContainer(UiObject2 container) {
1688         final int rightGestureStartOnScreen = getRightGestureStartOnScreen();
1689         return getVisibleBounds(container).right - rightGestureStartOnScreen;
1690     }
1691 
getBottomGestureStartOnScreen()1692     int getBottomGestureStartOnScreen() {
1693         return getRealDisplaySize().y - getBottomGestureSize();
1694     }
1695 
getRightGestureStartOnScreen()1696     int getRightGestureStartOnScreen() {
1697         return getRealDisplaySize().x - getWindowInsets().right - 1;
1698     }
1699 
1700     /**
1701      * Click on the ui object right away without waiting for animation.
1702      *
1703      * [UiObject2.click] would wait for all animations finished before clicking. Not waiting for
1704      * animations because in some scenarios there is a playing animations when the click is
1705      * attempted.
1706      */
clickObject(UiObject2 uiObject)1707     void clickObject(UiObject2 uiObject) {
1708         final long clickTime = SystemClock.uptimeMillis();
1709         final Point center = uiObject.getVisibleCenter();
1710         sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center,
1711                 GestureScope.DONT_EXPECT_PILFER);
1712         sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center,
1713                 GestureScope.DONT_EXPECT_PILFER);
1714     }
1715 
clickLauncherObject(UiObject2 object)1716     void clickLauncherObject(UiObject2 object) {
1717         clickObject(object);
1718     }
1719 
scrollToLastVisibleRow( UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, int appsListBottomPadding)1720     void scrollToLastVisibleRow(
1721             UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer,
1722             int appsListBottomPadding) {
1723         final int itemRowCurrentTopOnScreen = bottomVisibleIconBounds.top;
1724         final Rect containerRect = getVisibleBounds(container);
1725         final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
1726         final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
1727 
1728         scrollDownByDistance(container, distance, appsListBottomPadding);
1729     }
1730 
1731     /** Scrolls up by given distance within the container. */
scrollUpByDistance(UiObject2 container, int distance)1732     void scrollUpByDistance(UiObject2 container, int distance) {
1733         scrollUpByDistance(container, distance, 0);
1734     }
1735 
1736     /** Scrolls up by given distance within the container considering the given bottom padding. */
scrollUpByDistance(UiObject2 container, int distance, int bottomPadding)1737     void scrollUpByDistance(UiObject2 container, int distance, int bottomPadding) {
1738         final Rect containerRect = getVisibleBounds(container);
1739         final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
1740         scroll(
1741                 container,
1742                 Direction.UP,
1743                 new Rect(
1744                         0,
1745                         containerRect.height() - bottomGestureMarginInContainer - distance,
1746                         0,
1747                         bottomGestureMarginInContainer + bottomPadding),
1748                 /* steps= */ 10,
1749                 /* slowDown= */ true);
1750     }
1751 
scrollDownByDistance(UiObject2 container, int distance)1752     void scrollDownByDistance(UiObject2 container, int distance) {
1753         scrollDownByDistance(container, distance, 0);
1754     }
1755 
scrollDownByDistance(UiObject2 container, int distance, int bottomPadding)1756     void scrollDownByDistance(UiObject2 container, int distance, int bottomPadding) {
1757         final Rect containerRect = getVisibleBounds(container);
1758         final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
1759         scroll(
1760                 container,
1761                 Direction.DOWN,
1762                 new Rect(
1763                         0,
1764                         containerRect.height() - distance - bottomGestureMarginInContainer,
1765                         0,
1766                         bottomGestureMarginInContainer + bottomPadding),
1767                 /* steps= */ 10,
1768                 /* slowDown= */ true);
1769     }
1770 
scrollLeftByDistance(UiObject2 container, int distance)1771     void scrollLeftByDistance(UiObject2 container, int distance) {
1772         final Rect containerRect = getVisibleBounds(container);
1773         final int rightGestureMarginInContainer = getRightGestureMarginInContainer(container);
1774         final int leftGestureMargin = getTargetInsets().left + getEdgeSensitivityWidth();
1775         scroll(
1776                 container,
1777                 Direction.LEFT,
1778                 new Rect(leftGestureMargin,
1779                         0,
1780                         Math.max(containerRect.width() - distance - leftGestureMargin,
1781                                 rightGestureMarginInContainer),
1782                         0),
1783                 10,
1784                 true);
1785     }
1786 
scroll( UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown)1787     void scroll(
1788             UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) {
1789         final Rect rect = getVisibleBounds(container);
1790         if (margins != null) {
1791             rect.left += margins.left;
1792             rect.top += margins.top;
1793             rect.right -= margins.right;
1794             rect.bottom -= margins.bottom;
1795         }
1796 
1797         final int startX;
1798         final int startY;
1799         final int endX;
1800         final int endY;
1801 
1802         switch (direction) {
1803             case UP: {
1804                 startX = endX = rect.centerX();
1805                 startY = rect.top;
1806                 endY = rect.bottom - 1;
1807             }
1808             break;
1809             case DOWN: {
1810                 startX = endX = rect.centerX();
1811                 startY = rect.bottom - 1;
1812                 endY = rect.top;
1813             }
1814             break;
1815             case LEFT: {
1816                 startY = endY = rect.centerY();
1817                 startX = rect.left;
1818                 endX = rect.right - 1;
1819             }
1820             break;
1821             case RIGHT: {
1822                 startY = endY = rect.centerY();
1823                 startX = rect.right - 1;
1824                 endX = rect.left;
1825             }
1826             break;
1827             default:
1828                 fail("Unsupported direction");
1829                 return;
1830         }
1831 
1832         executeAndWaitForLauncherEvent(
1833                 () -> linearGesture(
1834                         startX, startY, endX, endY, steps, slowDown,
1835                         GestureScope.DONT_EXPECT_PILFER),
1836                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
1837                 () -> "Didn't receive a scroll end message: " + startX + ", " + startY
1838                         + ", " + endX + ", " + endY,
1839                 "scrolling");
1840     }
1841 
pointerScroll(float pointerX, float pointerY, Direction direction)1842     void pointerScroll(float pointerX, float pointerY, Direction direction) {
1843         executeAndWaitForLauncherEvent(
1844                 () -> injectEvent(getPointerMotionEvent(
1845                         ACTION_SCROLL, pointerX, pointerY, direction)),
1846                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
1847                 () -> "Didn't receive a scroll end message: " + direction + " scroll from ("
1848                         + pointerX + ", " + pointerY + ")",
1849                 "scrolling");
1850     }
1851 
1852     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
1853     // fixed interval each time.
linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown, GestureScope gestureScope)1854     public void linearGesture(int startX, int startY, int endX, int endY, int steps,
1855             boolean slowDown, GestureScope gestureScope) {
1856         log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
1857         final long downTime = SystemClock.uptimeMillis();
1858         final Point start = new Point(startX, startY);
1859         final Point end = new Point(endX, endY);
1860         long endTime = downTime;
1861         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
1862         try {
1863 
1864             if (mTrackpadGestureType != TrackpadGestureType.NONE) {
1865                 sendPointer(downTime, downTime,
1866                         getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1),
1867                         start, gestureScope);
1868                 if (mTrackpadGestureType == TrackpadGestureType.THREE_FINGER
1869                         || mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) {
1870                     sendPointer(downTime, downTime,
1871                             getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2),
1872                             start, gestureScope);
1873                     if (mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) {
1874                         sendPointer(downTime, downTime,
1875                                 getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 3),
1876                                 start, gestureScope);
1877                     }
1878                 }
1879             }
1880             endTime = movePointer(
1881                     start, end, steps, false, downTime, downTime, slowDown, gestureScope);
1882         } finally {
1883             if (mTrackpadGestureType != TrackpadGestureType.NONE) {
1884                 for (int i = mPointerCount; i >= 2; i--) {
1885                     sendPointer(downTime, downTime,
1886                             getPointerAction(MotionEvent.ACTION_POINTER_UP, i - 1),
1887                             start, gestureScope);
1888                 }
1889             }
1890             sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
1891         }
1892     }
1893 
getPointerAction(int action, int index)1894     private static int getPointerAction(int action, int index) {
1895         return action + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
1896     }
1897 
movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, long startTime, boolean slowDown, GestureScope gestureScope)1898     long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime,
1899             long startTime, boolean slowDown, GestureScope gestureScope) {
1900         long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS,
1901                 isDecelerating, start, end, gestureScope);
1902         if (slowDown) {
1903             endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end,
1904                     end, gestureScope);
1905         }
1906         return endTime;
1907     }
1908 
waitForIdle()1909     void waitForIdle() {
1910         mDevice.waitForIdle();
1911     }
1912 
getTouchSlop()1913     int getTouchSlop() {
1914         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
1915     }
1916 
getResources()1917     public Resources getResources() {
1918         return getContext().getResources();
1919     }
1920 
getPointerMotionEvent( int action, float x, float y, Direction direction)1921     private static MotionEvent getPointerMotionEvent(
1922             int action, float x, float y, Direction direction) {
1923         MotionEvent.PointerCoords[] coordinates = new MotionEvent.PointerCoords[1];
1924         coordinates[0] = new MotionEvent.PointerCoords();
1925         coordinates[0].x = x;
1926         coordinates[0].y = y;
1927         boolean isVertical = direction == Direction.UP || direction == Direction.DOWN;
1928         boolean isForward = direction == Direction.RIGHT || direction == Direction.DOWN;
1929         coordinates[0].setAxisValue(
1930                 isVertical ? MotionEvent.AXIS_VSCROLL : MotionEvent.AXIS_HSCROLL,
1931                 isForward ? 1f : -1f);
1932 
1933         MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
1934         properties[0] = new MotionEvent.PointerProperties();
1935         properties[0].id = 0;
1936         properties[0].toolType = MotionEvent.TOOL_TYPE_MOUSE;
1937 
1938         final long downTime = SystemClock.uptimeMillis();
1939         return MotionEvent.obtain(
1940                 downTime,
1941                 downTime,
1942                 action,
1943                 /* pointerCount= */ 1,
1944                 properties,
1945                 coordinates,
1946                 /* metaState= */ 0,
1947                 /* buttonState= */ 0,
1948                 /* xPrecision= */ 1f,
1949                 /* yPrecision= */ 1f,
1950                 /* deviceId= */ 0,
1951                 /* edgeFlags= */ 0,
1952                 InputDevice.SOURCE_CLASS_POINTER,
1953                 /* flags= */ 0);
1954     }
1955 
getTrackpadMotionEvent(long downTime, long eventTime, int action, float x, float y, int pointerCount, TrackpadGestureType gestureType)1956     private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime,
1957             int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) {
1958         MotionEvent.PointerProperties[] pointerProperties =
1959                 new MotionEvent.PointerProperties[pointerCount];
1960         MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
1961         boolean isMultiFingerGesture = gestureType != TrackpadGestureType.TWO_FINGER;
1962         for (int i = 0; i < pointerCount; i++) {
1963             pointerProperties[i] = getPointerProperties(i);
1964             pointerCoords[i] = getPointerCoords(x, y);
1965             if (isMultiFingerGesture) {
1966                 pointerCoords[i].setAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT,
1967                         gestureType == TrackpadGestureType.THREE_FINGER ? 3 : 4);
1968             }
1969         }
1970         return MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerProperties,
1971                 pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
1972                 InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_CLASS_POINTER, 0, 0,
1973                 isMultiFingerGesture ? MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
1974                         : MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE);
1975     }
1976 
getMotionEvent(long downTime, long eventTime, int action, float x, float y, int source, int toolType)1977     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
1978             float x, float y, int source, int toolType) {
1979         return MotionEvent.obtain(downTime, eventTime, action, 1,
1980                 new MotionEvent.PointerProperties[]{getPointerProperties(0, toolType)},
1981                 new MotionEvent.PointerCoords[]{getPointerCoords(x, y)},
1982                 0, 0, 1.0f, 1.0f, 0, 0, source, 0);
1983     }
1984 
getPointerProperties(int pointerId)1985     private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
1986         return getPointerProperties(pointerId, Configurator.getInstance().getToolType());
1987     }
1988 
getPointerProperties(int pointerId, int toolType)1989     private static MotionEvent.PointerProperties getPointerProperties(int pointerId, int toolType) {
1990         MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
1991         properties.id = pointerId;
1992         properties.toolType = toolType;
1993         return properties;
1994     }
1995 
getPointerCoords(float x, float y)1996     private static MotionEvent.PointerCoords getPointerCoords(float x, float y) {
1997         MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
1998         coords.pressure = 1;
1999         coords.size = 1;
2000         coords.x = x;
2001         coords.y = y;
2002         return coords;
2003     }
2004 
hasTIS()2005     private boolean hasTIS() {
2006         return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean(
2007                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
2008     }
2009 
isGridOnlyOverviewEnabled()2010     public boolean isGridOnlyOverviewEnabled() {
2011         return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean(
2012                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
2013     }
2014 
isAppPairsEnabled()2015     boolean isAppPairsEnabled() {
2016         return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean(
2017                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
2018     }
2019 
sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope)2020     public void sendPointer(long downTime, long currentTime, int action, Point point,
2021             GestureScope gestureScope) {
2022         sendPointer(downTime, currentTime, action, point, gestureScope,
2023                 InputDevice.SOURCE_TOUCHSCREEN, false);
2024     }
2025 
injectEvent(InputEvent event)2026     private void injectEvent(InputEvent event) {
2027         assertTrue("injectInputEvent failed: event=" + event,
2028                 mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
2029     }
2030 
sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source)2031     public void sendPointer(long downTime, long currentTime, int action, Point point,
2032             GestureScope gestureScope, int source) {
2033         sendPointer(downTime, currentTime, action, point, gestureScope, source, false);
2034     }
2035 
sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source, boolean isRightClick)2036     public void sendPointer(long downTime, long currentTime, int action, Point point,
2037             GestureScope gestureScope, int source, boolean isRightClick) {
2038         sendPointer(
2039                 downTime,
2040                 currentTime,
2041                 action,
2042                 point,
2043                 gestureScope,
2044                 source,
2045                 isRightClick,
2046                 Configurator.getInstance().getToolType());
2047     }
2048 
sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source, boolean isRightClick, int toolType)2049     public void sendPointer(long downTime, long currentTime, int action, Point point,
2050             GestureScope gestureScope, int source, boolean isRightClick, int toolType) {
2051         final boolean hasTIS = hasTIS();
2052         int pointerCount = mPointerCount;
2053 
2054         boolean isTrackpadGesture = mTrackpadGestureType != TrackpadGestureType.NONE;
2055         switch (action & MotionEvent.ACTION_MASK) {
2056             case MotionEvent.ACTION_DOWN:
2057                 if (isTrackpadGesture) {
2058                     mPointerCount = 1;
2059                     pointerCount = mPointerCount;
2060                 }
2061                 break;
2062             case MotionEvent.ACTION_UP:
2063                 if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) {
2064                     expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
2065                 }
2066                 break;
2067             case MotionEvent.ACTION_POINTER_DOWN:
2068                 mPointerCount++;
2069                 pointerCount = mPointerCount;
2070                 break;
2071             case MotionEvent.ACTION_POINTER_UP:
2072                 // When the gesture is handled outside, it's cancelled within launcher.
2073                 mPointerCount--;
2074                 break;
2075         }
2076 
2077         final MotionEvent event = isTrackpadGesture
2078                 ? getTrackpadMotionEvent(
2079                 downTime, currentTime, action, point.x, point.y, pointerCount,
2080                 mTrackpadGestureType)
2081                 : getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType);
2082         if (action == MotionEvent.ACTION_BUTTON_PRESS
2083                 || action == MotionEvent.ACTION_BUTTON_RELEASE) {
2084             event.setActionButton(MotionEvent.BUTTON_PRIMARY);
2085         }
2086         if (isRightClick) {
2087             event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY);
2088         }
2089         injectEvent(event);
2090     }
2091 
createKeyEvent(int keyCode, int metaState, boolean actionDown)2092     private KeyEvent createKeyEvent(int keyCode, int metaState, boolean actionDown) {
2093         long eventTime = SystemClock.uptimeMillis();
2094         return KeyEvent.obtain(
2095                 eventTime,
2096                 eventTime,
2097                 actionDown ? ACTION_DOWN : ACTION_UP,
2098                 keyCode,
2099                 /* repeat= */ 0,
2100                 metaState,
2101                 KeyCharacterMap.VIRTUAL_KEYBOARD,
2102                 /* scancode= */ 0,
2103                 /* flags= */ 0,
2104                 InputDevice.SOURCE_KEYBOARD,
2105                 /* characters =*/ null);
2106     }
2107 
2108     /**
2109      * Sends a {@link KeyEvent} with {@link ACTION_DOWN} for the given key codes without sending
2110      * a {@link KeyEvent} with {@link ACTION_UP}.
2111      */
pressAndHoldKeyCode(int keyCode, int metaState)2112     public void pressAndHoldKeyCode(int keyCode, int metaState) {
2113         injectEvent(createKeyEvent(keyCode, metaState, true));
2114     }
2115 
2116 
2117     /**
2118      * Sends a {@link KeyEvent} with {@link ACTION_UP} for the given key codes.
2119      */
unpressKeyCode(int keyCode, int metaState)2120     public void unpressKeyCode(int keyCode, int metaState) {
2121         injectEvent(createKeyEvent(keyCode, metaState, false));
2122     }
2123 
movePointer(long downTime, long startTime, long duration, Point from, Point to, GestureScope gestureScope)2124     public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
2125             GestureScope gestureScope) {
2126         return movePointer(downTime, startTime, duration, false, from, to, gestureScope);
2127     }
2128 
movePointer(long downTime, long startTime, long duration, boolean isDecelerating, Point from, Point to, GestureScope gestureScope)2129     public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating,
2130             Point from, Point to, GestureScope gestureScope) {
2131         log("movePointer: " + from + " to " + to);
2132         final Point point = new Point();
2133         long steps = duration / GESTURE_STEP_MS;
2134 
2135         long currentTime = startTime;
2136 
2137         if (isDecelerating) {
2138             // formula: V = V0 - D*T, assuming V = 0 when T = duration
2139 
2140             // vx0: initial speed at the x-dimension, set as twice the avg speed
2141             // dx: the constant deceleration at the x-dimension
2142             double vx0 = 2.0 * (to.x - from.x) / duration;
2143             double dx = vx0 / duration;
2144             // vy0: initial speed at the y-dimension, set as twice the avg speed
2145             // dy: the constant deceleration at the y-dimension
2146             double vy0 = 2.0 * (to.y - from.y) / duration;
2147             double dy = vy0 / duration;
2148 
2149             for (long i = 0; i < steps; ++i) {
2150                 sleep(GESTURE_STEP_MS);
2151                 currentTime += GESTURE_STEP_MS;
2152 
2153                 // formula: P = P0 + V0*T - (D*T^2/2)
2154                 final double t = (i + 1) * GESTURE_STEP_MS;
2155                 point.x = from.x + (int) (vx0 * t - 0.5 * dx * t * t);
2156                 point.y = from.y + (int) (vy0 * t - 0.5 * dy * t * t);
2157 
2158                 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
2159             }
2160         } else {
2161             for (long i = 0; i < steps; ++i) {
2162                 sleep(GESTURE_STEP_MS);
2163                 currentTime += GESTURE_STEP_MS;
2164 
2165                 final float progress = (currentTime - startTime) / (float) duration;
2166                 point.x = from.x + (int) (progress * (to.x - from.x));
2167                 point.y = from.y + (int) (progress * (to.y - from.y));
2168 
2169                 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
2170 
2171             }
2172         }
2173 
2174         return currentTime;
2175     }
2176 
getCurrentInteractionMode(Context context)2177     public static int getCurrentInteractionMode(Context context) {
2178         return getSystemIntegerRes(context, "config_navBarInteractionMode");
2179     }
2180 
2181     /**
2182      * Retrieve the resource value that defines if nav bar can moved or if it is fixed position.
2183      */
getNavBarCanMove(Context context)2184     private static boolean getNavBarCanMove(Context context) {
2185         return getSystemBooleanRes(context, "config_navBarCanMove");
2186     }
2187 
2188     @NonNull
clickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent)2189     UiObject2 clickAndGet(
2190             @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
2191         final Point targetCenter = target.getVisibleCenter();
2192         final long downTime = SystemClock.uptimeMillis();
2193         // Use stylus secondary button press to prevent using the exteded long press timeout rule
2194         // unnecessarily
2195         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
2196                 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
2197                 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
2198         try {
2199             expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
2200             final UiObject2 result = waitForLauncherObject(resName);
2201             return result;
2202         } finally {
2203             sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
2204                     GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
2205                     /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
2206         }
2207     }
2208 
2209     @NonNull
rightClickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent)2210     UiObject2 rightClickAndGet(
2211             @NonNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent) {
2212         final Point targetCenter = target.getVisibleCenter();
2213         final long downTime = SystemClock.uptimeMillis();
2214         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
2215                 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
2216                 /* isRightClick= */ true);
2217         try {
2218             expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent);
2219             final UiObject2 result = waitForLauncherObject(resName);
2220             return result;
2221         } finally {
2222             sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter,
2223                     GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
2224                     /* isRightClick= */ true);
2225         }
2226     }
2227 
getSystemBooleanRes(Context context, String resName)2228     private static boolean getSystemBooleanRes(Context context, String resName) {
2229         Resources res = context.getResources();
2230         int resId = res.getIdentifier(resName, "bool", "android");
2231 
2232         if (resId != 0) {
2233             return res.getBoolean(resId);
2234         } else {
2235             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
2236             return false;
2237         }
2238     }
2239 
getSystemIntegerRes(Context context, String resName)2240     private static int getSystemIntegerRes(Context context, String resName) {
2241         Resources res = context.getResources();
2242         int resId = res.getIdentifier(resName, "integer", "android");
2243 
2244         if (resId != 0) {
2245             return res.getInteger(resId);
2246         } else {
2247             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
2248             return -1;
2249         }
2250     }
2251 
getSystemDimensionResId(Context context, String resName)2252     private static int getSystemDimensionResId(Context context, String resName) {
2253         Resources res = context.getResources();
2254         int resId = res.getIdentifier(resName, "dimen", "android");
2255 
2256         if (resId != 0) {
2257             return resId;
2258         } else {
2259             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
2260             return -1;
2261         }
2262     }
2263 
sleep(int duration)2264     static void sleep(int duration) {
2265         SystemClock.sleep(duration);
2266     }
2267 
getEdgeSensitivityWidth()2268     int getEdgeSensitivityWidth() {
2269         try {
2270             final Context context = mInstrumentation.getTargetContext().createPackageContext(
2271                     getLauncherPackageName(), 0);
2272             return context.getResources().getDimensionPixelSize(
2273                     getSystemDimensionResId(context, "config_backGestureInset")) + 1;
2274         } catch (PackageManager.NameNotFoundException e) {
2275             fail("Can't get edge sensitivity: " + e);
2276             return 0;
2277         }
2278     }
2279 
2280     /** Returns the bounds of the display as a Point where x is width and y is height. */
getRealDisplaySize()2281     Point getRealDisplaySize() {
2282         final Rect displayBounds = getContext().getSystemService(WindowManager.class)
2283                 .getMaximumWindowMetrics()
2284                 .getBounds();
2285         return new Point(displayBounds.width(), displayBounds.height());
2286     }
2287 
enableDebugTracing()2288     public void enableDebugTracing() {
2289         getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
2290     }
2291 
disableSensorRotation()2292     private void disableSensorRotation() {
2293         getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION);
2294     }
2295 
disableDebugTracing()2296     public void disableDebugTracing() {
2297         getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
2298     }
2299 
forceGc()2300     public void forceGc() {
2301         // GC the system & sysui first before gc'ing launcher
2302         logShellCommand("cmd statusbar run-gc");
2303         getTestInfo(TestProtocol.REQUEST_FORCE_GC);
2304     }
2305 
getPid()2306     public Integer getPid() {
2307         final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID);
2308         return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null;
2309     }
2310 
getRecentTasks()2311     public ArrayList<ComponentName> getRecentTasks() {
2312         ArrayList<ComponentName> tasks = new ArrayList<>();
2313         ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST)
2314                 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
2315         for (String s : components) {
2316             tasks.add(ComponentName.unflattenFromString(s));
2317         }
2318         return tasks;
2319     }
2320 
2321     /** Reinitializes the workspace to its default layout. */
reinitializeLauncherData()2322     public void reinitializeLauncherData() {
2323         getTestInfo(TestProtocol.REQUEST_REINITIALIZE_DATA);
2324     }
2325 
2326     /** Clears the workspace, leaving it empty. */
clearLauncherData()2327     public void clearLauncherData() {
2328         getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
2329     }
2330 
2331     /** Shows the taskbar if it is hidden, otherwise does nothing. */
showTaskbarIfHidden()2332     public void showTaskbarIfHidden() {
2333         getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);
2334     }
2335 
2336     /** Shows the bubble bar if it is stashed, otherwise this does nothing. */
showBubbleBarIfHidden()2337     public void showBubbleBarIfHidden() {
2338         getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED);
2339     }
2340 
injectFakeTrackpad()2341     public void injectFakeTrackpad() {
2342         getTestInfo(TestProtocol.REQUEST_INJECT_FAKE_TRACKPAD);
2343     }
2344 
ejectFakeTrackpad()2345     public void ejectFakeTrackpad() {
2346         getTestInfo(TestProtocol.REQUEST_EJECT_FAKE_TRACKPAD);
2347     }
2348 
2349     /** Blocks the taskbar from automatically stashing based on time. */
enableBlockTimeout(boolean enable)2350     public void enableBlockTimeout(boolean enable) {
2351         getTestInfo(enable
2352                 ? TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT
2353                 : TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT);
2354     }
2355 
isTransientTaskbar()2356     public boolean isTransientTaskbar() {
2357         return getTestInfo(TestProtocol.REQUEST_IS_TRANSIENT_TASKBAR)
2358                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
2359     }
2360 
isImeDocked()2361     public boolean isImeDocked() {
2362         return getTestInfo(TestProtocol.REQUEST_TASKBAR_IME_DOCKED).getBoolean(
2363                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
2364     }
2365 
2366     /** Enables transient taskbar for testing purposes only. */
enableTransientTaskbar(boolean enable)2367     public void enableTransientTaskbar(boolean enable) {
2368         getTestInfo(enable
2369                 ? TestProtocol.REQUEST_ENABLE_TRANSIENT_TASKBAR
2370                 : TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR);
2371     }
2372 
2373     /**
2374      * Recreates the taskbar (outside of tests this is done for certain configuration changes).
2375      * The expected behavior is that the taskbar retains its current state after being recreated.
2376      * For example, if taskbar is currently stashed, it should still be stashed after recreating.
2377      */
recreateTaskbar()2378     public void recreateTaskbar() {
2379         getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR);
2380     }
2381 
2382     // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup.
2383 
2384     /** Refreshes the known overview target in TIS. */
refreshOverviewTarget()2385     public void refreshOverviewTarget() {
2386         getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET);
2387     }
2388 
getHotseatIconNames()2389     public List<String> getHotseatIconNames() {
2390         return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES)
2391                 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
2392     }
2393 
getActivities()2394     private String[] getActivities() {
2395         return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES)
2396                 .getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD);
2397     }
2398 
getRootedActivitiesList()2399     public String getRootedActivitiesList() {
2400         return String.join(", ", getActivities());
2401     }
2402 
2403     /** Returns whether no leaked activities are detected. */
noLeakedActivities(boolean requireOneActiveActivity)2404     public boolean noLeakedActivities(boolean requireOneActiveActivity) {
2405         final String[] activities = getActivities();
2406 
2407         for (String activity : activities) {
2408             if (activity.contains("(destroyed)")) {
2409                 return false;
2410             }
2411         }
2412         return activities.length <= (requireOneActiveActivity ? 1 : 2);
2413     }
2414 
getActivitiesCreated()2415     public int getActivitiesCreated() {
2416         return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT)
2417                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
2418     }
2419 
eventsCheck()2420     public Closable eventsCheck() {
2421         Assert.assertTrue("Nested event checking", mEventChecker == null);
2422         disableSensorRotation();
2423         final Integer initialPid = getPid();
2424         final LogEventChecker eventChecker = new LogEventChecker(this);
2425         eventChecker.setLogExclusionRule(event -> {
2426             Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event);
2427             if (matcher.find()) {
2428                 long keyEventFlags = Long.parseLong(matcher.group(1), 16);
2429                 // ignore KeyEvents with FLAG_CANCELED
2430                 return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0;
2431             }
2432             return false;
2433         });
2434 
2435         if (eventChecker.start()) mEventChecker = eventChecker;
2436 
2437         return () -> {
2438             if (initialPid != null && initialPid.intValue() != getPid()) {
2439                 if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
2440                 checkForAnomaly();
2441                 Assert.fail(
2442                         formatSystemHealthMessage(
2443                                 formatErrorWithEvents("Launcher crashed", false)));
2444             }
2445 
2446             if (mEventChecker != null) {
2447                 mEventChecker = null;
2448                 if (mCheckEventsForSuccessfulGestures) {
2449                     final String message = eventChecker.verify(WAIT_TIME_MS);
2450                     if (message != null) {
2451                         dumpDiagnostics(message);
2452                         checkForAnomaly();
2453                         Assert.fail(formatSystemHealthMessage(
2454                                 "http://go/tapl : successful gesture produced " + message));
2455                     }
2456                 } else {
2457                     eventChecker.finishNoWait();
2458                 }
2459             }
2460         };
2461     }
2462 
2463     /** Returns whether the Launcher is a Launcher3 one */
2464     public boolean isLauncher3() {
2465         if (mIsLauncher3 == null) {
2466             mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName());
2467         }
2468         return mIsLauncher3;
2469     }
2470 
2471     void expectEvent(String sequence, Pattern expected) {
2472         if (mEventChecker != null) {
2473             mEventChecker.expectPattern(sequence, expected);
2474         } else {
2475             Log.d(TAG, "Expecting: " + sequence + " / " + expected);
2476         }
2477     }
2478 
2479     Rect getVisibleBounds(UiObject2 object) {
2480         try {
2481             return object.getVisibleBounds();
2482         } catch (StaleObjectException e) {
2483             fail("Object disappeared from screen");
2484             return null;
2485         } catch (Throwable t) {
2486             fail(t.toString());
2487             return null;
2488         }
2489     }
2490 
2491     float getWindowCornerRadius() {
2492         // Return a larger corner radius to ensure gesture calculated from the radius are offset to
2493         // prevent overlapping
2494         final float tmpBuffer = 100f;
2495         final Resources resources = getResources();
2496         if (!supportsRoundedCornersOnWindows(resources)) {
2497             Log.d(TAG, "No rounded corners");
2498             return tmpBuffer;
2499         }
2500 
2501         // Radius that should be used in case top or bottom aren't defined.
2502         float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0);
2503 
2504         float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0);
2505         if (topRadius == 0f) {
2506             topRadius = defaultRadius;
2507         }
2508         float bottomRadius = ResourceUtils.getDimenByName(
2509                 "rounded_corner_radius_bottom", resources, 0);
2510         if (bottomRadius == 0f) {
2511             bottomRadius = defaultRadius;
2512         }
2513 
2514         // Always use the smallest radius to make sure the rounded corners will
2515         // completely cover the display.
2516         Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius);
2517         return Math.max(topRadius, bottomRadius) + tmpBuffer;
2518     }
2519 
2520     private Context getLauncherContext(Context baseContext)
2521             throws PackageManager.NameNotFoundException {
2522         // Workaround, use constructed context because both the instrumentation context and the
2523         // app context are not constructed with resources that take overlays into account
2524         return baseContext.createPackageContext(getLauncherPackageName(), 0);
2525     }
2526 
2527     private static boolean supportsRoundedCornersOnWindows(Resources resources) {
2528         return ResourceUtils.getBoolByName(
2529                 "config_supportsRoundedCornersOnWindows", resources, false);
2530     }
2531 
2532     /**
2533      * Taps outside container to dismiss, centered vertically and halfway to the edge of the screen.
2534      *
2535      * @param container container to be dismissed
2536      * @param tapRight  tap on the right of the container if true, or left otherwise
2537      */
2538     void touchOutsideContainer(UiObject2 container, boolean tapRight) {
2539         touchOutsideContainer(container, tapRight, true);
2540     }
2541 
2542     /**
2543      * Taps outside the container, to the right or left, and centered vertically.
2544      *
2545      * @param tapRight      if true touches to the right of the container, otherwise touches on left
2546      * @param halfwayToEdge if true touches halfway to the screen edge, if false touches 1 px from
2547      *                      container
2548      */
2549     void touchOutsideContainer(UiObject2 container, boolean tapRight, boolean halfwayToEdge) {
2550         try (LauncherInstrumentation.Closable c = addContextLayer(
2551                 "want to tap outside container on the " + (tapRight ? "right" : "left"))) {
2552             Rect containerBounds = getVisibleBounds(container);
2553 
2554             int x;
2555             if (halfwayToEdge) {
2556                 x = tapRight
2557                         ? (containerBounds.right + getRealDisplaySize().x) / 2
2558                         : containerBounds.left / 2;
2559             } else {
2560                 x = tapRight
2561                         ? containerBounds.right + 1
2562                         : containerBounds.left - 1;
2563             }
2564             // If IME is visible and overlaps the container bounds, touch above it.
2565             final Insets systemGestureRegion = getSystemGestureRegion();
2566             int bottomBound = Math.min(
2567                     containerBounds.bottom,
2568                     getRealDisplaySize().y - systemGestureRegion.bottom);
2569             int y = (bottomBound + containerBounds.top) / 2;
2570             // Do not tap in the status bar.
2571             y = Math.max(y, systemGestureRegion.top);
2572 
2573             final long downTime = SystemClock.uptimeMillis();
2574             final Point tapTarget = new Point(x, y);
2575             sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
2576                     LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
2577             sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
2578                     LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
2579         }
2580     }
2581 
2582     /**
2583      * Waits until a particular condition is true. Based on WaitMixin.
2584      */
2585     boolean waitAndGet(BooleanSupplier condition, long timeout, long interval) {
2586         long startTime = SystemClock.uptimeMillis();
2587 
2588         boolean result = condition.getAsBoolean();
2589         for (long elapsedTime = 0; !result; elapsedTime = SystemClock.uptimeMillis() - startTime) {
2590             if (elapsedTime >= timeout) {
2591                 break;
2592             }
2593             SystemClock.sleep(interval);
2594             result = condition.getAsBoolean();
2595         }
2596         return result;
2597     }
2598 
2599     /** Executes a runnable and waits for the wallpaper-open animation completion. */
2600     public void executeAndWaitForWallpaperAnimation(Runnable r, String actionName) {
2601         executeAndWaitForLauncherEvent(
2602                 () -> r.run(),
2603                 event -> TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE
2604                         .equals(event.getClassName().toString()),
2605                 () -> "Didn't detect finishing wallpaper-open animation",
2606                 actionName);
2607     }
2608 }
2609