From 5d129af1c7c6685f87042677deadad6832430aaf Mon Sep 17 00:00:00 2001
From: piotradamczyk5 <65554637+piotradamczyk5@users.noreply.github.com>
Date: Thu, 1 Oct 2020 13:39:39 +0200
Subject: [PATCH] test: Added cucumber sample app for testing #1118 (#1174)

* test: Added cucumber sample app for testing

* test: Added cucumber sample app for testing

* remove unused files
---
 test_projects/android/build.gradle            |   5 +-
 .../cucumber-android/.gitignore               |   1 +
 .../cucumber-android/build.gradle             |  65 +++
 .../src/main/AndroidManifest.xml              |   5 +
 .../java/AndroidJavaBackendFactory.java       |  31 ++
 .../runner/CucumberAndroidJUnitRunner.java    |  29 ++
 .../AndroidPatternCompiler.java               |  11 +
 .../cucumber/junit/AndroidFeatureRunner.java  |  45 ++
 .../AndroidJunitRuntimeOptionsFactory.java    |  57 +++
 .../cucumber/junit/AndroidLogcatReporter.java |  86 ++++
 .../cucumber/junit/AndroidPickleRunner.java   |  53 ++
 .../io/cucumber/junit/AndroidResource.java    |  53 ++
 .../cucumber/junit/AndroidResourceLoader.java |  68 +++
 .../java/io/cucumber/junit/Arguments.java     | 195 ++++++++
 .../io/cucumber/junit/CoverageDumper.java     | 104 ++++
 .../junit/CucumberAndroidJUnitArguments.java  |  95 ++++
 .../junit/CucumberArgumentsProvider.java      |   8 +
 .../cucumber/junit/CucumberJUnitRunner.java   | 295 +++++++++++
 .../junit/CucumberJUnitRunnerBuilder.java     |  16 +
 .../io/cucumber/junit/DebuggerWaiter.java     |  32 ++
 .../io/cucumber/junit/DexClassFinder.java     | 123 +++++
 .../io/cucumber/junit/FeatureCompiler.java    |  33 ++
 .../junit/MissingStepDefinitionError.java     |  16 +
 ...cumber.cucumberexpressions.PatternCompiler |   1 +
 .../src/test/java/com/vladium/emma/rt/RT.java |  39 ++
 .../junit/AndroidPatternCompilerTest.java     |  30 ++
 .../junit/AndroidResourceLoaderTest.java      | 111 +++++
 .../cucumber/junit/AndroidResourceTest.java   |  77 +++
 .../java/io/cucumber/junit/ArgumentsTest.java | 469 ++++++++++++++++++
 .../io/cucumber/junit/CoverageDumperTest.java | 153 ++++++
 .../io/cucumber/junit/DebuggerWaiterTest.java |  56 +++
 .../io/cucumber/junit/DexClassFinderTest.java | 178 +++++++
 .../junit/MissingStepDefinitionErrorTest.java |  24 +
 .../cucumber/junit/shadow/ShadowDexFile.java  |  23 +
 .../stub/unwanted/SomeUnwantedClass.java      |   4 +
 .../cucumber/junit/stub/wanted/Manifest.java  |   4 +
 .../java/io/cucumber/junit/stub/wanted/R.java |   8 +
 .../cucumber/junit/stub/wanted/SomeClass.java |   4 +
 .../junit/stub/wanted/SomeKotlinClass.kt      |   8 +
 .../src/test/resources/robolectric.properties |   1 +
 .../cucumber_sample_app/cukeulator/.gitignore |  28 ++
 .../cucumber_sample_app/cukeulator/README.md  |  77 +++
 .../cukeulator/build.gradle                   | 248 +++++++++
 .../assets/features/extra/calculate.feature   |  20 +
 .../features/operations/addition.feature      |  31 ++
 .../features/operations/division.feature      |  31 ++
 .../operations/multiplication.feature         |  31 ++
 .../features/operations/subtraction.feature   |  31 ++
 .../test/CalculatorActivitySteps.java         | 134 +++++
 .../test/CukeulatorAndroidJUnitRunner.java    |  61 +++
 .../test/InstrumentationNonCucumberTest.java  |  41 ++
 .../cucumber/cukeulator/test/KotlinSteps.kt   |  18 +
 .../test/SomeClassWithUnsupportedApi.java     |  51 ++
 .../cukeulator/test/SomeDependency.java       |   5 +
 .../test/TypeRegistryConfiguration.java       |  45 ++
 .../cukeulator/src/debug/AndroidManifest.xml  |   9 +
 .../cukeulator/src/main/AndroidManifest.xml   |  19 +
 .../cukeulator/CalculatorActivity.java        | 152 ++++++
 .../main/res/drawable-hdpi/ic_launcher.png    | Bin 0 -> 6468 bytes
 .../main/res/drawable-mdpi/ic_launcher.png    | Bin 0 -> 3664 bytes
 .../main/res/drawable-xhdpi/ic_launcher.png   | Bin 0 -> 9291 bytes
 .../main/res/drawable-xxhdpi/ic_launcher.png  | Bin 0 -> 16093 bytes
 .../main/res/layout/activity_calculator.xml   | 187 +++++++
 .../src/main/res/menu/menu_main.xml           |   5 +
 .../src/main/res/values-v21/styles.xml        |   5 +
 .../src/main/res/values-w820dp/dimens.xml     |   6 +
 .../cukeulator/src/main/res/values/dimens.xml |   5 +
 .../src/main/res/values/strings.xml           |   9 +
 .../cukeulator/src/main/res/values/styles.xml |   8 +
 .../android/multi-modules/multiapp/.gitignore |   1 +
 test_projects/android/ops.sh                  |  20 +
 test_projects/android/settings.gradle         |   4 +
 test_projects/ops.sh                          |   0
 73 files changed, 3897 insertions(+), 1 deletion(-)
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/.gitignore
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/build.gradle
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/AndroidManifest.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/cucumber/runtime/java/AndroidJavaBackendFactory.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/android/runner/CucumberAndroidJUnitRunner.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/cucumberexpressions/AndroidPatternCompiler.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidFeatureRunner.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidJunitRuntimeOptionsFactory.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidLogcatReporter.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidPickleRunner.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResource.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResourceLoader.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/Arguments.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CoverageDumper.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberAndroidJUnitArguments.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberArgumentsProvider.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunner.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunnerBuilder.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DebuggerWaiter.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DexClassFinder.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/FeatureCompiler.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/MissingStepDefinitionError.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/main/resources/META-INF/services/io.cucumber.cucumberexpressions.PatternCompiler
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/com/vladium/emma/rt/RT.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidPatternCompilerTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceLoaderTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/ArgumentsTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/CoverageDumperTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DebuggerWaiterTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DexClassFinderTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/MissingStepDefinitionErrorTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/shadow/ShadowDexFile.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/unwanted/SomeUnwantedClass.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/Manifest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/R.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeClass.java
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeKotlinClass.kt
 create mode 100644 test_projects/android/cucumber_sample_app/cucumber-android/src/test/resources/robolectric.properties
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/.gitignore
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/README.md
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/build.gradle
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/extra/calculate.feature
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/addition.feature
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/division.feature
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/multiplication.feature
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/subtraction.feature
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CukeulatorAndroidJUnitRunner.java
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/InstrumentationNonCucumberTest.java
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/KotlinSteps.kt
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeClassWithUnsupportedApi.java
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeDependency.java
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/TypeRegistryConfiguration.java
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/debug/AndroidManifest.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/AndroidManifest.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/java/cucumber/cukeulator/CalculatorActivity.java
 create mode 100755 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-hdpi/ic_launcher.png
 create mode 100755 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-mdpi/ic_launcher.png
 create mode 100755 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-xhdpi/ic_launcher.png
 create mode 100755 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-xxhdpi/ic_launcher.png
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/layout/activity_calculator.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/menu/menu_main.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-v21/styles.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-w820dp/dimens.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/dimens.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/strings.xml
 create mode 100644 test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/styles.xml
 mode change 100644 => 100755 test_projects/android/ops.sh
 mode change 100644 => 100755 test_projects/ops.sh

