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