1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.android.launcher3.ui;
17 
18 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
19 
20 import static androidx.test.InstrumentationRegistry.getInstrumentation;
21 
22 import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
23 import static com.android.launcher3.testing.shared.TestProtocol.WIDGET_CONFIG_NULL_EXTRA_INTENT;
24 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.content.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.pm.ActivityInfo;
35 import android.content.pm.LauncherActivityInfo;
36 import android.content.pm.LauncherApps;
37 import android.content.pm.PackageInfo;
38 import android.content.pm.PackageManager;
39 import android.graphics.Point;
40 import android.os.Debug;
41 import android.os.Process;
42 import android.os.RemoteException;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.platform.test.flag.junit.SetFlagsRule;
46 import android.system.OsConstants;
47 import android.util.Log;
48 
49 import androidx.annotation.NonNull;
50 import androidx.test.InstrumentationRegistry;
51 import androidx.test.uiautomator.By;
52 import androidx.test.uiautomator.BySelector;
53 import androidx.test.uiautomator.UiDevice;
54 import androidx.test.uiautomator.Until;
55 
56 import com.android.launcher3.Launcher;
57 import com.android.launcher3.LauncherState;
58 import com.android.launcher3.Utilities;
59 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
60 import com.android.launcher3.tapl.HomeAllApps;
61 import com.android.launcher3.tapl.HomeAppIcon;
62 import com.android.launcher3.tapl.LauncherInstrumentation;
63 import com.android.launcher3.tapl.TestHelpers;
64 import com.android.launcher3.testcomponent.TestCommandReceiver;
65 import com.android.launcher3.util.LooperExecutor;
66 import com.android.launcher3.util.SimpleBroadcastReceiver;
67 import com.android.launcher3.util.TestUtil;
68 import com.android.launcher3.util.Wait;
69 import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule;
70 import com.android.launcher3.util.rule.FailureWatcher;
71 import com.android.launcher3.util.rule.SamplerRule;
72 import com.android.launcher3.util.rule.ScreenRecordRule;
73 import com.android.launcher3.util.rule.ShellCommandRule;
74 import com.android.launcher3.util.rule.TestIsolationRule;
75 import com.android.launcher3.util.rule.TestStabilityRule;
76 import com.android.launcher3.util.rule.ViewCaptureRule;
77 
78 import org.junit.After;
79 import org.junit.Assert;
80 import org.junit.Before;
81 import org.junit.Rule;
82 import org.junit.rules.RuleChain;
83 import org.junit.rules.TestRule;
84 
85 import java.io.IOException;
86 import java.util.Objects;
87 import java.util.concurrent.Callable;
88 import java.util.concurrent.CountDownLatch;
89 import java.util.concurrent.TimeUnit;
90 import java.util.concurrent.TimeoutException;
91 import java.util.function.Consumer;
92 import java.util.function.Function;
93 import java.util.function.Supplier;
94 
95 /**
96  * Base class for all instrumentation tests providing various utility methods.
97  */
98 public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher> {
99 
100     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
101     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 10;
102 
103     public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT;
104     private static final String TAG = "AbstractLauncherUiTest";
105 
106     private static boolean sDumpWasGenerated = false;
107     private static boolean sActivityLeakReported = false;
108     private static boolean sSeenKeyguard = false;
109     private static boolean sFirstTimeWaitingForWizard = true;
110 
111     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
112 
113     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
114     protected final UiDevice mDevice = getUiDevice();
115     protected final LauncherInstrumentation mLauncher = createLauncherInstrumentation();
116 
117     @NonNull
createLauncherInstrumentation()118     public static LauncherInstrumentation createLauncherInstrumentation() {
119         waitForSetupWizardDismissal(); // precondition for creating LauncherInstrumentation
120         return new LauncherInstrumentation(true);
121     }
122 
123     protected Context mTargetContext;
124     protected String mTargetPackage;
125     private int mLauncherPid;
126 
127     /** Detects activity leaks and throws an exception if a leak is found. */
checkDetectedLeaks(LauncherInstrumentation launcher)128     public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
129         checkDetectedLeaks(launcher, false);
130     }
131 
132     /** Detects activity leaks and throws an exception if a leak is found. */
checkDetectedLeaks(LauncherInstrumentation launcher, boolean requireOneActiveActivityUnused)133     public static void checkDetectedLeaks(LauncherInstrumentation launcher,
134             boolean requireOneActiveActivityUnused) {
135         if (TestStabilityRule.isPresubmit()) return; // b/313501215
136 
137         final boolean requireOneActiveActivity =
138                 false; // workaround for leaks when there is an unexpected Recents activity
139 
140         if (sActivityLeakReported) return;
141 
142         // Check whether activity leak detector has found leaked activities.
143         Wait.atMost(() -> getActivityLeakErrorMessage(launcher, requireOneActiveActivity),
144                 () -> {
145                     launcher.forceGc();
146                     return MAIN_EXECUTOR.submit(
147                             () -> launcher.noLeakedActivities(requireOneActiveActivity)).get();
148                 }, DEFAULT_UI_TIMEOUT, launcher);
149     }
150 
getAppPackageName()151     public static String getAppPackageName() {
152         return getInstrumentation().getContext().getPackageName();
153     }
154 
getActivityLeakErrorMessage(LauncherInstrumentation launcher, boolean requireOneActiveActivity)155     private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher,
156             boolean requireOneActiveActivity) {
157         sActivityLeakReported = true;
158         return "Activity leak detector has found leaked activities, requirining 1 activity: "
159                 + requireOneActiveActivity + "; "
160                 + dumpHprofData(launcher, false, requireOneActiveActivity) + ".";
161     }
162 
dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak, boolean requireOneActiveActivity)163     private static String dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak,
164             boolean requireOneActiveActivity) {
165         if (intentionalLeak) return "intentional leak; not generating dump";
166 
167         String result;
168         if (sDumpWasGenerated) {
169             result = "dump has already been generated by another test";
170         } else {
171             try {
172                 final String fileName =
173                         getInstrumentation().getTargetContext().getFilesDir().getPath()
174                                 + "/ActivityLeakHeapDump.hprof";
175                 if (TestHelpers.isInLauncherProcess()) {
176                     Debug.dumpHprofData(fileName);
177                 } else {
178                     final UiDevice device = getUiDevice();
179                     device.executeShellCommand(
180                             "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
181                 }
182                 Log.d(TAG, "Saved leak dump, the leak is still present: "
183                         + !launcher.noLeakedActivities(requireOneActiveActivity));
184                 sDumpWasGenerated = true;
185                 result = "saved memory dump as an artifact";
186             } catch (Throwable e) {
187                 Log.e(TAG, "dumpHprofData failed", e);
188                 result = "failed to save memory dump";
189             }
190         }
191         return result + ". Full list of activities: " + launcher.getRootedActivitiesList();
192     }
193 
AbstractLauncherUiTest()194     protected AbstractLauncherUiTest() {
195         mLauncher.enableCheckEventsForSuccessfulGestures();
196         mLauncher.setAnomalyChecker(AbstractLauncherUiTest::verifyKeyguardInvisible);
197         try {
198             mDevice.setOrientationNatural();
199         } catch (RemoteException e) {
200             throw new RuntimeException(e);
201         }
202         if (TestHelpers.isInLauncherProcess()) {
203             Utilities.enableRunningInTestHarnessForTests();
204             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
205                             TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString())
206                     .getString("result"));
207         }
208         mLauncher.enableDebugTracing();
209         // Avoid double-reporting of Launcher crashes.
210         mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
211     }
212 
213     @Rule
214     public ShellCommandRule mDisableHeadsUpNotification =
215             ShellCommandRule.disableHeadsUpNotification();
216 
217     @Rule
218     public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
219 
220     @Rule
221     public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
222 
223     @Rule
224     public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule();
225 
initialize(AbstractLauncherUiTest test)226     public static void initialize(AbstractLauncherUiTest test) throws Exception {
227         test.reinitializeLauncherData();
228         test.mDevice.pressHome();
229         test.waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
230         test.waitForState("Launcher internal state didn't switch to Home",
231                 () -> LauncherState.NORMAL);
232         test.waitForResumed("Launcher internal state is still Background");
233         // Check that we switched to home.
234         test.mLauncher.getWorkspace();
235         AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
236     }
237 
clearPackageData(String pkg)238     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
239         final CountDownLatch count = new CountDownLatch(2);
240         final SimpleBroadcastReceiver broadcastReceiver =
241                 new SimpleBroadcastReceiver(i -> count.countDown());
242         broadcastReceiver.registerPkgActions(mTargetContext, pkg,
243                 Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
244 
245         mDevice.executeShellCommand("pm clear " + pkg);
246         assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
247         mTargetContext.unregisterReceiver(broadcastReceiver);
248     }
249 
getRulesInsideActivityMonitor()250     protected TestRule getRulesInsideActivityMonitor() {
251         final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
252                 Launcher.ACTIVITY_TRACKER::getCreatedActivity);
253         final RuleChain inner = RuleChain
254                 .outerRule(new PortraitLandscapeRunner<LAUNCHER_TYPE>(this))
255                 .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
256                 // .around(viewCaptureRule) // b/315482167
257                 .around(new TestIsolationRule(mLauncher, true));
258 
259         return TestHelpers.isInLauncherProcess()
260                 ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
261                 : inner;
262     }
263 
264     @Rule
265     public TestRule mOrderSensitiveRules = RuleChain
266             .outerRule(new SamplerRule())
267             .around(new TestStabilityRule())
268             .around(getRulesInsideActivityMonitor());
269 
getDevice()270     public UiDevice getDevice() {
271         return mDevice;
272     }
273 
274     @Before
setUp()275     public void setUp() throws Exception {
276         mLauncher.onTestStart();
277 
278         final String launcherPackageName = mDevice.getLauncherPackageName();
279         try {
280             final Context context = InstrumentationRegistry.getContext();
281             final PackageManager pm = context.getPackageManager();
282             final PackageInfo launcherPackage = pm.getPackageInfo(launcherPackageName, 0);
283 
284             if (!launcherPackage.versionName.equals("BuildFromAndroidStudio")) {
285                 Assert.assertEquals("Launcher version doesn't match tests version",
286                         pm.getPackageInfo(context.getPackageName(), 0).getLongVersionCode(),
287                         launcherPackage.getLongVersionCode());
288             }
289         } catch (PackageManager.NameNotFoundException e) {
290             throw new RuntimeException(e);
291         }
292 
293         mLauncherPid = 0;
294 
295         mTargetContext = InstrumentationRegistry.getTargetContext();
296         mTargetPackage = mTargetContext.getPackageName();
297         mLauncherPid = mLauncher.getPid();
298 
299         UserManager userManager = mTargetContext.getSystemService(UserManager.class);
300         if (userManager != null) {
301             for (UserHandle userHandle : userManager.getUserProfiles()) {
302                 if (!userHandle.isSystem()) {
303                     mDevice.executeShellCommand(
304                             "pm remove-user --wait " + userHandle.getIdentifier());
305                 }
306             }
307         }
308 
309         onTestStart();
310 
311         initialize(this);
312     }
313 
314     /** Method that should be called when a test starts. */
onTestStart()315     public static void onTestStart() {
316         waitForSetupWizardDismissal();
317 
318         if (TestStabilityRule.isPresubmit()) {
319             aggressivelyUnlockSysUi();
320         } else {
321             verifyKeyguardInvisible();
322         }
323     }
324 
hasSystemUiObject(String resId)325     private static boolean hasSystemUiObject(String resId) {
326         return getUiDevice().hasObject(
327                 By.res(SYSTEMUI_PACKAGE, resId));
328     }
329 
330     @NonNull
getUiDevice()331     private static UiDevice getUiDevice() {
332         return UiDevice.getInstance(getInstrumentation());
333     }
334 
aggressivelyUnlockSysUi()335     private static void aggressivelyUnlockSysUi() {
336         final UiDevice device = getUiDevice();
337         for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) {
338             Log.d(TAG, "Before attempting to unlock the phone");
339             try {
340                 device.executeShellCommand("input keyevent 82");
341             } catch (IOException e) {
342                 throw new RuntimeException(e);
343             }
344             device.waitForIdle();
345         }
346         Assert.assertTrue("Keyguard still visible",
347                 TestHelpers.wait(
348                         Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
349         Log.d(TAG, "Keyguard is not visible");
350     }
351 
352     /** Waits for setup wizard to go away. */
waitForSetupWizardDismissal()353     private static void waitForSetupWizardDismissal() {
354         if (!TestStabilityRule.isPresubmit()) return;
355 
356         if (sFirstTimeWaitingForWizard) {
357             try {
358                 getUiDevice().executeShellCommand(
359                         "am force-stop com.google.android.setupwizard");
360             } catch (IOException e) {
361                 throw new RuntimeException(e);
362             }
363         }
364 
365         final boolean wizardDismissed = TestHelpers.wait(
366                 Until.gone(By.pkg("com.google.android.setupwizard").depth(0)),
367                 sFirstTimeWaitingForWizard ? 120000 : 0);
368         sFirstTimeWaitingForWizard = false;
369         Assert.assertTrue("Setup wizard is still visible", wizardDismissed);
370     }
371 
372     /** Asserts that keyguard is not visible */
verifyKeyguardInvisible()373     public static void verifyKeyguardInvisible() {
374         final boolean keyguardAlreadyVisible = sSeenKeyguard;
375 
376         sSeenKeyguard = sSeenKeyguard
377                 || !TestHelpers.wait(
378                 Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000);
379 
380         Assert.assertFalse(
381                 "Keyguard is visible, which is likely caused by a crash in SysUI, seeing keyguard"
382                         + " for the first time = "
383                         + !keyguardAlreadyVisible,
384                 sSeenKeyguard);
385     }
386 
387     @After
verifyLauncherState()388     public void verifyLauncherState() {
389         try {
390             // Limits UI tests affecting tests running after them.
391             mLauncher.waitForLauncherInitialized();
392             if (mLauncherPid != 0) {
393                 assertEquals("Launcher crashed, pid mismatch:",
394                         mLauncherPid, mLauncher.getPid().intValue());
395             }
396         } finally {
397             mLauncher.onTestFinish();
398         }
399     }
400 
reinitializeLauncherData()401     protected void reinitializeLauncherData() {
402         reinitializeLauncherData(false);
403     }
404 
reinitializeLauncherData(boolean clearWorkspace)405     protected void reinitializeLauncherData(boolean clearWorkspace) {
406         if (clearWorkspace) {
407             mLauncher.clearLauncherData();
408         } else {
409             mLauncher.reinitializeLauncherData();
410         }
411         mLauncher.waitForLauncherInitialized();
412     }
413 
414     /**
415      * Runs the callback on the UI thread and returns the result.
416      */
getOnUiThread(final Callable<T> callback)417     protected <T> T getOnUiThread(final Callable<T> callback) {
418         try {
419             return mMainThreadExecutor.submit(callback).get(DEFAULT_UI_TIMEOUT,
420                     TimeUnit.MILLISECONDS);
421         } catch (TimeoutException e) {
422             Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e);
423             Process.sendSignal(Process.myPid(), OsConstants.SIGABRT);
424             throw new RuntimeException(e);
425         } catch (Throwable e) {
426             throw new RuntimeException(e);
427         }
428     }
429 
getFromLauncher(Function<LAUNCHER_TYPE, T> f)430     protected <T> T getFromLauncher(Function<LAUNCHER_TYPE, T> f) {
431         if (!TestHelpers.isInLauncherProcess()) return null;
432         return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedActivity()));
433     }
434 
executeOnLauncher(Consumer<LAUNCHER_TYPE> f)435     protected void executeOnLauncher(Consumer<LAUNCHER_TYPE> f) {
436         getFromLauncher(launcher -> {
437             f.accept(launcher);
438             return null;
439         });
440     }
441 
442     // Execute an action on Launcher, but forgive it when launcher is null.
443     // Launcher can be null if teardown is happening after a failed setup step where launcher
444     // activity failed to be created.
executeOnLauncherInTearDown(Consumer<LAUNCHER_TYPE> f)445     protected void executeOnLauncherInTearDown(Consumer<LAUNCHER_TYPE> f) {
446         executeOnLauncher(launcher -> {
447             if (launcher != null) f.accept(launcher);
448         });
449     }
450 
451     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
452     // expecting the results of that gesture because the wait can hide flakeness.
waitForState(String message, Supplier<LauncherState> state)453     protected void waitForState(String message, Supplier<LauncherState> state) {
454         waitForLauncherCondition(message,
455                 launcher -> launcher.getStateManager().getCurrentStableState() == state.get());
456     }
457 
458     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
459     // expecting the results of that gesture because the wait can hide flakeness.
waitForStateTransitionToEnd(String message, Supplier<LauncherState> state)460     protected void waitForStateTransitionToEnd(String message, Supplier<LauncherState> state) {
461         waitForLauncherCondition(message,
462                 launcher -> launcher.getStateManager().isInStableState(state.get())
463                         && !launcher.getStateManager().isInTransition());
464     }
465 
waitForResumed(String message)466     protected void waitForResumed(String message) {
467         waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed());
468     }
469 
470     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
471     // flakiness.
waitForLauncherCondition(String message, Function<LAUNCHER_TYPE, Boolean> condition)472     protected void waitForLauncherCondition(String
473             message, Function<LAUNCHER_TYPE, Boolean> condition) {
474         waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
475     }
476 
477     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
478     // flakiness.
getOnceNotNull(String message, Function<LAUNCHER_TYPE, O> f)479     protected <O> O getOnceNotNull(String message, Function<LAUNCHER_TYPE, O> f) {
480         return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT);
481     }
482 
483     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
484     // flakiness.
waitForLauncherCondition( String message, Function<LAUNCHER_TYPE, Boolean> condition, long timeout)485     protected void waitForLauncherCondition(
486             String message, Function<LAUNCHER_TYPE, Boolean> condition, long timeout) {
487         verifyKeyguardInvisible();
488         if (!TestHelpers.isInLauncherProcess()) return;
489         Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher);
490     }
491 
492     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
493     // flakiness.
getOnceNotNull(String message, Function<LAUNCHER_TYPE, T> f, long timeout)494     protected <T> T getOnceNotNull(String message, Function<LAUNCHER_TYPE, T> f, long timeout) {
495         if (!TestHelpers.isInLauncherProcess()) return null;
496 
497         final Object[] output = new Object[1];
498         Wait.atMost(message, () -> {
499             final Object fromLauncher = getFromLauncher(f);
500             output[0] = fromLauncher;
501             return fromLauncher != null;
502         }, timeout, mLauncher);
503         return (T) output[0];
504     }
505 
506     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
507     // flakiness.
waitForLauncherCondition( String message, Runnable testThreadAction, Function<LAUNCHER_TYPE, Boolean> condition, long timeout)508     protected void waitForLauncherCondition(
509             String message,
510             Runnable testThreadAction, Function<LAUNCHER_TYPE, Boolean> condition,
511             long timeout) {
512         if (!TestHelpers.isInLauncherProcess()) return;
513         Wait.atMost(message, () -> {
514             testThreadAction.run();
515             return getFromLauncher(condition);
516         }, timeout, mLauncher);
517     }
518 
getSettingsApp()519     protected LauncherActivityInfo getSettingsApp() {
520         return mTargetContext.getSystemService(LauncherApps.class)
521                 .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
522     }
523 
524     /**
525      * Broadcast receiver which blocks until the result is received.
526      */
527     public class BlockingBroadcastReceiver extends BroadcastReceiver {
528 
529         private final CountDownLatch latch = new CountDownLatch(1);
530         private Intent mIntent;
531 
BlockingBroadcastReceiver(String action)532         public BlockingBroadcastReceiver(String action) {
533             mTargetContext.registerReceiver(this, new IntentFilter(action),
534                     Context.RECEIVER_EXPORTED/*UNAUDITED*/);
535         }
536 
537         @Override
onReceive(Context context, Intent intent)538         public void onReceive(Context context, Intent intent) {
539             Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, intent == null
540                     ? "AbstractLauncherUiTest.onReceive(): inputted intent NULL"
541                     : "AbstractLauncherUiTest.onReceive(): inputted intent NOT NULL");
542             mIntent = intent;
543             latch.countDown();
544             Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
545                     "AbstractLauncherUiTest.onReceive() Countdown Latch started");
546         }
547 
blockingGetIntent()548         public Intent blockingGetIntent() throws InterruptedException {
549             Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
550                     "AbstractLauncherUiTest.blockingGetIntent()");
551             assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS));
552             mTargetContext.unregisterReceiver(this);
553             Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, mIntent == null
554                     ? "AbstractLauncherUiTest.onReceive(): mIntent NULL"
555                     : "AbstractLauncherUiTest.onReceive(): mIntent NOT NULL");
556             return mIntent;
557         }
558 
blockingGetExtraIntent()559         public Intent blockingGetExtraIntent() throws InterruptedException {
560             Intent intent = blockingGetIntent();
561             return intent == null ? null : (Intent) intent.getParcelableExtra(
562                     Intent.EXTRA_INTENT);
563         }
564     }
565 
startAppFast(String packageName)566     public static void startAppFast(String packageName) {
567         startIntent(
568                 getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
569                         packageName),
570                 By.pkg(packageName).depth(0),
571                 true /* newTask */);
572     }
573 
startTestActivity(String activityName, String activityLabel)574     public static void startTestActivity(String activityName, String activityLabel) {
575         final String packageName = getAppPackageName();
576         final Intent intent = getInstrumentation().getContext().getPackageManager().
577                 getLaunchIntentForPackage(packageName);
578         intent.setComponent(new ComponentName(packageName,
579                 "com.android.launcher3.tests." + activityName));
580         startIntent(intent, By.pkg(packageName).text(activityLabel),
581                 false /* newTask */);
582     }
583 
startTestActivity(int activityNumber)584     public static void startTestActivity(int activityNumber) {
585         startTestActivity("Activity" + activityNumber, "TestActivity" + activityNumber);
586     }
587 
startImeTestActivity()588     public static void startImeTestActivity() {
589         final String packageName = getAppPackageName();
590         final Intent intent = getInstrumentation().getContext().getPackageManager().
591                 getLaunchIntentForPackage(packageName);
592         intent.setComponent(new ComponentName(packageName,
593                 "com.android.launcher3.testcomponent.ImeTestActivity"));
594         startIntent(intent, By.pkg(packageName).text("ImeTestActivity"),
595                 false /* newTask */);
596     }
597 
598     /** Starts ExcludeFromRecentsTestActivity, which has excludeFromRecents="true". */
startExcludeFromRecentsTestActivity()599     public static void startExcludeFromRecentsTestActivity() {
600         final String packageName = getAppPackageName();
601         final Intent intent = getInstrumentation().getContext().getPackageManager()
602                 .getLaunchIntentForPackage(packageName);
603         intent.setComponent(new ComponentName(packageName,
604                 "com.android.launcher3.testcomponent.ExcludeFromRecentsTestActivity"));
605         startIntent(intent, By.pkg(packageName).text("ExcludeFromRecentsTestActivity"),
606                 false /* newTask */);
607     }
608 
startIntent(Intent intent, BySelector selector, boolean newTask)609     private static void startIntent(Intent intent, BySelector selector, boolean newTask) {
610         intent.addCategory(Intent.CATEGORY_LAUNCHER);
611         if (newTask) {
612             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
613         } else {
614             intent.addFlags(
615                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
616         }
617         getInstrumentation().getTargetContext().startActivity(intent);
618         assertTrue("App didn't start: " + selector,
619                 TestHelpers.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
620 
621         // Wait for the Launcher to stop.
622         final LauncherInstrumentation launcherInstrumentation = new LauncherInstrumentation();
623         Wait.atMost("Launcher activity didn't stop",
624                 () -> !launcherInstrumentation.isLauncherActivityStarted(),
625                 DEFAULT_ACTIVITY_TIMEOUT, launcherInstrumentation);
626     }
627 
resolveSystemAppInfo(String category)628     public static ActivityInfo resolveSystemAppInfo(String category) {
629         return getInstrumentation().getContext().getPackageManager().resolveActivity(
630                 new Intent(Intent.ACTION_MAIN).addCategory(category),
631                 PackageManager.MATCH_SYSTEM_ONLY).
632                 activityInfo;
633     }
634 
635 
resolveSystemApp(String category)636     public static String resolveSystemApp(String category) {
637         return resolveSystemAppInfo(category).packageName;
638     }
639 
closeLauncherActivity()640     protected void closeLauncherActivity() {
641         // Destroy Launcher activity.
642         executeOnLauncher(launcher -> {
643             if (launcher != null) {
644                 onLauncherActivityClose(launcher);
645                 launcher.finish();
646             }
647         });
648         waitForLauncherCondition(
649                 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
650     }
651 
isInLaunchedApp(LAUNCHER_TYPE launcher)652     protected boolean isInLaunchedApp(LAUNCHER_TYPE launcher) {
653         return launcher == null || !launcher.hasBeenResumed();
654     }
655 
isInState(Supplier<LauncherState> state)656     protected boolean isInState(Supplier<LauncherState> state) {
657         if (!TestHelpers.isInLauncherProcess()) return true;
658         return getFromLauncher(
659                 launcher -> launcher.getStateManager().getState() == state.get());
660     }
661 
getAllAppsScroll(LAUNCHER_TYPE launcher)662     protected int getAllAppsScroll(LAUNCHER_TYPE launcher) {
663         return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset();
664     }
665 
onLauncherActivityClose(LAUNCHER_TYPE launcher)666     protected void onLauncherActivityClose(LAUNCHER_TYPE launcher) {
667     }
668 
createShortcutInCenterIfNotExist(String name)669     protected HomeAppIcon createShortcutInCenterIfNotExist(String name) {
670         Point dimension = mLauncher.getWorkspace().getIconGridDimensions();
671         return createShortcutIfNotExist(name, dimension.x / 2, dimension.y / 2);
672     }
673 
createShortcutIfNotExist(String name, Point cellPosition)674     protected HomeAppIcon createShortcutIfNotExist(String name, Point cellPosition) {
675         return createShortcutIfNotExist(name, cellPosition.x, cellPosition.y);
676     }
677 
createShortcutIfNotExist(String name, int cellX, int cellY)678     protected HomeAppIcon createShortcutIfNotExist(String name, int cellX, int cellY) {
679         HomeAppIcon homeAppIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
680         Log.d(ICON_MISSING, "homeAppIcon: " + homeAppIcon + " name: " + name +
681                 " cell: " + cellX + ", " + cellY);
682         if (homeAppIcon == null) {
683             HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
684             allApps.freeze();
685             try {
686                 allApps.getAppIcon(name).dragToWorkspace(cellX, cellY);
687             } finally {
688                 allApps.unfreeze();
689             }
690             homeAppIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name);
691         }
692         return homeAppIcon;
693     }
694 
commitTransactionAndLoadHome(FavoriteItemsTransaction transaction)695     protected void commitTransactionAndLoadHome(FavoriteItemsTransaction transaction) {
696         transaction.commit();
697 
698         // Launch the home activity
699         UiDevice.getInstance(getInstrumentation()).pressHome();
700         mLauncher.waitForLauncherInitialized();
701     }
702 
703     /** Clears all recent tasks */
clearAllRecentTasks()704     protected void clearAllRecentTasks() {
705         if (!mLauncher.getRecentTasks().isEmpty()) {
706             mLauncher.goHome().switchToOverview().dismissAllTasks();
707         }
708     }
709 }
710