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