diff --git a/test_projects/android/build.gradle b/test_projects/android/build.gradle
index 0af7981577..9b27aac347 100644
--- a/test_projects/android/build.gradle
+++ b/test_projects/android/build.gradle
@@ -5,12 +5,15 @@ buildscript {
     repositories {
         google()
         jcenter()
-        
+        mavenLocal()
+        maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
+        maven { url 'https://jitpack.io' }
     }
     dependencies {
         // https://dl.google.com/dl/android/maven2/index.html
         classpath 'com.android.tools.build:gradle:3.5.3'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath "com.jaredsburrows:gradle-spoon-plugin:1.5.0"
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/.gitignore b/test_projects/android/cucumber_sample_app/cucumber-android/.gitignore
new file mode 100644
index 0000000000..e8e450bed8
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/.gitignore
@@ -0,0 +1 @@
+gen/
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/build.gradle b/test_projects/android/cucumber_sample_app/cucumber-android/build.gradle
new file mode 100644
index 0000000000..f6b5269a94
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/build.gradle
@@ -0,0 +1,65 @@
+import java.time.Duration
+
+apply plugin: 'com.android.library'
+apply plugin: 'signing'
+apply plugin: 'kotlin-android'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion '29.0.3'
+
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 29
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+}
+
+configurations.all {
+    // check for updates every build
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+
+dependencies {
+    api "io.cucumber:cucumber-java:4.8.1"
+    api "io.cucumber:cucumber-junit:4.8.1"
+    api 'junit:junit:4.13'
+    api "androidx.test:runner:1.2.0"
+    testImplementation "org.robolectric:robolectric:4.3.1"
+    testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
+    testImplementation "org.powermock:powermock-module-junit4:2.0.2"
+    testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+}
+
+task("generateJavadoc", type: Javadoc, group: 'documentation') {
+    source = android.sourceSets.main.java.srcDirs
+    destinationDir = new File("${project.buildDir}/javadoc")
+    options.addStringOption('Xdoclint:none', '-quiet')
+}
+
+task javadocJar(type: Jar, dependsOn: generateJavadoc) {
+    archiveClassifier.set 'javadoc'
+    from generateJavadoc.destinationDir
+}
+
+task sourcesJar(type: Jar) {
+    from android.sourceSets.main.java.srcDirs
+    archiveClassifier.set 'sources'
+}
+
+
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/AndroidManifest.xml b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..fcebf64962
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="io.cucumber.android"
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:tools="http://schemas.android.com/tools">
+</manifest>
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/cucumber/runtime/java/AndroidJavaBackendFactory.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/cucumber/runtime/java/AndroidJavaBackendFactory.java
new file mode 100644
index 0000000000..119d663508
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/cucumber/runtime/java/AndroidJavaBackendFactory.java
@@ -0,0 +1,31 @@
+package cucumber.runtime.java;
+
+import cucumber.api.java.ObjectFactory;
+import cucumber.runtime.BackendSupplier;
+import cucumber.runtime.ClassFinder;
+import cucumber.runtime.DefaultTypeRegistryConfiguration;
+import cucumber.runtime.Env;
+import cucumber.runtime.Reflections;
+import io.cucumber.core.api.TypeRegistryConfigurer;
+import io.cucumber.core.options.RuntimeOptions;
+import io.cucumber.stepexpression.TypeRegistry;
+
+import static java.util.Collections.singletonList;
+
+/**
+ * This factory is responsible for creating the {@see JavaBackend} with dex class finder.
+ */
+public class AndroidJavaBackendFactory {
+    public static BackendSupplier createBackend(RuntimeOptions runtimeOptions, ClassFinder classFinder) {
+        return () -> {
+            final Reflections reflections = new Reflections(classFinder);
+            final ObjectFactory delegateObjectFactory = ObjectFactoryLoader.loadObjectFactory(classFinder,
+                    JavaBackend.getObjectFactoryClassName(Env.INSTANCE), JavaBackend.getDeprecatedObjectFactoryClassName(Env.INSTANCE));
+            final TypeRegistryConfigurer typeRegistryConfigurer = reflections.instantiateExactlyOneSubclass(TypeRegistryConfigurer.class,
+                    runtimeOptions.getGlue(), new Class[0], new Object[0], new DefaultTypeRegistryConfiguration());
+            final TypeRegistry typeRegistry = new TypeRegistry(typeRegistryConfigurer.locale());
+            typeRegistryConfigurer.configureTypeRegistry(typeRegistry);
+            return singletonList(new JavaBackend(delegateObjectFactory, classFinder, typeRegistry));
+        };
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/android/runner/CucumberAndroidJUnitRunner.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/android/runner/CucumberAndroidJUnitRunner.java
new file mode 100644
index 0000000000..fe64e686c8
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/android/runner/CucumberAndroidJUnitRunner.java
@@ -0,0 +1,29 @@
+package io.cucumber.android.runner;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnitRunner;
+
+import io.cucumber.junit.CucumberArgumentsProvider;
+import io.cucumber.junit.CucumberAndroidJUnitArguments;
+
+/**
+ * {@link AndroidJUnitRunner} for cucumber tests. It supports running tests from Android Tests Orchestrator
+ */
+public class CucumberAndroidJUnitRunner extends AndroidJUnitRunner implements CucumberArgumentsProvider {
+
+    private CucumberAndroidJUnitArguments cucumberJUnitRunnerCore;
+
+    @Override
+    public void onCreate(final Bundle bundle) {
+        cucumberJUnitRunnerCore = new CucumberAndroidJUnitArguments(bundle);
+        super.onCreate(cucumberJUnitRunnerCore.processArgs());
+    }
+
+    @NonNull
+    @Override
+    public CucumberAndroidJUnitArguments getArguments() {
+        return cucumberJUnitRunnerCore;
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/cucumberexpressions/AndroidPatternCompiler.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/cucumberexpressions/AndroidPatternCompiler.java
new file mode 100644
index 0000000000..0e055fec51
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/cucumberexpressions/AndroidPatternCompiler.java
@@ -0,0 +1,11 @@
+package io.cucumber.cucumberexpressions;
+
+import java.util.regex.Pattern;
+
+public class AndroidPatternCompiler implements PatternCompiler {
+
+	@Override
+	public Pattern compile(String regexp, int flags) {
+		return Pattern.compile(regexp,flags& ~Pattern.UNICODE_CHARACTER_CLASS);
+	}
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidFeatureRunner.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidFeatureRunner.java
new file mode 100644
index 0000000000..79b99a3cad
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidFeatureRunner.java
@@ -0,0 +1,45 @@
+package io.cucumber.junit;
+
+import cucumber.runtime.model.CucumberFeature;
+import gherkin.ast.Feature;
+import io.cucumber.junit.AndroidPickleRunner;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+
+import java.util.List;
+
+public class AndroidFeatureRunner extends ParentRunner<AndroidPickleRunner> {
+
+	private final List<AndroidPickleRunner> children;
+	private final CucumberFeature cucumberFeature;
+
+	public AndroidFeatureRunner(Class<?> testClass, CucumberFeature cucumberFeature, List<AndroidPickleRunner> children) throws InitializationError	{
+		super(testClass);
+		this.cucumberFeature = cucumberFeature;
+		this.children = children;
+	}
+
+	@Override
+	public String getName() {
+		Feature feature = cucumberFeature.getGherkinFeature().getFeature();
+		return feature.getKeyword() + ": " + feature.getName();
+	}
+
+	@Override
+	protected List<AndroidPickleRunner> getChildren() {
+		return children;
+	}
+
+	@Override
+	protected Description describeChild(AndroidPickleRunner child) {
+		return child.getDescription();
+	}
+
+	@Override
+	protected void runChild(AndroidPickleRunner child, RunNotifier notifier) {
+		child.run(notifier);
+	}
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidJunitRuntimeOptionsFactory.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidJunitRuntimeOptionsFactory.java
new file mode 100644
index 0000000000..c9c0d76ae3
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidJunitRuntimeOptionsFactory.java
@@ -0,0 +1,57 @@
+package io.cucumber.junit;
+
+import android.content.Context;
+import android.util.Log;
+
+import cucumber.runtime.ClassFinder;
+import cucumber.runtime.CucumberException;
+import cucumber.runtime.Env;
+import cucumber.runtime.io.MultiLoader;
+import cucumber.runtime.io.ResourceLoader;
+import io.cucumber.core.model.GluePath;
+import io.cucumber.core.options.CucumberOptionsAnnotationParser;
+import io.cucumber.core.options.EnvironmentOptionsParser;
+import io.cucumber.core.options.RuntimeOptions;
+
+class AndroidJunitRuntimeOptionsFactory {
+    private static final String TAG = "cucumber-android";
+
+    static class Options {
+        final RuntimeOptions runtimeOptions;
+        final JUnitOptions jUnitOptions;
+
+        Options(RuntimeOptions runtimeOptions, JUnitOptions jUnitOptions) {
+            this.runtimeOptions = runtimeOptions;
+            this.jUnitOptions = jUnitOptions;
+        }
+    }
+
+    static Options createRuntimeOptions(Context context, ClassFinder classFinder, ClassLoader classLoader) {
+        for (final Class<?> clazz : classFinder.getDescendants(Object.class, GluePath.parse(context.getPackageName()))) {
+            if (clazz.isAnnotationPresent(CucumberOptions.class)) {
+                Log.d(TAG, "Found CucumberOptions in class " + clazz.getName());
+                ResourceLoader resourceLoader = new MultiLoader(classLoader);
+
+                RuntimeOptions runtimeOptions = new EnvironmentOptionsParser(resourceLoader)
+                        .parse(Env.INSTANCE)
+                        .build(new CucumberOptionsAnnotationParser(resourceLoader)
+                                .withOptionsProvider(new JUnitCucumberOptionsProvider())
+                                .parse(clazz)
+                                .build()
+                        );
+
+                JUnitOptions junitOptions = new JUnitOptionsParser()
+                        .parse(runtimeOptions.getJunitOptions())
+                        .setStrict(runtimeOptions.isStrict())
+                        .build(new JUnitOptionsParser()
+                                .parse(clazz)
+                                .build()
+                        );
+
+                return new Options(runtimeOptions, junitOptions);
+            }
+        }
+
+        throw new CucumberException("No CucumberOptions annotation");
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidLogcatReporter.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidLogcatReporter.java
new file mode 100644
index 0000000000..ec59079cdb
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidLogcatReporter.java
@@ -0,0 +1,86 @@
+package io.cucumber.junit;
+
+import android.util.Log;
+import cucumber.api.PickleStepTestStep;
+import cucumber.api.event.ConcurrentEventListener;
+import cucumber.api.event.EventHandler;
+import cucumber.api.event.EventPublisher;
+import cucumber.api.event.TestCaseStarted;
+import cucumber.api.event.TestRunFinished;
+import cucumber.api.event.TestStepStarted;
+import cucumber.runtime.UndefinedStepsTracker;
+import cucumber.runtime.formatter.Stats;
+
+/**
+ * Logs information about the currently executed statements to androids logcat.
+ */
+public final class AndroidLogcatReporter implements ConcurrentEventListener {
+
+    /**
+     * The log tag to be used when logging to logcat.
+     */
+    private final String logTag;
+
+    /**
+     * The event handler that logs the {@link TestCaseStarted} events.
+     */
+    private final EventHandler<TestCaseStarted> testCaseStartedHandler = new EventHandler<TestCaseStarted>() {
+        @Override
+        public void receive(TestCaseStarted event) {
+            Log.d(logTag, String.format("%s", event.testCase.getName()));
+        }
+    };
+
+    private final Stats stats;
+
+    private final UndefinedStepsTracker undefinedStepsTracker;
+
+    /**
+     * The event handler that logs the {@link TestStepStarted} events.
+     */
+    private final EventHandler<TestStepStarted> testStepStartedHandler = new EventHandler<TestStepStarted>() {
+        @Override
+        public void receive(TestStepStarted event) {
+            if (event.testStep instanceof PickleStepTestStep) {
+                PickleStepTestStep testStep = (PickleStepTestStep) event.testStep;
+                Log.d(logTag, String.format("%s", testStep.getStepText()));
+            }
+        }
+    };
+
+    /**
+     * The event handler that logs the {@link TestRunFinished} events.
+     */
+    private EventHandler<TestRunFinished> runFinishHandler = new EventHandler<TestRunFinished>() {
+
+        @Override
+        public void receive(TestRunFinished event) {
+            for (final Throwable throwable : stats.getErrors()) {
+                Log.e(logTag, throwable.toString());
+            }
+
+            for (final String snippet : undefinedStepsTracker.getSnippets()) {
+                Log.w(logTag, snippet);
+            }
+        }
+    };
+
+    /**
+     * Creates a new instance for the given parameters.
+     *
+     * @param undefinedStepsTracker
+     * @param logTag the tag to use for logging to logcat
+     */
+    public AndroidLogcatReporter(Stats stats, UndefinedStepsTracker undefinedStepsTracker, final String logTag) {
+        this.stats = stats;
+        this.undefinedStepsTracker = undefinedStepsTracker;
+        this.logTag = logTag;
+    }
+
+    @Override
+    public void setEventPublisher(final EventPublisher publisher) {
+        publisher.registerHandlerFor(TestCaseStarted.class, testCaseStartedHandler);
+        publisher.registerHandlerFor(TestStepStarted.class, testStepStartedHandler);
+        publisher.registerHandlerFor(TestRunFinished.class, runFinishHandler);
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidPickleRunner.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidPickleRunner.java
new file mode 100644
index 0000000000..32eb29fab2
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidPickleRunner.java
@@ -0,0 +1,53 @@
+package io.cucumber.junit;
+
+import cucumber.runner.Runner;
+import cucumber.runtime.model.CucumberFeature;
+import gherkin.events.PickleEvent;
+import gherkin.pickles.PickleStep;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+
+public class AndroidPickleRunner implements PickleRunners.PickleRunner {
+
+	private final Runner runner;
+	private final PickleEvent pickleEvent;
+	private final JUnitOptions jUnitOptions;
+	private Description description;
+	private final CucumberFeature feature;
+	private String scenarioName;
+
+	public AndroidPickleRunner(Runner runner, PickleEvent pickleEvent, JUnitOptions jUnitOptions, CucumberFeature feature, String scenarioName) {
+		this.runner = runner;
+		this.pickleEvent = pickleEvent;
+		this.jUnitOptions = jUnitOptions;
+		this.feature = feature;
+		this.scenarioName = scenarioName;
+	}
+
+	@Override
+	public Description getDescription()	{
+		if (description == null)
+		{
+			description = makeDescriptionFromPickle();
+		}
+		return description;
+	}
+
+	@Override
+	public Description describeChild(PickleStep step) {
+		throw new UnsupportedOperationException("This pickle runner does not wish to describe its children");
+	}
+
+	@Override
+	public void run(final RunNotifier notifier)	{
+		JUnitReporter jUnitReporter = new JUnitReporter(runner.getBus(), jUnitOptions);
+		jUnitReporter.startExecutionUnit(this, notifier);
+		runner.runPickle(pickleEvent);
+		jUnitReporter.finishExecutionUnit();
+	}
+
+	private Description makeDescriptionFromPickle()	{
+		return Description.createTestDescription(feature.getName(), scenarioName, new PickleRunners.PickleId(pickleEvent));
+	}
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResource.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResource.java
new file mode 100644
index 0000000000..b6060966fc
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResource.java
@@ -0,0 +1,53 @@
+package io.cucumber.junit;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import cucumber.runtime.io.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+/**
+ * Android specific implementation of {@link cucumber.runtime.io.Resource} which is apple
+ * to create {@link InputStream}s for android assets.
+ */
+public final class AndroidResource implements Resource {
+
+    /**
+     * The {@link Context} to get the {@link InputStream} from
+     */
+    private final Context context;
+
+    /**
+     * The path of the resource.
+     */
+    private final URI path;
+    private final String pathInAssets;
+
+    /**
+     * Creates a new instance for the given parameters.
+     * @param context the {@link Context} to create the {@link InputStream} from
+     * @param path the path to the resource
+     */
+    AndroidResource(final Context context, final URI path) {
+        this.context = context;
+        this.path = path;
+        this.pathInAssets = path.getSchemeSpecificPart();
+    }
+
+    @Override
+    public URI getPath() {
+        return path;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return context.getAssets().open(pathInAssets, AssetManager.ACCESS_UNKNOWN);
+    }
+
+    @Override
+    public String toString() {
+        return "AndroidResource (" + pathInAssets + ")";
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResourceLoader.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResourceLoader.java
new file mode 100644
index 0000000000..5b1322432a
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/AndroidResourceLoader.java
@@ -0,0 +1,68 @@
+package io.cucumber.junit;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import cucumber.runtime.CucumberException;
+import cucumber.runtime.io.Resource;
+import cucumber.runtime.io.ResourceLoader;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Android specific implementation of {@link cucumber.runtime.io.ResourceLoader} which loads non-class resources such as .feature files.
+ */
+final class AndroidResourceLoader implements ResourceLoader {
+
+    /**
+     * The format of the resource path.
+     */
+    private static final String RESOURCE_PATH_FORMAT = "%s/%s";
+
+    /**
+     * The {@link Context} to get the resources from.
+     */
+    private final Context context;
+
+    /**
+     * Creates a new instance for the given parameter.
+     *
+     * @param context the {@link Context} to get resources from
+     */
+    AndroidResourceLoader(final Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public Iterable<Resource> resources(final URI path, final String suffix) {
+        try {
+            final List<Resource> resources = new ArrayList<>();
+            final AssetManager assetManager = context.getAssets();
+            addResourceRecursive(resources, assetManager, path, suffix);
+            return resources;
+        } catch (final IOException e) {
+            throw new CucumberException("Error loading resources from " + path + " with suffix " + suffix, e);
+        }
+    }
+
+    private void addResourceRecursive(final List<Resource> resources,
+                                      final AssetManager assetManager,
+                                      final URI path,
+                                      final String suffix) throws IOException {
+        String schemeSpecificPart = path.getSchemeSpecificPart();
+        if (schemeSpecificPart.endsWith(suffix)) {
+            resources.add(new AndroidResource(context, path));
+            return;
+        }
+
+        String[] list = assetManager.list(schemeSpecificPart);
+        if (list != null) {
+            for (String name : list) {
+                String subPath = String.format(RESOURCE_PATH_FORMAT, path.toString(), name);
+                addResourceRecursive(resources, assetManager, URI.create(subPath), suffix);
+            }
+        }
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/Arguments.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/Arguments.java
new file mode 100644
index 0000000000..339969ef6e
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/Arguments.java
@@ -0,0 +1,195 @@
+package io.cucumber.junit;
+
+import android.os.Bundle;
+
+/**
+ * Holds instrumentation arguments.
+ */
+public class Arguments {
+
+    private static final String VALUE_SEPARATOR = "--";
+
+    /**
+     * Keys of supported arguments.
+     */
+    static class KEY {
+        static final String COUNT_ENABLED = "count";
+        static final String DEBUG_ENABLED = "debug";
+        static final String COVERAGE_ENABLED = "coverage";
+        static final String COVERAGE_DATA_FILE_PATH = "coverageFile";
+    }
+
+    /**
+     * Default values of supported arguments.
+     */
+    static class DEFAULT {
+        static final String COVERAGE_DATA_FILE_PATH = "coverage.ec";
+    }
+
+    private final boolean countEnabled;
+    private final boolean debugEnabled;
+    private final boolean coverageEnabled;
+    private final String coverageDataFilePath;
+    private final String cucumberOptions;
+
+    /**
+     * Constructs a new instance with arguments extracted from the given {@code bundle}.
+     *
+     * @param bundle the {@link Bundle} to extract the arguments from
+     */
+    public Arguments(final Bundle bundle) {
+        countEnabled = getBooleanArgument(bundle, KEY.COUNT_ENABLED);
+        debugEnabled = getBooleanArgument(bundle, KEY.DEBUG_ENABLED);
+        coverageEnabled = getBooleanArgument(bundle, KEY.COVERAGE_ENABLED);
+        coverageDataFilePath = getStringArgument(bundle, KEY.COVERAGE_DATA_FILE_PATH, DEFAULT.COVERAGE_DATA_FILE_PATH);
+        cucumberOptions = getCucumberOptionsString(bundle);
+    }
+
+    /**
+     * @return whether tests should not be executed, but just being counted
+     */
+    public boolean isCountEnabled() {
+        return countEnabled;
+    }
+
+    /**
+     * @return whether debugging is enabled or not
+     */
+    public boolean isDebugEnabled() {
+        return debugEnabled;
+    }
+
+    /**
+     * @return the path to the coverage data file, defaults to "coverage.ec"
+     */
+    public String coverageDataFilePath() {
+        return coverageDataFilePath;
+    }
+
+    /**
+     * @return whether coverage is enabled or not
+     */
+    public boolean isCoverageEnabled() {
+        return coverageEnabled;
+    }
+
+    /**
+     * @return the cucumber options string
+     */
+    public String getCucumberOptions() {
+        return cucumberOptions;
+    }
+
+    /**
+     * Extracts a boolean value from the bundle which is stored as string.
+     * Given the string value is "true" the boolean value will be {@code true},
+     * given the string value is "false the boolean value will be {@code false}.
+     * The case in the string is ignored. In case no value is found this method
+     * returns false. In case the given {@code bundle} is {@code null} {@code false}
+     * will be returned.
+     *
+     * @param bundle the {@link Bundle} to get the value from
+     * @param key    the key to get the value for
+     * @return the boolean representation of the string value found for the given key,
+     * or false in case no value was found
+     */
+    private boolean getBooleanArgument(final Bundle bundle, final String key) {
+
+        if (bundle == null) {
+            return false;
+        }
+
+        final String tagString = bundle.getString(key);
+        return tagString != null && Boolean.parseBoolean(tagString);
+    }
+
+    /**
+     * Extracts a string value from the bundle, gracefully falling back to the provided {@code defaultValue}
+     * in case no value could be found for the given {@code key} or the {@code bundle} was {@code null}.
+     *
+     * @param bundle       the {@link Bundle} to get the value from
+     * @param key          the key to get the value for
+     * @param defaultValue the default value to take in case no value could be found or the {@code bundle} was {@code null}
+     * @return the string value for the given {@code key}
+     */
+    private String getStringArgument(final Bundle bundle, final String key, final String defaultValue) {
+        if (bundle == null) {
+            return defaultValue;
+        }
+        return bundle.getString(key, defaultValue);
+    }
+
+    /**
+     * Adds the given {@code optionKey} and its {@code optionValue} tot he given string buffer. This method will split
+     * potential multiple option values separated by {@link Arguments#VALUE_SEPARATOR} into a space
+     * separated list of those values.
+     */
+    private void appendOption(final StringBuilder sb, final String optionKey, final String optionValue) {
+        for (final String value : optionValue.split(VALUE_SEPARATOR)) {
+            sb.append(sb.length() == 0 || optionKey.isEmpty() ? "" : " ").append(optionKey).append(optionValue.isEmpty() ? "" : " " + value);
+        }
+    }
+
+    /**
+     * Returns a Cucumber options compatible string based on the argument extras found.
+     * <p/>
+     * The bundle <em>cannot</em> contain multiple entries for the same key,
+     * however certain Cucumber options can be passed multiple times (e.g.
+     * {@code --tags}). The solution is to pass values separated by
+     * {@link Arguments#VALUE_SEPARATOR} which will result
+     * in multiple {@code --key value} pairs being created.
+     *
+     * @param bundle the {@link Bundle} to get the values from
+     * @return the cucumber options string
+     */
+    private String getCucumberOptionsString(final Bundle bundle) {
+
+        if (bundle == null) {
+            return "";
+        }
+
+        final String cucumberOptions = bundle.getString("cucumberOptions");
+        if (cucumberOptions != null) {
+            return cucumberOptions;
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        String features = "";
+
+        for (final String key : bundle.keySet()) {
+            if ("glue".equals(key)) {
+                appendOption(sb, "--glue", bundle.getString(key));
+            } else if ("format".equals(key)) {
+                appendOption(sb, "--format", bundle.getString(key));
+            } else if ("plugin".equals(key)) {
+                appendOption(sb, "--plugin", bundle.getString(key));
+            } else if ("tags".equals(key)) {
+                appendOption(sb, "--tags", bundle.getString(key));
+            } else if ("name".equals(key)) {
+                appendOption(sb, "--name", bundle.getString(key));
+            } else if ("dryRun".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--dry-run", "");
+            } else if ("log".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--dry-run", "");
+            } else if ("noDryRun".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--no-dry-run", "");
+            } else if ("monochrome".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--monochrome", "");
+            } else if ("noMonochrome".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--no-monochrome", "");
+            } else if ("strict".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--strict", "");
+            } else if ("noStrict".equals(key) && getBooleanArgument(bundle, key)) {
+                appendOption(sb, "--no-strict", "");
+            } else if ("snippets".equals(key)) {
+                appendOption(sb, "--snippets", bundle.getString(key));
+            } else if ("features".equals(key)) {
+                features = bundle.getString(key);
+            }
+        }
+        // Even though not strictly required, wait until everything else
+        // has been added before adding any feature references
+        appendOption(sb, "", features);
+        return sb.toString();
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CoverageDumper.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CoverageDumper.java
new file mode 100644
index 0000000000..2b2e8caa80
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CoverageDumper.java
@@ -0,0 +1,104 @@
+package io.cucumber.junit;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Dumps coverage data into a file.
+ */
+public class CoverageDumper {
+
+    /**
+     * The key for the result bundle value which will contain the path to file containing the coverage data.
+     */
+    private static final String RESULT_KEY_COVERAGE_PATH = "coverageFilePath";
+
+    /**
+     * The string format to be appended to the result stream in case coverage data could be dumped successfully.
+     */
+    private static final String RESULT_STREAM_SUCCESS_OUTPUT_FORMAT = "Generated code coverage data to %s";
+
+    /**
+     * The string to be logged with logcat in case coverage data could not be dumped successfully.
+     */
+    private static final String LOG_ERROR_OUTPUT = "Failed to generate coverage.";
+
+    /**
+     * The string to be appended to the result stream in case coverage data could not be dumped successfully.
+     */
+    private static final String RESULT_STREAM_ERROR_OUTPUT = "Error: Failed to generate coverage. Check logcat for details.";
+
+    /**
+     * The implementation of the code coverage tool.
+     * Currently known implementations are emma and jacoco.
+     */
+    private static final String IMPLEMENTATION_CLASS = "com.vladium.emma.rt.RT";
+
+    /**
+     * The method to call for dumping the coverage data.
+     */
+    private static final String IMPLEMENTATION_METHOD = "dumpCoverageData";
+
+    /**
+     * The arguments to work with.
+     */
+    private final Arguments arguments;
+
+    /**
+     * Creates a new instance for the given arguments.
+     *
+     * @param arguments the arguments to work with
+     */
+    public CoverageDumper(final Arguments arguments) {
+        this.arguments = arguments;
+    }
+
+    /**
+     * Dumps the coverage data into the given file, if code coverage is enabled.
+     *
+     * @param bundle the {@link Bundle} to put coverage information into
+     */
+    public void requestDump(final Bundle bundle) {
+
+        if (!arguments.isCoverageEnabled()) {
+            return;
+        }
+
+        final String coverageDateFilePath = arguments.coverageDataFilePath();
+        final File coverageFile = new File(coverageDateFilePath);
+
+        try {
+            final Class dumperClass = Class.forName(IMPLEMENTATION_CLASS);
+            final Method dumperMethod = dumperClass.getMethod(IMPLEMENTATION_METHOD, coverageFile.getClass(), boolean.class, boolean.class);
+            dumperMethod.invoke(null, coverageFile, false, false);
+
+            bundle.putString(RESULT_KEY_COVERAGE_PATH, coverageDateFilePath);
+            appendNewLineToResultStream(bundle, String.format(RESULT_STREAM_SUCCESS_OUTPUT_FORMAT, coverageDateFilePath));
+        } catch (final ClassNotFoundException e) {
+            reportError(bundle, e);
+        } catch (final SecurityException e) {
+            reportError(bundle, e);
+        } catch (final NoSuchMethodException e) {
+            reportError(bundle, e);
+        } catch (final IllegalAccessException e) {
+            reportError(bundle, e);
+        } catch (final InvocationTargetException e) {
+            reportError(bundle, e);
+        }
+    }
+
+    private void reportError(final Bundle results, final Exception e) {
+        Log.e(CucumberJUnitRunner.TAG, LOG_ERROR_OUTPUT, e);
+        appendNewLineToResultStream(results, RESULT_STREAM_ERROR_OUTPUT);
+    }
+
+    private void appendNewLineToResultStream(final Bundle results, final String message) {
+        final String currentStream = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+        results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, currentStream + "\n" + message);
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberAndroidJUnitArguments.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberAndroidJUnitArguments.java
new file mode 100644
index 0000000000..54c403cf3a
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberAndroidJUnitArguments.java
@@ -0,0 +1,95 @@
+package io.cucumber.junit;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.runner.AndroidJUnitRunner;
+
+/**
+ * This class is responsible for preparing bundle to{@link AndroidJUnitRunner}
+ * for cucumber tests. It prepares bundle for running tests from Android Tests Orchestrator
+ * <p>
+ * Runner argument are linked to the {@link androidx.test.internal.runner.RunnerArgs}
+ */
+public class CucumberAndroidJUnitArguments {
+
+    /**
+     * External API argument keys.
+     */
+    public static class Args {
+
+        /**
+         * User default android junit runner. public interface
+         */
+        public static final String USE_DEFAULT_ANDROID_RUNNER = "cucumberUseAndroidJUnitRunner";
+    }
+
+    /**
+     * Cucumber internal use argument keys.
+     */
+    static class InternalCucumberAndroidArgs {
+
+        static final String CUCUMBER_ANDROID_TEST_CLASS = "cucumberAndroidTestClass";
+    }
+
+    /**
+     * Runner argument are linked to the {@link androidx.test.internal.runner.RunnerArgs}.
+     */
+    private static class AndroidJunitRunnerArgs {
+        /**
+         * {@link androidx.test.internal.runner.RunnerArgs#ARGUMENT_RUNNER_BUILDER}
+         */
+        private static final String ARGUMENT_ORCHESTRATOR_RUNNER_BUILDER = "runnerBuilder";
+
+        /**
+         * {@link androidx.test.internal.runner.RunnerArgs#ARGUMENT_TEST_CLASS}
+         */
+        private static final String ARGUMENT_ORCHESTRATOR_CLASS = "class";
+    }
+
+    private static final String TRUE = Boolean.TRUE.toString();
+    private static final String FALSE = Boolean.FALSE.toString();
+
+    @NonNull
+    private final Bundle originalArgs;
+
+    @Nullable
+    private Bundle processedArgs;
+
+    public CucumberAndroidJUnitArguments(@NonNull Bundle arguments) {
+        this.originalArgs = new Bundle(arguments);
+    }
+
+    public Bundle processArgs() {
+        processedArgs = new Bundle(originalArgs);
+
+        if (TRUE.equals(originalArgs.getString(Args.USE_DEFAULT_ANDROID_RUNNER, FALSE))) {
+            return processedArgs;
+        }
+
+        processedArgs.putString(AndroidJunitRunnerArgs.ARGUMENT_ORCHESTRATOR_RUNNER_BUILDER, CucumberJUnitRunnerBuilder.class.getName());
+
+        String testClass = originalArgs.getString(AndroidJunitRunnerArgs.ARGUMENT_ORCHESTRATOR_CLASS);
+        if (testClass != null && !testClass.isEmpty()) {
+
+            //if this runner is executed for single class (e.g. from orchestrator or spoon), we set
+            //special option to let CucumberJUnitRunner handle this
+            processedArgs.putString(InternalCucumberAndroidArgs.CUCUMBER_ANDROID_TEST_CLASS, testClass);
+        }
+
+        //there is no need to scan all classes - we can fake this execution to be for single class
+        //because we delegate test execution to CucumberJUnitRunner
+        processedArgs.putString(AndroidJunitRunnerArgs.ARGUMENT_ORCHESTRATOR_CLASS, CucumberJUnitRunnerBuilder.class.getName());
+
+        return processedArgs;
+    }
+
+    @NonNull
+    Bundle getRunnerArgs() {
+        if (processedArgs == null) {
+            processedArgs = processArgs();
+        }
+        return processedArgs;
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberArgumentsProvider.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberArgumentsProvider.java
new file mode 100644
index 0000000000..3ab3a8396e
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberArgumentsProvider.java
@@ -0,0 +1,8 @@
+package io.cucumber.junit;
+
+import androidx.annotation.NonNull;
+
+public interface CucumberArgumentsProvider {
+    @NonNull
+    CucumberAndroidJUnitArguments getArguments();
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunner.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunner.java
new file mode 100644
index 0000000000..edce3643a7
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunner.java
@@ -0,0 +1,295 @@
+package io.cucumber.junit;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import cucumber.api.event.TestRunFinished;
+import cucumber.api.event.TestRunStarted;
+import cucumber.runner.EventBus;
+import cucumber.runner.Runner;
+import cucumber.runner.ThreadLocalRunnerSupplier;
+import cucumber.runner.TimeService;
+import cucumber.runner.TimeServiceEventBus;
+import cucumber.runtime.ClassFinder;
+import cucumber.runtime.CucumberException;
+import cucumber.runtime.FeaturePathFeatureSupplier;
+import cucumber.runtime.FeatureSupplier;
+import cucumber.runtime.UndefinedStepsTracker;
+import cucumber.runtime.Utils;
+import cucumber.runtime.filter.Filters;
+import cucumber.runtime.formatter.PluginFactory;
+import cucumber.runtime.formatter.Plugins;
+import cucumber.runtime.formatter.Stats;
+import cucumber.runtime.java.AndroidJavaBackendFactory;
+import cucumber.runtime.model.CucumberFeature;
+import cucumber.runtime.model.FeatureLoader;
+import dalvik.system.DexFile;
+import gherkin.ast.Examples;
+import gherkin.ast.Feature;
+import gherkin.ast.ScenarioDefinition;
+import gherkin.ast.ScenarioOutline;
+import gherkin.ast.TableRow;
+import gherkin.events.PickleEvent;
+import io.cucumber.core.options.RuntimeOptions;
+
+public class CucumberJUnitRunner extends ParentRunner<AndroidFeatureRunner> implements Filterable {
+
+
+    /**
+     * The logcat tag to log all cucumber related information to.
+     */
+    static final String TAG = "cucumber-android";
+
+    /**
+     * The system property name of the cucumber options.
+     */
+    private static final String CUCUMBER_OPTIONS_SYSTEM_PROPERTY = "cucumber.options";
+
+    /**
+     * The {@link RuntimeOptions} to get the {@link CucumberFeature}s from.
+     */
+    private List<AndroidFeatureRunner> children = new ArrayList<>();
+
+    private EventBus bus;
+
+    public CucumberJUnitRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        Bundle runnerArguments = getRunnerBundle(instrumentation);
+        Arguments arguments = new Arguments(runnerArguments);
+
+        trySetCucumberOptionsToSystemProperties(arguments);
+        Context context = instrumentation.getContext();
+        ClassLoader classLoader = context.getClassLoader();
+        ClassFinder classFinder = createDexClassFinder(context);
+        AndroidJunitRuntimeOptionsFactory.Options options = AndroidJunitRuntimeOptionsFactory.createRuntimeOptions(context, classFinder, classLoader);
+
+        bus = new TimeServiceEventBus(TimeService.SYSTEM);
+
+        Runner runner = new ThreadLocalRunnerSupplier(options.runtimeOptions, bus, AndroidJavaBackendFactory.createBackend(options.runtimeOptions, classFinder)).get();
+        FeatureLoader featureLoader = new FeatureLoader(new AndroidResourceLoader(context));
+        FeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(featureLoader, options.runtimeOptions);
+        Filters filters = new Filters(options.runtimeOptions);
+        UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
+        undefinedStepsTracker.setEventPublisher(bus);
+        Stats stats = new Stats();
+        stats.setEventPublisher(bus);
+
+        Plugins plugins = new Plugins(classLoader, new PluginFactory(), options.runtimeOptions);
+        plugins.addPlugin(new AndroidLogcatReporter(stats, undefinedStepsTracker, TAG));
+        //must be after registering plugins
+        if (options.runtimeOptions.isMultiThreaded()) {
+            plugins.setSerialEventBusOnEventListenerPlugins(bus);
+        } else {
+            plugins.setEventBusOnEventListenerPlugins(bus);
+        }
+
+        //check if this is for single scenario
+        String testClassNameFromRunner = runnerArguments.getString(CucumberAndroidJUnitArguments.InternalCucumberAndroidArgs.CUCUMBER_ANDROID_TEST_CLASS);
+        String requestedFeatureName = null;
+        String requestedScenarioName = null;
+        if (testClassNameFromRunner != null) {
+            String[] split = testClassNameFromRunner.split("#");
+            if (split.length > 1) {
+                requestedFeatureName = split[0];
+                requestedScenarioName = split[1];
+            } else {
+                Log.e(TAG, "CucumberJUnitRunner: invalid argument '" + CucumberAndroidJUnitArguments.InternalCucumberAndroidArgs.CUCUMBER_ANDROID_TEST_CLASS + "' = '" + testClassNameFromRunner + "'");
+            }
+        }
+        // Start the run before reading the features.
+        // Allows the test source read events to be broadcast properly
+        List<CucumberFeature> features = featureSupplier.get();
+        Collection<String> featuresNames = new HashSet<>(features.size());
+        StringBuilder duplicateScenariosNameMessage = new StringBuilder();
+        bus.send(new TestRunStarted(bus.getTime(), bus.getTimeMillis()));
+
+
+        for (CucumberFeature feature : features) {
+            feature.sendTestSourceRead(bus);
+            String featureName = feature.getName();
+            if (requestedFeatureName != null && !requestedFeatureName.equals(featureName)) {
+                continue;
+            }
+            List<PickleEvent> pickles = feature.getPickles();
+            List<AndroidPickleRunner> pickleRunners = new ArrayList<>(pickles.size());
+            Collection<String> pickleNames = new HashSet<>(pickles.size());
+            for (PickleEvent pickleEvent : pickles) {
+
+                if (filters.matchesFilters(pickleEvent)) {
+                    String currentScenarioName = getScenarioName(pickleEvent, feature.getGherkinFeature().getFeature());
+                    if (pickleNames.contains(currentScenarioName)) {
+                        // in case of scenario name duplication in single feature:
+                        addDuplicateScenarioMessage(duplicateScenariosNameMessage, featureName, currentScenarioName);
+                    }
+                    pickleNames.add(currentScenarioName);
+
+                    if (requestedScenarioName != null) {
+                        if (requestedScenarioName.equals(currentScenarioName)) {
+                            AndroidPickleRunner pickleRunner = new AndroidPickleRunner(runner, pickleEvent, options.jUnitOptions, feature, currentScenarioName);
+                            pickleRunners.add(pickleRunner);
+                            children.add(new AndroidFeatureRunner(testClass, feature, pickleRunners));
+                            throwErrorIfDuplicateScenarios(duplicateScenariosNameMessage);
+                            return;
+                        }
+                    } else {
+                        AndroidPickleRunner pickleRunner = new AndroidPickleRunner(runner, pickleEvent, options.jUnitOptions, feature, currentScenarioName);
+                        pickleRunners.add(pickleRunner);
+                    }
+                }
+            }
+            addFeatureIfHasChildren(testClass, featuresNames, duplicateScenariosNameMessage, feature, featureName, pickleRunners);
+        }
+        throwErrorIfDuplicateScenarios(duplicateScenariosNameMessage);
+    }
+
+    private Bundle getRunnerBundle(Instrumentation instrumentation) throws InitializationError {
+        if (!(instrumentation instanceof CucumberArgumentsProvider)) {
+            Log.e(TAG, "Runner must implement CucumberArgumentsProvider");
+            throw new InitializationError("Use runner that implements CucumberArgumentsProvider class.");
+        }
+
+        return ((CucumberArgumentsProvider) instrumentation).getArguments().getRunnerArgs();
+    }
+
+    private void addFeatureIfHasChildren(Class<?> testClass, Collection<String> featuresNames, StringBuilder duplicateScenariosNameMessage,
+                                         CucumberFeature feature, String featureName, List<AndroidPickleRunner> pickleRunners) throws InitializationError {
+        if (!pickleRunners.isEmpty()) {
+            if (featuresNames.contains(featureName)) {
+                // in case of feature name duplication:
+                addDuplicateFeatureMessage(duplicateScenariosNameMessage, featureName);
+            }
+            featuresNames.add(featureName);
+            children.add(new AndroidFeatureRunner(testClass, feature, pickleRunners));
+        }
+    }
+
+    private static void throwErrorIfDuplicateScenarios(CharSequence duplicateScenariosNameMessage) throws InitializationError {
+        if (duplicateScenariosNameMessage.length() > 0) {
+            InitializationError error = new InitializationError(duplicateScenariosNameMessage.toString());
+            Log.e(TAG, "CucumberJUnitRunner: ", error);
+            throw error;
+        }
+    }
+
+    private static void addDuplicateFeatureMessage(StringBuilder duplicateScenariosNameMessage, String featureName) {
+        duplicateScenariosNameMessage
+                .append('\n')
+                .append("Duplicate feature name '")
+                .append(featureName)
+                .append("'");
+    }
+
+    private static void addDuplicateScenarioMessage(StringBuilder duplicateScenariosNameMessage, String featureName, String currentScenarioName) {
+        duplicateScenariosNameMessage.append('\n')
+                .append("Duplicate scenario name '")
+                .append(currentScenarioName)
+                .append("' in feature '")
+                .append(featureName)
+                .append("'");
+    }
+
+    private static String getScenarioName(PickleEvent pickleEvent, Feature feature) {
+        int exampleNumber = findExampleNumber(pickleEvent, feature);
+        String pickleName = pickleEvent.pickle.getName();
+        if (exampleNumber > 0) {
+            // if this is example we always add example number in case of sharding
+            return Utils.getUniqueTestNameForScenarioExample(pickleName, exampleNumber);
+        }
+        return pickleName;
+    }
+
+
+    private static int findExampleNumber(PickleEvent pickleEvent, Feature feature) {
+        int pickleLine = getLine(pickleEvent);
+        for (ScenarioDefinition definition : feature.getChildren()) {
+            if (definition instanceof ScenarioOutline) {
+                List<Examples> examples = ((ScenarioOutline) definition).getExamples();
+                int index = 0;
+                for (Examples example : examples) {
+                    List<TableRow> tableBody = example.getTableBody();
+                    for (TableRow row : tableBody) {
+                        if (row.getLocation().getLine() == pickleLine) {
+                            return index + 1;
+                        }
+                        index++;
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+
+    private static int getLine(PickleEvent pickleEvent) {
+        return pickleEvent.pickle.getLocations().get(0).getLine();
+    }
+
+
+    @Override
+    protected List<AndroidFeatureRunner> getChildren() {
+        return children;
+    }
+
+    @Override
+    protected Description describeChild(AndroidFeatureRunner child) {
+        return child.getDescription();
+    }
+
+    @Override
+    protected void runChild(AndroidFeatureRunner child, RunNotifier notifier) {
+        child.run(notifier);
+    }
+
+
+    private static void trySetCucumberOptionsToSystemProperties(final Arguments arguments) {
+        final String cucumberOptions = arguments.getCucumberOptions();
+        if (!cucumberOptions.isEmpty()) {
+            Log.d(TAG, "Setting cucumber.options from arguments: '" + cucumberOptions + "'");
+            System.setProperty(CUCUMBER_OPTIONS_SYSTEM_PROPERTY, cucumberOptions);
+        }
+    }
+
+    private static ClassFinder createDexClassFinder(final Context context) {
+        final String apkPath = context.getPackageCodePath();
+        return new DexClassFinder(newDexFile(apkPath));
+    }
+
+    private static DexFile newDexFile(final String apkPath) {
+        try {
+            return new DexFile(apkPath);
+        } catch (final IOException e) {
+            throw new CucumberException("Failed to open " + apkPath);
+        }
+    }
+
+    @Override
+    protected Statement childrenInvoker(RunNotifier notifier) {
+        final Statement features = super.childrenInvoker(notifier);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                features.evaluate();
+                bus.send(new TestRunFinished(bus.getTime(), bus.getTimeMillis()));
+            }
+        };
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunnerBuilder.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunnerBuilder.java
new file mode 100644
index 0000000000..4fcb8da7f3
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/CucumberJUnitRunnerBuilder.java
@@ -0,0 +1,16 @@
+package io.cucumber.junit;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+
+public class CucumberJUnitRunnerBuilder extends RunnerBuilder {
+    @Override
+    public Runner runnerForClass(Class<?> testClass) throws InitializationError {
+        if (testClass.equals(getClass())) {
+            return new CucumberJUnitRunner(testClass);
+        }
+
+        return null;
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DebuggerWaiter.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DebuggerWaiter.java
new file mode 100644
index 0000000000..94bfe644d3
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DebuggerWaiter.java
@@ -0,0 +1,32 @@
+package io.cucumber.junit;
+
+import android.os.Debug;
+
+/**
+ * Waits for the debugger, if configured through the given {@link Arguments}.
+ */
+public final class DebuggerWaiter {
+
+    /**
+     * The arguments to work with.
+     */
+    private final Arguments arguments;
+
+    /**
+     * Creates a new instance for the given arguments.
+     *
+     * @param arguments the {@link Arguments} which specify whether waiting is required.
+     */
+    public DebuggerWaiter(final Arguments arguments) {
+        this.arguments = arguments;
+    }
+
+    /**
+     * Waits until a debugger is attached, if configured.
+     */
+    public void requestWaitForDebugger() {
+        if (arguments.isDebugEnabled()) {
+            Debug.waitForDebugger();
+        }
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DexClassFinder.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DexClassFinder.java
new file mode 100644
index 0000000000..39a8490c32
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/DexClassFinder.java
@@ -0,0 +1,123 @@
+package io.cucumber.junit;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import cucumber.runtime.ClassFinder;
+import dalvik.system.DexFile;
+import io.cucumber.core.model.Classpath;
+
+/**
+ * Android specific implementation of {@link ClassFinder} which loads classes contained in the provided {@link DexFile}.
+ */
+final class DexClassFinder implements ClassFinder {
+
+    /**
+     * Symbol name of the manifest class.
+     */
+    private static final String MANIFEST_CLASS_NAME = "Manifest";
+
+    /**
+     * Symbol name of the resource class.
+     */
+    private static final String RESOURCE_CLASS_NAME = "R";
+
+    /**
+     * Symbol name prefix of any inner class of the resource class.
+     */
+    private static final String RESOURCE_INNER_CLASS_NAME_PREFIX = "R$";
+
+    /**
+     * The file name separator.
+     */
+    private static final String FILE_NAME_SEPARATOR = ".";
+
+    /**
+     * The class loader to actually load the classes specified by the {@link DexFile}.
+     */
+    private static final ClassLoader CLASS_LOADER = DexClassFinder.class.getClassLoader();
+
+    /**
+     * The "symbol" representing the default package.
+     */
+    private static final String DEFAULT_PACKAGE = "";
+    private static final Pattern PATH_SEPARATOR_PATTERN = Pattern.compile("/", Pattern.LITERAL);
+    /**
+     * The {@link DexFile} to load classes from
+     */
+    private final DexFile dexFile;
+
+    /**
+     * Creates a new instance for the given parameter.
+     *
+     * @param dexFile the {@link DexFile} to load classes from
+     */
+    DexClassFinder(final DexFile dexFile) {
+        this.dexFile = dexFile;
+    }
+
+    @Override
+    public <T> Collection<Class<? extends T>> getDescendants(final Class<T> parentType, final URI packageName) {
+        final List<Class<? extends T>> result = new ArrayList<Class<? extends T>>();
+        String packageNameString = PATH_SEPARATOR_PATTERN.matcher(Classpath.resourceName(packageName)).replaceAll(Matcher.quoteReplacement("."));
+        final Enumeration<String> entries = dexFile.entries();
+        while (entries.hasMoreElements()) {
+            final String className = entries.nextElement();
+            if (isInPackage(className, packageNameString) && !isGenerated(className)) {
+                try {
+                    final Class<? extends T> clazz = loadClass(className);
+                    if (!parentType.equals(clazz) && parentType.isAssignableFrom(clazz) && canGetMethods(clazz)) {
+                        result.add(clazz.asSubclass(parentType));
+                    }
+                } catch (ClassNotFoundException ignored) {
+                    //ignore, class cannot be loaded - same as in ResourceLoaderClassFinder
+                } catch (NoClassDefFoundError ignored){
+                    //ignore, class cannot be loaded - same as in ResourceLoaderClassFinder
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public <T> Class<? extends T> loadClass(final String className) throws ClassNotFoundException {
+        return (Class<? extends T>) Class.forName(className, false, CLASS_LOADER);
+    }
+
+    private boolean isInPackage(final String className, final String packageName) {
+        final int lastDotIndex = className.lastIndexOf(FILE_NAME_SEPARATOR);
+        final String classPackage = lastDotIndex == -1 ? DEFAULT_PACKAGE : className.substring(0, lastDotIndex);
+        return classPackage.startsWith(packageName);
+    }
+
+    private static boolean isGenerated(final String className) {
+        return isAndroidGenerated(className) || isKotlinGenerated(className);
+    }
+
+    private static boolean isAndroidGenerated(final String className) {
+        final int lastDotIndex = className.lastIndexOf(FILE_NAME_SEPARATOR);
+        final String shortName = lastDotIndex == -1 ? className : className.substring(lastDotIndex + 1);
+        return shortName.equals(MANIFEST_CLASS_NAME) || shortName.equals(RESOURCE_CLASS_NAME) || shortName.startsWith(RESOURCE_INNER_CLASS_NAME_PREFIX);
+    }
+
+    private static boolean isKotlinGenerated(String className) {
+        return className.contains("$$inlined$");
+    }
+
+    /**
+     * On older apis obtaining all methods via {@link Class#getMethods()} can lead to NoClassDefFoundError if such methods has parameters with unavailable on this api classes such as {@link java.util.function.Function}
+     */
+    private static boolean canGetMethods(Class<?> clazz) {
+        try {
+            clazz.getMethods();
+        } catch (NoClassDefFoundError ignored){
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/FeatureCompiler.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/FeatureCompiler.java
new file mode 100644
index 0000000000..aafac2830e
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/FeatureCompiler.java
@@ -0,0 +1,33 @@
+package io.cucumber.junit;
+
+import cucumber.runtime.filter.Filters;
+import cucumber.runtime.model.CucumberFeature;
+import gherkin.events.PickleEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to count scenarios, including outlined.
+ */
+final class FeatureCompiler {
+
+    /**
+     * Compilers the given {@code cucumberFeatures} to {@link PickleEvent}s.
+     *
+     * @param cucumberFeatures the list of {@link CucumberFeature} to compile
+     * @return the compiled pickles in {@link PickleEvent}s
+     */
+    static List<PickleEvent> compile(final List<CucumberFeature> cucumberFeatures, final Filters filters) {
+        List<PickleEvent> pickles = new ArrayList<PickleEvent>();
+        for (final CucumberFeature feature : cucumberFeatures) {
+            for (final PickleEvent pickleEvent : feature.getPickles()) {
+                if (filters.matchesFilters(pickleEvent)) {
+                    pickles.add(pickleEvent);
+                }
+            }
+        }
+        return pickles;
+    }
+
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/MissingStepDefinitionError.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/MissingStepDefinitionError.java
new file mode 100644
index 0000000000..c5f07377b5
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/java/io/cucumber/junit/MissingStepDefinitionError.java
@@ -0,0 +1,16 @@
+package io.cucumber.junit;
+
+/**
+ * Indicates that there was a missing step in the execution of the scenario lifecycle.
+ */
+final class MissingStepDefinitionError extends AssertionError {
+
+    /**
+     * Creates a new instance for the given snippet.
+     *
+     * @param snippet the suggested snippet which could be implemented to avoid this exception
+     */
+    MissingStepDefinitionError(final String snippet) {
+        super(String.format("\n\n%s", snippet));
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/main/resources/META-INF/services/io.cucumber.cucumberexpressions.PatternCompiler b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/resources/META-INF/services/io.cucumber.cucumberexpressions.PatternCompiler
new file mode 100644
index 0000000000..1b7a15a7fb
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/main/resources/META-INF/services/io.cucumber.cucumberexpressions.PatternCompiler
@@ -0,0 +1 @@
+io.cucumber.cucumberexpressions.AndroidPatternCompiler
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/com/vladium/emma/rt/RT.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/com/vladium/emma/rt/RT.java
new file mode 100644
index 0000000000..abffbe158f
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/com/vladium/emma/rt/RT.java
@@ -0,0 +1,39 @@
+package com.vladium.emma.rt;
+
+import java.io.File;
+
+/**
+ * This is just a stub implementation to test the code coverage logic, it should not be used in multi threaded tests.
+ */
+public class RT {
+
+    private static File lastFile;
+    private static Throwable throwable;
+
+    private RT() {
+    }
+
+    public static void dumpCoverageData(final File file, final boolean merge, final boolean stopDataCollection) throws Throwable {
+
+        if (throwable != null) {
+            throw throwable;
+        }
+
+        file.createNewFile();
+        lastFile = file;
+    }
+
+    public static void throwOnNextInvocation(final Throwable throwable) {
+        RT.throwable = throwable;
+    }
+
+    public static void resetMock() {
+        lastFile = null;
+        throwable = null;
+    }
+
+    public static File getLastFile() {
+        return lastFile;
+    }
+}
+
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidPatternCompilerTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidPatternCompilerTest.java
new file mode 100644
index 0000000000..684a30d7e9
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidPatternCompilerTest.java
@@ -0,0 +1,30 @@
+package io.cucumber.junit;
+
+import io.cucumber.cucumberexpressions.AndroidPatternCompiler;
+import io.cucumber.cucumberexpressions.PatternCompiler;
+
+import org.junit.Test;
+
+import java.util.ServiceLoader;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AndroidPatternCompilerTest {
+
+	@Test
+	public void compiles_pattern_only_with_supported_flag() {
+
+		AndroidPatternCompiler compiler = (AndroidPatternCompiler) ServiceLoader.load(PatternCompiler.class).iterator().next();
+
+		Pattern pattern = compiler.compile("HELLO", Pattern.UNICODE_CHARACTER_CLASS);
+
+		assertFalse(pattern.matcher("hello").find());
+
+
+		pattern = compiler.compile("HELLO", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
+
+		assertTrue(pattern.matcher("hello").find());
+	}
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceLoaderTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceLoaderTest.java
new file mode 100644
index 0000000000..a8f92a7976
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceLoaderTest.java
@@ -0,0 +1,111 @@
+package io.cucumber.junit;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import com.google.common.collect.Lists;
+import cucumber.runtime.io.Resource;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.Mockito.RETURNS_SMART_NULLS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class AndroidResourceLoaderTest {
+
+    private final Context context = mock(Context.class);
+    private final AssetManager assetManager = mock(AssetManager.class, RETURNS_SMART_NULLS);
+    private final AndroidResourceLoader androidResourceLoader = new AndroidResourceLoader(context);
+
+    @Before
+    public void beforeEachTest() {
+        when(context.getAssets()).thenReturn(assetManager);
+    }
+
+    @Test
+    public void retrieves_resource_by_given_path_and_suffix() {
+
+        // given
+        final URI path = URI.create("file:some/path/some.feature");
+        final String suffix = "feature";
+
+        // when
+        final List<Resource> resources = Lists.newArrayList(androidResourceLoader.resources(path, suffix));
+
+        // then
+        assertThat(resources.size(), is(1));
+        assertThat(resources.get(0).getPath(), is(path));
+    }
+
+    @Test
+    public void retrieves_resources_recursively_from_given_path() throws IOException {
+
+        // given
+        final String path = "file:dir";
+        final String dir = "dir";
+        final String dirFile = "dir.feature";
+        final String subDir = "subdir";
+        final String subDirFile = "subdir.feature";
+        final String suffix = "feature";
+
+        when(assetManager.list(dir)).thenReturn(new String[]{subDir, dirFile});
+        when(assetManager.list(dir + "/" + subDir)).thenReturn(new String[]{subDirFile});
+
+        // when
+        final List<Resource> resources = Lists.newArrayList(androidResourceLoader.resources(URI.create(path), suffix));
+
+        // then
+        assertThat(resources.size(), is(2));
+        assertThat(resources, hasItem(withPath(path + "/" + dirFile)));
+        assertThat(resources, hasItem(withPath(path + "/" + subDir + "/" + subDirFile)));
+    }
+
+    @Test
+    public void only_retrieves_those_resources_which_end_the_specified_suffix() throws IOException {
+
+        // given
+        final String dir = "dir";
+        String path = "file:" + dir;
+        final String expected = "expected.feature";
+        final String unexpected = "unexpected.thingy";
+        final String suffix = "feature";
+        when(assetManager.list(dir)).thenReturn(new String[]{expected, unexpected});
+
+        // when
+        final List<Resource> resources = Lists.newArrayList(androidResourceLoader.resources(URI.create(path), suffix));
+
+        // then
+        assertThat(resources.size(), is(1));
+        assertThat(resources, hasItem(withPath(path + "/" + expected)));
+    }
+
+    private static Matcher<? super Resource> withPath(final String path) {
+        return new TypeSafeMatcher<Resource>() {
+            @Override
+            protected boolean matchesSafely(final Resource item) {
+                return item.getPath().toString().equals(path);
+            }
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText("resource with path: " + path);
+            }
+        };
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceTest.java
new file mode 100644
index 0000000000..893141ec73
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/AndroidResourceTest.java
@@ -0,0 +1,77 @@
+package io.cucumber.junit;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class AndroidResourceTest {
+
+    @Rule
+    public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+    
+    private final Context context = Mockito.mock(Context.class);
+
+    @Test
+    public void getPath_returns_given_path() {
+
+        // given
+        final URI path = URI.create("file:some/path.feature");
+        final AndroidResource androidResource = new AndroidResource(context, path);
+
+        // when
+        final URI result = androidResource.getPath();
+
+        // then
+        assertThat(result, is(path));
+    }
+
+   
+    @Test
+    public void toString_outputs_the_path() {
+
+        // given
+        final URI path = URI.create("file:some/path.feature");
+        final AndroidResource androidResource = new AndroidResource(context, path);
+
+        // when
+        final String result = androidResource.toString();
+
+        // then
+        assertEquals("AndroidResource (" + path.getSchemeSpecificPart() + ")",result);
+    }
+
+    @Test
+    public void getInputStream_returns_asset_stream() throws IOException {
+
+        // given
+        final URI path = URI.create("file:some/path.feature");
+        AssetManager assetManager = Mockito.mock(AssetManager.class);
+        InputStream stream = Mockito.mock(InputStream.class);
+        Mockito.when(assetManager.open("some/path.feature",AssetManager.ACCESS_UNKNOWN)).thenReturn(stream);
+        Mockito.when(context.getAssets()).thenReturn(assetManager);
+        final AndroidResource androidResource = new AndroidResource(context, path);
+
+        // when
+        final InputStream result = androidResource.getInputStream();
+
+        // then
+        assertSame(stream, result);
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/ArgumentsTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/ArgumentsTest.java
new file mode 100644
index 0000000000..2e1c74b9b7
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/ArgumentsTest.java
@@ -0,0 +1,469 @@
+package io.cucumber.junit;
+
+import android.os.Bundle;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.spy;
+
+@Config(manifest = Config.NONE)
+@RunWith(RobolectricTestRunner.class)
+public class ArgumentsTest {
+
+    @Test
+    public void handles_null_bundle_gracefully() {
+
+        // given
+        final Arguments arguments = new Arguments(null);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is(""));
+    }
+
+    @Test
+    public void handles_empty_bundle_gracefully() {
+
+        // given
+        final Arguments arguments = new Arguments(new Bundle());
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is(""));
+    }
+
+    @Test
+    public void supports_glue_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("glue", "glue/code/path");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--glue glue/code/path"));
+    }
+
+    @Test
+    public void supports_format_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("format", "someFormat");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--format someFormat"));
+    }
+
+    @Test
+    public void supports_plugin_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("plugin", "someFormat");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--plugin someFormat"));
+    }
+
+    @Test
+    public void supports_tags_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("tags", "@someTag");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--tags @someTag"));
+    }
+
+    @Test
+    public void supports_name_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("name", "someName");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--name someName"));
+    }
+
+    @Test
+    public void supports_dryRun_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("dryRun", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--dry-run"));
+    }
+
+    @Test
+    public void supports_log_as_alias_for_dryRun_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("log", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--dry-run"));
+    }
+
+    @Test
+    public void supports_noDryRun_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("noDryRun", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--no-dry-run"));
+    }
+
+    @Test
+    public void supports_monochrome_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("monochrome", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--monochrome"));
+    }
+
+    @Test
+    public void supports_noMonochrome_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("noMonochrome", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--no-monochrome"));
+    }
+
+    @Test
+    public void supports_strict_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("strict", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--strict"));
+    }
+
+    @Test
+    public void supports_noStrict_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("noStrict", "true");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--no-strict"));
+    }
+
+    @Test
+    public void supports_snippets_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("snippets", "someSnippet");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--snippets someSnippet"));
+    }
+
+    @Test
+    public void supports_features_as_direct_bundle_argument() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("features", "someFeature");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        // TODO does this space makes sense?
+        assertThat(cucumberOptions, is(" someFeature"));
+    }
+
+    @Test
+    public void supports_multiple_values() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("name", "Feature1--Feature2");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--name Feature1 --name Feature2"));
+    }
+
+    @Test
+    public void supports_single_cucumber_options_string() {
+
+        // given
+        final List<String> cucumberOptions = Lists.newArrayList("--tags @mytag",
+                                                                "--monochrome",
+                                                                "--name MyFeature",
+                                                                "--dry-run",
+                                                                "--glue com.someglue.Glue",
+                                                                "--format pretty",
+                                                                "--snippets underscore",
+                                                                "--strict",
+                                                                "--dotcucumber",
+                                                                "test features");
+        final Bundle bundle = new Bundle();
+        bundle.putString("cucumberOptions", Joiner.on(" ").join(cucumberOptions));
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        for (final String cucumberOption : cucumberOptions) {
+            assertThat(arguments.getCucumberOptions(), containsString(cucumberOption));
+        }
+    }
+
+    @Test
+    public void single_cucumber_options_string_takes_precedence_over_direct_bundle_argument() {
+
+        // given
+        final String cucumberOptions = "--tags @mytag1";
+        final Bundle bundle = new Bundle();
+        bundle.putString("cucumberOptions", cucumberOptions);
+        bundle.putString("tags", "@mytag2");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.getCucumberOptions(), is(cucumberOptions));
+    }
+
+    @Test
+    public void supports_spaces_in_values() {
+
+        // given
+        final Bundle bundle = new Bundle();
+        bundle.putString("name", "'Name with spaces'");
+        final Arguments arguments = new Arguments(bundle);
+
+        // when
+        final String cucumberOptions = arguments.getCucumberOptions();
+
+        // then
+        assertThat(cucumberOptions, is("--name 'Name with spaces'"));
+    }
+
+    @Test
+    public void isCountEnabled_returns_true_when_bundle_contains_true() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.COUNT_ENABLED, "true");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isCountEnabled(), is(true));
+    }
+
+    @Test
+    public void isCountEnabled_returns_false_when_bundle_contains_false() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.COUNT_ENABLED, "false");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isCountEnabled(), is(false));
+    }
+
+    @Test
+    public void isCountEnabled_returns_false_when_bundle_contains_no_value() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isCountEnabled(), is(false));
+    }
+
+    @Test
+    public void isDebugEnabled_returns_true_when_bundle_contains_true() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.DEBUG_ENABLED, "true");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isDebugEnabled(), is(true));
+    }
+
+    @Test
+    public void isDebugEnabled_returns_false_when_bundle_contains_false() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.DEBUG_ENABLED, "false");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isDebugEnabled(), is(false));
+    }
+
+    @Test
+    public void isDebugEnabled_returns_false_when_bundle_contains_no_value() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isDebugEnabled(), is(false));
+    }
+
+    @Test
+    public void coverageDataFilePath_returns_value_when_bundle_contains_value() {
+        // given
+        final String fileName = "some_custome_file.name";
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.COVERAGE_DATA_FILE_PATH, fileName);
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.coverageDataFilePath(), is(fileName));
+    }
+
+    @Test
+    public void coverageDataFilePath_returns_default_value_when_bundle_contains_no_value() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.coverageDataFilePath(), is(Arguments.DEFAULT.COVERAGE_DATA_FILE_PATH));
+    }
+
+    @Test
+    public void isCoverageEnabled_returns_true_when_bundle_contains_true() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.COVERAGE_ENABLED, "true");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isCoverageEnabled(), is(true));
+    }
+
+    @Test
+    public void isCoverageEnabled_returns_false_when_bundle_contains_false() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+        bundle.putString(Arguments.KEY.COVERAGE_ENABLED, "false");
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isCoverageEnabled(), is(false));
+    }
+
+    @Test
+    public void isCoverageEnabled_returns_false_when_bundle_contains_no_value() {
+        // given
+        final Bundle bundle = spy(new Bundle());
+
+        // when
+        final Arguments arguments = new Arguments(bundle);
+
+        // then
+        assertThat(arguments.isCoverageEnabled(), is(false));
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/CoverageDumperTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/CoverageDumperTest.java
new file mode 100644
index 0000000000..bff30df421
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/CoverageDumperTest.java
@@ -0,0 +1,153 @@
+package io.cucumber.junit;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import com.vladium.emma.rt.RT;
+import java.io.File;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.AdditionalMatchers.and;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CoverageDumperTest {
+
+    @Rule
+    public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    private final Bundle bundle = mock(Bundle.class);
+    private final Arguments arguments = mock(Arguments.class);
+    private final CoverageDumper coverageDumper = new CoverageDumper(arguments);
+
+    @Before
+    public void beforeEach() {
+        RT.resetMock();
+    }
+
+    @Test
+    public void does_not_dump_when_flag_is_disabled() {
+
+        // given
+        when(arguments.isCoverageEnabled()).thenReturn(false);
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        verifyZeroInteractions(bundle);
+    }
+
+    @Test
+    public void dumps_file_when_flag_is_enabled() {
+
+        // given
+        final String fileName = temporaryFolder.getRoot().getAbsolutePath() + File.separator + "foo.bar";
+        when(arguments.isCoverageEnabled()).thenReturn(true);
+        when(arguments.coverageDataFilePath()).thenReturn(fileName);
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        assertThat(new File(fileName).exists(), is(true));
+    }
+
+    @Test
+    public void puts_path_to_coverage_file_into_bundle() {
+
+        // given
+        final String fileName = temporaryFolder.getRoot().getAbsolutePath() + File.separator + "foo.bar";
+        when(arguments.isCoverageEnabled()).thenReturn(true);
+        when(arguments.coverageDataFilePath()).thenReturn(fileName);
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        verify(bundle).putString("coverageFilePath", fileName);
+    }
+
+    @Test
+    public void appends_message_about_dumped_coverage_data_to_result_stream() {
+
+        // given
+        final String fileName = temporaryFolder.getRoot().getAbsolutePath() + File.separator + "foo.bar";
+        final String previousStream = "previous stream data";
+        when(arguments.isCoverageEnabled()).thenReturn(true);
+        when(arguments.coverageDataFilePath()).thenReturn(fileName);
+        when(bundle.getString(Instrumentation.REPORT_KEY_STREAMRESULT)).thenReturn(previousStream);
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        verify(bundle).putString(eq(Instrumentation.REPORT_KEY_STREAMRESULT), and(contains(previousStream), contains(fileName)));
+    }
+
+    @Test
+    public void passes_file_for_specified_name_to_code_coverage_dumper_implementation() {
+
+        // given
+        final String fileName = temporaryFolder.getRoot().getAbsolutePath() + File.separator + "foo.bar";
+        when(arguments.isCoverageEnabled()).thenReturn(true);
+        when(arguments.coverageDataFilePath()).thenReturn(fileName);
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        assertThat(RT.getLastFile().getAbsolutePath(), is(fileName));
+    }
+
+
+    @Test
+    public void adds_error_message_to_result_stream_when_coverage_class_can_not_be_found() {
+
+        // given
+        final String fileName = temporaryFolder.getRoot().getAbsolutePath() + File.separator + "foo.bar";
+        final String previousStream = "previous stream data";
+        when(arguments.isCoverageEnabled()).thenReturn(true);
+        when(arguments.coverageDataFilePath()).thenReturn(fileName);
+        when(bundle.getString(Instrumentation.REPORT_KEY_STREAMRESULT)).thenReturn(previousStream);
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        verify(bundle).putString(eq(Instrumentation.REPORT_KEY_STREAMRESULT), and(contains(previousStream), contains(fileName)));
+    }
+
+    @Test
+    public void adds_error_message_to_result_stream_when_file_cannot_be_dumped() {
+
+        // given
+        final String fileName = temporaryFolder.getRoot().getAbsolutePath() + File.separator + "foo.bar";
+        final String previousStream = "previous stream data";
+        when(arguments.isCoverageEnabled()).thenReturn(true);
+        when(arguments.coverageDataFilePath()).thenReturn(fileName);
+        when(bundle.getString(Instrumentation.REPORT_KEY_STREAMRESULT)).thenReturn(previousStream);
+        RT.throwOnNextInvocation(new RuntimeException("something terrible happened"));
+
+        // when
+        coverageDumper.requestDump(bundle);
+
+        // then
+        verify(bundle).putString(eq(Instrumentation.REPORT_KEY_STREAMRESULT), and(contains(previousStream), contains("Error: Failed to generate coverage. Check logcat for details.")));
+
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DebuggerWaiterTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DebuggerWaiterTest.java
new file mode 100644
index 0000000000..ea3425d66f
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DebuggerWaiterTest.java
@@ -0,0 +1,56 @@
+package io.cucumber.junit;
+
+import android.os.Debug;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.verifyStatic;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(Debug.class)
+public class DebuggerWaiterTest {
+
+
+    @Test
+    public void waits_for_debugger_when_flag_is_set() {
+        // given
+        final Arguments arguments = mock(Arguments.class);
+        when(arguments.isDebugEnabled()).thenReturn(true);
+
+        mockStatic(Debug.class);
+
+        final DebuggerWaiter waiter = new DebuggerWaiter(arguments);
+
+        // when
+        waiter.requestWaitForDebugger();
+
+        // then
+        verifyStatic(Debug.class);
+        Debug.waitForDebugger();
+    }
+
+    @Test
+    public void does_not_wait_for_debugger_when_flag_is_not_set() {
+        // given
+        final Arguments arguments = mock(Arguments.class);
+        when(arguments.isDebugEnabled()).thenReturn(false);
+
+        mockStatic(Debug.class);
+
+        final DebuggerWaiter waiter = new DebuggerWaiter(arguments);
+
+        // when
+        waiter.requestWaitForDebugger();
+
+        // then
+        verifyStatic(Debug.class,never());
+        Debug.waitForDebugger();
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DexClassFinderTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DexClassFinderTest.java
new file mode 100644
index 0000000000..9b35fe3f38
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/DexClassFinderTest.java
@@ -0,0 +1,178 @@
+package io.cucumber.junit;
+
+import com.google.common.collect.Lists;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.collection.IsIterableContainingInOrder;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import dalvik.system.DexFile;
+import io.cucumber.core.model.GluePath;
+import io.cucumber.junit.shadow.ShadowDexFile;
+import io.cucumber.junit.stub.unwanted.SomeUnwantedClass;
+import io.cucumber.junit.stub.wanted.Manifest;
+import io.cucumber.junit.stub.wanted.R;
+import io.cucumber.junit.stub.wanted.SomeClass;
+import io.cucumber.junit.stub.wanted.SomeKotlinClass;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowDexFile.class}, manifest = Config.NONE)
+public class DexClassFinderTest {
+
+    private DexFile dexFile;
+    private DexClassFinder dexClassFinder;
+
+    @Before
+    public void beforeEachTest() throws IOException {
+        dexFile = new DexFile("notImportant");
+        dexClassFinder = new DexClassFinder(dexFile);
+    }
+
+    @Test
+    public void only_loads_classes_from_specified_package() throws Exception {
+
+        // given
+        setDexFileEntries(SomeClass.class, SomeKotlinClass.class, SomeUnwantedClass.class);
+
+        // when
+        final Collection<Class<?>> descendants = getDescendants(Object.class, SomeClass.class.getPackage());
+
+        // then
+        assertThat(descendants, IsIterableContainingInOrder.<Class<?>>contains(SomeClass.class, SomeKotlinClass.class));
+    }
+
+    private <T> Collection<Class<? extends T>> getDescendants(Class<T> parentType, Package javaPackage)
+    {
+        return dexClassFinder.getDescendants(parentType, GluePath.parse(javaPackage.getName()));
+    }
+
+    @Test
+    public void does_not_load_manifest_class() throws Exception {
+
+        // given
+        setDexFileEntries(SomeClass.class, Manifest.class);
+
+        // when
+        final Collection<Class<?>> descendants = getDescendants(Object.class, SomeClass.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(SomeClass.class));
+    }
+
+    @Test
+    public void does_not_load_R_class() throws Exception {
+
+        // given
+        setDexFileEntries(SomeClass.class, R.class);
+
+        // when
+        final Collection<Class<?>> descendants = getDescendants(Object.class, SomeClass.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(SomeClass.class));
+    }
+
+    @Test
+    public void does_not_load_R_inner_class() throws Exception {
+
+        // given
+        setDexFileEntries(SomeClass.class, R.SomeInnerClass.class);
+
+        // when
+        final Collection<Class<?>> descendants = getDescendants(Object.class, SomeClass.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(SomeClass.class));
+    }
+
+    @Test
+    public void only_loads_class_which_is_not_the_parent_type() throws Exception {
+
+        // given
+        setDexFileEntries(Integer.class, Number.class);
+
+        // when
+        final Class parentType = Number.class;
+        @SuppressWarnings("unchecked")
+        final Collection<Class<?>> descendants = getDescendants(parentType, Object.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(Integer.class));
+    }
+
+    @Test
+    public void only_loads_class_which_is_assignable_to_parent_type() throws Exception {
+
+        // given
+        setDexFileEntries(Integer.class, String.class);
+
+        // when
+        final Class parentType = Number.class;
+        @SuppressWarnings("unchecked")
+        final Collection<Class<?>> descendants = getDescendants(parentType, Object.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(Integer.class));
+    }
+
+    @Test
+    public void does_not_load_kotlin_inlined_classes() throws Exception {
+        // given
+        Class<?> kotlinInlinedFunClass = Class.forName("io.cucumber.junit.stub.wanted.SomeKotlinClass$someFun$$inlined$sortedBy$1");
+        setDexFileEntries(SomeClass.class, kotlinInlinedFunClass);
+
+        // when
+        final Collection<Class<?>> descendants = getDescendants(Object.class, SomeClass.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(SomeClass.class));
+    }
+
+    @Test
+    public void does_not_throw_exception_if_class_not_found() throws Exception {
+        // given
+        setDexFileEntries(Arrays.asList(SomeClass.class.getName(),"SomeNotExistentClass"));
+
+        // when
+        final Collection<Class<?>> descendants = getDescendants(Object.class, SomeClass.class.getPackage());
+
+        // then
+        assertThat(descendants, containsOnly(SomeClass.class));
+    }
+
+    private Matcher<Iterable<? extends Class<?>>> containsOnly(final Class<?> type) {
+        return IsIterableContainingInOrder.<Class<?>>contains(type);
+    }
+
+    private void setDexFileEntries(final Class... entryClasses) throws NoSuchFieldException, IllegalAccessException {
+        Collection<String> entries = classToName(entryClasses);
+        setDexFileEntries(entries);
+    }
+
+    private void setDexFileEntries(Collection<String> entries) throws NoSuchFieldException, IllegalAccessException {
+        final Field roboData = DexFile.class.getDeclaredField("__robo_data__");
+        final ShadowDexFile shadowDexFile = (ShadowDexFile) roboData.get(dexFile);
+        shadowDexFile.setEntries(entries);
+    }
+
+    private Collection<String> classToName(final Class... entryClasses) {
+        final List<String> names = Lists.newArrayList();
+        for (final Class entryClass : entryClasses) {
+            names.add(entryClass.getName());
+        }
+
+        return names;
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/MissingStepDefinitionErrorTest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/MissingStepDefinitionErrorTest.java
new file mode 100644
index 0000000000..925c6ac80f
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/MissingStepDefinitionErrorTest.java
@@ -0,0 +1,24 @@
+package io.cucumber.junit;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+;
+
+public class MissingStepDefinitionErrorTest {
+
+    @Test
+    public void puts_snippet_with_preceeding_new_line_into_exception_message() {
+
+        // given
+        final String snippet = "some snippet";
+
+        // when
+        final String message = new MissingStepDefinitionError(snippet).getMessage();
+
+        // then
+        assertThat(message, is("\n\nsome snippet"));
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/shadow/ShadowDexFile.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/shadow/ShadowDexFile.java
new file mode 100644
index 0000000000..04cdd65559
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/shadow/ShadowDexFile.java
@@ -0,0 +1,23 @@
+package io.cucumber.junit.shadow;
+
+import dalvik.system.DexFile;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(DexFile.class)
+public class ShadowDexFile {
+
+    private Enumeration<String> entries;
+
+    @Implementation
+    public Enumeration<String> entries() {
+        return entries;
+    }
+
+    public void setEntries(final Collection<String> entries) {
+        this.entries = Collections.enumeration(entries);
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/unwanted/SomeUnwantedClass.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/unwanted/SomeUnwantedClass.java
new file mode 100644
index 0000000000..a2c2486be7
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/unwanted/SomeUnwantedClass.java
@@ -0,0 +1,4 @@
+package io.cucumber.junit.stub.unwanted;
+
+public class SomeUnwantedClass {
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/Manifest.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/Manifest.java
new file mode 100644
index 0000000000..7669d07779
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/Manifest.java
@@ -0,0 +1,4 @@
+package io.cucumber.junit.stub.wanted;
+
+public class Manifest {
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/R.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/R.java
new file mode 100644
index 0000000000..4ebb7bfe35
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/R.java
@@ -0,0 +1,8 @@
+package io.cucumber.junit.stub.wanted;
+
+public class R {
+
+    public static class SomeInnerClass {
+
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeClass.java b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeClass.java
new file mode 100644
index 0000000000..3768496b0e
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeClass.java
@@ -0,0 +1,4 @@
+package io.cucumber.junit.stub.wanted;
+
+public class SomeClass {
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeKotlinClass.kt b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeKotlinClass.kt
new file mode 100644
index 0000000000..52c3c0263b
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/java/io/cucumber/junit/stub/wanted/SomeKotlinClass.kt
@@ -0,0 +1,8 @@
+package io.cucumber.junit.stub.wanted
+
+class SomeKotlinClass {
+
+    fun someFun(){
+        listOf(1,3,5).sortedBy { it }
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cucumber-android/src/test/resources/robolectric.properties b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/resources/robolectric.properties
new file mode 100644
index 0000000000..932b01b9eb
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cucumber-android/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=28
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/.gitignore b/test_projects/android/cucumber_sample_app/cukeulator/.gitignore
new file mode 100644
index 0000000000..4c5d941320
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/.gitignore
@@ -0,0 +1,28 @@
+# Built application files
+/*/build/
+build/
+
+# Crashlytics configuations
+com_crashlytics_export_strings.xml
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Gradle generated files
+.gradle/
+
+# Signing files
+.signing/
+
+# User-specific configurations
+.idea/
+*.iml
+
+# OS-specific files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/README.md b/test_projects/android/cucumber_sample_app/cukeulator/README.md
new file mode 100644
index 0000000000..861dbc4782
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/README.md
@@ -0,0 +1,77 @@
+## Cukeulator Example Application
+This is the example test-project for the Cukeulator app for Android Studio 3.0+
+
+### Setup
+Features must be placed in `androidTest/assets/features/`. Subdirectories are allowed.
+
+The rest of the dependencies are added automatically in `cukeulator/build.gradle`.
+
+The cucumber-android dependency is added as (see `cukeulator/build.gradle`):
+
+```
+androidTestImplementation 'io.cucumber:cucumber-android:<version>'
+```
+
+### Using gradle
+To build the cukeulator apk:
+```
+./gradlew --parallel :cukeulator:assemble
+```
+The build generates an apk in cukeulator/build/outputs/apk/debug/cukeulator-debug.apk.
+
+
+To build the instrumentation test apk:
+```
+./gradlew --parallel :cukeulator:assembleDebugTest
+```
+
+To install the apk on a device:
+```
+adb install -r cukeulator/build/outputs/apk/debug/cukeulator-debug.apk
+```
+
+To install the test apk on a device:
+```
+adb install -r cukeulator/build/outputs/apk/androidTest/debug/cukeulator-debug-androidTest.apk
+```
+
+To verify that the test is installed, run:
+
+```
+adb shell pm list instrumentation
+```
+
+The command output should display;
+
+```
+instrumentation:cucumber.cukeulator.test/.CukeulatorAndroidJUnitRunner (target=cucumber.cukeulator)
+```
+
+To run the test:
+
+```
+./gradlew :cukeulator:connectedCheck
+```
+
+As an alternative option, the test can be run with adb:
+
+```
+adb shell am instrument -w cucumber.cukeulator.test/cucumber.cukeulator.test.CukeulatorAndroidJUnitRunner
+```
+
+### Using an Android Studio IDE
+1. Import the example to Android Studio: `File > Import Project`.
+2. Create a test run configuration:
+    1.  Run > Edit Configurations
+    2. Click `+` button and select Android Instrumented Tests
+    3. Specify test name: `CalculatorTest`
+    4. Select module: `cukeulator`
+    5. Enter a Specific instrumentation runner: `cucumber.cukeulator.test/cucumber.cukeulator.test.CukeulatorAndroidJUnitRunner`
+    6. Click Ok
+
+### Output
+Filter for the logcat tag `cucumber-android` in [DDMS](https://developer.android.com/tools/debugging/ddms.html).
+
+### Using this project with locally built Cucumber-JVM
+See [cukeulator/build.gradle](build.gradle) under `dependencies`.  
+There is a source-code comment which explains how to use a locally built Cucumber-JVM Android library.
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/build.gradle b/test_projects/android/cucumber_sample_app/cukeulator/build.gradle
new file mode 100644
index 0000000000..6298f34c2e
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/build.gradle
@@ -0,0 +1,248 @@
+apply plugin: 'com.android.application'
+apply plugin: 'jacoco'
+apply plugin: "com.jaredsburrows.spoon"
+apply plugin: "kotlin-android"
+
+android {
+
+    compileSdkVersion 29
+    buildToolsVersion '29.0.3'
+
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 29
+        multiDexEnabled true
+        applicationId "cucumber.cukeulator"
+        testApplicationId "cucumber.cukeulator.test"
+        testInstrumentationRunner "cucumber.cukeulator.test.CukeulatorAndroidJUnitRunner"
+        versionCode 1
+        versionName '1.0'
+        //testInstrumentationRunnerArguments = [
+            // cucumberUseAndroidJUnitRunner: getProperty("cucumberUseAndroidJUnitRunner"),
+            // uncomment this to clear app data before each test when running with orchestrator
+            // clearPackageData: 'true'
+        //]
+    }
+
+
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+
+        debug {}
+    }
+
+    packagingOptions {
+        exclude 'LICENSE.txt'
+    }
+
+    // With the following option we does not have to mock everything,
+    // e.g. super calls cannot be mocked with Mockito only (just with Powermock).
+    testOptions {
+        unitTests.returnDefaultValues = true
+        animationsDisabled true
+//        uncomment this to run tests with orchestrator
+//        execution 'ANDROIDX_TEST_ORCHESTRATOR'
+    }
+
+}
+
+// ==================================================================
+// Project dependencies
+// ==================================================================
+
+dependencies {
+    //
+    // The following dependency works, if you build Cucumber-JVM on your local machine:
+    //
+    // androidTestImplementation 'io.cucumber:cucumber-android:2.3.2-SNAPSHOT'
+    //
+    // (If you enable it, you must disable the dependency to the stable Cucumber version above.)
+    //
+    // If you have not yet built Cucumber-JVM on your local machine, build it with:
+    //
+    //                   cd <cucumber-jvm-source-root>
+    //                   mvn clean install
+    //
+    // Hint: you find the Cucumber-JVM source root under a parent directory of this file.
+    //
+    // If you only change the Android source, you also can do:
+    //
+    //                   cd <cucumber-jvm-source-root>/android
+    //                   mvn install
+    //
+    // Now, after you have been built Cucumber-JVM with one of the commands above, it is published
+    // to your local Maven repository and may be used by other projects on your local machine.
+    //
+    // For our example project, that means you can execute the feature files, with the just built
+    // Cucumber-JVM library by using the following command (press Alt+F12 in Android Studio):
+    //
+    //                   gradlew connectedCheck --refresh-dependencies
+    //
+    // The --refresh-dependencies option seems not to be required anymore for recent Android Studio
+    // and Gradle versions. But, if your Cucumber-JVM snapshot dependency is not updated
+    // automatically by Gradle, the flag always bypasses any caching of dependencies.
+
+
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+
+//    testImplementation 'junit:junit:4.12'
+//    testImplementation 'org.mockito:mockito-core:2.10.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    androidTestImplementation 'androidx.test:rules:1.3.0'
+    androidTestUtil 'androidx.test:orchestrator:1.3.0'
+
+    // Use the stable Cucumber version
+    androidTestImplementation project(":cucumber_sample_app::cucumber-android")
+    androidTestImplementation "io.cucumber:cucumber-picocontainer:4.8.1"
+
+}
+
+// ==================================================================
+// Custom tasks
+// ==================================================================
+
+
+task runInstrumentationTests {
+    group "verification"
+    mustRunAfter "deleteExistingCucumberReports"
+    dependsOn "deleteExistingCucumberReports","spoonDebugAndroidTest"
+    finalizedBy "downloadCucumberReports"
+}
+
+spoon {
+    debug = true
+    //this is faster but can be set to false
+    singleInstrumentationCall = true
+    grantAll = true
+    shard = true
+}
+
+
+/*
+ * Downloads all Cucumber reports from the connected device.
+ */
+task downloadCucumberReports {
+    group "Verification"
+    description "Downloads the rich Cucumber report files (HTML, XML, JSON) from the connected device"
+
+    doLast {
+        def deviceSourcePath = getCucumberDevicePath()
+        def localReportPath = new File(buildDir, "reports/cucumber")
+        if (!localReportPath.exists()) {
+            localReportPath.mkdirs()
+        }
+        if (!localReportPath.exists()) {
+            throw new GradleException("Could not create $localReportPath")
+        }
+        def adb = getAdbPath()
+        def files = getCucumberReportFileNames()
+        files.each { fileName ->
+            println fileName
+            exec {
+                commandLine adb, 'pull', "$deviceSourcePath/$fileName", localReportPath
+            }
+        }
+    }
+}
+
+/**
+ * Deletes existing Cucumber reports on the device.
+ */
+task deleteExistingCucumberReports {
+    group "Verification"
+    description "Removes the rich Cucumber report files (HTML, XML, JSON) from the connected device"
+    doLast {
+        def deviceSourcePath = getCucumberDevicePath()
+        def files = getCucumberReportFileNames()
+        files.each { fileName ->
+            def deviceFileName = deviceSourcePath + '/' + fileName
+            def output2 = executeAdb('if [ -d "' + deviceFileName + '" ]; then rm -r "' + deviceFileName + '"; else rm -r "' + deviceFileName + '" ; fi')
+            println output2
+        }
+    }
+}
+
+/**
+ * Sets the required permissions for Cucumber to write on the internal storage.
+ */
+task grantPermissions(dependsOn: 'installDebug') {
+    doLast {
+        def adb = getAdbPath()
+        // We only set the permissions for the main application
+        def mainPackageName = android.defaultConfig.applicationId
+        def readPermission = "android.permission.READ_EXTERNAL_STORAGE"
+        def writePermission = "android.permission.WRITE_EXTERNAL_STORAGE"
+        exec { commandLine adb, 'shell', 'pm', 'grant', mainPackageName, readPermission }
+        exec { commandLine adb, 'shell', 'pm', 'grant', mainPackageName, writePermission }
+    }
+}
+
+
+// ==================================================================
+// Utility methods
+// ==================================================================
+
+/**
+ * Utility method to get the full ADB path
+ * @return the absolute ADB path
+ */
+String getAdbPath() {
+    def adb = android.getAdbExecutable().toString()
+    if (adb.isEmpty()) {
+        throw new GradleException("Could not detect adb path")
+    }
+    return adb
+}
+
+/**
+ * Sometime adb returns '\r' character multiple times.
+ * @param s the original string returned by adb
+ * @return the fixed string without '\r'
+ */
+static def fixAdbOutput(String s) {
+    return s.replaceAll("[\r\n]+", "\n").trim()
+}
+
+/**
+ * Runs the adb tool
+ * @param program the program which is executed on the connected device
+ * @return the output of the adb tool
+ */
+def executeAdb(String program) {
+    def process = new ProcessBuilder(getAdbPath(), "shell", program).redirectErrorStream(true).start()
+    String text = new BufferedReader(new InputStreamReader(process.inputStream)).text
+    return fixAdbOutput(text)
+}
+
+/**
+ * The path which is used to store the Cucumber files.
+ * @return
+ */
+def getCucumberDevicePath() {
+    return 'sdcard/Android/data/cucumber.cukeulator/files/reports'
+}
+
+/**
+ * @return the known Cucumber report files/directories
+ */
+static def getCucumberReportFileNames() {
+    return ['cucumber.xml', 'cucumber.html']
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/extra/calculate.feature b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/extra/calculate.feature
new file mode 100644
index 0000000000..ad05180c70
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/extra/calculate.feature
@@ -0,0 +1,20 @@
+Feature: Calculate a result
+  Perform an arithmetic operation on two numbers using a mathematical operator
+  """The purpose of this feature is to illustrate how existing step-definitions
+  can be efficiently reused."""
+
+  Scenario Outline: Enter a digit, an operator and another digit
+    Given I have a CalculatorActivity
+    When I press <num1>
+    And I press <op>
+    And I press <num2>
+    And I press =
+    Then I should see "<result>" on the display
+
+  Examples:
+    | num1 | num2 | op | result   |
+    | 9    | 8    | +  | 17.0     |
+    | 7    | 6    | –  | 1.0      |
+    | 5    | 4    | x  | 20.0     |
+    | 3    | 2    | /  | 1.5      |
+    | 1    | 0    | /  | Infinity |
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/addition.feature b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/addition.feature
new file mode 100644
index 0000000000..9199cd5f08
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/addition.feature
@@ -0,0 +1,31 @@
+Feature: Add two numbers
+  Calculate the sum of two numbers which consist of one or more digits
+
+  Scenario Outline: Enter one digit per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    And I press +
+    And I press <num2>
+    And I press =
+    Then I should see "<sum>" on the display
+
+  Examples:
+    | num1 | num2 | sum |
+    | 0    | 0    | 0.0 |
+    | 0    | 1    | 1.0 |
+    | 1    | 1    | 2.0 |
+
+  Scenario Outline: Enter two digits per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    When I press <num2>
+    And I press +
+    And I press <num3>
+    And I press <num4>
+    And I press =
+    Then I should see "<sum>" on the display
+
+  Examples:
+    | num1 | num2 | num3 | num4 | sum   |
+    | 0    | 0    | 2    | 0    | 20.0  |
+    | 9    | 8    | 7    | 6    | 174.0 |
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/division.feature b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/division.feature
new file mode 100644
index 0000000000..e6dc09a875
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/division.feature
@@ -0,0 +1,31 @@
+Feature: Divide two numbers
+  Calculate the quotient of two numbers which consist of one or more digits
+
+  Scenario Outline: Enter one digit per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    And I press /
+    And I press <num2>
+    And I press =
+    Then I should see "<quotient>" on the display
+
+  Examples:
+    | num1 | num2 | quotient |
+    | 0    | 0    | NaN      |
+    | 1    | 0    | Infinity |
+    | 1    | 2    | 0.5      |
+
+  Scenario Outline: Enter two digits per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    When I press <num2>
+    And I press /
+    And I press <num3>
+    And I press <num4>
+    And I press =
+    Then I should see "<quotient>" on the display
+
+  Examples:
+    | num1 | num2 | num3 | num4 | quotient |
+    | 2    | 2    | 2    | 2    | 1.0      |
+    | 2    | 0    | 1    | 0    | 2.0      |
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/multiplication.feature b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/multiplication.feature
new file mode 100644
index 0000000000..311d4ecb1d
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/multiplication.feature
@@ -0,0 +1,31 @@
+Feature: Multiply two numbers
+  Calculate the product of two numbers which consist of one or more digits
+
+  Scenario Outline: Enter one digit per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    And I press x
+    And I press <num2>
+    And I press =
+    Then I should see "<product>" on the display
+
+  Examples:
+    | num1 | num2 | product |
+    | 0    | 0    | 0.0     |
+    | 0    | 1    | 0.0     |
+    | 1    | 2    | 2.0     |
+
+  Scenario Outline: Enter two digits per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    When I press <num2>
+    And I press x
+    And I press <num3>
+    And I press <num4>
+    And I press =
+    Then I should see "<product>" on the display
+
+  Examples:
+    | num1 | num2 | num3 | num4 | product |
+    | 2    | 2    | 2    | 2    | 484.0   |
+    | 2    | 0    | 1    | 0    | 200.0   |
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/subtraction.feature b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/subtraction.feature
new file mode 100644
index 0000000000..51a3771ac5
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/assets/features/operations/subtraction.feature
@@ -0,0 +1,31 @@
+Feature: Subtract two numbers
+  Calculate the difference of two numbers which consist of one or more digits
+
+  Scenario Outline: Enter one digit per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    And I press –
+    And I press <num2>
+    And I press =
+    Then I should see "<delta>" on the display
+
+  Examples:
+    | num1 | num2 | delta |
+    | 0    | 0    | 0.0   |
+    | 0    | 1    | -1.0  |
+    | 1    | 2    | -1.0  |
+
+  Scenario Outline: Enter two digits per number and press =
+    Given I have a CalculatorActivity
+    When I press <num1>
+    When I press <num2>
+    And I press –
+    And I press <num3>
+    And I press <num4>
+    And I press =
+    Then I should see "<delta>" on the display
+
+  Examples:
+    | num1 | num2 | num3 | num4 | delta |
+    | 2    | 2    | 2    | 2    | 0.0   |
+    | 2    | 0    | 1    | 0    | 10.0  |
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java
new file mode 100644
index 0000000000..e51edb16a2
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CalculatorActivitySteps.java
@@ -0,0 +1,134 @@
+package cucumber.cukeulator.test;
+
+import android.app.Activity;
+
+import androidx.test.rule.ActivityTestRule;
+
+import cucumber.cukeulator.CalculatorActivity;
+import cucumber.cukeulator.R;
+import io.cucumber.java.After;
+import io.cucumber.java.Before;
+import io.cucumber.java.en.Given;
+import io.cucumber.java.en.When;
+import io.cucumber.junit.CucumberJUnitRunner;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * We use {@link ActivityTestRule} in order to have access to methods like getActivity
+ * and getInstrumentation.
+ * </p>
+ * The CucumberOptions annotation is mandatory for exactly one of the classes in the test project.
+ * Only the first annotated class that is found will be used, others are ignored. If no class is
+ * annotated, an exception is thrown.
+ * <p/>
+ * The options need to at least specify features = "features". Features must be placed inside
+ * assets/features/ of the test project (or a subdirectory thereof).
+ */
+public class CalculatorActivitySteps {
+
+    /**
+     * Since {@link CucumberJUnitRunner} has the control over the
+     * test lifecycle, activity test rules must not be launched automatically. Automatic launching of test rules is only
+     * feasible for JUnit tests. Fortunately, we are able to launch the activity in Cucumber's {@link Before} method.
+     */
+    ActivityTestRule rule = new ActivityTestRule<>(CalculatorActivity.class, false, false);
+
+    public CalculatorActivitySteps(SomeDependency dependency) {
+        assertNotNull(dependency);
+    }
+
+    /**
+     * We launch the activity in Cucumber's {@link Before} hook.
+     * See the notes above for the reasons why we are doing this.
+     *
+     * @throws Exception any possible Exception
+     */
+    @Before
+    public void launchActivity() throws Exception {
+        rule.launchActivity(null);
+    }
+
+    /**
+     * All the clean up of application's data and state after each scenario must happen here
+     */
+    @After
+    public void finishActivity() throws Exception {
+        getActivity().finish();
+    }
+
+    /**
+     * Gets the activity from our test rule.
+     *
+     * @return the activity
+     */
+    private Activity getActivity() {
+        return rule.getActivity();
+    }
+
+    @Given("I have a CalculatorActivity")
+    public void I_have_a_CalculatorActivity() {
+        assertNotNull(getActivity());
+    }
+
+    @When("I press {digit}")
+    public void I_press_d(final int d) {
+        switch (d) {
+            case 0:
+                onView(withId(R.id.btn_d_0)).perform(click());
+                break;
+            case 1:
+                onView(withId(R.id.btn_d_1)).perform(click());
+                break;
+            case 2:
+                onView(withId(R.id.btn_d_2)).perform(click());
+                break;
+            case 3:
+                onView(withId(R.id.btn_d_3)).perform(click());
+                break;
+            case 4:
+                onView(withId(R.id.btn_d_4)).perform(click());
+                break;
+            case 5:
+                onView(withId(R.id.btn_d_5)).perform(click());
+                break;
+            case 6:
+                onView(withId(R.id.btn_d_6)).perform(click());
+                break;
+            case 7:
+                onView(withId(R.id.btn_d_7)).perform(click());
+                break;
+            case 8:
+                onView(withId(R.id.btn_d_8)).perform(click());
+                break;
+            case 9:
+                onView(withId(R.id.btn_d_9)).perform(click());
+                break;
+        }
+    }
+
+    @When("I press {operator}")
+    public void I_press_op(final char op) {
+        switch (op) {
+            case '+':
+                onView(withId(R.id.btn_op_add)).perform(click());
+                break;
+            case '–':
+                onView(withId(R.id.btn_op_subtract)).perform(click());
+                break;
+            case 'x':
+                onView(withId(R.id.btn_op_multiply)).perform(click());
+                break;
+            case '/':
+                onView(withId(R.id.btn_op_divide)).perform(click());
+                break;
+            case '=':
+                onView(withId(R.id.btn_op_equals)).perform(click());
+                break;
+        }
+    }
+
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CukeulatorAndroidJUnitRunner.java b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CukeulatorAndroidJUnitRunner.java
new file mode 100644
index 0000000000..40e5e3a96a
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/CukeulatorAndroidJUnitRunner.java
@@ -0,0 +1,61 @@
+package cucumber.cukeulator.test;
+
+import android.os.Bundle;
+
+import java.io.File;
+
+import io.cucumber.android.runner.CucumberAndroidJUnitRunner;
+import io.cucumber.junit.CucumberOptions;
+
+/**
+ * The CucumberOptions annotation is mandatory for exactly one of the classes in the test project.
+ * Only the first annotated class that is found will be used, others are ignored. If no class is
+ * annotated, an exception is thrown. This annotation does not have to placed in runner class
+ */
+@CucumberOptions(
+        features = "features",
+        strict = true
+)
+public class CukeulatorAndroidJUnitRunner extends CucumberAndroidJUnitRunner {
+
+    @Override
+    public void onCreate(final Bundle bundle) {
+        bundle.putString("plugin", getPluginConfigurationString()); // we programmatically create the plugin configuration
+        //it crashes on Android R without it
+        new File(getAbsoluteFilesPath()).mkdirs();
+        super.onCreate(bundle);
+    }
+
+    /**
+     * Since we want to checkout the external storage directory programmatically, we create the plugin configuration
+     * here, instead of the {@link CucumberOptions} annotation.
+     *
+     * @return the plugin string for the configuration, which contains XML, HTML and JSON paths
+     */
+    private String getPluginConfigurationString() {
+        String cucumber = "cucumber";
+        String separator = "--";
+        return "junit:" + getCucumberXml(cucumber) + separator +
+                "html:" + getCucumberHtml(cucumber);
+    }
+
+    private String getCucumberHtml(String cucumber) {
+        return getAbsoluteFilesPath() + "/" + cucumber + ".html";
+    }
+
+    private String getCucumberXml(String cucumber) {
+        return getAbsoluteFilesPath() + "/" + cucumber + ".xml";
+    }
+
+    /**
+     * The path which is used for the report files.
+     *
+     * @return the absolute path for the report files
+     */
+    private String getAbsoluteFilesPath() {
+
+        //sdcard/Android/data/cucumber.cukeulator
+        File directory = getTargetContext().getExternalFilesDir(null);
+        return new File(directory, "reports").getAbsolutePath();
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/InstrumentationNonCucumberTest.java b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/InstrumentationNonCucumberTest.java
new file mode 100644
index 0000000000..1dd6ec47a0
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/InstrumentationNonCucumberTest.java
@@ -0,0 +1,41 @@
+package cucumber.cukeulator.test;
+
+import android.content.Intent;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import cucumber.cukeulator.CalculatorActivity;
+import cucumber.cukeulator.R;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+/**
+ * The aim of this test is to make sure that it is possible to run non cucumber instrumentation tests.
+ */
+public class InstrumentationNonCucumberTest {
+
+    private ActivityTestRule<CalculatorActivity> activityTestRule = new ActivityTestRule<>(CalculatorActivity.class, false, false);
+
+    @Before
+    public void setUp() throws Exception {
+        activityTestRule.launchActivity(new Intent());
+    }
+
+    @SmallTest
+    @Test
+    public void assert_that_click_on_0_is_visible_in_the_text_cal_display() {
+        onView(withId(R.id.btn_d_0))
+                .perform(click());
+
+        onView(withId(R.id.txt_calc_display))
+                .check(matches(withText("0")));
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/KotlinSteps.kt b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/KotlinSteps.kt
new file mode 100644
index 0000000000..23c9289663
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/KotlinSteps.kt
@@ -0,0 +1,18 @@
+package cucumber.cukeulator.test
+
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import io.cucumber.java.en.Then
+import cucumber.cukeulator.R
+
+
+class KotlinSteps {
+
+
+    @Then("I should see {string} on the display")
+    fun I_should_see_s_on_the_display(s: String?) {
+        Espresso.onView(withId(R.id.txt_calc_display)).check(ViewAssertions.matches(ViewMatchers.withText(s)))
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeClassWithUnsupportedApi.java b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeClassWithUnsupportedApi.java
new file mode 100644
index 0000000000..c1c0bdae1e
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeClassWithUnsupportedApi.java
@@ -0,0 +1,51 @@
+package cucumber.cukeulator.test;
+
+import java.util.Comparator;
+import java.util.function.Function;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongFunction;
+
+public class SomeClassWithUnsupportedApi implements Comparator<Integer> {
+	
+
+	@Override
+	public int compare(Integer o1, Integer o2) {
+		return 0;
+	}
+
+	@Override
+	public Comparator<Integer> reversed() {
+		return null;
+	}
+
+	@Override
+	public Comparator<Integer> thenComparing(Comparator<? super Integer> other) {
+		return null;
+	}
+
+	@Override
+	public <U> Comparator<Integer> thenComparing(Function<? super Integer, ? extends U> keyExtractor, Comparator<? super U> keyComparator) {
+		return null;
+	}
+
+	@Override
+	public <U extends Comparable<? super U>> Comparator<Integer> thenComparing(Function<? super Integer, ? extends U> keyExtractor) {
+		return null;
+	}
+
+	@Override
+	public Comparator<Integer> thenComparingInt(ToIntFunction<? super Integer> keyExtractor) {
+		return null;
+	}
+
+	@Override
+	public Comparator<Integer> thenComparingLong(ToLongFunction<? super Integer> keyExtractor) {
+		return null;
+	}
+
+	@Override
+	public Comparator<Integer> thenComparingDouble(ToDoubleFunction<? super Integer> keyExtractor) {
+		return null;
+	}
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeDependency.java b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeDependency.java
new file mode 100644
index 0000000000..79910fe762
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/SomeDependency.java
@@ -0,0 +1,5 @@
+package cucumber.cukeulator.test;
+
+// Dummy class to demonstrate dependency injection
+public class SomeDependency {
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/TypeRegistryConfiguration.java b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/TypeRegistryConfiguration.java
new file mode 100644
index 0000000000..79997c6a24
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/androidTest/java/cucumber/cukeulator/test/TypeRegistryConfiguration.java
@@ -0,0 +1,45 @@
+package cucumber.cukeulator.test;
+
+import java.util.Locale;
+
+import io.cucumber.core.api.TypeRegistry;
+import io.cucumber.core.api.TypeRegistryConfigurer;
+import io.cucumber.cucumberexpressions.ParameterType;
+import io.cucumber.cucumberexpressions.Transformer;
+
+import static java.util.Locale.ENGLISH;
+
+public class TypeRegistryConfiguration implements TypeRegistryConfigurer {
+
+    @Override
+    public Locale locale() {
+        return ENGLISH;
+    }
+
+    @Override
+    public void configureTypeRegistry(TypeRegistry typeRegistry) {
+        typeRegistry.defineParameterType(new ParameterType<Integer>(
+                "digit",
+                "[0-9]",
+                Integer.class,
+                new Transformer<Integer>() {
+                    @Override
+                    public Integer transform(String text) {
+                        return Integer.parseInt(text);
+                    }
+                })
+        );
+
+        typeRegistry.defineParameterType(new ParameterType<Character>(
+                "operator",
+                "[+–x\\/=]",
+                Character.class,
+                new Transformer<Character>() {
+                    @Override
+                    public Character transform(String text) {
+                        return text.charAt(0);
+                    }
+                })
+        );
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/debug/AndroidManifest.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000..c00cb3f03b
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/debug/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cucumber.cukeulator">
+
+    <!-- For Cucumber reports, we require the following permissions.
+	     (We place this in this debug manifest to avoid that this permissions are provided by default in release APKs.) -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+</manifest>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/AndroidManifest.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..3f5d610f68
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cucumber.cukeulator">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="cucumber.cukeulator.CalculatorActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/java/cucumber/cukeulator/CalculatorActivity.java b/test_projects/android/cucumber_sample_app/cukeulator/src/main/java/cucumber/cukeulator/CalculatorActivity.java
new file mode 100644
index 0000000000..5f97b77d94
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/java/cucumber/cukeulator/CalculatorActivity.java
@@ -0,0 +1,152 @@
+package cucumber.cukeulator;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class CalculatorActivity extends Activity {
+    private static enum Operation {ADD, SUB, MULT, DIV, NONE}
+
+    private TextView txtCalcDisplay;
+    private TextView txtCalcOperator;
+    private Operation operation;
+    private boolean decimals;
+    private boolean resetDisplay;
+    private boolean performOperation;
+    private double value;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_calculator);
+        txtCalcDisplay = (TextView) findViewById(R.id.txt_calc_display);
+        txtCalcOperator = (TextView) findViewById(R.id.txt_calc_operator);
+        operation = Operation.NONE;
+    }
+
+    public void onDigitPressed(View v) {
+        if (resetDisplay) {
+            txtCalcDisplay.setText(null);
+            resetDisplay = false;
+        }
+        txtCalcOperator.setText(null);
+
+        if (decimals || !only0IsDisplayed()) txtCalcDisplay.append(((Button) v).getText());
+
+        if (operation != Operation.NONE) performOperation = true;
+    }
+
+    public void onOperatorPressed(View v) {
+        if (performOperation) {
+            performOperation();
+            performOperation = false;
+        }
+        switch (v.getId()) {
+            case R.id.btn_op_divide:
+                operation = Operation.DIV;
+                txtCalcOperator.setText("/");
+                break;
+            case R.id.btn_op_multiply:
+                operation = Operation.MULT;
+                txtCalcOperator.setText("x");
+                break;
+            case R.id.btn_op_subtract:
+                operation = Operation.SUB;
+                txtCalcOperator.setText("–");
+                break;
+            case R.id.btn_op_add:
+                operation = Operation.ADD;
+                txtCalcOperator.setText("+");
+                break;
+            case R.id.btn_op_equals:
+                break;
+            default:
+                throw new RuntimeException("Unsupported operation.");
+        }
+        resetDisplay = true;
+        value = getDisplayValue();
+    }
+
+    public void onSpecialPressed(View v) {
+        switch (v.getId()) {
+            case R.id.btn_spec_sqroot: {
+                double value = getDisplayValue();
+                double sqrt = Math.sqrt(value);
+                txtCalcDisplay.setText(Double.toString(sqrt));
+                break;
+            }
+            case R.id.btn_spec_pi: {
+                resetDisplay = false;
+                txtCalcOperator.setText(null);
+                txtCalcDisplay.setText(Double.toString(Math.PI));
+                if (operation != Operation.NONE) performOperation = true;
+                return;
+            }
+            case R.id.btn_spec_percent: {
+                double value = getDisplayValue();
+                double percent = value / 100.0F;
+                txtCalcDisplay.setText(Double.toString(percent));
+                break;
+            }
+            case R.id.btn_spec_comma: {
+                if (!decimals) {
+                    String text = displayIsEmpty() ? "0." : ".";
+                    txtCalcDisplay.append(text);
+                    decimals = true;
+                }
+                break;
+            }
+            case R.id.btn_spec_clear: {
+                value = 0;
+                decimals = false;
+                operation = Operation.NONE;
+                txtCalcDisplay.setText(null);
+                txtCalcOperator.setText(null);
+                break;
+            }
+        }
+        resetDisplay = false;
+        performOperation = false;
+    }
+
+    private void performOperation() {
+        double display = getDisplayValue();
+
+        switch (operation) {
+            case DIV:
+                value = value / display;
+                break;
+            case MULT:
+                value = value * display;
+                break;
+            case SUB:
+                value = value - display;
+                break;
+            case ADD:
+                value = value + display;
+                break;
+            case NONE:
+                return;
+            default:
+                throw new RuntimeException("Unsupported operation.");
+        }
+        txtCalcOperator.setText(null);
+        txtCalcDisplay.setText(Double.toString(value));
+    }
+
+    private boolean only0IsDisplayed() {
+        CharSequence text = txtCalcDisplay.getText();
+        return text.length() == 1 && text.charAt(0) == '0';
+    }
+
+    private boolean displayIsEmpty() {
+        return txtCalcDisplay.getText().length() == 0;
+    }
+
+    private double getDisplayValue() {
+        String display = txtCalcDisplay.getText().toString();
+        return display.isEmpty() ? 0.0F : Double.parseDouble(display);
+    }
+}
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-hdpi/ic_launcher.png b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000000000000000000000000000000000000..019f515cadfec581021dcbb023d4d7dcd4a699b2
GIT binary patch
literal 6468
zcmV-K8N23*P)<h;3K|Lk000e1NJLTq002k;002k`1^@s6RqeA!000>aNkl<ZcwX&X
z2XvKXntqdz-V5oK&_N-MRJvlFwVf@F&ggn{#*QtHLD-FYM(4QdtR8n}_2{gQBZ$<H
zgeq;78Uh3oZgNQ^Aw8s$UT(6_^WFPjlH4fKy|}Q)k8{t>y??pi_k3@8-tuE3KacN4
zz~*Osz8`^~0lJKY{1||2aZg_NW3K(b6xbotj}PKq`}?x<`5}w6U9x0J*tF?0-*Itq
zz0uaz#@)u|hg^Qyxm?i`hK9t@*Vj|p+}iT?XP<oX7yOuEJU=+z725dt__*J7_dN$Y
zI=g3ec6P{w3HD-VXNPw?^=|xo!pL_!e8%w!ICiDL9Ky;63<en(7%;y2P4A}9{pRoc
z)m*&2eGC^{o6<LI*zn$XKpHPV6PDd_+hew3^B!fBf-^8Mt_>K^CjBJmaD!}ZZKc1z
zU)<bWj&Iq#DGfi|b2)VzFF=k97A$&c$i^nq(a}i;2dT!_-r&Y^?n8qECHwa7O~eY?
z@%rAyj0Yf|<D8m0|0Qg}Do000p;%w5g8&Wm_vsIO^K~-T&~`aB8#h2HDJdB?HWOCb
z|33i8Fwmzzc;FlB0+gfxvR&!m;3ys*?&3eu@0t>#ei-lait|dv1_t`|hYudGE<lM%
zDH*WN0LVc++}$NLB?&-zNLyRmRjqPPPEO+L>MEyBotD!@XG{PM=#L&gXl;O!G6si?
z0Kv-1(j|+<*VjiTPl*->M|^h`4%*7b3ypH2p-~PUJ|gEUs}(?l0|4mgVe0~vkeHN#
zA2b0pB*{sMa?ju2B~DI`BRCExr91`R$&H5dAAZM|0lbBrY51`V7cNL`ZLQ3hF++|V
zIU=Q{rDn({PMj!#fq`=U_1DYc!-u7+s>*zfuKSi-ZV_i^XE}KApy+fu^D#d^KjF*Q
zXQJfhZj*}hRhk#m=N&z2ZGaLp3<d&ZuVRd})MUB)o;$@JpUEw>!G&{>v{Bmn`ufbr
z2$;r2c#PL+9`V$vQwpGE%a$qfSzB9YrW5?WzP?^=y6Gm_y?eKWgoKETi;KE1y=r)P
zxb*h+%C23zWa`wZO7%G|Uc4v=4jhogdC8KKoh#=mDvkFX8q^=lvo=5pi5dL{gOVUy
zAe5StEO*`WcWUQ4J3GxFaKap1JQvZ?(P2KOuEmaXF|-R^U0r4nPM<!lK)B(C8`QC$
zo*pG>9B0p-m5PcAb!_e0wX$^SQY9H2+z{TE_u9B|qa-IMOHfddI!3A7yLYd|$0f+t
ztvlpgd4&m}A$|U_JS$63LPAF0fCi9_0_cuAZx>fLSG7~xAYdE>!5zk&MqC>Nk5bg#
z-7TJ;o+Au=VPT<K(Bj35)iDCA0Yr7HtgKYW5F*IJg$vC9+1uMg&7GvZy<M`hvLrDv
zQK=(GV`HQ2-@jjCV&Y`y_OGPu+&OJS^e6I<Sr?!<0My^tztSGNWd|upNlud6esPO%
zf&@bwc6PQZPNM`l;@oaXf;K2UR8gMKMRj*yI@aEPQGqaX<}7vk(W6Jq0P!<oL76^n
zx*W|r3JFHgV+xe%AYx)-@OuNI*O$oT$&(?acIqBOa3*^FS<J&pc7L^3O27TqWFJLe
za3bHz0L3L_^!E3yBp?hvX{2c{C^5q&h*Cn4r<Az4xk+nls~I3(%kwE6loB4(Y!L5p
z{``5FJ$tq?#5|AE#=-m0KvPNxERSo_#dqF!?%cVOmzO7V=FCyQ&%sSPapHso2M5d7
zUw<w7(lWD(ohmqKU4UZZ;xb@xS5i9cq4xyn#v5-`qF!BHZ3e;H+gt4}7eiaXMQekg
za52rz%@P(Cro551&@*SwC>5jnY~8w5;^N{YARxdD5bY9e6b*7+U7gwZ^Zt6hUfHLd
zoE%9=NHAa5)YPPaiI0z$gNF`D38aZe8q?FCE-0`vKrwL{J$=29pa}rTUXl{$DPze+
z(12=#fDs_tp3u-xIS0Mhq=ev6!U#lERFssLmn(0_QBqQ(Y({Eos_fjkQ>hKzI|qR!
zK=fw3jvJz>H9@A0TC`}9Y~Q|J0jpidjW`5gc&{Tzj*1SyVFbuVUv&DEbpe_a6O+-?
zLxAkX0WSiyV8H@48A^*bxDZN3OG}F~mcxy-rZ$uSx@umhWd`WQ2mp<2K|z61G0h|T
z`uf5)%~UqZ%gal7H4Rv*EAN+<mZsE+U{Vb=6)P?-R_d9bpD$;MO#q3F{!Gzn>jHG$
zoH-fTHKk&XFtiB}TZ;3fNt2Z8)&|`(7f#nt3DS5GkMkJ6Cujsn1AxaU31wwvO7*BV
zlrnAb{s@+pUE_kOI>XhLyoahs;2Ihl%*SZE2pDZy5yFf@s4+JL*-*W%sL09yT{kDA
zySH0O5WUk~ciyRtGXWS5f_ZjV!5>s@ZVGK74frWg2?Y>TtXQYBE<m%cyDp=ryJzJD
z*dS+!@G=-^s*n~|Tm^WHIR(uVk`HM%2sA}sQe13hfM(6g=<M!>iW!+JLAsU(H?j>9
zZ;BE1rFy+}0h%&p+Dp#PPOBm!BChK6#^pAx6Cug@it{C9WoJ`xuJtmC$VsQ)eDkfD
zx88dD<>qizAFr#cm5)FEP`7T~S|;tS3DAP{8*aEcbK$}z<FC41g-|s&H_DpTe+58a
zS|6aLO9{|oEPDuK%NB06L>ew!a;q91bm0*ZQ7UF?ZfR06(eN=iI3(_H!48O#7*aJh
zT^K<Xc&@=PC_dgk>U-Lc=9Xr->q~Tk=kR+!--)<CBlmjIpVeE`-^j~i=Ej=&7AeB&
z)t~7$Y`6jdEm?9?=7NQbRN<C6n?Jt$&WJ!&1Hli(?xBYsK{(JW=fQ8hd@S?VeO(>!
zf<cJKnx(U+Uz|<(9qk@N%>4ED%0xeZxzN}kw)RNO4dYviSHYgRI5~hkddrqAn*h*R
z3z_W!cY5l{<={hJ>KG+x_3F=c8#i77fEF)aW(J76`Nki9FP&Y+T#&ZY{5uE>fBdnh
zq^GxAN)QIPA?@Mm=^?IQ$L$@Rc%d>2$q5SymnKZO3B-~7%E#MFajLe9?Fx7oc*_9)
zK&fx2lP)CQNK$>gJrz@LYHmd^+9$psg1*>*b?etkVbN&|fEcj8@cfIo&esG`6IASP
zx{aIGSy{!>7Njp)a$_cpwNf#r6kd6Cm0B#*u8hIz>l;)A9SRj&{`B)8Y29*4S12xS
z?rL(Zgft*W%;5|mCPhX=H5#O*v0f(FJ4jG~zXIw)W0OjgctJfvf<mOawpv=jz}@iK
z0X&QhxemY8h0O`}3qZKzFRRylCMOF{B1Cag?4E4Bs;Zh53|Wz}3aV(7;wHprU#$6D
zx5?T7r2(MDi<c^ZST>}6d3oiFid&GWGG9|uQ=?+A@UU=6PtQPT&?UzTj*BxE<^t()
zadC#dX;u?;b99zzQ>IBneVv@EsRTgw03%46aJ>frXh7gP(bF6Eia@d*p+{S@O0zH)
zbH#nCYHFnyAVvj;iGM(#eER8MB>(s^6~oP$bG_8!cm+Zh1A^gKGJh|Arogwkxk=U%
zpv@bsD?tQk(V``p^C>|<$Od`W7t)`B4QVkh7>m-<tC29!D__o3JA5GTkT|*m94BX`
z8ptWB^Sxa>WcG~N2sCS?xV%*CAQ_(S9*Tf+m-`I^66zZ$QIV67Oa4}>8tc@iOoV;w
zf%>%}!Dc{`Y<lEm*s@^x=;QyCBS#NgsMsKW^NrWv5`TYV>O=vw_H*6l%^R%`5L`R}
zTF6wL>R|ZYi_eaT);R&xE!3{RypjNQ$-e*IBOYGHB)T?+;KkgW-QZo~5W!bVVR0dh
zv@uny4Myw1{=qU8QbzAo(@<>@^L2N1%k1g1A;BTwHXk4hdDy~>^}<oS_13$vQ<@EG
zRsdyXspbXi@t~BHG^p5;%(OJBn6Wm0rU-fD#!FGRwz%M+V3{&?nsTkBSlsZ+hB^lZ
zIxrwei9L;RSy`!Yr-u*bl)}R!#2t}(yUI3JSTJ~7IEv`#DJm;at+D}UEjXGhykl%^
zg793U1U1XLFIQg~fEF%V41gd(reVXCj#b?Uej141)fY6p0ph1Thbs2v+BLfD?9JAf
zAQ<S(wE0FAvxULjw{MRzH!~b0fJ35~n8;XAq`IaG>f<^>ItD=kC)nADr-!%lG7XTz
z(LAUhZk@aDifc(F7(=mOFno05JAc24%KaA?pFu+GjD?CZJ$&mex0{KmlAv`2D9hRa
zrKHcFZ<HWP2R+Jr@BcgOOt+Z>ArS?&8UlCmmj@qsNabQmAolJao@!mJh8Rq}MYdqq
z;h_<VMK@n;1FLp~i#K+t_4f8Db#lVu0^p(Q;puuzsachKGIf=ai2C5(WYamhS)xNS
zZS)ub?Rf05C(J~#1puvI|HTymC@Cd9ZNcKq)U^3(4kU(ezw@Tj|EQ=))W0uC2e=9)
zDj4qik%u1xm+6wC5*>)52LPQQkr81iy_{3m+ruM8MMfze)Bv%kbc6&4NhgTpMQ|(H
zFE=!61qX+KK2%FbZ;wi|kW8|>jh?O#_RAl}mpo|Gru86-g_ZyT*k_hM$JRTu4cf4N
zjV>oA+qx2z1c2txUt|ObYD9ouf8$l<a>K$xl?(2Nx|}~>34ns-i6@>Gs=&#jQ{o0g
z8wjc5Ai?8s1YlEOV9Ane5JwRpo<k56QDkuu>J=6mE|t~ir48VFd3lN#?nMc%tg3;e
z^+@o<0L8YyT(<^9@dO$w{NU9B6-PP?7EfyNyYv;{X(lg*m~JB^C@1#{00i~QM5mdW
zAOXtEd|6t-SSNx-dmxlxh*5wVyEkH^XP-@1ZvI$7K8|@}m;GRKJV5%Y6iapibDlnR
z26zw%U2T;(g8u}9NfYQ6)XQjdeBC?|^hSd4l}l55o8m~`Ac|B)5=F*Y5ka9Uru+Q!
zzbc|QdJGO4WYx>Bh_9(sO%+2NrpvWHKuPJTsgNKOK=eE-R-`ND%<fSG#GYXSLygGD
zNN}W;SY(GB%0Gfo!_BBxSQsmy9PVJ!vu9o>HPvv>)fI|Kvsyv|N*l!C@98VilcMF@
zb7fNBYE0A<B&uI8j5zH}bV#HG!zq07$w#UhYXJ~!-D|JDq12Fr03pn<K0x#4C8bk>
zQc|J1s?}BGkFY@v4fPh2nuJ9?@|(xi4)58!3pU6IkT$qLFL<!IsF~9MpFEjwNffd8
zu&^kZGI=WMUV2F0IR)bIL83SpWRr$`^X3hxxE{c?M)D4BWX6hrMS8^;4>E1C831kF
znqyrXM1WFL7bt*+mx~#Yjvi<<SD^<7gGRdY(ZdZBRt=%>dJI<vN9ByTP^u=q7Fo5i
zzBaOXlsPG?Go_C67+wO`evd^?Z^Vh(pu`iPZQF9K4bZ&wloUu%3IJll4F8x2>w%#h
zJ)97CaVP>vmuzy;=JW8iJ#3J%1jB@wVML>Xx)wH$9EP-j^neZF%n=PA#(pC^1ujZC
z(glef4I+)c9UOwTsRSr%^ZF|TP;&D8%wzzhycnqa+O@0Uk}p+N)#Pz35asA!+<pf-
z;0F+BmO}-M{W{v9XX!++Nf*wpj#h*cnj6;6A%_~m^;*Kr28+}xrkA9lS$Q>g(1?Qv
zzd`N2SY<j#|JMEY{Tj7AW8YE>RBX$Z4FG7X^(81dIW03O`4T{X{`33bK9?G#w284^
zcK>}3C|6wxChcR&C^8_W-QeiPb)g}l%7*nr0-Rv9-C?Vopmt25^!4>iKN4<%{z0Gy
zwPJ6QN-qSyOvkpjcPQz#hvYC)-??+U6oYe(9*psr-Slubl2zKEEm<3MJ9ccdK0rxH
zsR|&9E8YCN@4hV;Apumk=xAdJnrY7vNXA1C{YJU@vhp(V1W9AE%MVST<={yi1K>|l
z;ZbVwodCiCq>#xjsvL7i7eF{|0fZuoi1-I_Z>k!l$Qyj98ZCO{I<B~mf$#PmThX6b
zWC;+|^5KUcHMiwa#j>+D>UQkB0szfROvwaDY7*4>H~;N*6;4c>Hq97TpnHNN3<<MG
zAAM5ggU;x6NOu_X!3;s_O|^5IfkBxJqF7s7BhBrt0LT$8+!@m54M6IYeRHz4SNEz!
z!mMYIhTcXsU<E;iYe6_W&;#HL$p{27-M&2+^><4qP5boQM;=!OUsJK{Et_>auLM8|
z^OB7KnG$BNz4kAP2hE*32W>9p=G;&u$lX&<KPO#azbA@Lf=L@E7l8Cey{Rd|0g^If
z>P&<YRZ`PLChdq6Y=laUkbHJ@c8Q0Ri$p~j6J{3?w$NxZ9HH^AL<<a=da!Q*cu**M
zin67!&@v}Ypt)I|9>z_g4a&~mq}#P?hxJt~At5;v4dqJ3ILTMQq+3iG#o@9Y9v&&r
zJ@+CyIXWcoL>{Ud-WH;7H)tcXXI+n8pGv8!uMsDx*YLsLaC3B3QdWw9x1+byB2q`M
zfP@AFql+XQ%~P9&Ei|JC0ebxLr_I4?D>gJIhXC!gGC=Y3(&OeOWhNy8AQK<S0y$$W
zHF9O5YGOCte5+#B`}gfuC8FVj5kIqlGtiI73Fjd#OBOzSkQz*zKFh3PCB;P+8AaY7
zn<idp%tC$#-Sj6Fh|$qt-{yYAA$ju2XH_oSC_ycf3xK}*YL|5ZiU&aP@rjTiW1feD
z?s{}4kl))u6||lqb<P-%PYrJ*Cm3TrL8P6d9-14?rSLlhLu<XsMqTX0$>XYcz@&Wz
zT7JJpcN^#N#1qeeMi`}-Dwdm*rQ5yR>Hx*3$HgaQszjJ6Cv9L=6{(65s=<jP?y*Dk
z%mp+e8D;>V87V;sHNb$K;DMOiW@Te^W|E*XezF4*q+KcppBjA*y;@XMwDNpeSsUc|
ziG1}ltfp$c+nmGl<<Bd|%|X_l1AzAI`O4Z76rUa&Ym^|(%{zShkwlFVi<M#01NZ!s
zDjYT=GLMLwG!jGrT#h_6(;@`W8DWPHsujZrGm16vUX0YKIt5C7Mv_1Vt0+aOh=0?@
zb!fpe0yJ{NhF}kWXC|U;04O(?0PVIeK(VoqptyON^AeNP?{n9NKKhgRVSy8n8XZ8A
z%NYc*7dqY$94d0}uauXm0nhP8TGShfE_SB1w6>{4eS2Fgh+-Im)mrGJ9r(@2ss?|f
z3oJbdC7t6)V|@M1e#^xJXfk#t?vW*w6Jw-1c+vAOWSH|ol%Q=}3D6#E0~D(yNCSwX
z+xOw0B*flM9AE(Z0Z=zwxE(-f0CRTu<*${SKUY}}>F|TJ1R@dEh>V~&4EqJ?!7<j)
z$b)*Jf}_I-F3bQRLjWSnIeYegrBWU2a^+z6ESdT}cmIQ`pnUn|YB`>tX8|I=NJr|0
zji5#qYnJV~*}6UZt_VO0nczZZH{bE$e@FyexDB6r2GQ#V31mQ72may;fM^HwWhHR)
zK9H7Rl__Mxs}B9n1IYPIo&=(Zbn3;P4n+2r9c#9TO<7|*bm)NW08?hwbTT{?M=2UU
z3DCXw-VdTWgS9TnKX!BsfNbQYmsXioxUG#6l%qqz!TJ&u69a$}l8n(jB&g|w_r(n=
zrlN92)^OF_^={Z4?_WNka@41bPb((v1t!aIgG`!{xvgPHWfaMSSXL&J=I}yhvbD|l
z?1!_RgQ~24^YwnkbH*AEJn*oh3u{(?CdZChCd_!viWQmWjA9#X;<jx$y8ZjD3=kd&
zc_B77?v=QBJ}hh;wg2^@c#hE@)DG1N_{aN{1RXtg2(@!#HcuPmL56;V%$*a9f?p*9
z+6q-596r?3LLiD$r%p$={66e@wgo=im<Rv)5t%-HrmSE01&sL#i*uOZPk(WR*+}#8
zhaKCq0nk3HNsv9B+VQ|cBFkfA=dFsxqeaTgV=A5K+~^&HNjo8t?t)C90ePOH!c&$#
zM7TIKGz=ap1X;%}IfEc`tjcQi<SC#7KHx4*>OO;`p0*|jij9c}3wKeH$ja*I;eunF
zv-W^X8=S+oty_xo^Yd<iin1(x>4CN}6?9C&z4YaE7$93yFqxa@&W(R0CN}<>ZsNR%
z8r=46TS_5GD<FYqi;9X0u-fl@&i!(ah1-C11)yX8R{$s#HSRm&VS}#Je=~8Jy|T(-
zJ`Vzbs*tGr5LMQ{;Q<*wX!4!^xxi%s!o?1FVC8PO;opL9EM)Tnn+~*q)he_8iEcth
zUI<k^@Xfwj<f`7pMt^`uAo#G`{|X>Rk$!mQ;cZCMZ8K)fa-R(QLXenOX1SU14qs%t
zr2n?4yKA3~$41v0tcM77gGQhA{=p&Uw(*Y7L(Frt&k>*b-T~5CfpDdy_{<>Q^N_!K
z5P-4FOB?i~Rg5cPDDDCYiic-ej^|M0(Gcl}WVq2UUn2uS^B@v<?XW$Ckeqh_&K^vi
z`Qnj*;;8jpPQ`dS&!H5tMT40y)^pfqLEy%7{nY>=C4?`gCE0b#8WPFY_ezhp`0*@&
zK=65Q0>c;o=6BZ`K(sXkg)f42+5g7ohb~b2@40>mxZ~M?pW+<N&S=s!uK&X3hXV9d
eEaa;CjsF2v=*9hN8J*_<0000<MNUMnLSTY-D=!-W

literal 0
HcmV?d00001

diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-mdpi/ic_launcher.png b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000000000000000000000000000000000000..1838c339a6cca308f5ee9aac6091784d15eb109e
GIT binary patch
literal 3664
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

literal 0
HcmV?d00001

diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-xhdpi/ic_launcher.png b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000000000000000000000000000000000000..2f5cc8fe8721bdc192b7436ba3a125580ac4e429
GIT binary patch
literal 9291
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

literal 0
HcmV?d00001

diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-xxhdpi/ic_launcher.png b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000000000000000000000000000000000000..1814a2578e7575ee9edf598ade0211cf0e2f4212
GIT binary patch
literal 16093
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

literal 0
HcmV?d00001

diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/layout/activity_calculator.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/layout/activity_calculator.xml
new file mode 100644
index 0000000000..57d8882632
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/layout/activity_calculator.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+    <TextView
+            android:id="@+id/txt_calc_operator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="24dp"
+            android:layout_marginLeft="10dp"
+            android:layout_marginRight="10dp"
+            android:textSize="24dp"
+            android:gravity="center"
+            android:layout_alignLeft="@+id/layout_calc_btns"
+            android:layout_above="@+id/layout_calc_btns"/>
+
+    <TextView
+            android:id="@+id/txt_calc_display"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="24dp"
+            android:layout_marginLeft="10dp"
+            android:layout_marginRight="10dp"
+            android:textSize="24dp"
+            android:gravity="right"
+            android:layout_alignRight="@+id/layout_calc_btns"
+            android:layout_above="@+id/layout_calc_btns"
+            android:layout_toRightOf="@+id/txt_calc_operator"
+            android:hint="@string/txt_calc_display_hint"/>
+
+    <GridLayout
+            android:id="@+id/layout_calc_btns"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="24dp"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:useDefaultMargins="false"
+            android:columnCount="4">
+
+        <!-- row 4 -->
+        <Button
+                android:id="@+id/btn_spec_sqroot"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onSpecialPressed"
+                android:text="√"/>
+
+        <Button
+                android:id="@+id/btn_spec_pi"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onSpecialPressed"
+                android:text="π"/>
+
+        <Button
+                android:id="@+id/btn_spec_percent"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onSpecialPressed"
+                android:text="%"/>
+
+        <Button
+                android:id="@+id/btn_spec_clear"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onSpecialPressed"
+                android:text="C"/>
+
+        <!-- row 3 -->
+        <Button
+                android:id="@+id/btn_d_7"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="7"/>
+
+        <Button
+                android:id="@+id/btn_d_8"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="8"/>
+
+        <Button
+                android:id="@+id/btn_d_9"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="9"/>
+
+        <Button
+                android:id="@+id/btn_op_divide"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onOperatorPressed"
+                android:text="/"/>
+
+        <!-- row 2 -->
+        <Button
+                android:id="@+id/btn_d_4"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="4"/>
+
+        <Button
+                android:id="@+id/btn_d_5"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="5"/>
+
+        <Button
+                android:id="@+id/btn_d_6"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="6"/>
+
+        <Button
+                android:id="@+id/btn_op_multiply"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onOperatorPressed"
+                android:text="x"/>
+
+        <!-- row 1 -->
+        <Button
+                android:id="@+id/btn_d_1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="1"/>
+
+        <Button
+                android:id="@+id/btn_d_2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="2"/>
+
+        <Button
+                android:id="@+id/btn_d_3"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="3"/>
+
+        <Button
+                android:id="@+id/btn_op_subtract"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onOperatorPressed"
+                android:text="–"/>
+
+        <!-- row 0 -->
+        <Button
+                android:id="@+id/btn_d_0"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onDigitPressed"
+                android:text="0"/>
+
+        <Button
+                android:id="@+id/btn_spec_comma"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onSpecialPressed"
+                android:text="."/>
+
+        <Button
+                android:id="@+id/btn_op_equals"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onOperatorPressed"
+                android:text="="/>
+
+        <Button
+                android:id="@+id/btn_op_add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:onClick="onOperatorPressed"
+                android:text="+"/>
+    </GridLayout>
+</RelativeLayout>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/menu/menu_main.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000000..87a750ece0
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/menu/menu_main.xml
@@ -0,0 +1,5 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+    <item android:id="@+id/action_settings" android:title="@string/action_settings"
+        android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-v21/styles.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000000..dba3c417be
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-v21/styles.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="AppTheme" parent="android:Theme.Material.Light">
+    </style>
+</resources>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-w820dp/dimens.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000000..63fc816444
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/dimens.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/dimens.xml
new file mode 100644
index 0000000000..47c8224673
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/strings.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..1d58a11872
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Cukeulator</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="action_settings">Settings</string>
+    <string name="txt_calc_display_hint">0.0</string>
+
+</resources>
diff --git a/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/styles.xml b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..ff6c9d2c0f
--- /dev/null
+++ b/test_projects/android/cucumber_sample_app/cukeulator/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+    </style>
+
+</resources>
diff --git a/test_projects/android/multi-modules/multiapp/.gitignore b/test_projects/android/multi-modules/multiapp/.gitignore
index 796b96d1c4..cfabd204f6 100644
--- a/test_projects/android/multi-modules/multiapp/.gitignore
+++ b/test_projects/android/multi-modules/multiapp/.gitignore
@@ -1 +1,2 @@
 /build
+*.apk
diff --git a/test_projects/android/ops.sh b/test_projects/android/ops.sh
old mode 100644
new mode 100755
index fc98c7de9d..00636bef56
--- a/test_projects/android/ops.sh
+++ b/test_projects/android/ops.sh
@@ -113,4 +113,24 @@ function multi_module_apks() {
     esac done
 }
 
+function cucumber_sample_app() {
+  local dir=$TEST_PROJECTS_ANDROID
+  local outputDir="$FLANK_FIXTURES_TMP/apk/cucumber_sample_app/"
+
+  for arg in "$@"; do case "$arg" in
+
+    '--generate' | '-g')
+      "$dir/gradlew" -p "$dir" \
+        :cucumber_sample_app:cukeulator:assemble \
+        :cucumber_sample_app:cukeulator:assembleAndroidTest
+      ;;
+
+    '--copy' | '-c')
+      mkdir -p "$outputDir"
+      find "$dir/cucumber_sample_app" -type f -name "*.apk" -exec cp {} "$outputDir" \;
+      ;;
+
+    esac done
+}
+
 echo "Android test projects ops loaded"
diff --git a/test_projects/android/settings.gradle b/test_projects/android/settings.gradle
index c60d26caa1..8a4647ef91 100644
--- a/test_projects/android/settings.gradle
+++ b/test_projects/android/settings.gradle
@@ -27,3 +27,7 @@ include ':app',
         ':dir1:testModule',
         ':dir2:testModule',
         ':dir3:testModule'
+
+
+include ':cucumber_sample_app'
+include ':cucumber_sample_app:cucumber-android', ':cucumber_sample_app:cukeulator'
diff --git a/test_projects/ops.sh b/test_projects/ops.sh
old mode 100644
new mode 100755