• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.test.InstrumentationRegistry.getInstrumentation;
19 
20 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
21 
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import static java.lang.System.exit;
26 
27 import android.app.Instrumentation;
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.Direction;
43 import androidx.test.uiautomator.UiDevice;
44 import androidx.test.uiautomator.UiObject2;
45 import androidx.test.uiautomator.Until;
46 
47 import com.android.launcher3.Launcher;
48 import com.android.launcher3.LauncherAppState;
49 import com.android.launcher3.LauncherModel;
50 import com.android.launcher3.LauncherSettings;
51 import com.android.launcher3.LauncherState;
52 import com.android.launcher3.MainThreadExecutor;
53 import com.android.launcher3.ResourceUtils;
54 import com.android.launcher3.Utilities;
55 import com.android.launcher3.compat.LauncherAppsCompat;
56 import com.android.launcher3.model.AppLaunchTracker;
57 import com.android.launcher3.tapl.LauncherInstrumentation;
58 import com.android.launcher3.tapl.TestHelpers;
59 import com.android.launcher3.util.Wait;
60 import com.android.launcher3.util.rule.FailureWatcher;
61 import com.android.launcher3.util.rule.LauncherActivityRule;
62 import com.android.launcher3.util.rule.ShellCommandRule;
63 
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Rule;
67 import org.junit.rules.RuleChain;
68 import org.junit.rules.TestRule;
69 
70 import java.io.IOException;
71 import java.lang.annotation.ElementType;
72 import java.lang.annotation.Retention;
73 import java.lang.annotation.RetentionPolicy;
74 import java.lang.annotation.Target;
75 import java.util.concurrent.Callable;
76 import java.util.concurrent.CountDownLatch;
77 import java.util.concurrent.TimeUnit;
78 import java.util.function.Consumer;
79 import java.util.function.Function;
80 
81 /**
82  * Base class for all instrumentation tests providing various utility methods.
83  */
84 public abstract class AbstractLauncherUiTest {
85 
86     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
87     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
88 
89     public static final long SHORT_UI_TIMEOUT = 300;
90     public static final long DEFAULT_UI_TIMEOUT = 10000;
91     private static final String TAG = "AbstractLauncherUiTest";
92 
93     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
94     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
95     protected final LauncherInstrumentation mLauncher =
96             new LauncherInstrumentation(getInstrumentation());
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()) Utilities.enableRunningInTestHarnessForTests();
107     }
108 
109     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
110 
111     @Rule
112     public ShellCommandRule mDefaultLauncherRule =
113             TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null;
114 
115     @Rule
116     public ShellCommandRule mDisableHeadsUpNotification =
117             ShellCommandRule.disableHeadsUpNotification();
118 
119     // Annotation for tests that need to be run in portrait and landscape modes.
120     @Retention(RetentionPolicy.RUNTIME)
121     @Target(ElementType.METHOD)
122     protected @interface PortraitLandscape {
123     }
124 
getRulesInsideActivityMonitor()125     protected TestRule getRulesInsideActivityMonitor() {
126         return RuleChain.
127                 outerRule(new PortraitLandscapeRunner(this)).
128                 around(new FailureWatcher(mDevice));
129     }
130 
131     @Rule
132     public TestRule mOrderSensitiveRules = RuleChain.
133             outerRule(mActivityMonitor).
134             around(getRulesInsideActivityMonitor());
135 
getDevice()136     public UiDevice getDevice() {
137         return mDevice;
138     }
139 
140     @Before
setUp()141     public void setUp() throws Exception {
142         // Disable app tracker
143         AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
144 
145         mTargetContext = InstrumentationRegistry.getTargetContext();
146         mTargetPackage = mTargetContext.getPackageName();
147         // Unlock the phone
148         mDevice.executeShellCommand("input keyevent 82");
149     }
150 
151     @After
verifyLauncherState()152     public void verifyLauncherState() {
153         try {
154             // Limits UI tests affecting tests running after them.
155             waitForModelLoaded();
156         } catch (Throwable t) {
157             Log.e(TAG,
158                     "Couldn't deinit after a test, exiting tests, see logs for failures that "
159                             + "could have caused this",
160                     t);
161             exit(1);
162         }
163     }
164 
lockRotation(boolean naturalOrientation)165     protected void lockRotation(boolean naturalOrientation) throws RemoteException {
166         if (naturalOrientation) {
167             mDevice.setOrientationNatural();
168         } else {
169             mDevice.setOrientationRight();
170         }
171     }
172 
clearLauncherData()173     protected void clearLauncherData() throws IOException {
174         if (TestHelpers.isInLauncherProcess()) {
175             LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
176                     LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
177             resetLoaderState();
178         } else {
179             mDevice.executeShellCommand("pm clear " + mDevice.getLauncherPackageName());
180         }
181     }
182 
183     /**
184      * Scrolls the {@param container} until it finds an object matching {@param condition}.
185      *
186      * @return the matching object.
187      */
scrollAndFind(UiObject2 container, BySelector condition)188     protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
189         final int margin = ResourceUtils.getNavbarSize(
190                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
191         container.setGestureMargins(0, 0, 0, margin);
192 
193         int i = 0;
194         for (; ; ) {
195             // findObject can only execute after spring settles.
196             mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
197             UiObject2 widget = container.findObject(condition);
198             if (widget != null && widget.getVisibleBounds().intersects(
199                     0, 0, mDevice.getDisplayWidth(),
200                     mDevice.getDisplayHeight() - margin)) {
201                 return widget;
202             }
203             if (++i > 40) fail("Too many attempts");
204             container.scroll(Direction.DOWN, 1f);
205         }
206     }
207 
208     /**
209      * Removes all icons from homescreen and hotseat.
210      */
clearHomescreen()211     public void clearHomescreen() throws Throwable {
212         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
213                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
214         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
215                 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
216         resetLoaderState();
217     }
218 
resetLoaderState()219     protected void resetLoaderState() {
220         try {
221             mMainThreadExecutor.execute(
222                     () -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload());
223         } catch (Throwable t) {
224             throw new IllegalArgumentException(t);
225         }
226         waitForModelLoaded();
227     }
228 
waitForModelLoaded()229     protected void waitForModelLoaded() {
230         waitForLauncherCondition("Launcher model didn't load", launcher -> {
231             final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel();
232             return model.getCallback() == null || model.isModelLoaded();
233         });
234     }
235 
236     /**
237      * Runs the callback on the UI thread and returns the result.
238      */
getOnUiThread(final Callable<T> callback)239     protected <T> T getOnUiThread(final Callable<T> callback) {
240         try {
241             return mMainThreadExecutor.submit(callback).get();
242         } catch (Exception e) {
243             throw new RuntimeException(e);
244         }
245     }
246 
getFromLauncher(Function<Launcher, T> f)247     protected <T> T getFromLauncher(Function<Launcher, T> f) {
248         if (!TestHelpers.isInLauncherProcess()) return null;
249         return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
250     }
251 
executeOnLauncher(Consumer<Launcher> f)252     protected void executeOnLauncher(Consumer<Launcher> f) {
253         getFromLauncher(launcher -> {
254             f.accept(launcher);
255             return null;
256         });
257     }
258 
259     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
260     // the results of that gesture because the wait can hide flakeness.
waitForState(String message, LauncherState state)261     protected void waitForState(String message, LauncherState state) {
262         waitForLauncherCondition(message,
263                 launcher -> launcher.getStateManager().getCurrentStableState() == state);
264     }
265 
waitForResumed(String message)266     protected void waitForResumed(String message) {
267         waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed());
268     }
269 
270     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
271     // flakiness.
waitForLauncherCondition(String message, Function<Launcher, Boolean> condition)272     protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) {
273         waitForLauncherCondition(message, condition, 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.
waitForLauncherCondition( String message, Runnable testThreadAction, Function<Launcher, Boolean> condition, long timeout)286     protected void waitForLauncherCondition(
287             String message,
288             Runnable testThreadAction, Function<Launcher, Boolean> condition,
289             long timeout) {
290         if (!TestHelpers.isInLauncherProcess()) return;
291         Wait.atMost(message, () -> {
292             testThreadAction.run();
293             return getFromLauncher(condition);
294         }, timeout);
295     }
296 
getSettingsApp()297     protected LauncherActivityInfo getSettingsApp() {
298         return LauncherAppsCompat.getInstance(mTargetContext)
299                 .getActivityList("com.android.settings",
300                         Process.myUserHandle()).get(0);
301     }
302 
303     /**
304      * Broadcast receiver which blocks until the result is received.
305      */
306     public class BlockingBroadcastReceiver extends BroadcastReceiver {
307 
308         private final CountDownLatch latch = new CountDownLatch(1);
309         private Intent mIntent;
310 
BlockingBroadcastReceiver(String action)311         public BlockingBroadcastReceiver(String action) {
312             mTargetContext.registerReceiver(this, new IntentFilter(action));
313         }
314 
315         @Override
onReceive(Context context, Intent intent)316         public void onReceive(Context context, Intent intent) {
317             mIntent = intent;
318             latch.countDown();
319         }
320 
blockingGetIntent()321         public Intent blockingGetIntent() throws InterruptedException {
322             latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
323             mTargetContext.unregisterReceiver(this);
324             return mIntent;
325         }
326 
blockingGetExtraIntent()327         public Intent blockingGetExtraIntent() throws InterruptedException {
328             Intent intent = blockingGetIntent();
329             return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
330         }
331     }
332 
startAppFast(String packageName)333     protected void startAppFast(String packageName) {
334         final Instrumentation instrumentation = getInstrumentation();
335         final Intent intent = instrumentation.getContext().getPackageManager().
336                 getLaunchIntentForPackage(packageName);
337         intent.addCategory(Intent.CATEGORY_LAUNCHER);
338         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
339         instrumentation.getTargetContext().startActivity(intent);
340         assertTrue(packageName + " didn't start",
341                 mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), DEFAULT_UI_TIMEOUT));
342     }
343 
startTestActivity(int activityNumber)344     protected void startTestActivity(int activityNumber) {
345         final String packageName = getAppPackageName();
346         final Instrumentation instrumentation = getInstrumentation();
347         final Intent intent = instrumentation.getContext().getPackageManager().
348                 getLaunchIntentForPackage(packageName);
349         intent.addCategory(Intent.CATEGORY_LAUNCHER);
350         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
351         intent.setComponent(new ComponentName(packageName,
352                 "com.android.launcher3.tests.Activity" + activityNumber));
353         instrumentation.getTargetContext().startActivity(intent);
354         assertTrue(packageName + " didn't start",
355                 mDevice.wait(
356                         Until.hasObject(By.pkg(packageName).text("TestActivity" + activityNumber)),
357                         DEFAULT_UI_TIMEOUT));
358     }
359 
resolveSystemApp(String category)360     public static String resolveSystemApp(String category) {
361         return getInstrumentation().getContext().getPackageManager().resolveActivity(
362                 new Intent(Intent.ACTION_MAIN).addCategory(category),
363                 PackageManager.MATCH_SYSTEM_ONLY).
364                 activityInfo.packageName;
365     }
366 
closeLauncherActivity()367     protected void closeLauncherActivity() {
368         // Destroy Launcher activity.
369         executeOnLauncher(launcher -> {
370             if (launcher != null) {
371                 launcher.finish();
372             }
373         });
374         waitForLauncherCondition(
375                 "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
376     }
377 
isInBackground(Launcher launcher)378     protected boolean isInBackground(Launcher launcher) {
379         return !launcher.hasBeenResumed();
380     }
381 
isInState(LauncherState state)382     protected boolean isInState(LauncherState state) {
383         if (!TestHelpers.isInLauncherProcess()) return true;
384         return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
385     }
386 
getAllAppsScroll(Launcher launcher)387     protected int getAllAppsScroll(Launcher launcher) {
388         return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
389     }
390 }
391