Home | History | Annotate | Download | only in ui
      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
     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 
     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      */
    103     protected Launcher startLauncher() {
    104         return (Launcher) getInstrumentation().startActivitySync(getHomeIntent());
    105     }
    106 
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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 
    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      */
    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 
    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      */
    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      */
    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 
    309     protected UiObject2 findViewById(int id) {
    310         return mDevice.wait(Until.findObject(getSelectorForId(id)), DEFAULT_UI_TIMEOUT);
    311     }
    312 
    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 
    327         public BlockingBroadcastReceiver(String action) {
    328             mTargetContext.registerReceiver(this, new IntentFilter(action));
    329         }
    330 
    331         @Override
    332         public void onReceive(Context context, Intent intent) {
    333             mIntent = intent;
    334             latch.countDown();
    335         }
    336 
    337         public Intent blockingGetIntent() throws InterruptedException {
    338             latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
    339             mTargetContext.unregisterReceiver(this);
    340             return mIntent;
    341         }
    342 
    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