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