• 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 android.content.BroadcastReceiver;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.graphics.Point;
27 import android.os.ParcelFileDescriptor;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.SystemClock;
31 import android.support.test.uiautomator.By;
32 import android.support.test.uiautomator.BySelector;
33 import android.support.test.uiautomator.Direction;
34 import android.support.test.uiautomator.UiDevice;
35 import android.support.test.uiautomator.UiObject2;
36 import android.support.test.uiautomator.Until;
37 import android.test.InstrumentationTestCase;
38 import android.view.MotionEvent;
39 
40 import com.android.launcher3.Launcher;
41 import com.android.launcher3.LauncherAppState;
42 import com.android.launcher3.LauncherAppWidgetProviderInfo;
43 import com.android.launcher3.LauncherSettings;
44 import com.android.launcher3.MainThreadExecutor;
45 import com.android.launcher3.R;
46 import com.android.launcher3.Utilities;
47 import com.android.launcher3.compat.AppWidgetManagerCompat;
48 import com.android.launcher3.config.FeatureFlags;
49 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
50 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
51 import com.android.launcher3.util.ManagedProfileHeuristic;
52 
53 import java.io.BufferedReader;
54 import java.io.FileInputStream;
55 import java.io.IOException;
56 import java.io.InputStreamReader;
57 import java.util.ArrayList;
58 import java.util.Locale;
59 import java.util.concurrent.Callable;
60 import java.util.concurrent.CountDownLatch;
61 import java.util.concurrent.TimeUnit;
62 
63 /**
64  * Base class for all instrumentation tests providing various utility methods.
65  */
66 public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
67 
68     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
69     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
70 
71     public static final long DEFAULT_UI_TIMEOUT = 3000;
72     public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
73 
74     protected UiDevice mDevice;
75     protected Context mTargetContext;
76     protected String mTargetPackage;
77 
78     @Override
setUp()79     protected void setUp() throws Exception {
80         super.setUp();
81 
82         mDevice = UiDevice.getInstance(getInstrumentation());
83         mTargetContext = getInstrumentation().getTargetContext();
84         mTargetPackage = mTargetContext.getPackageName();
85     }
86 
lockRotation(boolean naturalOrientation)87     protected void lockRotation(boolean naturalOrientation) throws RemoteException {
88         Utilities.getPrefs(mTargetContext)
89                 .edit()
90                 .putBoolean(Utilities.ALLOW_ROTATION_PREFERENCE_KEY, !naturalOrientation)
91                 .commit();
92 
93         if (naturalOrientation) {
94             mDevice.setOrientationNatural();
95         } else {
96             mDevice.setOrientationRight();
97         }
98     }
99 
100     /**
101      * Starts the launcher activity in the target package and returns the Launcher instance.
102      */
startLauncher()103     protected Launcher startLauncher() {
104         return (Launcher) getInstrumentation().startActivitySync(getHomeIntent());
105     }
106 
getHomeIntent()107     protected Intent getHomeIntent() {
108         return new Intent(Intent.ACTION_MAIN)
109                 .addCategory(Intent.CATEGORY_HOME)
110                 .setPackage(mTargetPackage)
111                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
112     }
113 
114     /**
115      * Grants the launcher permission to bind widgets.
116      */
grantWidgetPermission()117     protected void grantWidgetPermission() throws IOException {
118         // Check bind widget permission
119         if (mTargetContext.getPackageManager().checkPermission(
120                 mTargetPackage, android.Manifest.permission.BIND_APPWIDGET)
121                 != PackageManager.PERMISSION_GRANTED) {
122             runShellCommand("appwidget grantbind --package " + mTargetPackage);
123         }
124     }
125 
126     /**
127      * Sets the target launcher as default launcher.
128      */
setDefaultLauncher()129     protected void setDefaultLauncher() throws IOException {
130         ActivityInfo launcher = mTargetContext.getPackageManager()
131                 .queryIntentActivities(getHomeIntent(), 0).get(0).activityInfo;
132         runShellCommand("cmd package set-home-activity " +
133                 new ComponentName(launcher.packageName, launcher.name).flattenToString());
134     }
135 
runShellCommand(String command)136     protected void runShellCommand(String command) throws IOException {
137         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
138                 .executeShellCommand(command);
139 
140         // Read the input stream fully.
141         FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
142         while (fis.read() != -1);
143         fis.close();
144     }
145 
146     /**
147      * Opens all apps and returns the recycler view
148      */
openAllApps()149     protected UiObject2 openAllApps() {
150         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
151             // clicking on the page indicator brings up all apps tray on non tablets.
152             findViewById(R.id.page_indicator).click();
153         } else {
154             mDevice.wait(Until.findObject(
155                     By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
156                     DEFAULT_UI_TIMEOUT).click();
157         }
158         return findViewById(R.id.apps_list_view);
159     }
160 
161     /**
162      * Opens widget tray and returns the recycler view.
163      */
openWidgetsTray()164     protected UiObject2 openWidgetsTray() {
165         mDevice.pressMenu(); // Enter overview mode.
166         mDevice.wait(Until.findObject(
167                 By.text(mTargetContext.getString(R.string.widget_button_text)
168                         .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click();
169         return findViewById(R.id.widgets_list_view);
170     }
171 
172     /**
173      * Scrolls the {@param container} until it finds an object matching {@param condition}.
174      * @return the matching object.
175      */
scrollAndFind(UiObject2 container, BySelector condition)176     protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
177         do {
178             UiObject2 widget = container.findObject(condition);
179             if (widget != null) {
180                 return widget;
181             }
182         } while (container.scroll(Direction.DOWN, 1f));
183         return container.findObject(condition);
184     }
185 
186     /**
187      * Drags an icon to the center of homescreen.
188      */
dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts)189     protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
190         Point center = icon.getVisibleCenter();
191 
192         // Action Down
193         sendPointer(MotionEvent.ACTION_DOWN, center);
194 
195         UiObject2 dragLayer = findViewById(R.id.drag_layer);
196 
197         if (expectedToShowShortcuts) {
198             // Make sure shortcuts show up, and then move a bit to hide them.
199             assertNotNull(findViewById(R.id.deep_shortcuts_container));
200 
201             Point moveLocation = new Point(center);
202             int distanceToMove = mTargetContext.getResources().getDimensionPixelSize(
203                     R.dimen.deep_shortcuts_start_drag_threshold) + 50;
204             if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
205                 moveLocation.y -= distanceToMove;
206             } else {
207                 moveLocation.y += distanceToMove;
208             }
209             movePointer(center, moveLocation);
210 
211             assertNull(findViewById(R.id.deep_shortcuts_container));
212         }
213 
214         // Wait until Remove/Delete target is visible
215         assertNotNull(findViewById(R.id.delete_target_text));
216 
217         Point moveLocation = dragLayer.getVisibleCenter();
218 
219         // Move to center
220         movePointer(center, moveLocation);
221         sendPointer(MotionEvent.ACTION_UP, center);
222 
223         // Wait until remove target is gone.
224         mDevice.wait(Until.gone(getSelectorForId(R.id.delete_target_text)), DEFAULT_UI_TIMEOUT);
225     }
226 
movePointer(Point from, Point to)227     private void movePointer(Point from, Point to) {
228         while(!from.equals(to)) {
229             from.x = getNextMoveValue(to.x, from.x);
230             from.y = getNextMoveValue(to.y, from.y);
231             sendPointer(MotionEvent.ACTION_MOVE, from);
232         }
233     }
234 
getNextMoveValue(int targetValue, int oldValue)235     private int getNextMoveValue(int targetValue, int oldValue) {
236         if (targetValue - oldValue > 10) {
237             return oldValue + 10;
238         } else if (targetValue - oldValue < -10) {
239             return oldValue - 10;
240         } else {
241             return targetValue;
242         }
243     }
244 
sendPointer(int action, Point point)245     protected void sendPointer(int action, Point point) {
246         MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
247                 SystemClock.uptimeMillis(), action, point.x, point.y, 0);
248         getInstrumentation().sendPointerSync(event);
249         event.recycle();
250     }
251 
252     /**
253      * Removes all icons from homescreen and hotseat.
254      */
clearHomescreen()255     public void clearHomescreen() throws Throwable {
256         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
257                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
258         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
259                 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
260         resetLoaderState();
261     }
262 
resetLoaderState()263     protected void resetLoaderState() {
264         try {
265             runTestOnUiThread(new Runnable() {
266                 @Override
267                 public void run() {
268                     ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
269                     LauncherAppState.getInstance(mTargetContext).getModel().forceReload();
270                 }
271             });
272         } catch (Throwable t) {
273             throw new IllegalArgumentException(t);
274         }
275     }
276 
277     /**
278      * Runs the callback on the UI thread and returns the result.
279      */
getOnUiThread(final Callable<T> callback)280     protected <T> T getOnUiThread(final Callable<T> callback) {
281         try {
282             return new MainThreadExecutor().submit(callback).get();
283         } catch (Exception e) {
284             throw new RuntimeException(e);
285         }
286     }
287 
288     /**
289      * Finds a widget provider which can fit on the home screen.
290      * @param hasConfigureScreen if true, a provider with a config screen is returned.
291      */
findWidgetProvider(final boolean hasConfigureScreen)292     protected LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
293         LauncherAppWidgetProviderInfo info =
294                 getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
295             @Override
296             public LauncherAppWidgetProviderInfo call() throws Exception {
297                 ComponentName cn = new ComponentName(getInstrumentation().getContext(),
298                         hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
299                 return AppWidgetManagerCompat.getInstance(mTargetContext)
300                         .findProvider(cn, Process.myUserHandle());
301             }
302         });
303         if (info == null) {
304             throw new IllegalArgumentException("No valid widget provider");
305         }
306         return info;
307     }
308 
findViewById(int id)309     protected UiObject2 findViewById(int id) {
310         return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT);
311     }
312 
getSelectorForId(int id)313     protected BySelector getSelectorForId(int id) {
314         String name = mTargetContext.getResources().getResourceEntryName(id);
315         return By.res(mTargetPackage, name);
316     }
317 
318 
319     /**
320      * Broadcast receiver which blocks until the result is received.
321      */
322     public class BlockingBroadcastReceiver extends BroadcastReceiver {
323 
324         private final CountDownLatch latch = new CountDownLatch(1);
325         private Intent mIntent;
326 
BlockingBroadcastReceiver(String action)327         public BlockingBroadcastReceiver(String action) {
328             mTargetContext.registerReceiver(this, new IntentFilter(action));
329         }
330 
331         @Override
onReceive(Context context, Intent intent)332         public void onReceive(Context context, Intent intent) {
333             mIntent = intent;
334             latch.countDown();
335         }
336 
blockingGetIntent()337         public Intent blockingGetIntent() throws InterruptedException {
338             latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
339             mTargetContext.unregisterReceiver(this);
340             return mIntent;
341         }
342 
blockingGetExtraIntent()343         public Intent blockingGetExtraIntent() throws InterruptedException {
344             Intent intent = blockingGetIntent();
345             return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
346         }
347     }
348 }
349