Home | History | Annotate | Download | only in launcherhelper
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.support.test.launcherhelper;
     17 
     18 import android.graphics.Rect;
     19 import android.os.RemoteException;
     20 import android.os.SystemClock;
     21 import android.support.test.uiautomator.By;
     22 import android.support.test.uiautomator.BySelector;
     23 import android.support.test.uiautomator.Direction;
     24 import android.support.test.uiautomator.UiDevice;
     25 import android.support.test.uiautomator.UiObject2;
     26 import android.support.test.uiautomator.Until;
     27 import android.util.Log;
     28 
     29 /**
     30  * A helper class for generic launcher interactions that can be abstracted across different types
     31  * of launchers.
     32  *
     33  */
     34 public class CommonLauncherHelper {
     35 
     36     private static final String LOG_TAG = CommonLauncherHelper.class.getSimpleName();
     37     private static final int MAX_SCROLL_ATTEMPTS = 40;
     38     private static final int MIN_INTERACT_SIZE = 100;
     39     private static final int APP_LAUNCH_TIMEOUT = 10000;
     40     private static CommonLauncherHelper sInstance;
     41     private UiDevice mDevice;
     42 
     43     private CommonLauncherHelper(UiDevice uiDevice) {
     44         mDevice = uiDevice;
     45     }
     46 
     47     /**
     48      * Retrieves the singleton instance of {@link CommonLauncherHelper}
     49      * @param uiDevice
     50      * @return
     51      */
     52     public static CommonLauncherHelper getInstance(UiDevice uiDevice) {
     53         if (sInstance == null) {
     54             sInstance = new CommonLauncherHelper(uiDevice);
     55         }
     56         return sInstance;
     57     }
     58 
     59     /**
     60      * Scrolls a container back to the beginning
     61      * @param container
     62      * @param backDirection
     63      */
     64     public void scrollBackToBeginning(UiObject2 container, Direction backDirection) {
     65         scrollBackToBeginning(container, backDirection, MAX_SCROLL_ATTEMPTS);
     66     }
     67 
     68     /**
     69      * Scrolls a container back to the beginning
     70      * @param container
     71      * @param backDirection
     72      * @param maxAttempts
     73      */
     74     public void scrollBackToBeginning(UiObject2 container, Direction backDirection, int maxAttempts) {
     75         int attempts = 0;
     76         while (container.scroll(backDirection, 0.25f)) {
     77             attempts++;
     78             if (attempts > maxAttempts) {
     79                 throw new RuntimeException(
     80                         "scrollBackToBeginning: exceeded max attempts: " + maxAttempts);
     81             }
     82         }
     83     }
     84 
     85     /**
     86      * Ensures that the described widget has enough visible portion by scrolling its container if
     87      * necessary
     88      * @param app
     89      * @param container
     90      * @param dir
     91      */
     92     private void ensureIconVisible(BySelector app, UiObject2 container, Direction dir) {
     93         UiObject2 appIcon = mDevice.findObject(app);
     94         Rect appR = appIcon.getVisibleBounds();
     95         Rect containerR = container.getVisibleBounds();
     96         int size = 0;
     97         int containerSize = 0;
     98         if (Direction.DOWN.equals(dir) || Direction.UP.equals(dir)) {
     99             size = appR.height();
    100             containerSize = containerR.height();
    101         } else {
    102             size = appR.width();
    103             containerSize = containerR.width();
    104         }
    105         if (size < MIN_INTERACT_SIZE) {
    106             // try to figure out how much percentage of the container needs to be scrolled in order
    107             // to reveal the app icon to have the MIN_INTERACT_SIZE
    108             float pct = ((float)(MIN_INTERACT_SIZE - size)) / containerSize;
    109             if (pct < 0.2f) {
    110                 pct = 0.2f;
    111             }
    112             container.scroll(dir, pct);
    113         }
    114     }
    115 
    116     /**
    117      * Triggers app launch by interacting with its launcher icon as described, optionally verify
    118      * that the frontend UI has the expected app package name
    119      * @param launcherStrategy
    120      * @param app
    121      * @param packageName
    122      * @return
    123      */
    124     public long launchApp(ILauncherStrategy launcherStrategy, BySelector app,
    125             String packageName) {
    126         return launchApp(launcherStrategy, app, packageName, MAX_SCROLL_ATTEMPTS);
    127     }
    128 
    129     /**
    130      * Triggers app launch by interacting with its launcher icon as described, optionally verify
    131      * that the frontend UI has the expected app package name
    132      * @param launcherStrategy
    133      * @param app
    134      * @param packageName
    135      * @param maxScrollAttempts
    136      * @return the SystemClock#uptimeMillis timestamp just before launching the application.
    137      */
    138     public long launchApp(ILauncherStrategy launcherStrategy, BySelector app,
    139             String packageName, int maxScrollAttempts) {
    140         unlockDeviceIfAsleep();
    141 
    142         if (isAppOpen(packageName)) {
    143             // Application is already open
    144             return 0;
    145         }
    146 
    147         // Go to the home page
    148         launcherStrategy.open();
    149         Direction dir = launcherStrategy.getAllAppsScrollDirection();
    150         // attempt to find the app icon if it's not already on the screen
    151         if (!mDevice.hasObject(app)) {
    152             UiObject2 container = launcherStrategy.openAllApps(false);
    153 
    154             if (!mDevice.hasObject(app)) {
    155                 scrollBackToBeginning(container, Direction.reverse(dir));
    156                 int attempts = 0;
    157                 while (!mDevice.hasObject(app) && container.scroll(dir, 0.8f)) {
    158                     attempts++;
    159                     if (attempts > maxScrollAttempts) {
    160                         throw new RuntimeException(
    161                                 "launchApp: exceeded max attempts to locate app icon: "
    162                                         + maxScrollAttempts);
    163                     }
    164                 }
    165             }
    166             // HACK-ish: ensure icon has enough parts revealed for it to be clicked on
    167             ensureIconVisible(app, container, dir);
    168         }
    169 
    170         long ready = SystemClock.uptimeMillis();
    171         if (!mDevice.findObject(app).clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT)) {
    172             Log.w(LOG_TAG, "no new window detected after app launch attempt.");
    173             return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP;
    174         }
    175         mDevice.waitForIdle();
    176         if (packageName != null) {
    177             Log.w(LOG_TAG, String.format(
    178                     "No UI element with package name %s detected.", packageName));
    179             boolean success = mDevice.wait(Until.hasObject(
    180                     By.pkg(packageName).depth(0)), APP_LAUNCH_TIMEOUT);
    181             if (success) {
    182                 return ready;
    183             } else {
    184                 return ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP;
    185             }
    186         } else {
    187             return ready;
    188         }
    189     }
    190 
    191     private boolean isAppOpen (String appPackage) {
    192         return mDevice.hasObject(By.pkg(appPackage).depth(0));
    193     }
    194 
    195     private void unlockDeviceIfAsleep () {
    196         // Turn screen on if necessary
    197         try {
    198             if (!mDevice.isScreenOn()) {
    199                 mDevice.wakeUp();
    200             }
    201         } catch (RemoteException e) {
    202             Log.e(LOG_TAG, "Failed to unlock the screen-off device.", e);
    203         }
    204         // Check for lock screen element
    205         if (mDevice.hasObject(By.res("com.android.systemui", "keyguard_bottom_area"))) {
    206             mDevice.pressMenu();
    207         }
    208     }
    209 }
    210