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 com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
19 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
21 
22 import static org.junit.Assert.assertTrue;
23 
24 import static java.lang.System.exit;
25 
26 import static androidx.test.InstrumentationRegistry.getInstrumentation;
27 
28 import android.content.BroadcastReceiver;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.LauncherActivityInfo;
34 import android.content.pm.PackageManager;
35 import android.os.Process;
36 import android.os.RemoteException;
37 import android.util.Log;
38 
39 import androidx.test.InstrumentationRegistry;
40 import androidx.test.uiautomator.By;
41 import androidx.test.uiautomator.BySelector;
42 import androidx.test.uiautomator.UiDevice;
43 import androidx.test.uiautomator.Until;
44 
45 import com.android.launcher3.Launcher;
46 import com.android.launcher3.LauncherAppState;
47 import com.android.launcher3.LauncherModel;
48 import com.android.launcher3.LauncherSettings;
49 import com.android.launcher3.LauncherState;
50 import com.android.launcher3.LauncherStateManager;
51 import com.android.launcher3.Utilities;
52 import com.android.launcher3.compat.LauncherAppsCompat;
53 import com.android.launcher3.model.AppLaunchTracker;
54 import com.android.launcher3.tapl.LauncherInstrumentation;
55 import com.android.launcher3.tapl.TestHelpers;
56 import com.android.launcher3.testcomponent.TestCommandReceiver;
57 import com.android.launcher3.testing.TestProtocol;
58 import com.android.launcher3.util.LooperExecutor;
59 import com.android.launcher3.util.PackageManagerHelper;
60 import com.android.launcher3.util.Wait;
61 import com.android.launcher3.util.rule.FailureWatcher;
62 import com.android.launcher3.util.rule.LauncherActivityRule;
63 import com.android.launcher3.util.rule.ShellCommandRule;
64 import com.android.launcher3.util.rule.TestStabilityRule;
65 
66 import org.junit.After;
67 import org.junit.Before;
68 import org.junit.Rule;
69 import org.junit.rules.RuleChain;
70 import org.junit.rules.TestRule;
71 
72 import java.io.IOException;
73 import java.lang.annotation.ElementType;
74 import java.lang.annotation.Retention;
75 import java.lang.annotation.RetentionPolicy;
76 import java.lang.annotation.Target;
77 import java.util.concurrent.Callable;
78 import java.util.concurrent.CountDownLatch;
79 import java.util.concurrent.TimeUnit;
80 import java.util.function.Consumer;
81 import java.util.function.Function;
82 
83 /**
84  * Base class for all instrumentation tests providing various utility methods.
85  */
86 public abstract class AbstractLauncherUiTest {
87 
88     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
89     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
90 
91     public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
92     private static final String TAG = "AbstractLauncherUiTest";
93 
94     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
95     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
96     protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
97     protected Context mTargetContext;
98     protected String mTargetPackage;
99 
AbstractLauncherUiTest()100     protected AbstractLauncherUiTest() {
101         try {
102             mDevice.setOrientationNatural();
103         } catch (RemoteException e) {
104             throw new RuntimeException(e);
105         }
106         if (TestHelpers.isInLauncherProcess()) {
107             Utilities.enableRunningInTestHarnessForTests();
108             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
109                     TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
110                     getString("result"));
111             mLauncher.setOnSettledStateAction(
112                     containerType -> executeOnLauncher(
113                             launcher ->
114                                     checkLauncherIntegrity(launcher, containerType)));
115         }
116         mLauncher.enableDebugTracing();
117     }
118 
119     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
120 
121     @Rule
122     public ShellCommandRule mDefaultLauncherRule =
123             TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null;
124 
125     @Rule
126     public ShellCommandRule mDisableHeadsUpNotification =
127             ShellCommandRule.disableHeadsUpNotification();
128 
clearPackageData(String pkg)129     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
130         final CountDownLatch count = new CountDownLatch(2);
131         final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
132             @Override
133             public void onReceive(Context context, Intent intent) {
134                 count.countDown();
135             }
136         };
137         mTargetContext.registerReceiver(broadcastReceiver,
138                 PackageManagerHelper.getPackageFilter(pkg,
139                         Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED));
140 
141         mDevice.executeShellCommand("pm clear " + pkg);
142         assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
143         mTargetContext.unregisterReceiver(broadcastReceiver);
144     }
145 
146     // Annotation for tests that need to be run in portrait and landscape modes.
147     @Retention(RetentionPolicy.RUNTIME)
148     @Target(ElementType.METHOD)
149     protected @interface PortraitLandscape {
150     }
151 
getRulesInsideActivityMonitor()152     protected TestRule getRulesInsideActivityMonitor() {
153         return RuleChain.
154                 outerRule(new PortraitLandscapeRunner(this)).
155                 around(new FailureWatcher(mDevice));
156     }
157 
158     @Rule
159     public TestRule mOrderSensitiveRules = RuleChain.
160             outerRule(new TestStabilityRule()).
161             around(mActivityMonitor).
162             around(getRulesInsideActivityMonitor());
163 
getDevice()164     public UiDevice getDevice() {
165         return mDevice;
166     }
167 
168     @Before
setUp()169     public void setUp() throws Exception {
170         // Disable app tracker
171         AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
172 
173         mTargetContext = InstrumentationRegistry.getTargetContext();
174         mTargetPackage = mTargetContext.getPackageName();
175     }
176 
177     @After
verifyLauncherState()178     public void verifyLauncherState() {
179         try {
180             // Limits UI tests affecting tests running after them.
181             waitForModelLoaded();
182         } catch (Throwable t) {
183             Log.e(TAG,
184                     "Couldn't deinit after a test, exiting tests, see logs for failures that "
185                             + "could have caused this",
186                     t);
187             exit(1);
188         }
189     }
190 
clearLauncherData()191     protected void clearLauncherData() throws IOException, InterruptedException {
192         if (TestHelpers.isInLauncherProcess()) {
193             LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
194                     LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
195             resetLoaderState();
196         } else {
197             clearPackageData(mDevice.getLauncherPackageName());
198             mLauncher.enableDebugTracing();
199         }
200     }
201 
202     /**
203      * Removes all icons from homescreen and hotseat.
204      */
clearHomescreen()205     public void clearHomescreen() throws Throwable {
206         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
207                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
208         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
209                 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
210         resetLoaderState();
211     }
212 
resetLoaderState()213     protected void resetLoaderState() {
214         try {
215             mMainThreadExecutor.execute(
216                     () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload());
217         } catch (Throwable t) {
218             throw new IllegalArgumentException(t);
219         }
220         waitForModelLoaded();
221     }
222 
waitForModelLoaded()223     protected void waitForModelLoaded() {
224         waitForLauncherCondition("Launcher model didn't load", launcher -> {
225             final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel();
226             return model.getCallback() == null || model.isModelLoaded();
227         });
228     }
229 
230     /**
231      * Runs the callback on the UI thread and returns the result.
232      */
getOnUiThread(final Callable<T> callback)233     protected <T> T getOnUiThread(final Callable<T> callback) {
234         try {
235             return mMainThreadExecutor.submit(callback).get();
236         } catch (Exception e) {
237             throw new RuntimeException(e);
238         }
239     }
240 
getFromLauncher(Function<Launcher, T> f)241     protected <T> T getFromLauncher(Function<Launcher, T> f) {
242         if (!TestHelpers.isInLauncherProcess()) return null;
243         return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
244     }
245 
executeOnLauncher(Consumer<Launcher> f)246     protected void executeOnLauncher(Consumer<Launcher> f) {
247         getFromLauncher(launcher -> {
248             f.accept(launcher);
249             return null;
250         });
251     }
252 
253     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
254     // the results of that gesture because the wait can hide flakeness.
waitForState(String message, LauncherState state)255     protected void waitForState(String message, LauncherState state) {
256         waitForLauncherCondition(message,
257                 launcher -> launcher.getStateManager().getCurrentStableState() == state);
258     }
259 
waitForResumed(String message)260     protected void waitForResumed(String message) {
261         waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed());
262     }
263 
264     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
265     // flakiness.
waitForLauncherCondition(String message, Function<Launcher, Boolean> condition)266     protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) {
267         waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
268     }
269 
270     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
271     // flakiness.
getOnceNotNull(String message, Function<Launcher, T> f)272     protected <T> T getOnceNotNull(String message, Function<Launcher, T> f) {
273         return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT);
274     }
275 
276     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
277     // flakiness.
waitForLauncherCondition( String message, Function<Launcher, Boolean> condition, long timeout)278     protected void waitForLauncherCondition(
279             String message, Function<Launcher, Boolean> condition, long timeout) {
280         if (!TestHelpers.isInLauncherProcess()) return;
281         Wait.atMost(message, () -> getFromLauncher(condition), timeout);
282     }
283 
284     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
285     // flakiness.
getOnceNotNull(String message, Function<Launcher, T> f, long timeout)286     protected <T> T getOnceNotNull(String message, Function<Launcher, T> f, long timeout) {
287         if (!TestHelpers.isInLauncherProcess()) return null;
288 
289         final Object[] output = new Object[1];
290         Wait.atMost(message, () -> {
291             final Object fromLauncher = getFromLauncher(f);
292             output[0] = fromLauncher;
293             return fromLauncher != null;
294         }, timeout);
295         return (T) output[0];
296     }
297 
298     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
299     // flakiness.
waitForLauncherCondition( String message, Runnable testThreadAction, Function<Launcher, Boolean> condition, long timeout)300     protected void waitForLauncherCondition(
301             String message,
302             Runnable testThreadAction, Function<Launcher, Boolean> condition,
303             long timeout) {
304         if (!TestHelpers.isInLauncherProcess()) return;
305         Wait.atMost(message, () -> {
306             testThreadAction.run();
307             return getFromLauncher(condition);
308         }, timeout);
309     }
310 
getSettingsApp()311     protected LauncherActivityInfo getSettingsApp() {
312         return LauncherAppsCompat.getInstance(mTargetContext)
313                 .getActivityList("com.android.settings",
314                         Process.myUserHandle()).get(0);
315     }
316 
317     /**
318      * Broadcast receiver which blocks until the result is received.
319      */
320     public class BlockingBroadcastReceiver extends BroadcastReceiver {
321 
322         private final CountDownLatch latch = new CountDownLatch(1);
323         private Intent mIntent;
324 
BlockingBroadcastReceiver(String action)325         public BlockingBroadcastReceiver(String action) {
326             mTargetContext.registerReceiver(this, new IntentFilter(action));
327         }
328 
329         @Override
onReceive(Context context, Intent intent)330         public void onReceive(Context context, Intent intent) {
331             mIntent = intent;
332             latch.countDown();
333         }
334 
blockingGetIntent()335         public Intent blockingGetIntent() throws InterruptedException {
336             latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
337             mTargetContext.unregisterReceiver(this);
338             return mIntent;
339         }
340 
blockingGetExtraIntent()341         public Intent blockingGetExtraIntent() throws InterruptedException {
342             Intent intent = blockingGetIntent();
343             return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
344         }
345     }
346 
startAppFast(String packageName)347     protected void startAppFast(String packageName) {
348         startIntent(
349                 getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
350                         packageName),
351                 By.pkg(packageName).depth(0),
352                 true /* newTask */);
353     }
354 
startTestActivity(int activityNumber)355     protected void startTestActivity(int activityNumber) {
356         final String packageName = getAppPackageName();
357         final Intent intent = getInstrumentation().getContext().getPackageManager().
358                 getLaunchIntentForPackage(packageName);
359         intent.setComponent(new ComponentName(packageName,
360                 "com.android.launcher3.tests.Activity" + activityNumber));
361         startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber),
362                 false /* newTask */);
363     }
364 
startIntent(Intent intent, BySelector selector, boolean newTask)365     private void startIntent(Intent intent, BySelector selector, boolean newTask) {
366         intent.addCategory(Intent.CATEGORY_LAUNCHER);
367         if (newTask) {
368             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
369         } else {
370             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
371         }
372         getInstrumentation().getTargetContext().startActivity(intent);
373         assertTrue("App didn't start: " + selector,
374                 mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
375     }
376 
resolveSystemApp(String category)377     public static String resolveSystemApp(String category) {
378         return getInstrumentation().getContext().getPackageManager().resolveActivity(
379                 new Intent(Intent.ACTION_MAIN).addCategory(category),
380                 PackageManager.MATCH_SYSTEM_ONLY).
381                 activityInfo.packageName;
382     }
383 
closeLauncherActivity()384     protected void closeLauncherActivity() {
385         // Destroy Launcher activity.
386         executeOnLauncher(launcher -> {
387             if (launcher != null) {
388                 launcher.finish();
389             }
390         });
391         waitForLauncherCondition(
392                 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
393     }
394 
isInBackground(Launcher launcher)395     protected boolean isInBackground(Launcher launcher) {
396         return !launcher.hasBeenResumed();
397     }
398 
isInState(LauncherState state)399     protected boolean isInState(LauncherState state) {
400         if (!TestHelpers.isInLauncherProcess()) return true;
401         return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
402     }
403 
getAllAppsScroll(Launcher launcher)404     protected int getAllAppsScroll(Launcher launcher) {
405         return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
406     }
407 
checkLauncherIntegrity( Launcher launcher, ContainerType expectedContainerType)408     private static void checkLauncherIntegrity(
409             Launcher launcher, ContainerType expectedContainerType) {
410         if (launcher != null) {
411             final LauncherStateManager stateManager = launcher.getStateManager();
412             final LauncherState stableState = stateManager.getCurrentStableState();
413 
414             assertTrue("Stable state != state: " + stableState.getClass().getSimpleName() + ", "
415                             + stateManager.getState().getClass().getSimpleName(),
416                     stableState == stateManager.getState());
417 
418             final boolean isResumed = launcher.hasBeenResumed();
419             assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
420                     isResumed == launcher.isStarted());
421             assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
422                     isResumed == launcher.isUserActive());
423 
424             final int ordinal = stableState.ordinal;
425 
426             switch (expectedContainerType) {
427                 case WORKSPACE:
428                 case WIDGETS: {
429                     assertTrue(
430                             "Launcher is not resumed in state: " + expectedContainerType,
431                             isResumed);
432                     assertTrue(TestProtocol.stateOrdinalToString(ordinal),
433                             ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
434                     break;
435                 }
436                 case ALL_APPS: {
437                     assertTrue(
438                             "Launcher is not resumed in state: " + expectedContainerType,
439                             isResumed);
440                     assertTrue(TestProtocol.stateOrdinalToString(ordinal),
441                             ordinal == TestProtocol.ALL_APPS_STATE_ORDINAL);
442                     break;
443                 }
444                 case OVERVIEW: {
445                     assertTrue(
446                             "Launcher is not resumed in state: " + expectedContainerType,
447                             isResumed);
448                     assertTrue(TestProtocol.stateOrdinalToString(ordinal),
449                             ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
450                     break;
451                 }
452                 case BACKGROUND: {
453                     assertTrue("Launcher is resumed in state: " + expectedContainerType,
454                             !isResumed);
455                     assertTrue(TestProtocol.stateOrdinalToString(ordinal),
456                             ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
457                     break;
458                 }
459                 default:
460                     throw new IllegalArgumentException(
461                             "Illegal container: " + expectedContainerType);
462             }
463         } else {
464             assertTrue(
465                     "Container type is not BACKGROUND or FALLBACK_OVERVIEW: "
466                             + expectedContainerType,
467                     expectedContainerType == ContainerType.BACKGROUND ||
468                             expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
469         }
470     }
471 }
472