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