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