• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.tapl;
18 
19 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
20 import static android.content.pm.PackageManager.DONT_KILL_APP;
21 import static android.content.pm.PackageManager.MATCH_ALL;
22 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
23 
24 import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
25 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
26 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
27 
28 import android.app.ActivityManager;
29 import android.app.Instrumentation;
30 import android.app.UiAutomation;
31 import android.content.ComponentName;
32 import android.content.ContentProviderClient;
33 import android.content.ContentResolver;
34 import android.content.Context;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ProviderInfo;
37 import android.content.res.Resources;
38 import android.graphics.Insets;
39 import android.graphics.Point;
40 import android.graphics.Rect;
41 import android.net.Uri;
42 import android.os.Bundle;
43 import android.os.DeadObjectException;
44 import android.os.Parcelable;
45 import android.os.RemoteException;
46 import android.os.SystemClock;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.view.InputDevice;
50 import android.view.MotionEvent;
51 import android.view.ViewConfiguration;
52 import android.view.WindowManager;
53 import android.view.accessibility.AccessibilityEvent;
54 
55 import androidx.annotation.NonNull;
56 import androidx.annotation.Nullable;
57 import androidx.test.InstrumentationRegistry;
58 import androidx.test.uiautomator.By;
59 import androidx.test.uiautomator.BySelector;
60 import androidx.test.uiautomator.Configurator;
61 import androidx.test.uiautomator.Direction;
62 import androidx.test.uiautomator.StaleObjectException;
63 import androidx.test.uiautomator.UiDevice;
64 import androidx.test.uiautomator.UiObject2;
65 import androidx.test.uiautomator.Until;
66 
67 import com.android.launcher3.testing.shared.ResourceUtils;
68 import com.android.launcher3.testing.shared.TestInformationRequest;
69 import com.android.launcher3.testing.shared.TestProtocol;
70 import com.android.systemui.shared.system.QuickStepContract;
71 
72 import org.junit.Assert;
73 
74 import java.io.ByteArrayOutputStream;
75 import java.io.IOException;
76 import java.lang.ref.WeakReference;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Deque;
80 import java.util.LinkedList;
81 import java.util.List;
82 import java.util.Optional;
83 import java.util.concurrent.TimeoutException;
84 import java.util.function.BooleanSupplier;
85 import java.util.function.Consumer;
86 import java.util.function.Function;
87 import java.util.function.Supplier;
88 import java.util.regex.Pattern;
89 import java.util.stream.Collectors;
90 
91 /**
92  * The main tapl object. The only object that can be explicitly constructed by the using code. It
93  * produces all other objects.
94  */
95 public final class LauncherInstrumentation {
96 
97     private static final String TAG = "Tapl";
98     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
99     private static final int GESTURE_STEP_MS = 16;
100 
101     static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
102     static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
103     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
104     static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
105     static final Pattern EVENT_START = Pattern.compile("start:");
106 
107     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
108     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
109     static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPatternTIS("ACTION_CANCEL");
110 
111     private static final Pattern EVENT_KEY_BACK_DOWN =
112             getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
113     private static final Pattern EVENT_KEY_BACK_UP =
114             getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
115     private static final Pattern EVENT_ON_BACK_INVOKED = Pattern.compile("onBackInvoked");
116 
117     private final String mLauncherPackage;
118     private Boolean mIsLauncher3;
119     private long mTestStartTime = -1;
120 
121     // Types for launcher containers that the user is interacting with. "Background" is a
122     // pseudo-container corresponding to inactive launcher covered by another app.
123     public enum ContainerType {
124         WORKSPACE, HOME_ALL_APPS, OVERVIEW, SPLIT_SCREEN_SELECT, WIDGETS, FALLBACK_OVERVIEW,
125         LAUNCHED_APP, TASKBAR_ALL_APPS
126     }
127 
128     public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON}
129 
130     // Where the gesture happens: outside of Launcher, inside or from inside to outside and
131     // whether the gesture recognition triggers pilfer.
132     public enum GestureScope {
133         OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE,
134         INSIDE_TO_OUTSIDE_WITHOUT_PILFER,
135         INSIDE_TO_OUTSIDE_WITH_KEYCODE, // For gestures that will trigger a keycode from TIS.
136         OUTSIDE_WITH_KEYCODE,
137     }
138 
139     /**
140      * Represents a point in the code at which a callback can run.
141      */
142     public enum CALLBACK_RUN_POINT {
143         CALLBACK_HOLD_BEFORE_DROP
144     }
145 
146     private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null;
147 
148     // Base class for launcher containers.
149     abstract static class VisibleContainer {
150         protected final LauncherInstrumentation mLauncher;
151 
VisibleContainer(LauncherInstrumentation launcher)152         protected VisibleContainer(LauncherInstrumentation launcher) {
153             mLauncher = launcher;
154             launcher.setActiveContainer(this);
155         }
156 
getContainerType()157         protected abstract ContainerType getContainerType();
158 
159         /**
160          * Asserts that the launcher is in the mode matching 'this' object.
161          *
162          * @return UI object for the container.
163          */
verifyActiveContainer()164         final UiObject2 verifyActiveContainer() {
165             mLauncher.assertTrue("Attempt to use a stale container",
166                     this == sActiveContainer.get());
167             return mLauncher.verifyContainerType(getContainerType());
168         }
169     }
170 
171     public interface Closable extends AutoCloseable {
close()172         void close();
173     }
174 
175     private static final String WORKSPACE_RES_ID = "workspace";
176     private static final String APPS_RES_ID = "apps_view";
177     private static final String OVERVIEW_RES_ID = "overview_panel";
178     private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
179     private static final String CONTEXT_MENU_RES_ID = "popup_container";
180     static final String TASKBAR_RES_ID = "taskbar_view";
181     private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder";
182     public static final int WAIT_TIME_MS = 30000;
183     static final long DEFAULT_POLL_INTERVAL = 1000;
184     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
185     private static final String ANDROID_PACKAGE = "android";
186 
187     private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
188 
189     private final UiDevice mDevice;
190     private final Instrumentation mInstrumentation;
191     private Integer mExpectedRotation = null;
192     private final Uri mTestProviderUri;
193     private final Deque<String> mDiagnosticContext = new LinkedList<>();
194     private Function<Long, String> mSystemHealthSupplier;
195 
196     private boolean mIgnoreTaskbarVisibility = false;
197 
198     private Consumer<ContainerType> mOnSettledStateAction;
199 
200     private LogEventChecker mEventChecker;
201 
202     private boolean mCheckEventsForSuccessfulGestures = false;
203     private Runnable mOnLauncherCrashed;
204 
getTouchEventPattern(String prefix, String action)205     private static Pattern getTouchEventPattern(String prefix, String action) {
206         // The pattern includes checks that we don't get a multi-touch events or other surprises.
207         return Pattern.compile(
208                 prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
209                         + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
210     }
211 
getTouchEventPattern(String action)212     private static Pattern getTouchEventPattern(String action) {
213         return getTouchEventPattern("Touch event", action);
214     }
215 
getTouchEventPatternTIS(String action)216     private static Pattern getTouchEventPatternTIS(String action) {
217         return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
218     }
219 
getKeyEventPattern(String action, String keyCode)220     private static Pattern getKeyEventPattern(String action, String keyCode) {
221         return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
222     }
223 
224     /**
225      * Constructs the root of TAPL hierarchy. You get all other objects from it.
226      */
LauncherInstrumentation()227     public LauncherInstrumentation() {
228         this(InstrumentationRegistry.getInstrumentation());
229     }
230 
231     /**
232      * Constructs the root of TAPL hierarchy. You get all other objects from it.
233      * Deprecated: use the constructor without parameters instead.
234      */
235     @Deprecated
LauncherInstrumentation(Instrumentation instrumentation)236     public LauncherInstrumentation(Instrumentation instrumentation) {
237         mInstrumentation = instrumentation;
238         mDevice = UiDevice.getInstance(instrumentation);
239 
240         // Launcher should run in test harness so that custom accessibility protocol between
241         // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
242         // into Launcher.
243         assertTrue("Device must run in a test harness. "
244                         + "Run `adb shell setprop ro.test_harness 1` to enable it.",
245                 TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
246 
247         final String testPackage = getContext().getPackageName();
248         final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
249 
250         // Launcher package. As during inproc tests the tested launcher may not be selected as the
251         // current launcher, choosing target package for inproc. For out-of-proc, use the installed
252         // launcher package.
253         mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation()
254                 ? getLauncherPackageName()
255                 : targetPackage;
256 
257         String testProviderAuthority = mLauncherPackage + ".TestInfo";
258         mTestProviderUri = new Uri.Builder()
259                 .scheme(ContentResolver.SCHEME_CONTENT)
260                 .authority(testProviderAuthority)
261                 .build();
262 
263         mInstrumentation.getUiAutomation().grantRuntimePermission(
264                 testPackage, "android.permission.WRITE_SECURE_SETTINGS");
265 
266         PackageManager pm = getContext().getPackageManager();
267         ProviderInfo pi = pm.resolveContentProvider(
268                 testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS);
269         assertNotNull("Cannot find content provider for " + testProviderAuthority, pi);
270         ComponentName cn = new ComponentName(pi.packageName, pi.name);
271 
272         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
273             if (TestHelpers.isInLauncherProcess()) {
274                 pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
275                 // b/195031154
276                 SystemClock.sleep(5000);
277             } else {
278                 try {
279                     final int userId = getContext().getUserId();
280                     final String launcherPidCommand = "pidof " + pi.packageName;
281                     final String initialPid = mDevice.executeShellCommand(launcherPidCommand)
282                             .replaceAll("\\s", "");
283                     mDevice.executeShellCommand(
284                             "pm enable --user " + userId + " " + cn.flattenToString());
285                     // Wait for Launcher restart after enabling test provider.
286                     for (int i = 0; i < 100; ++i) {
287                         final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
288                                 .replaceAll("\\s", "");
289                         if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
290                         if (i == 99) fail("Launcher didn't restart after enabling test provider");
291                         SystemClock.sleep(100);
292                     }
293                 } catch (IOException e) {
294                     fail(e.toString());
295                 }
296             }
297         }
298     }
299 
300     /**
301      * Gradle only supports out of process instrumentation. The test package is automatically
302      * generated by appending `.test` to the target package.
303      */
isGradleInstrumentation()304     private boolean isGradleInstrumentation() {
305         final String testPackage = getContext().getPackageName();
306         final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
307         final String testSuffix = ".test";
308 
309         return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length()
310                 && testPackage.substring(0, testPackage.length() - testSuffix.length())
311                 .equals(targetPackage);
312     }
313 
enableCheckEventsForSuccessfulGestures()314     public void enableCheckEventsForSuccessfulGestures() {
315         mCheckEventsForSuccessfulGestures = true;
316     }
317 
setOnLauncherCrashed(Runnable onLauncherCrashed)318     public void setOnLauncherCrashed(Runnable onLauncherCrashed) {
319         mOnLauncherCrashed = onLauncherCrashed;
320     }
321 
getContext()322     Context getContext() {
323         return mInstrumentation.getContext();
324     }
325 
getTestInfo(String request)326     Bundle getTestInfo(String request) {
327         return getTestInfo(request, /*arg=*/ null);
328     }
329 
getTestInfo(String request, String arg)330     Bundle getTestInfo(String request, String arg) {
331         return getTestInfo(request, arg, null);
332     }
333 
getTestInfo(String request, String arg, Bundle extra)334     Bundle getTestInfo(String request, String arg, Bundle extra) {
335         try (ContentProviderClient client = getContext().getContentResolver()
336                 .acquireContentProviderClient(mTestProviderUri)) {
337             return client.call(request, arg, extra);
338         } catch (DeadObjectException e) {
339             fail("Launcher crashed");
340             return null;
341         } catch (RemoteException e) {
342             throw new RuntimeException(e);
343         }
344     }
345 
getTestInfo(TestInformationRequest request)346     Bundle getTestInfo(TestInformationRequest request) {
347         Bundle extra = new Bundle();
348         extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request);
349         return getTestInfo(request.getRequestName(), null, extra);
350     }
351 
getTargetInsets()352     Insets getTargetInsets() {
353         return getTestInfo(TestProtocol.REQUEST_TARGET_INSETS)
354                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
355     }
356 
getWindowInsets()357     Insets getWindowInsets() {
358         return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS)
359                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
360     }
361 
isTablet()362     public boolean isTablet() {
363         return getTestInfo(TestProtocol.REQUEST_IS_TABLET)
364                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
365     }
366 
isTwoPanels()367     public boolean isTwoPanels() {
368         return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS)
369                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
370     }
371 
getFocusedTaskHeightForTablet()372     int getFocusedTaskHeightForTablet() {
373         return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt(
374                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
375     }
376 
getGridTaskRectForTablet()377     Rect getGridTaskRectForTablet() {
378         return ((Rect) getTestInfo(TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET)
379                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD));
380     }
381 
getOverviewPageSpacing()382     int getOverviewPageSpacing() {
383         return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_PAGE_SPACING)
384                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
385     }
386 
getExactScreenCenterX()387     float getExactScreenCenterX() {
388         return getRealDisplaySize().x / 2f;
389     }
390 
setEnableRotation(boolean on)391     public void setEnableRotation(boolean on) {
392         getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on));
393     }
394 
setEnableSuggestion(boolean enableSuggestion)395     public void setEnableSuggestion(boolean enableSuggestion) {
396         getTestInfo(TestProtocol.REQUEST_ENABLE_SUGGESTION, Boolean.toString(enableSuggestion));
397     }
398 
hadNontestEvents()399     public boolean hadNontestEvents() {
400         return getTestInfo(TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS)
401                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
402     }
403 
setActiveContainer(VisibleContainer container)404     void setActiveContainer(VisibleContainer container) {
405         sActiveContainer = new WeakReference<>(container);
406     }
407 
408     /**
409      * Sets the accesibility interactive timeout to be effectively indefinite (UI using this
410      * accesibility timeout will not automatically dismiss if true).
411      */
setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout)412     void setIndefiniteAccessibilityInteractiveUiTimeout(boolean indefiniteTimeout) {
413         final String cmd = indefiniteTimeout
414                 ? "settings put secure accessibility_interactive_ui_timeout_ms 10000"
415                 : "settings delete secure accessibility_interactive_ui_timeout_ms";
416         logShellCommand(cmd);
417     }
418 
getNavigationModel()419     public NavigationModel getNavigationModel() {
420         final Context baseContext = mInstrumentation.getTargetContext();
421         try {
422             // Workaround, use constructed context because both the instrumentation context and the
423             // app context are not constructed with resources that take overlays into account
424             final Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0);
425             for (int i = 0; i < 100; ++i) {
426                 final int currentInteractionMode = getCurrentInteractionMode(ctx);
427                 final NavigationModel model = getNavigationModel(currentInteractionMode);
428                 log("Interaction mode = " + currentInteractionMode + " (" + model + ")");
429                 if (model != null) return model;
430                 Thread.sleep(100);
431             }
432             fail("Can't detect navigation mode");
433         } catch (Exception e) {
434             fail(e.toString());
435         }
436         return NavigationModel.THREE_BUTTON;
437     }
438 
getNavigationModel(int currentInteractionMode)439     public static NavigationModel getNavigationModel(int currentInteractionMode) {
440         if (QuickStepContract.isGesturalMode(currentInteractionMode)) {
441             return NavigationModel.ZERO_BUTTON;
442         } else if (QuickStepContract.isLegacyMode(currentInteractionMode)) {
443             return NavigationModel.THREE_BUTTON;
444         }
445         return null;
446     }
447 
log(String message)448     static void log(String message) {
449         Log.d(TAG, message);
450     }
451 
addContextLayer(String piece)452     Closable addContextLayer(String piece) {
453         mDiagnosticContext.addLast(piece);
454         log("Entering context: " + piece);
455         return () -> {
456             log("Leaving context: " + piece);
457             mDiagnosticContext.removeLast();
458         };
459     }
460 
dumpViewHierarchy()461     public void dumpViewHierarchy() {
462         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
463         try {
464             mDevice.dumpWindowHierarchy(stream);
465             stream.flush();
466             stream.close();
467             for (String line : stream.toString().split("\\r?\\n")) {
468                 Log.e(TAG, line.trim());
469             }
470         } catch (IOException e) {
471             Log.e(TAG, "error dumping XML to logcat", e);
472         }
473     }
474 
getSystemAnomalyMessage( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews)475     public String getSystemAnomalyMessage(
476             boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) {
477         try {
478             {
479                 final StringBuilder sb = new StringBuilder();
480 
481                 UiObject2 object =
482                         mDevice.findObject(By.res("android", "alertTitle").pkg("android"));
483                 if (object != null) {
484                     sb.append("TITLE: ").append(object.getText());
485                 }
486 
487                 object = mDevice.findObject(By.res("android", "message").pkg("android"));
488                 if (object != null) {
489                     sb.append(" PACKAGE: ").append(object.getApplicationPackage())
490                             .append(" MESSAGE: ").append(object.getText());
491                 }
492 
493                 if (sb.length() != 0) {
494                     return "System alert popup is visible: " + sb;
495                 }
496             }
497 
498             if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
499 
500             if (!ignoreOnlySystemUiViews) {
501                 final String visibleApps = mDevice.findObjects(getAnyObjectSelector())
502                         .stream()
503                         .map(LauncherInstrumentation::getApplicationPackageSafe)
504                         .distinct()
505                         .filter(pkg -> pkg != null)
506                         .collect(Collectors.joining(","));
507                 if (SYSTEMUI_PACKAGE.equals(visibleApps)) return "Only System UI views are visible";
508             }
509             if (!ignoreNavmodeChangeStates) {
510                 if (!mDevice.wait(Until.hasObject(getAnyObjectSelector()), WAIT_TIME_MS)) {
511                     return "Screen is empty";
512                 }
513             }
514 
515             final String navigationModeError = getNavigationModeMismatchError(true);
516             if (navigationModeError != null) return navigationModeError;
517         } catch (Throwable e) {
518             Log.w(TAG, "getSystemAnomalyMessage failed", e);
519         }
520 
521         return null;
522     }
523 
checkForAnomaly()524     private void checkForAnomaly() {
525         checkForAnomaly(false, false);
526     }
527 
checkForAnomaly( boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews)528     public void checkForAnomaly(
529             boolean ignoreNavmodeChangeStates, boolean ignoreOnlySystemUiViews) {
530         final String systemAnomalyMessage =
531                 getSystemAnomalyMessage(ignoreNavmodeChangeStates, ignoreOnlySystemUiViews);
532         if (systemAnomalyMessage != null) {
533             Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
534                     "http://go/tapl : Tests are broken by a non-Launcher system error: "
535                             + systemAnomalyMessage, false)));
536         }
537     }
538 
getVisiblePackages()539     private String getVisiblePackages() {
540         final String apps = mDevice.findObjects(getAnyObjectSelector())
541                 .stream()
542                 .map(LauncherInstrumentation::getApplicationPackageSafe)
543                 .distinct()
544                 .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg))
545                 .collect(Collectors.joining(", "));
546         return !apps.isEmpty()
547                 ? "active app: " + apps
548                 : "the test doesn't see views from any app, including Launcher";
549     }
550 
getApplicationPackageSafe(UiObject2 object)551     private static String getApplicationPackageSafe(UiObject2 object) {
552         try {
553             return object.getApplicationPackage();
554         } catch (StaleObjectException e) {
555             // We are looking at all object in the system; external ones can suddenly go away.
556             return null;
557         }
558     }
559 
getVisibleStateMessage()560     private String getVisibleStateMessage() {
561         if (hasLauncherObject(CONTEXT_MENU_RES_ID)) return "Context Menu";
562         if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
563         if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview";
564         if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
565         if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
566         return "LaunchedApp (" + getVisiblePackages() + ")";
567     }
568 
setSystemHealthSupplier(Function<Long, String> supplier)569     public void setSystemHealthSupplier(Function<Long, String> supplier) {
570         this.mSystemHealthSupplier = supplier;
571     }
572 
setOnSettledStateAction(Consumer<ContainerType> onSettledStateAction)573     public void setOnSettledStateAction(Consumer<ContainerType> onSettledStateAction) {
574         mOnSettledStateAction = onSettledStateAction;
575     }
576 
onTestStart()577     public void onTestStart() {
578         mTestStartTime = System.currentTimeMillis();
579     }
580 
onTestFinish()581     public void onTestFinish() {
582         mTestStartTime = -1;
583     }
584 
formatSystemHealthMessage(String message)585     private String formatSystemHealthMessage(String message) {
586         final String testPackage = getContext().getPackageName();
587 
588         mInstrumentation.getUiAutomation().grantRuntimePermission(
589                 testPackage, "android.permission.READ_LOGS");
590         mInstrumentation.getUiAutomation().grantRuntimePermission(
591                 testPackage, "android.permission.PACKAGE_USAGE_STATS");
592 
593         if (mTestStartTime > 0) {
594             final String systemHealth = mSystemHealthSupplier != null
595                     ? mSystemHealthSupplier.apply(mTestStartTime)
596                     : TestHelpers.getSystemHealthMessage(getContext(), mTestStartTime);
597 
598             if (systemHealth != null) {
599                 message += ";\nPerhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n"
600                         + systemHealth + "\n>>>>>>>>>>>>>>>>>>";
601             }
602         }
603         Log.d(TAG, "About to throw the error: " + message, new Exception());
604         return message;
605     }
606 
formatErrorWithEvents(String message, boolean checkEvents)607     private String formatErrorWithEvents(String message, boolean checkEvents) {
608         if (mEventChecker != null) {
609             final LogEventChecker eventChecker = mEventChecker;
610             mEventChecker = null;
611             if (checkEvents) {
612                 final String eventMismatch = eventChecker.verify(0, false);
613                 if (eventMismatch != null) {
614                     message = message + ";\n" + eventMismatch;
615                 }
616             } else {
617                 eventChecker.finishNoWait();
618             }
619         }
620 
621         dumpDiagnostics(message);
622 
623         log("Hierarchy dump for: " + message);
624         dumpViewHierarchy();
625 
626         return message;
627     }
628 
dumpDiagnostics(String message)629     private void dumpDiagnostics(String message) {
630         log("Diagnostics for failure: " + message);
631         log("Input:");
632         logShellCommand("dumpsys input");
633         log("TIS:");
634         logShellCommand("dumpsys activity service TouchInteractionService");
635     }
636 
logShellCommand(String command)637     private void logShellCommand(String command) {
638         try {
639             for (String line : mDevice.executeShellCommand(command).split("\\n")) {
640                 SystemClock.sleep(10);
641                 log(line);
642             }
643         } catch (IOException e) {
644             log("Failed to execute " + command);
645         }
646     }
647 
fail(String message)648     void fail(String message) {
649         checkForAnomaly();
650         Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
651                 "http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription()
652                         + "; now visible state is " + getVisibleStateMessage(), true)));
653     }
654 
getContextDescription()655     private String getContextDescription() {
656         return mDiagnosticContext.isEmpty()
657                 ? "(no context)" : String.join(", ", mDiagnosticContext);
658     }
659 
assertTrue(String message, boolean condition)660     void assertTrue(String message, boolean condition) {
661         if (!condition) {
662             fail(message);
663         }
664     }
665 
assertNotNull(String message, Object object)666     void assertNotNull(String message, Object object) {
667         assertTrue(message, object != null);
668     }
669 
failEquals(String message, Object actual)670     private void failEquals(String message, Object actual) {
671         fail(message + ". " + "Actual: " + actual);
672     }
673 
assertEquals(String message, int expected, int actual)674     private void assertEquals(String message, int expected, int actual) {
675         if (expected != actual) {
676             fail(message + " expected: " + expected + " but was: " + actual);
677         }
678     }
679 
assertEquals(String message, String expected, String actual)680     void assertEquals(String message, String expected, String actual) {
681         if (!TextUtils.equals(expected, actual)) {
682             fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
683         }
684     }
685 
assertEquals(String message, long expected, long actual)686     void assertEquals(String message, long expected, long actual) {
687         if (expected != actual) {
688             fail(message + " expected: " + expected + " but was: " + actual);
689         }
690     }
691 
assertNotEquals(String message, int unexpected, int actual)692     void assertNotEquals(String message, int unexpected, int actual) {
693         if (unexpected == actual) {
694             failEquals(message, actual);
695         }
696     }
697 
698     /**
699      * Whether to ignore verifying the task bar visibility during instrumenting.
700      *
701      * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly
702      *                                verifying the task bar visibility with
703      *                                {@link VisibleContainer#verifyActiveContainer}.
704      *                                {@code false} otherwise.
705      */
setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility)706     public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) {
707         mIgnoreTaskbarVisibility = ignoreTaskbarVisibility;
708     }
709 
710     /**
711      * Sets expected rotation.
712      * TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one.
713      * Null parameter disables checks. The initial state is "no checks".
714      */
setExpectedRotation(Integer expectedRotation)715     public void setExpectedRotation(Integer expectedRotation) {
716         mExpectedRotation = expectedRotation;
717     }
718 
getNavigationModeMismatchError(boolean waitForCorrectState)719     public String getNavigationModeMismatchError(boolean waitForCorrectState) {
720         final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0;
721         final NavigationModel navigationModel = getNavigationModel();
722         String resPackage = getNavigationButtonResPackage();
723         if (navigationModel == NavigationModel.THREE_BUTTON) {
724             if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) {
725                 return "Recents button not present in 3-button mode";
726             }
727         } else {
728             if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) {
729                 return "Recents button is present in non-3-button mode";
730             }
731         }
732 
733         if (navigationModel == NavigationModel.ZERO_BUTTON) {
734             if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) {
735                 return "Home button is present in gestural mode";
736             }
737         } else {
738             if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) {
739                 return "Home button not present in non-gestural mode";
740             }
741         }
742         return null;
743     }
744 
getNavigationButtonResPackage()745     private String getNavigationButtonResPackage() {
746         return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
747     }
748 
verifyContainerType(ContainerType containerType)749     private UiObject2 verifyContainerType(ContainerType containerType) {
750         waitForLauncherInitialized();
751 
752         if (mExpectedRotation != null) {
753             assertEquals("Unexpected display rotation",
754                     mExpectedRotation, mDevice.getDisplayRotation());
755         }
756 
757         final String error = getNavigationModeMismatchError(true);
758         assertTrue(error, error == null);
759 
760         log("verifyContainerType: " + containerType);
761 
762         final UiObject2 container = verifyVisibleObjects(containerType);
763 
764         if (mOnSettledStateAction != null) mOnSettledStateAction.accept(containerType);
765 
766         return container;
767     }
768 
verifyVisibleObjects(ContainerType containerType)769     private UiObject2 verifyVisibleObjects(ContainerType containerType) {
770         try (Closable c = addContextLayer(
771                 "but the current state is not " + containerType.name())) {
772             switch (containerType) {
773                 case WORKSPACE: {
774                     waitUntilLauncherObjectGone(APPS_RES_ID);
775                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
776                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
777                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
778 
779                     if (is3PLauncher() && isTablet()) {
780                         waitForSystemLauncherObject(TASKBAR_RES_ID);
781                     } else {
782                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
783                     }
784 
785                     return waitForLauncherObject(WORKSPACE_RES_ID);
786                 }
787                 case WIDGETS: {
788                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
789                     waitUntilLauncherObjectGone(APPS_RES_ID);
790                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
791                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
792 
793                     if (is3PLauncher() && isTablet()) {
794                         waitForSystemLauncherObject(TASKBAR_RES_ID);
795                     } else {
796                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
797                     }
798 
799                     return waitForLauncherObject(WIDGETS_RES_ID);
800                 }
801                 case TASKBAR_ALL_APPS: {
802                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
803                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
804                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
805                     waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
806                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
807 
808                     return waitForLauncherObject(APPS_RES_ID);
809                 }
810                 case HOME_ALL_APPS: {
811                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
812                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
813                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
814                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
815 
816                     if (is3PLauncher() && isTablet()) {
817                         waitForSystemLauncherObject(TASKBAR_RES_ID);
818                     } else {
819                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
820                     }
821 
822                     return waitForLauncherObject(APPS_RES_ID);
823                 }
824                 case OVERVIEW:
825                 case FALLBACK_OVERVIEW: {
826                     waitUntilLauncherObjectGone(APPS_RES_ID);
827                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
828                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
829                     if (isTablet()) {
830                         waitForSystemLauncherObject(TASKBAR_RES_ID);
831                     } else {
832                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
833                     }
834                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
835 
836                     return waitForSystemLauncherObject(OVERVIEW_RES_ID);
837                 }
838                 case SPLIT_SCREEN_SELECT: {
839                     waitUntilLauncherObjectGone(APPS_RES_ID);
840                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
841                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
842                     if (isTablet()) {
843                         waitForSystemLauncherObject(TASKBAR_RES_ID);
844                     } else {
845                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
846                     }
847 
848                     waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
849                     return waitForSystemLauncherObject(OVERVIEW_RES_ID);
850                 }
851                 case LAUNCHED_APP: {
852                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
853                     waitUntilLauncherObjectGone(APPS_RES_ID);
854                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
855                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
856                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
857 
858                     if (mIgnoreTaskbarVisibility) {
859                         return null;
860                     }
861 
862                     if (isTablet()) {
863                         waitForSystemLauncherObject(TASKBAR_RES_ID);
864                     } else {
865                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
866                     }
867                     return null;
868                 }
869                 default:
870                     fail("Invalid state: " + containerType);
871                     return null;
872             }
873         }
874     }
875 
waitForModelQueueCleared()876     public void waitForModelQueueCleared() {
877         getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED);
878     }
879 
waitForLauncherInitialized()880     public void waitForLauncherInitialized() {
881         for (int i = 0; i < 100; ++i) {
882             if (getTestInfo(
883                     TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).
884                     getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) {
885                 return;
886             }
887             SystemClock.sleep(100);
888         }
889         checkForAnomaly();
890         fail("Launcher didn't initialize");
891     }
892 
executeAndWaitForLauncherEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)893     Parcelable executeAndWaitForLauncherEvent(Runnable command,
894             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message,
895             String actionName) {
896         return executeAndWaitForEvent(
897                 command,
898                 e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e),
899                 message, actionName);
900     }
901 
executeAndWaitForEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName)902     Parcelable executeAndWaitForEvent(Runnable command,
903             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message,
904             String actionName) {
905         try (LauncherInstrumentation.Closable c = addContextLayer(actionName)) {
906             try {
907                 final AccessibilityEvent event =
908                         mInstrumentation.getUiAutomation().executeAndWaitForEvent(
909                                 command, eventFilter, WAIT_TIME_MS);
910                 assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
911                 final Parcelable parcelableData = event.getParcelableData();
912                 event.recycle();
913                 return parcelableData;
914             } catch (TimeoutException e) {
915                 fail(message.get());
916                 return null;
917             }
918         }
919     }
920 
921     /**
922      * Get the resource ID of visible floating view.
923      */
getFloatingResId()924     private Optional<String> getFloatingResId() {
925         if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
926             return Optional.of(CONTEXT_MENU_RES_ID);
927         }
928         if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) {
929             return Optional.of(FOLDER_CONTENT_RES_ID);
930         }
931         return Optional.empty();
932     }
933 
934     /**
935      * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content.
936      */
swipeUpToCloseFloatingView(boolean gestureStartFromLauncher)937     private void swipeUpToCloseFloatingView(boolean gestureStartFromLauncher) {
938         final Point displaySize = getRealDisplaySize();
939 
940         final Optional<String> floatingRes = getFloatingResId();
941 
942         if (!floatingRes.isPresent()) {
943             return;
944         }
945 
946         GestureScope gestureScope = gestureStartFromLauncher
947                 // Without the navigation bar layer, the gesture scope on tablets remains inside the
948                 // launcher process.
949                 ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
950                 : GestureScope.OUTSIDE_WITH_PILFER;
951         linearGesture(
952                 displaySize.x / 2, displaySize.y - 1,
953                 displaySize.x / 2, 0,
954                 ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
955                 false, gestureScope);
956 
957         try (LauncherInstrumentation.Closable c1 = addContextLayer(
958                 String.format("Swiped up from floating view %s to home", floatingRes.get()))) {
959             waitUntilLauncherObjectGone(floatingRes.get());
960             waitForLauncherObject(getAnyObjectSelector());
961         }
962     }
963 
964     /**
965      * @return the Workspace object.
966      * @deprecated use goHome().
967      * Presses nav bar home button.
968      */
969     @Deprecated
pressHome()970     public Workspace pressHome() {
971         return goHome();
972     }
973 
974     /**
975      * Goes to home by swiping up in zero-button mode or pressing Home button.
976      * Calling it after another TAPL call is safe because all TAPL methods wait for the animations
977      * to finish.
978      * When calling it after a non-TAPL method, make sure that all animations have already
979      * completed, otherwise it may detect the current state (for example "Application" or "Home")
980      * incorrectly.
981      * The method expects either app or Launcher to be active when it's called. Other states, such
982      * as visible notification shade are not supported.
983      *
984      * @return the Workspace object.
985      */
goHome()986     public Workspace goHome() {
987         try (LauncherInstrumentation.Closable e = eventsCheck();
988              LauncherInstrumentation.Closable c = addContextLayer("want to switch to home")) {
989             waitForLauncherInitialized();
990             // Click home, then wait for any accessibility event, then wait until accessibility
991             // events stop.
992             // We need waiting for any accessibility event generated after pressing Home because
993             // otherwise waitForIdle may return immediately in case when there was a big enough
994             // pause in accessibility events prior to pressing Home.
995             final String action;
996             if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
997                 checkForAnomaly(false, true);
998 
999                 final Point displaySize = getRealDisplaySize();
1000 
1001                 boolean gestureStartFromLauncher =
1002                         isTablet() ? !isLauncher3() : isLauncherVisible();
1003 
1004                 // CLose floating views before going back to home.
1005                 swipeUpToCloseFloatingView(gestureStartFromLauncher);
1006 
1007                 if (hasLauncherObject(WORKSPACE_RES_ID)) {
1008                     log(action = "already at home");
1009                 } else {
1010                     log("Hierarchy before swiping up to home:");
1011                     dumpViewHierarchy();
1012                     action = "swiping up to home";
1013 
1014                     swipeToState(
1015                             displaySize.x / 2, displaySize.y - 1,
1016                             displaySize.x / 2, displaySize.y / 2,
1017                             ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
1018                             gestureStartFromLauncher ? GestureScope.INSIDE_TO_OUTSIDE
1019                                     : GestureScope.OUTSIDE_WITH_PILFER);
1020                 }
1021             } else {
1022                 log("Hierarchy before clicking home:");
1023                 dumpViewHierarchy();
1024                 action = "clicking home button";
1025                 if (isTablet()) {
1026                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
1027                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
1028                 }
1029 
1030                 runToState(
1031                         waitForNavigationUiObject("home")::click,
1032                         NORMAL_STATE_ORDINAL,
1033                         !hasLauncherObject(WORKSPACE_RES_ID)
1034                                 && (hasLauncherObject(APPS_RES_ID)
1035                                 || hasSystemLauncherObject(OVERVIEW_RES_ID)),
1036                         action);
1037             }
1038             try (LauncherInstrumentation.Closable c1 = addContextLayer(
1039                     "performed action to switch to Home - " + action)) {
1040                 return getWorkspace();
1041             }
1042         }
1043     }
1044 
1045     /**
1046      * Press navbar back button or swipe back if in gesture navigation mode.
1047      */
pressBack()1048     public void pressBack() {
1049         try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) {
1050             waitForLauncherInitialized();
1051             final boolean launcherVisible =
1052                     isTablet() ? isLauncherContainerVisible() : isLauncherVisible();
1053             if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
1054                 final Point displaySize = getRealDisplaySize();
1055                 final GestureScope gestureScope =
1056                         launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
1057                                 : GestureScope.OUTSIDE_WITH_KEYCODE;
1058                 // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the
1059                 //  issue is solved.
1060                 linearGesture(0, displaySize.y / 4, displaySize.x / 2, displaySize.y / 4,
1061                         10, false, gestureScope);
1062             } else {
1063                 waitForNavigationUiObject("back").click();
1064                 if (isTablet()) {
1065                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
1066                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
1067                 }
1068             }
1069             if (launcherVisible) {
1070                 if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) {
1071                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED);
1072                 } else {
1073                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
1074                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
1075                 }
1076             }
1077         }
1078     }
1079 
getAnyObjectSelector()1080     private static BySelector getAnyObjectSelector() {
1081         return By.textStartsWith("");
1082     }
1083 
isLauncherVisible()1084     boolean isLauncherVisible() {
1085         mDevice.waitForIdle();
1086         return hasLauncherObject(getAnyObjectSelector());
1087     }
1088 
isLauncherContainerVisible()1089     boolean isLauncherContainerVisible() {
1090         final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID};
1091         return Arrays.stream(containerResources).anyMatch(
1092                 r -> r.equals(OVERVIEW_RES_ID) ? hasSystemLauncherObject(r) : hasLauncherObject(r));
1093     }
1094 
1095     /**
1096      * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
1097      * launcher is not in that state.
1098      *
1099      * @return Workspace object.
1100      */
1101     @NonNull
getWorkspace()1102     public Workspace getWorkspace() {
1103         try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) {
1104             return new Workspace(this);
1105         }
1106     }
1107 
1108     /**
1109      * Gets the LaunchedApp object if another app is active. Fails if the launcher is not in that
1110      * state.
1111      *
1112      * @return LaunchedApp object.
1113      */
1114     @NonNull
getLaunchedAppState()1115     public LaunchedAppState getLaunchedAppState() {
1116         return new LaunchedAppState(this);
1117     }
1118 
1119     /**
1120      * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
1121      * not in that state.
1122      *
1123      * @return Widgets object.
1124      */
1125     @NonNull
getAllWidgets()1126     public Widgets getAllWidgets() {
1127         try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) {
1128             return new Widgets(this);
1129         }
1130     }
1131 
1132     @NonNull
getAddToHomeScreenPrompt()1133     public AddToHomeScreenPrompt getAddToHomeScreenPrompt() {
1134         try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) {
1135             return new AddToHomeScreenPrompt(this);
1136         }
1137     }
1138 
1139     /**
1140      * Gets the Overview object if the current state is showing the overview panel. Fails if the
1141      * launcher is not in that state.
1142      *
1143      * @return Overview object.
1144      */
1145     @NonNull
getOverview()1146     public Overview getOverview() {
1147         try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) {
1148             return new Overview(this);
1149         }
1150     }
1151 
1152     /**
1153      * Gets the homescreen All Apps object if the current state is showing the all apps panel opened
1154      * by swiping from workspace. Fails if the launcher is not in that state. Please don't call this
1155      * method if App Apps was opened by swiping up from Overview, as it won't fail and will return
1156      * an incorrect object.
1157      *
1158      * @return Home All Apps object.
1159      */
1160     @NonNull
getAllApps()1161     public HomeAllApps getAllApps() {
1162         try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
1163             return new HomeAllApps(this);
1164         }
1165     }
1166 
waitUntilLauncherObjectGone(String resId)1167     void waitUntilLauncherObjectGone(String resId) {
1168         waitUntilGoneBySelector(getLauncherObjectSelector(resId));
1169     }
1170 
waitUntilOverviewObjectGone(String resId)1171     void waitUntilOverviewObjectGone(String resId) {
1172         waitUntilGoneBySelector(getOverviewObjectSelector(resId));
1173     }
1174 
waitUntilSystemLauncherObjectGone(String resId)1175     void waitUntilSystemLauncherObjectGone(String resId) {
1176         if (is3PLauncher()) {
1177             waitUntilOverviewObjectGone(resId);
1178         } else {
1179             waitUntilLauncherObjectGone(resId);
1180         }
1181     }
1182 
waitUntilLauncherObjectGone(BySelector selector)1183     void waitUntilLauncherObjectGone(BySelector selector) {
1184         waitUntilGoneBySelector(makeLauncherSelector(selector));
1185     }
1186 
waitUntilGoneBySelector(BySelector launcherSelector)1187     private void waitUntilGoneBySelector(BySelector launcherSelector) {
1188         assertTrue("Unexpected launcher object visible: " + launcherSelector,
1189                 mDevice.wait(Until.gone(launcherSelector),
1190                         WAIT_TIME_MS));
1191     }
1192 
hasSystemUiObject(String resId)1193     private boolean hasSystemUiObject(String resId) {
1194         return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
1195     }
1196 
1197     @NonNull
waitForSystemUiObject(String resId)1198     UiObject2 waitForSystemUiObject(String resId) {
1199         final UiObject2 object = mDevice.wait(
1200                 Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS);
1201         assertNotNull("Can't find a systemui object with id: " + resId, object);
1202         return object;
1203     }
1204 
1205     @NonNull
waitForSystemUiObject(BySelector selector)1206     UiObject2 waitForSystemUiObject(BySelector selector) {
1207         final UiObject2 object = TestHelpers.wait(
1208                 Until.findObject(selector), WAIT_TIME_MS);
1209         assertNotNull("Can't find a systemui object with selector: " + selector, object);
1210         return object;
1211     }
1212 
1213     @NonNull
waitForNavigationUiObject(String resId)1214     UiObject2 waitForNavigationUiObject(String resId) {
1215         String resPackage = getNavigationButtonResPackage();
1216         final UiObject2 object = mDevice.wait(
1217                 Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS);
1218         assertNotNull("Can't find a navigation UI object with id: " + resId, object);
1219         return object;
1220     }
1221 
1222     @Nullable
findObjectInContainer(UiObject2 container, String resName)1223     UiObject2 findObjectInContainer(UiObject2 container, String resName) {
1224         try {
1225             return container.findObject(getLauncherObjectSelector(resName));
1226         } catch (StaleObjectException e) {
1227             fail("The container disappeared from screen");
1228             return null;
1229         }
1230     }
1231 
1232     @Nullable
findObjectInContainer(UiObject2 container, BySelector selector)1233     UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
1234         try {
1235             return container.findObject(selector);
1236         } catch (StaleObjectException e) {
1237             fail("The container disappeared from screen");
1238             return null;
1239         }
1240     }
1241 
1242     @NonNull
getObjectsInContainer(UiObject2 container, String resName)1243     List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
1244         try {
1245             return container.findObjects(getLauncherObjectSelector(resName));
1246         } catch (StaleObjectException e) {
1247             fail("The container disappeared from screen");
1248             return null;
1249         }
1250     }
1251 
1252     @NonNull
waitForObjectInContainer(UiObject2 container, String resName)1253     UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
1254         try {
1255             final UiObject2 object = container.wait(
1256                     Until.findObject(getLauncherObjectSelector(resName)),
1257                     WAIT_TIME_MS);
1258             assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
1259                     + container.getResourceName(), object);
1260             return object;
1261         } catch (StaleObjectException e) {
1262             fail("The container disappeared from screen");
1263             return null;
1264         }
1265     }
1266 
waitForObjectEnabled(UiObject2 object, String waitReason)1267     void waitForObjectEnabled(UiObject2 object, String waitReason) {
1268         try {
1269             assertTrue("Timed out waiting for object to be enabled for " + waitReason + " "
1270                             + object.getResourceName(),
1271                     object.wait(Until.enabled(true), WAIT_TIME_MS));
1272         } catch (StaleObjectException e) {
1273             fail("The object disappeared from screen");
1274         }
1275     }
1276 
1277     @NonNull
waitForObjectInContainer(UiObject2 container, BySelector selector)1278     UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
1279         return waitForObjectsInContainer(container, selector).get(0);
1280     }
1281 
1282     @NonNull
waitForObjectsInContainer( UiObject2 container, BySelector selector)1283     List<UiObject2> waitForObjectsInContainer(
1284             UiObject2 container, BySelector selector) {
1285         try {
1286             final List<UiObject2> objects = container.wait(
1287                     Until.findObjects(selector),
1288                     WAIT_TIME_MS);
1289             assertNotNull("Can't find views in Launcher, id: " + selector + " in container: "
1290                     + container.getResourceName(), objects);
1291             assertTrue("Can't find views in Launcher, id: " + selector + " in container: "
1292                     + container.getResourceName(), objects.size() > 0);
1293             return objects;
1294         } catch (StaleObjectException e) {
1295             fail("The container disappeared from screen");
1296             return null;
1297         }
1298     }
1299 
getChildren(UiObject2 container)1300     List<UiObject2> getChildren(UiObject2 container) {
1301         try {
1302             return container.getChildren();
1303         } catch (StaleObjectException e) {
1304             fail("The container disappeared from screen");
1305             return null;
1306         }
1307     }
1308 
hasLauncherObject(String resId)1309     private boolean hasLauncherObject(String resId) {
1310         return mDevice.hasObject(getLauncherObjectSelector(resId));
1311     }
1312 
hasSystemLauncherObject(String resId)1313     private boolean hasSystemLauncherObject(String resId) {
1314         return mDevice.hasObject(is3PLauncher() ? getOverviewObjectSelector(resId)
1315                 : getLauncherObjectSelector(resId));
1316     }
1317 
hasLauncherObject(BySelector selector)1318     boolean hasLauncherObject(BySelector selector) {
1319         return mDevice.hasObject(makeLauncherSelector(selector));
1320     }
1321 
makeLauncherSelector(BySelector selector)1322     private BySelector makeLauncherSelector(BySelector selector) {
1323         return By.copy(selector).pkg(getLauncherPackageName());
1324     }
1325 
1326     @NonNull
waitForOverviewObject(String resName)1327     UiObject2 waitForOverviewObject(String resName) {
1328         return waitForObjectBySelector(getOverviewObjectSelector(resName));
1329     }
1330 
1331     @NonNull
waitForLauncherObject(String resName)1332     UiObject2 waitForLauncherObject(String resName) {
1333         return waitForObjectBySelector(getLauncherObjectSelector(resName));
1334     }
1335 
1336     @NonNull
waitForSystemLauncherObject(String resName)1337     UiObject2 waitForSystemLauncherObject(String resName) {
1338         return is3PLauncher() ? waitForOverviewObject(resName)
1339                 : waitForLauncherObject(resName);
1340     }
1341 
1342     @NonNull
waitForLauncherObject(BySelector selector)1343     UiObject2 waitForLauncherObject(BySelector selector) {
1344         return waitForObjectBySelector(makeLauncherSelector(selector));
1345     }
1346 
1347     @NonNull
tryWaitForLauncherObject(BySelector selector, long timeout)1348     UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
1349         return tryWaitForObjectBySelector(makeLauncherSelector(selector), timeout);
1350     }
1351 
1352     @NonNull
waitForAndroidObject(String resId)1353     UiObject2 waitForAndroidObject(String resId) {
1354         final UiObject2 object = TestHelpers.wait(
1355                 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS);
1356         assertNotNull("Can't find a android object with id: " + resId, object);
1357         return object;
1358     }
1359 
1360     @NonNull
waitForObjectsBySelector(BySelector selector)1361     List<UiObject2> waitForObjectsBySelector(BySelector selector) {
1362         final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS);
1363         assertNotNull("Can't find any view in Launcher, selector: " + selector, objects);
1364         return objects;
1365     }
1366 
waitForObjectBySelector(BySelector selector)1367     private UiObject2 waitForObjectBySelector(BySelector selector) {
1368         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
1369         assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
1370         return object;
1371     }
1372 
tryWaitForObjectBySelector(BySelector selector, long timeout)1373     private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) {
1374         return mDevice.wait(Until.findObject(selector), timeout);
1375     }
1376 
getLauncherObjectSelector(String resName)1377     BySelector getLauncherObjectSelector(String resName) {
1378         return By.res(getLauncherPackageName(), resName);
1379     }
1380 
getOverviewObjectSelector(String resName)1381     BySelector getOverviewObjectSelector(String resName) {
1382         return By.res(getOverviewPackageName(), resName);
1383     }
1384 
getLauncherPackageName()1385     String getLauncherPackageName() {
1386         return mDevice.getLauncherPackageName();
1387     }
1388 
is3PLauncher()1389     boolean is3PLauncher() {
1390         return !getOverviewPackageName().equals(getLauncherPackageName());
1391     }
1392 
1393     @NonNull
getDevice()1394     public UiDevice getDevice() {
1395         return mDevice;
1396     }
1397 
eventListToString(List<Integer> actualEvents)1398     private static String eventListToString(List<Integer> actualEvents) {
1399         if (actualEvents.isEmpty()) return "no events";
1400 
1401         return "["
1402                 + actualEvents.stream()
1403                 .map(state -> TestProtocol.stateOrdinalToString(state))
1404                 .collect(Collectors.joining(", "))
1405                 + "]";
1406     }
1407 
runToState(Runnable command, int expectedState, boolean requireEvent, String actionName)1408     void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) {
1409         if (requireEvent) {
1410             runToState(command, expectedState, actionName);
1411         } else {
1412             command.run();
1413         }
1414     }
1415 
runToState(Runnable command, int expectedState, String actionName)1416     void runToState(Runnable command, int expectedState, String actionName) {
1417         final List<Integer> actualEvents = new ArrayList<>();
1418         executeAndWaitForLauncherEvent(
1419                 command,
1420                 event -> isSwitchToStateEvent(event, expectedState, actualEvents),
1421                 () -> "Failed to receive an event for the state change: expected ["
1422                         + TestProtocol.stateOrdinalToString(expectedState)
1423                         + "], actual: " + eventListToString(actualEvents),
1424                 actionName);
1425     }
1426 
isSwitchToStateEvent( AccessibilityEvent event, int expectedState, List<Integer> actualEvents)1427     private boolean isSwitchToStateEvent(
1428             AccessibilityEvent event, int expectedState, List<Integer> actualEvents) {
1429         if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false;
1430 
1431         final Bundle parcel = (Bundle) event.getParcelableData();
1432         final int actualState = parcel.getInt(TestProtocol.STATE_FIELD);
1433         actualEvents.add(actualState);
1434         return actualState == expectedState;
1435     }
1436 
swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState, GestureScope gestureScope)1437     void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState,
1438             GestureScope gestureScope) {
1439         runToState(
1440                 () -> linearGesture(startX, startY, endX, endY, steps, false, gestureScope),
1441                 expectedState,
1442                 "swiping");
1443     }
1444 
getBottomGestureSize()1445     int getBottomGestureSize() {
1446         return Math.max(getWindowInsets().bottom, ResourceUtils.getNavbarSize(
1447                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1;
1448     }
1449 
getBottomGestureMarginInContainer(UiObject2 container)1450     int getBottomGestureMarginInContainer(UiObject2 container) {
1451         final int bottomGestureStartOnScreen = getBottomGestureStartOnScreen();
1452         return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
1453     }
1454 
getRightGestureMarginInContainer(UiObject2 container)1455     int getRightGestureMarginInContainer(UiObject2 container) {
1456         final int rightGestureStartOnScreen = getRightGestureStartOnScreen();
1457         return getVisibleBounds(container).right - rightGestureStartOnScreen;
1458     }
1459 
getBottomGestureStartOnScreen()1460     int getBottomGestureStartOnScreen() {
1461         return getRealDisplaySize().y - getBottomGestureSize();
1462     }
1463 
getRightGestureStartOnScreen()1464     int getRightGestureStartOnScreen() {
1465         return getRealDisplaySize().x - getWindowInsets().right - 1;
1466     }
1467 
1468     /**
1469      * Click on the ui object right away without waiting for animation.
1470      *
1471      * [UiObject2.click] would wait for all animations finished before clicking. Not waiting for
1472      * animations because in some scenarios there is a playing animations when the click is
1473      * attempted.
1474      */
clickObject(UiObject2 uiObject, GestureScope gestureScope)1475     void clickObject(UiObject2 uiObject, GestureScope gestureScope) {
1476         final long clickTime = SystemClock.uptimeMillis();
1477         final Point center = uiObject.getVisibleCenter();
1478         sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center, gestureScope);
1479         sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center, gestureScope);
1480     }
1481 
clickLauncherObject(UiObject2 object)1482     void clickLauncherObject(UiObject2 object) {
1483         clickObject(object, GestureScope.INSIDE);
1484     }
1485 
scrollToLastVisibleRow( UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer, int appsListBottomPadding)1486     void scrollToLastVisibleRow(
1487             UiObject2 container, Rect bottomVisibleIconBounds, int topPaddingInContainer,
1488             int appsListBottomPadding) {
1489         final int itemRowCurrentTopOnScreen = bottomVisibleIconBounds.top;
1490         final Rect containerRect = getVisibleBounds(container);
1491         final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
1492         final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
1493 
1494         scrollDownByDistance(container, distance, appsListBottomPadding);
1495     }
1496 
scrollDownByDistance(UiObject2 container, int distance)1497     void scrollDownByDistance(UiObject2 container, int distance) {
1498         scrollDownByDistance(container, distance, 0);
1499     }
1500 
scrollDownByDistance(UiObject2 container, int distance, int bottomPadding)1501     void scrollDownByDistance(UiObject2 container, int distance, int bottomPadding) {
1502         final Rect containerRect = getVisibleBounds(container);
1503         final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
1504         scroll(
1505                 container,
1506                 Direction.DOWN,
1507                 new Rect(
1508                         0,
1509                         containerRect.height() - distance - bottomGestureMarginInContainer,
1510                         0,
1511                         bottomGestureMarginInContainer + bottomPadding),
1512                 /* steps= */ 10,
1513                 /* slowDown= */ true);
1514     }
1515 
scrollLeftByDistance(UiObject2 container, int distance)1516     void scrollLeftByDistance(UiObject2 container, int distance) {
1517         final Rect containerRect = getVisibleBounds(container);
1518         final int rightGestureMarginInContainer = getRightGestureMarginInContainer(container);
1519         final int leftGestureMargin = getTargetInsets().left + getEdgeSensitivityWidth();
1520         scroll(
1521                 container,
1522                 Direction.LEFT,
1523                 new Rect(leftGestureMargin, 0,
1524                         containerRect.width() - distance - rightGestureMarginInContainer, 0),
1525                 10,
1526                 true);
1527     }
1528 
scroll( UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown)1529     void scroll(
1530             UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) {
1531         final Rect rect = getVisibleBounds(container);
1532         if (margins != null) {
1533             rect.left += margins.left;
1534             rect.top += margins.top;
1535             rect.right -= margins.right;
1536             rect.bottom -= margins.bottom;
1537         }
1538 
1539         final int startX;
1540         final int startY;
1541         final int endX;
1542         final int endY;
1543 
1544         switch (direction) {
1545             case UP: {
1546                 startX = endX = rect.centerX();
1547                 startY = rect.top;
1548                 endY = rect.bottom - 1;
1549             }
1550             break;
1551             case DOWN: {
1552                 startX = endX = rect.centerX();
1553                 startY = rect.bottom - 1;
1554                 endY = rect.top;
1555             }
1556             break;
1557             case LEFT: {
1558                 startY = endY = rect.centerY();
1559                 startX = rect.left;
1560                 endX = rect.right - 1;
1561             }
1562             break;
1563             case RIGHT: {
1564                 startY = endY = rect.centerY();
1565                 startX = rect.right - 1;
1566                 endX = rect.left;
1567             }
1568             break;
1569             default:
1570                 fail("Unsupported direction");
1571                 return;
1572         }
1573 
1574         executeAndWaitForLauncherEvent(
1575                 () -> linearGesture(
1576                         startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
1577                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
1578                 () -> "Didn't receive a scroll end message: " + startX + ", " + startY
1579                         + ", " + endX + ", " + endY,
1580                 "scrolling");
1581     }
1582 
1583     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
1584     // fixed interval each time.
linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown, GestureScope gestureScope)1585     public void linearGesture(int startX, int startY, int endX, int endY, int steps,
1586             boolean slowDown,
1587             GestureScope gestureScope) {
1588         log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
1589         final long downTime = SystemClock.uptimeMillis();
1590         final Point start = new Point(startX, startY);
1591         final Point end = new Point(endX, endY);
1592         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
1593         final long endTime = movePointer(
1594                 start, end, steps, false, downTime, downTime, slowDown, gestureScope);
1595         sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
1596     }
1597 
movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime, long startTime, boolean slowDown, GestureScope gestureScope)1598     long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime,
1599             long startTime, boolean slowDown, GestureScope gestureScope) {
1600         long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS,
1601                 isDecelerating, start, end, gestureScope);
1602         if (slowDown) {
1603             endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end,
1604                     end, gestureScope);
1605         }
1606         return endTime;
1607     }
1608 
waitForIdle()1609     void waitForIdle() {
1610         mDevice.waitForIdle();
1611     }
1612 
getTouchSlop()1613     int getTouchSlop() {
1614         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
1615     }
1616 
getResources()1617     public Resources getResources() {
1618         return getContext().getResources();
1619     }
1620 
getMotionEvent(long downTime, long eventTime, int action, float x, float y)1621     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
1622             float x, float y) {
1623         MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
1624         properties.id = 0;
1625         properties.toolType = Configurator.getInstance().getToolType();
1626 
1627         MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
1628         coords.pressure = 1;
1629         coords.size = 1;
1630         coords.x = x;
1631         coords.y = y;
1632 
1633         return MotionEvent.obtain(downTime, eventTime, action, 1,
1634                 new MotionEvent.PointerProperties[]{properties},
1635                 new MotionEvent.PointerCoords[]{coords},
1636                 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
1637     }
1638 
hasTIS()1639     private boolean hasTIS() {
1640         return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean(TestProtocol.REQUEST_HAS_TIS);
1641     }
1642 
1643 
sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope)1644     public void sendPointer(long downTime, long currentTime, int action, Point point,
1645             GestureScope gestureScope) {
1646         final boolean hasTIS = hasTIS();
1647         switch (action) {
1648             case MotionEvent.ACTION_DOWN:
1649                 if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
1650                         && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
1651                         && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
1652                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
1653                 }
1654                 if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) {
1655                     expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
1656                 }
1657                 break;
1658             case MotionEvent.ACTION_UP:
1659                 if (hasTIS && gestureScope != GestureScope.INSIDE
1660                         && gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
1661                         && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
1662                         || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
1663                     expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
1664                 }
1665                 if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
1666                         && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
1667                         && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
1668                     expectEvent(TestProtocol.SEQUENCE_MAIN,
1669                             gestureScope == GestureScope.INSIDE
1670                                     || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
1671                                     ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
1672                 }
1673                 if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) {
1674                     expectEvent(TestProtocol.SEQUENCE_TIS,
1675                             gestureScope == GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
1676                                     || gestureScope == GestureScope.OUTSIDE_WITH_KEYCODE
1677                                     ? EVENT_TOUCH_CANCEL_TIS : EVENT_TOUCH_UP_TIS);
1678                 }
1679                 break;
1680         }
1681 
1682         final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
1683         assertTrue("injectInputEvent failed",
1684                 mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
1685         event.recycle();
1686     }
1687 
movePointer(long downTime, long startTime, long duration, Point from, Point to, GestureScope gestureScope)1688     public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
1689             GestureScope gestureScope) {
1690         return movePointer(
1691                 downTime, startTime, duration, false, from, to, gestureScope);
1692     }
1693 
movePointer(long downTime, long startTime, long duration, boolean isDecelerating, Point from, Point to, GestureScope gestureScope)1694     public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating,
1695             Point from, Point to, GestureScope gestureScope) {
1696         log("movePointer: " + from + " to " + to);
1697         final Point point = new Point();
1698         long steps = duration / GESTURE_STEP_MS;
1699 
1700         long currentTime = startTime;
1701 
1702         if (isDecelerating) {
1703             // formula: V = V0 - D*T, assuming V = 0 when T = duration
1704 
1705             // vx0: initial speed at the x-dimension, set as twice the avg speed
1706             // dx: the constant deceleration at the x-dimension
1707             double vx0 = 2.0 * (to.x - from.x) / duration;
1708             double dx = vx0 / duration;
1709             // vy0: initial speed at the y-dimension, set as twice the avg speed
1710             // dy: the constant deceleration at the y-dimension
1711             double vy0 = 2.0 * (to.y - from.y) / duration;
1712             double dy = vy0 / duration;
1713 
1714             for (long i = 0; i < steps; ++i) {
1715                 sleep(GESTURE_STEP_MS);
1716                 currentTime += GESTURE_STEP_MS;
1717 
1718                 // formula: P = P0 + V0*T - (D*T^2/2)
1719                 final double t = (i + 1) * GESTURE_STEP_MS;
1720                 point.x = from.x + (int) (vx0 * t - 0.5 * dx * t * t);
1721                 point.y = from.y + (int) (vy0 * t - 0.5 * dy * t * t);
1722 
1723                 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
1724             }
1725         } else {
1726             for (long i = 0; i < steps; ++i) {
1727                 sleep(GESTURE_STEP_MS);
1728                 currentTime += GESTURE_STEP_MS;
1729 
1730                 final float progress = (currentTime - startTime) / (float) duration;
1731                 point.x = from.x + (int) (progress * (to.x - from.x));
1732                 point.y = from.y + (int) (progress * (to.y - from.y));
1733 
1734                 sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
1735 
1736             }
1737         }
1738 
1739         return currentTime;
1740     }
1741 
getCurrentInteractionMode(Context context)1742     public static int getCurrentInteractionMode(Context context) {
1743         return getSystemIntegerRes(context, "config_navBarInteractionMode");
1744     }
1745 
1746     @NonNull
clickAndGet( @onNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent)1747     UiObject2 clickAndGet(
1748             @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
1749         final Point targetCenter = target.getVisibleCenter();
1750         final long downTime = SystemClock.uptimeMillis();
1751         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.INSIDE);
1752         expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
1753         final UiObject2 result = waitForLauncherObject(resName);
1754         sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
1755                 GestureScope.INSIDE);
1756         return result;
1757     }
1758 
getSystemIntegerRes(Context context, String resName)1759     private static int getSystemIntegerRes(Context context, String resName) {
1760         Resources res = context.getResources();
1761         int resId = res.getIdentifier(resName, "integer", "android");
1762 
1763         if (resId != 0) {
1764             return res.getInteger(resId);
1765         } else {
1766             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
1767             return -1;
1768         }
1769     }
1770 
getSystemDimensionResId(Context context, String resName)1771     private static int getSystemDimensionResId(Context context, String resName) {
1772         Resources res = context.getResources();
1773         int resId = res.getIdentifier(resName, "dimen", "android");
1774 
1775         if (resId != 0) {
1776             return resId;
1777         } else {
1778             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
1779             return -1;
1780         }
1781     }
1782 
sleep(int duration)1783     static void sleep(int duration) {
1784         SystemClock.sleep(duration);
1785     }
1786 
getEdgeSensitivityWidth()1787     int getEdgeSensitivityWidth() {
1788         try {
1789             final Context context = mInstrumentation.getTargetContext().createPackageContext(
1790                     getLauncherPackageName(), 0);
1791             return context.getResources().getDimensionPixelSize(
1792                     getSystemDimensionResId(context, "config_backGestureInset")) + 1;
1793         } catch (PackageManager.NameNotFoundException e) {
1794             fail("Can't get edge sensitivity: " + e);
1795             return 0;
1796         }
1797     }
1798 
getRealDisplaySize()1799     Point getRealDisplaySize() {
1800         final Rect displayBounds = getContext().getSystemService(WindowManager.class)
1801                 .getMaximumWindowMetrics()
1802                 .getBounds();
1803         return new Point(displayBounds.width(), displayBounds.height());
1804     }
1805 
enableDebugTracing()1806     public void enableDebugTracing() {
1807         getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
1808     }
1809 
disableSensorRotation()1810     private void disableSensorRotation() {
1811         getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION);
1812     }
1813 
disableDebugTracing()1814     public void disableDebugTracing() {
1815         getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
1816     }
1817 
forceGc()1818     public void forceGc() {
1819         // GC the system & sysui first before gc'ing launcher
1820         logShellCommand("cmd statusbar run-gc");
1821         getTestInfo(TestProtocol.REQUEST_FORCE_GC);
1822     }
1823 
getPid()1824     public Integer getPid() {
1825         final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID);
1826         return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null;
1827     }
1828 
produceViewLeak()1829     public void produceViewLeak() {
1830         getTestInfo(TestProtocol.REQUEST_VIEW_LEAK);
1831     }
1832 
printViewLeak()1833     public void printViewLeak() {
1834         getTestInfo(TestProtocol.PRINT_VIEW_LEAK);
1835     }
1836 
getRecentTasks()1837     public ArrayList<ComponentName> getRecentTasks() {
1838         ArrayList<ComponentName> tasks = new ArrayList<>();
1839         ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST)
1840                 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
1841         for (String s : components) {
1842             tasks.add(ComponentName.unflattenFromString(s));
1843         }
1844         return tasks;
1845     }
1846 
clearLauncherData()1847     public void clearLauncherData() {
1848         getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
1849     }
1850 
1851     /**
1852      * Reloads the workspace with a test layout that includes the Test Activity app icon on the
1853      * hotseat.
1854      */
useTestWorkspaceLayoutOnReload()1855     public void useTestWorkspaceLayoutOnReload() {
1856         getTestInfo(TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT);
1857     }
1858 
1859     /**
1860      * Reloads the workspace with a test layout that includes Maps/Play on workspace, and
1861      * Dialer/Messaging/Chrome/Camera on hotseat.
1862      */
useTest2WorkspaceLayoutOnReload()1863     public void useTest2WorkspaceLayoutOnReload() {
1864         getTestInfo(TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT);
1865     }
1866 
1867 
1868     /**
1869      * Reloads the workspace with a test layout that includes the chrome Activity app icon on the
1870      * hotseat.
1871      */
useTaplWorkspaceLayoutOnReload()1872     public void useTaplWorkspaceLayoutOnReload() {
1873         getTestInfo(TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT);
1874     }
1875 
1876     /** Reloads the workspace with the default layout defined by the user's grid size selection. */
useDefaultWorkspaceLayoutOnReload()1877     public void useDefaultWorkspaceLayoutOnReload() {
1878         getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT);
1879     }
1880 
1881     /** Shows the taskbar if it is hidden, otherwise does nothing. */
showTaskbarIfHidden()1882     public void showTaskbarIfHidden() {
1883         getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);
1884     }
1885 
1886     /** Blocks the taskbar from automatically stashing based on time. */
enableBlockTimeout(boolean enable)1887     public void enableBlockTimeout(boolean enable) {
1888         getTestInfo(enable
1889                 ? TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT
1890                 : TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT);
1891     }
1892 
1893     /** Enables transient taskbar for testing purposes only. */
enableTransientTaskbar(boolean enable)1894     public void enableTransientTaskbar(boolean enable) {
1895         getTestInfo(enable
1896                 ? TestProtocol.REQUEST_ENABLE_TRANSIENT_TASKBAR
1897                 : TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR);
1898     }
1899 
1900     /**
1901      * Recreates the taskbar (outside of tests this is done for certain configuration changes).
1902      * The expected behavior is that the taskbar retains its current state after being recreated.
1903      * For example, if taskbar is currently stashed, it should still be stashed after recreating.
1904      */
recreateTaskbar()1905     public void recreateTaskbar() {
1906         getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR);
1907     }
1908 
getHotseatIconNames()1909     public List<String> getHotseatIconNames() {
1910         return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES)
1911                 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
1912     }
1913 
getActivities()1914     private String[] getActivities() {
1915         return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES)
1916                 .getStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD);
1917     }
1918 
getRootedActivitiesList()1919     public String getRootedActivitiesList() {
1920         return String.join(", ", getActivities());
1921     }
1922 
noLeakedActivities()1923     public boolean noLeakedActivities() {
1924         final String[] activities = getActivities();
1925         for (String activity : activities) {
1926             if (activity.contains("(destroyed)")) {
1927                 return false;
1928             }
1929         }
1930         return activities.length <= 2;
1931     }
1932 
getActivitiesCreated()1933     public int getActivitiesCreated() {
1934         return getTestInfo(TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT)
1935                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
1936     }
1937 
eventsCheck()1938     public Closable eventsCheck() {
1939         Assert.assertTrue("Nested event checking", mEventChecker == null);
1940         disableSensorRotation();
1941         final Integer initialPid = getPid();
1942         final LogEventChecker eventChecker = new LogEventChecker(this);
1943         if (eventChecker.start()) mEventChecker = eventChecker;
1944 
1945         return () -> {
1946             if (initialPid != null && initialPid.intValue() != getPid()) {
1947                 if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
1948                 checkForAnomaly();
1949                 Assert.fail(
1950                         formatSystemHealthMessage(
1951                                 formatErrorWithEvents("Launcher crashed", false)));
1952             }
1953 
1954             if (mEventChecker != null) {
1955                 mEventChecker = null;
1956                 if (mCheckEventsForSuccessfulGestures) {
1957                     final String message = eventChecker.verify(WAIT_TIME_MS, true);
1958                     if (message != null) {
1959                         dumpDiagnostics(message);
1960                         checkForAnomaly();
1961                         Assert.fail(formatSystemHealthMessage(
1962                                 "http://go/tapl : successful gesture produced " + message));
1963                     }
1964                 } else {
1965                     eventChecker.finishNoWait();
1966                 }
1967             }
1968         };
1969     }
1970 
1971     boolean isLauncher3() {
1972         if (mIsLauncher3 == null) {
1973             mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName());
1974         }
1975         return mIsLauncher3;
1976     }
1977 
1978     void expectEvent(String sequence, Pattern expected) {
1979         if (mEventChecker != null) {
1980             mEventChecker.expectPattern(sequence, expected);
1981         } else {
1982             Log.d(TAG, "Expecting: " + sequence + " / " + expected);
1983         }
1984     }
1985 
1986     Rect getVisibleBounds(UiObject2 object) {
1987         try {
1988             return object.getVisibleBounds();
1989         } catch (StaleObjectException e) {
1990             fail("Object disappeared from screen");
1991             return null;
1992         } catch (Throwable t) {
1993             fail(t.toString());
1994             return null;
1995         }
1996     }
1997 
1998     float getWindowCornerRadius() {
1999         // TODO(b/197326121): Check if the touch is overlapping with the corners by offsetting
2000         final float tmpBuffer = 100f;
2001         final Resources resources = getResources();
2002         if (!supportsRoundedCornersOnWindows(resources)) {
2003             Log.d(TAG, "No rounded corners");
2004             return tmpBuffer;
2005         }
2006 
2007         // Radius that should be used in case top or bottom aren't defined.
2008         float defaultRadius = ResourceUtils.getDimenByName("rounded_corner_radius", resources, 0);
2009 
2010         float topRadius = ResourceUtils.getDimenByName("rounded_corner_radius_top", resources, 0);
2011         if (topRadius == 0f) {
2012             topRadius = defaultRadius;
2013         }
2014         float bottomRadius = ResourceUtils.getDimenByName(
2015                 "rounded_corner_radius_bottom", resources, 0);
2016         if (bottomRadius == 0f) {
2017             bottomRadius = defaultRadius;
2018         }
2019 
2020         // Always use the smallest radius to make sure the rounded corners will
2021         // completely cover the display.
2022         Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius);
2023         return Math.max(topRadius, bottomRadius) + tmpBuffer;
2024     }
2025 
2026     private static boolean supportsRoundedCornersOnWindows(Resources resources) {
2027         return ResourceUtils.getBoolByName(
2028                 "config_supportsRoundedCornersOnWindows", resources, false);
2029     }
2030 
2031     /**
2032      * Taps outside container to dismiss.
2033      *
2034      * @param container container to be dismissed
2035      * @param tapRight  tap on the right of the container if true, or left otherwise
2036      */
2037     void touchOutsideContainer(UiObject2 container, boolean tapRight) {
2038         try (LauncherInstrumentation.Closable c = addContextLayer(
2039                 "want to tap outside container on the " + (tapRight ? "right" : "left"))) {
2040             Rect containerBounds = getVisibleBounds(container);
2041             final long downTime = SystemClock.uptimeMillis();
2042             final Point tapTarget = new Point(
2043                     tapRight
2044                             ? (containerBounds.right + getRealDisplaySize().x) / 2
2045                             : containerBounds.left / 2,
2046                     containerBounds.top + 1);
2047             sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
2048                     LauncherInstrumentation.GestureScope.INSIDE);
2049             sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
2050                     LauncherInstrumentation.GestureScope.INSIDE);
2051         }
2052     }
2053 
2054     /**
2055      * Sets the consumer to run callbacks at all run-points.
2056      */
2057     public void setRunPointCallback(Consumer<CALLBACK_RUN_POINT> callback) {
2058         mCallbackAtRunPoint = callback;
2059     }
2060 
2061     /**
2062      * Runs the callback at the specified point if it exists.
2063      */
2064     void runCallbackIfActive(CALLBACK_RUN_POINT runPoint) {
2065         if (mCallbackAtRunPoint != null) {
2066             mCallbackAtRunPoint.accept(runPoint);
2067         }
2068     }
2069 
2070     /**
2071      * Waits until a particular condition is true. Based on WaitMixin.
2072      */
2073     boolean waitAndGet(BooleanSupplier condition, long timeout, long interval) {
2074         long startTime = SystemClock.uptimeMillis();
2075 
2076         boolean result = condition.getAsBoolean();
2077         for (long elapsedTime = 0; !result; elapsedTime = SystemClock.uptimeMillis() - startTime) {
2078             if (elapsedTime >= timeout) {
2079                 break;
2080             }
2081             SystemClock.sleep(interval);
2082             result = condition.getAsBoolean();
2083         }
2084         return result;
2085     }
2086 }
2087