Home | History | Annotate | Download | only in appsmoke
      1 /*
      2  * Copyright (C) 2016 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 
     17 package android.test.appsmoke;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.IActivityController;
     21 import android.app.Instrumentation;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.LauncherActivityInfo;
     25 import android.content.pm.LauncherApps;
     26 import android.content.pm.PackageManager;
     27 import android.os.Bundle;
     28 import android.os.RemoteException;
     29 import android.os.SystemClock;
     30 import android.os.UserHandle;
     31 import android.os.UserManager;
     32 import android.support.test.InstrumentationRegistry;
     33 import android.support.test.launcherhelper.ILauncherStrategy;
     34 import android.support.test.launcherhelper.LauncherStrategyFactory;
     35 import android.support.test.uiautomator.UiDevice;
     36 import android.util.Log;
     37 
     38 import org.junit.After;
     39 import org.junit.AfterClass;
     40 import org.junit.Assert;
     41 import org.junit.Before;
     42 import org.junit.BeforeClass;
     43 import org.junit.Test;
     44 import org.junit.runner.RunWith;
     45 import org.junit.runners.Parameterized;
     46 import org.junit.runners.Parameterized.Parameter;
     47 import org.junit.runners.Parameterized.Parameters;
     48 
     49 import java.util.ArrayList;
     50 import java.util.Arrays;
     51 import java.util.Collection;
     52 import java.util.Collections;
     53 import java.util.HashSet;
     54 import java.util.List;
     55 import java.util.Set;
     56 
     57 @RunWith(Parameterized.class)
     58 public class AppSmokeTest {
     59 
     60     private static final String TAG = AppSmokeTest.class.getSimpleName();
     61     private static final String EXCLUDE_LIST = "exclude_apps";
     62     private static final String DEBUG_LIST = "debug_apps";
     63     private static final long WAIT_FOR_ANR = 6000;
     64 
     65     @Parameter
     66     public LaunchParameter mAppInfo;
     67 
     68     private boolean mAppHasError = false;
     69     private boolean mLaunchIntentDetected = false;
     70     private boolean mHasLeanback = false;
     71     private ILauncherStrategy mLauncherStrategy = null;
     72     private static UiDevice sDevice = null;
     73 
     74     /**
     75      * Convenient internal class to hold some launch specific data
     76      */
     77     private static class LaunchParameter implements Comparable<LaunchParameter>{
     78         public String appName;
     79         public String packageName;
     80         public String activityName;
     81 
     82         private LaunchParameter(String appName, String packageName, String activityName) {
     83             this.appName = appName;
     84             this.packageName = packageName;
     85             this.activityName = activityName;
     86         }
     87 
     88         @Override
     89         public int compareTo(LaunchParameter another) {
     90             return appName.compareTo(another.appName);
     91         }
     92 
     93         @Override
     94         public String toString() {
     95             return appName;
     96         }
     97 
     98         public String toLongString() {
     99             return String.format("%s [activity: %s/%s]", appName, packageName, activityName);
    100         }
    101     }
    102 
    103     /**
    104      * an activity controller to detect app launch crashes/ANR etc
    105      */
    106     private IActivityController mActivityController = new IActivityController.Stub() {
    107 
    108         @Override
    109         public int systemNotResponding(String msg) throws RemoteException {
    110             // let system die
    111             return -1;
    112         }
    113 
    114         @Override
    115         public int appNotResponding(String processName, int pid, String processStats)
    116                 throws RemoteException {
    117             if (processName.startsWith(mAppInfo.packageName)) {
    118                 mAppHasError = true;
    119             }
    120             // kill app
    121             return -1;
    122         }
    123 
    124         @Override
    125         public int appEarlyNotResponding(String processName, int pid, String annotation)
    126                 throws RemoteException {
    127             // do nothing
    128             return 0;
    129         }
    130 
    131         @Override
    132         public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
    133                 long timeMillis, String stackTrace) throws RemoteException {
    134             if (processName.startsWith(mAppInfo.packageName)) {
    135                 mAppHasError = true;
    136             }
    137             return false;
    138         }
    139 
    140         @Override
    141         public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
    142             Log.d(TAG, String.format("activityStarting: pkg=%s intent=%s",
    143                     pkg, intent.toInsecureString()));
    144             // always allow starting
    145             if (pkg.equals(mAppInfo.packageName)) {
    146                 mLaunchIntentDetected = true;
    147             }
    148             return true;
    149         }
    150 
    151         @Override
    152         public boolean activityResuming(String pkg) throws RemoteException {
    153             Log.d(TAG, String.format("activityResuming: pkg=%s", pkg));
    154             // always allow resuming
    155             return true;
    156         }
    157     };
    158 
    159     /**
    160      * Generate the list of apps to test for launches by querying package manager
    161      * @return
    162      */
    163     @Parameters(name = "{0}")
    164     public static Collection<LaunchParameter> generateAppsList() {
    165         Instrumentation instr = InstrumentationRegistry.getInstrumentation();
    166         Bundle args = InstrumentationRegistry.getArguments();
    167         Context ctx = instr.getTargetContext();
    168         List<LaunchParameter> ret = new ArrayList<>();
    169         Set<String> excludedApps = new HashSet<>();
    170         Set<String> debugApps = new HashSet<>();
    171 
    172         // parse list of app names that should be execluded from launch tests
    173         if (args.containsKey(EXCLUDE_LIST)) {
    174             excludedApps.addAll(Arrays.asList(args.getString(EXCLUDE_LIST).split(",")));
    175         }
    176         // parse list of app names used for debugging (i.e. essentially a whitelist)
    177         if (args.containsKey(DEBUG_LIST)) {
    178             debugApps.addAll(Arrays.asList(args.getString(DEBUG_LIST).split(",")));
    179         }
    180         LauncherApps la = (LauncherApps)ctx.getSystemService(Context.LAUNCHER_APPS_SERVICE);
    181         UserManager um = (UserManager)ctx.getSystemService(Context.USER_SERVICE);
    182         List<LauncherActivityInfo> activities = new ArrayList<>();
    183         for (UserHandle handle : um.getUserProfiles()) {
    184             activities.addAll(la.getActivityList(null, handle));
    185         }
    186         for (LauncherActivityInfo info : activities) {
    187             String label = info.getLabel().toString();
    188             if (!debugApps.isEmpty()) {
    189                 if (!debugApps.contains(label)) {
    190                     // if debug apps non-empty, we are essentially in whitelist mode
    191                     // bypass any apps not on list
    192                     continue;
    193                 }
    194             } else if (excludedApps.contains(label)) {
    195                 // if not debugging apps, bypass any excluded apps
    196                 continue;
    197             }
    198             ret.add(new LaunchParameter(label, info
    199                     .getApplicationInfo().packageName, info.getName()));
    200         }
    201         Collections.sort(ret);
    202         return ret;
    203     }
    204 
    205     @Before
    206     public void before() throws RemoteException {
    207         ActivityManager.getService().setActivityController(mActivityController, false);
    208         LauncherStrategyFactory factory = LauncherStrategyFactory.getInstance(sDevice);
    209         mLauncherStrategy = factory.getLauncherStrategy();
    210         // Inject an instance of instrumentation only if leanback. This enables to launch any app
    211         // in the Apps and Games row on leanback launcher.
    212         Instrumentation instr = InstrumentationRegistry.getInstrumentation();
    213         mHasLeanback = hasLeanback(instr.getTargetContext());
    214         if (mHasLeanback) {
    215             factory.getLeanbackLauncherStrategy().setInstrumentation(instr);
    216         }
    217         mAppHasError = false;
    218         mLaunchIntentDetected = false;
    219     }
    220 
    221     @BeforeClass
    222     public static void beforeClass() throws RemoteException {
    223         sDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    224         sDevice.setOrientationNatural();
    225     }
    226 
    227     @After
    228     public void after() throws RemoteException {
    229         sDevice.pressHome();
    230         ActivityManager.getService().forceStopPackage(
    231                 mAppInfo.packageName, UserHandle.USER_ALL);
    232         ActivityManager.getService().setActivityController(null, false);
    233     }
    234 
    235     @AfterClass
    236     public static void afterClass() throws RemoteException {
    237         sDevice.unfreezeRotation();
    238     }
    239 
    240     @Test
    241     public void testAppLaunch() {
    242         Log.d(TAG, "Launching: " + mAppInfo.toLongString());
    243         long timestamp = mLauncherStrategy.launch(mAppInfo.appName, mAppInfo.packageName);
    244         boolean launchResult = (timestamp != ILauncherStrategy.LAUNCH_FAILED_TIMESTAMP);
    245         if (launchResult) {
    246             // poke app to check if it's responsive
    247             pokeApp();
    248             SystemClock.sleep(WAIT_FOR_ANR);
    249         }
    250         if (mAppHasError) {
    251             Assert.fail("app crash or ANR detected");
    252         }
    253         if (!launchResult && !mLaunchIntentDetected) {
    254             Assert.fail("no app crash or ANR detected, but failed to launch via UI");
    255         }
    256         // if launchResult is false but mLaunchIntentDetected is true, we consider it as success
    257         // this happens when an app is a trampoline activity to something else
    258     }
    259 
    260     private void pokeApp() {
    261         // The swipe action on leanback launcher that doesn't support swipe gesture may
    262         // cause unnecessary focus change and test to fail.
    263         // Use the dpad key to poke the app instead.
    264         if (!mHasLeanback) {
    265             int w = sDevice.getDisplayWidth();
    266             int h = sDevice.getDisplayHeight();
    267             int dY = h / 4;
    268             boolean ret = sDevice.swipe(w / 2, h / 2 + dY, w / 2, h / 2 - dY, 40);
    269             if (!ret) {
    270                 Log.w(TAG, "Failed while attempting to poke front end window with swipe");
    271             }
    272         } else {
    273             sDevice.pressDPadUp();
    274         }
    275     }
    276 
    277     private boolean hasLeanback(Context context) {
    278         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
    279     }
    280 }
    281