Edit this Doc Android Coverage Guide


Project Structure

You may need the following structure of files:

   |____ MainActivity.java    Your main activity
   |____ InstrumentActivityListener.java     A customized interface for exporting coverage to files
   |____ InstrumentActivity.java    Activity launched for coverage. But in most cases, this should be same as MainActivity. The only difference is that it will include a `InstrumentActivityListener` to export coverage data to disk files.
   |____ JacocoInstrumentation.java    The instrument class created by you. Also it needs to implement `InstrumentActivitylistener`.
   |____ EndEmmaBroadCast.java    A broadcast receiver which will be invoked by appium-uiautomator2-driver at the end of testing. You need implementing logic to invoke InstrumentActivityListener so as to export coverage to files.

Configure followings in your caps:
* automationName : uiautomator2 (case irrelevant)
* androidCoverage : {package}/{instrumentation class}, in our example, com.example.pkg/com.example.pkg.JacocoInstrumentation
* appWaitActivity : the FQCN of the activity of InstrumentActivity, in our example, com.example.pkg.InstrumentActivity
* appWaitPackage : {package},in our example, com.example.pkg
androidCoverageEndIntent : The action of the broadcast receiver to invoke the exporting of coverage data to files, in our example com.example.pkg.END_EMMA

Appium (appium-uiautomator2-driver) will launch app via command like:adb shell am instrument -e coverage true -w com.example.pkg/com.example.pkg.JacocoInstrumentation
After testing is done, Appium (appium-uiautomator2-driver) will execute adb shell am broadcast -a com.example.pkg.END_EMMA to export coverage to files(If you implement such export in the broadcast receiver)


[1] Appium Testing Project - Configure Caps

Please refer to "Project Structure" -> "Configure followings in your caps"

[2] Android Project

Define instrumentation class and broadcast receiver in AndroidManifest.xml:

        android:targetPackage="com.example.pkg" >

    <!-- adb shell am broadcast -a com.example.pkg.END_EMMA -->
    <receiver android:name="com.example.pkg.EndEmmaBroadcast" >
           <action android:name="com.example.pkg.END_EMMA" />

Then, EndEmmaBroadcast.java

package com.example.pkg;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Process;

// adb shell am broadcast -a com.example.pkg.END_EMMA
public class EndEmmaBroadcast extends BroadcastReceiver {
    InstrumentActivityListener activityListener;

    public void setInstrumentActivityListener(InstrumentActivityListener listener){
        this.activityListener = listener;
    public void onReceive(Context context, Intent intent) {
        // once coverage is dumped, the processes is ended.

After that, JacocoInstrumentation.java

package com.example.pkg;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class JacocoInstrumentation  extends Instrumentation implements InstrumentActivityListener {
    public static String TAG = "JacocoInstrumentation:";
    private static String DEFAULT_COVERAGE_FILE_PATH = null;
    private final Bundle mResults = new Bundle();
    private Intent mIntent;
    private static final boolean LOGD = true;
    private boolean mCoverage = true;
    private String mCoverageFilePath;

    public JacocoInstrumentation() {
    public void onCreate(Bundle arguments) {
        Log.d(TAG, "onCreate(" + arguments + ")");
        // bad notation, better use NAME+TimeSeed because you might generate more than 1 corage file
        DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath().toString() + "/coverage.ec";
        File file = new File(DEFAULT_COVERAGE_FILE_PATH);
            }catch (IOException e){
                Log.d(TAG,"File Exception :"+e);
        if(arguments != null) {
            mCoverageFilePath = arguments.getString("coverageFile");
        mIntent = new Intent(getTargetContext(), InstrumentActivity.class);
    public void onStart() {
        // Register broadcast receiver and start InstrumentActivity
        InstrumentActivity activity = (InstrumentActivity) startActivitySync(mIntent);
        EndEmmaBroadcast broadcast = new EndEmmaBroadcast();
        activity.registerReceiver(broadcast, new IntentFilter("com.example.pkg.END_EMMA"));
    private String getCoverageFilePath() {
        if (mCoverageFilePath == null) {
            return DEFAULT_COVERAGE_FILE_PATH;
        } else {
            return mCoverageFilePath;
    private void generateCoverageReport() {
        Log.d(TAG, "generateCoverageReport():" + getCoverageFilePath());
        OutputStream out = null;
        try {
            out = new FileOutputStream(getCoverageFilePath(), false);
            Object agent = Class.forName("org.jacoco.agent.rt.RT")
            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false));
        } catch (Exception e) {
            Log.d(TAG, e.toString(), e);
        } finally {
            if (out != null) {
                try {
                } catch (IOException e) {

    public void onActivityEnd() {
        if (LOGD)      Log.d(TAG, "onActivityFinished()");
        if (mCoverage) {
        finish(Activity.RESULT_OK, mResults);

Then, InstrumentActivityListener.java

package com.example.pkg;

public interface InstrumentActivityListener {
    void onActivityEnd();

InstrumentActivity.java (This is optional, you can use MainActivity)

package com.example.pkg;
import android.app.Instrumentation;
import android.os.Bundle;
import android.util.Log;

public class InstrumentActivity extends MainActivity {
    public static String TAG = "IntrumentedActivity";
    private InstrumentActivityListener listener;

    public void setInstrumentActivityListener(InstrumentActivityListener listener) {
        this.listener = listener;

    // Generate output report when the activity is destroyed
    public void onDestroy() {
        Log.d(TAG, "onDestroy()");
        if (listener != null) {

Finally, the most important part is gradle:


apply plugin: 'jacoco' // add plugin for jacoco


android {
    defaultConfig {
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    buildTypes {
        release {
            testCoverageEnabled = true

dependencies {
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.0'

[3] Now, build apk and run Appium tests!

The coverage.ec will be generated at /data/data/com.example.pkg/files. Pull it out.

[4] About generating HTML reports

To get the HTML report of coverage.ec, you need following steps: [1] pull it into file system by adb pull [2] create the following task in your gradle file:

def coverageSourceDirs = [

task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true
        html.enabled = true
    classDirectories = fileTree(
            dir: './build/intermediates/classes/debug',
            excludes: ['**/R*.class',
    sourceDirectories = files(coverageSourceDirs)
    // NOTE: Put your ec file here
    executionData = files("SOME PATH/coverage.ec")

    doFirst {
        new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))