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