Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f58e5b6c authored by Maxim Siniavine's avatar Maxim Siniavine
Browse files

Added app launch test.

The test will start each app and report the time it takes for
the app to start in milliseconds.

Change-Id: I974ac36f70f0d982aa01e46824fe138eb641d680
parent 89ac38bf
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := tests

# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_PACKAGE_NAME := AppLaunch

LOCAL_CERTIFICATE := platform
LOCAL_JAVA_LIBRARIES := android.test.runner

include $(BUILD_PACKAGE)

# Use the following include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))
 No newline at end of file
+13 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.tests.applaunch"
    android:sharedUserId="android.uid.system" >
    <instrumentation android:label="Measure app start up time"
                     android:name="android.test.InstrumentationTestRunner"
                     android:targetPackage="com.android.tests.applaunch" />

    <application android:label="App Launch Test">
        <uses-library android:name="android.test.runner" />
    </application>
</manifest>
 No newline at end of file
+216 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tests.applaunch;

import android.app.ActivityManager;
import android.app.ActivityManager.ProcessErrorStateInfo;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestRunner;
import android.util.Log;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This test is intended to measure the time it takes for the apps to start.
 * Names of the applications are passed in command line, and the
 * test starts each application, and reports the start up time in milliseconds.
 * The instrumentation expects the following key to be passed on the command line:
 * apps - A list of applications to start and their corresponding result keys
 * in the following format:
 * -e apps <app name>^<result key>|<app name>^<result key>
 */
public class AppLaunch extends InstrumentationTestCase {

    private static final int JOIN_TIMEOUT = 10000;
    private static final String TAG = "AppLaunch";
    private static final String KEY_APPS = "apps";

    private Map<String, Intent> mNameToIntent;
    private Map<String, String> mNameToProcess;
    private Map<String, String> mNameToResultKey;

    private IActivityManager mAm;

    public void testMeasureStartUpTime() throws RemoteException {
        InstrumentationTestRunner instrumentation =
                (InstrumentationTestRunner)getInstrumentation();
        Bundle args = instrumentation.getBundle();
        mAm = ActivityManagerNative.getDefault();

        createMappings();
        parseArgs(args);

        Bundle results = new Bundle();
        for (String app : mNameToResultKey.keySet()) {
            try {
                startApp(app, results);
                closeApp();
            } catch (NameNotFoundException e) {
                Log.i(TAG, "Application " + app + " not found");
            }

        }
        instrumentation.sendStatus(0, results);
    }

    private void parseArgs(Bundle args) {
        mNameToResultKey = new HashMap<String, String>();
        String appList = args.getString(KEY_APPS);

        if (appList == null)
            return;

        String appNames[] = appList.split("\\|");
        for (String pair : appNames) {
            String[] parts = pair.split("\\^");
            if (parts.length != 2) {
                Log.e(TAG, "The apps key is incorectly formatted");
                fail();
            }

            mNameToResultKey.put(parts[0], parts[1]);
        }
    }

    private void createMappings() {
        mNameToIntent = new HashMap<String, Intent>();
        mNameToProcess = new HashMap<String, String>();

        PackageManager pm = getInstrumentation().getContext()
                .getPackageManager();
        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
        intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
        List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
        if (ris == null || ris.isEmpty()) {
            Log.i(TAG, "Could not find any apps");
        } else {
            for (ResolveInfo ri : ris) {
                Intent startIntent = new Intent(intentToResolve);
                startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                startIntent.setClassName(ri.activityInfo.packageName,
                        ri.activityInfo.name);
                mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
                mNameToProcess.put(ri.loadLabel(pm).toString(),
                        ri.activityInfo.processName);
            }
        }
    }

    private void startApp(String appName, Bundle results)
            throws NameNotFoundException, RemoteException {
        Log.i(TAG, "Starting " + appName);

        Intent startIntent = mNameToIntent.get(appName);
        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
        Thread t = new Thread(runnable);
        long startTime = System.currentTimeMillis();
        t.start();
        try {
            t.join(JOIN_TIMEOUT);
        } catch (InterruptedException e) {
            // ignore
        }
        if(t.isAlive() || (runnable.getResult() != null &&
                runnable.getResult().result != ActivityManager.START_SUCCESS)) {
            Log.w(TAG, "Assuming app " + appName + " crashed.");
            reportError(appName, mNameToProcess.get(appName), results);
            return;
        }
        long startUpTime = System.currentTimeMillis() - startTime;
        results.putString(mNameToResultKey.get(appName), String.valueOf(startUpTime));
        sleep(5000);
    }

    private void closeApp() {
        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
        homeIntent.addCategory(Intent.CATEGORY_HOME);
        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        getInstrumentation().getContext().startActivity(homeIntent);
        sleep(3000);
    }

    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            // ignore
        }
    }

    private void reportError(String appName, String processName, Bundle results) {
        ActivityManager am = (ActivityManager) getInstrumentation()
                .getContext().getSystemService(Context.ACTIVITY_SERVICE);
        List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
        if (crashes != null) {
            for (ProcessErrorStateInfo crash : crashes) {
                if (!crash.processName.equals(processName))
                    continue;

                Log.w(TAG, appName + " crashed: " + crash.shortMsg);
                results.putString(mNameToResultKey.get(appName), crash.shortMsg);
                return;
            }
        }

        results.putString(mNameToResultKey.get(appName),
                "Crashed for unknown reason");
        Log.w(TAG, appName
                + " not found in process list, most likely it is crashed");
    }

    private class AppLaunchRunnable implements Runnable {
        private Intent mLaunchIntent;
        private IActivityManager.WaitResult mResult;
        public AppLaunchRunnable(Intent intent) {
            mLaunchIntent = intent;
        }

        public IActivityManager.WaitResult getResult() {
            return mResult;
        }

        public void run() {
            try {
                String mimeType = mLaunchIntent.getType();
                if (mimeType == null && mLaunchIntent.getData() != null
                        && "content".equals(mLaunchIntent.getData().getScheme())) {
                    mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
                            UserHandle.USER_CURRENT);
                }

                mResult = mAm.startActivityAndWait(null, mLaunchIntent, mimeType,
                        null, null, 0, mLaunchIntent.getFlags(), null, null, null,
                        UserHandle.USER_CURRENT);
            } catch (RemoteException e) {
                Log.w(TAG, "Error launching app", e);
            }
        }
    }
}