Merge "Add OWNERS in external/droiddriver" am: 059f2d82ef
am: ef314d759d

Change-Id: I9fab03e2cb384fc08ab5dae3bc182c61fdde0603
diff --git a/.gitignore b/.gitignore
index 3d0ebbd..93237bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,17 +3,14 @@
 
 # gradle junk
 .gradle
-gradle/
-gradlew
-gradlew.bat
 build
 
 # Android Studio junk
 .idea/
 *.iml
 
-# Don't check in properties
-*.properties
+# Don't check in local.properties
+local.properties
 
 .DS_Store
 
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 2a752d6..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := droiddriver
-LOCAL_MODULE_TAGS := optional
-LOCAL_SDK_VERSION := 19
-
-LOCAL_JAVACFLAGS += -Xlint:deprecation -Xlint:unchecked
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/build.gradle b/build.gradle
index 3af6346..8cc329e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,6 @@
 // sdk.dir for the Android SDK path, you can run
 // $ ANDROID_HOME=/path/to/android-sdk gradle build
 
-// Gradle >= 2.4 required
 buildscript {
     ext.bintrayUser    = project.hasProperty('bintrayUser') ? project.bintrayUser : System.getenv('BINTRAY_USER')
     ext.bintrayKey     = project.hasProperty('bintrayKey')  ? project.bintrayKey  : System.getenv('BINTRAY_KEY')
@@ -12,7 +11,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.0.1'
+        classpath 'com.android.tools.build:gradle:1.3.0'
         classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
         classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
         if (bintrayEnabled) {
@@ -29,17 +28,25 @@
 apply plugin: 'android-sdk-manager'
 apply plugin: 'com.android.library'
 
+repositories {
+    jcenter()
+}
+
+dependencies {
+    compile 'com.android.support.test:runner:0.4.1'
+}
+
 tasks.withType(JavaCompile) {
-    options.compilerArgs << '-Xlint:deprecation'
+    options.compilerArgs << '-Xlint:deprecation' << '-Xlint:unchecked'
 }
 
 android {
-    compileSdkVersion 21
+    compileSdkVersion 23
     buildToolsVersion '21.1.2'
 
     defaultConfig {
         minSdkVersion 8
-        targetSdkVersion 21
+        targetSdkVersion 23
         versionCode 1
         versionName version
     }
diff --git a/contributing.md b/contributing.md
index f8ae829..645ef1b 100644
--- a/contributing.md
+++ b/contributing.md
@@ -6,18 +6,13 @@
 
 Code changes should be [submitted to AOSP](contributing_aosp.md) and then they'll be synced to GitHub once they've passed code reivew on Gerrit.
 
-#### Requirements
+#### Build
 
-Gradle 2.2.1 or better is required to be installed on the system. In Android Studio, you'll need to provide the gradle location.
-
-On Mac OSX with homebrew, `brew install gradle` will install gradle. To locate the path, use `brew info gradle` The homebrew path follows this format: `/usr/local/Cellar/gradle/2.2.1/libexec`
-
-If you installed gradle using the zip (`gradle-2.2.1-bin.zip`), then the path will be the `gradle-2.2.1` folder.
+`./gradlew build`
 
 #### Import into Android Studio
 
 - Clone from git
 - Launch Android Studio and select `Open an existing Android Studio project`
 - Navigate to `droiddriver/build.gradle` and press Choose
-- Select `Use local gradle distribution` and enter the Gradle path
 - Android Studio will now import the project successfully
diff --git a/contributing_aosp.md b/contributing_aosp.md
index c57a0d1..58bca60 100644
--- a/contributing_aosp.md
+++ b/contributing_aosp.md
@@ -16,9 +16,12 @@
 ```
 
 The code should be downloaded to the current dir. You may see some lines in the output like:
-curl: (22) The requested URL returned error: 401 Unauthorized
+
+`curl: (22) The requested URL returned error: 401 Unauthorized`
+
 These messages seem non-fatal and you should see these dirs after it is done:
-build/  external/  frameworks/  Makefile  prebuilts/
+
+`build/  external/  frameworks/  Makefile  prebuilts/`
 
 #### Submitting Patches
 
@@ -51,16 +54,3 @@
 - `repo upload`
 
 The [`repo prune`](https://source.android.com/source/using-repo.html) command can be used to delete already merged branches.
-
-#### Building
-
-This sets up environment and some bash functions, particularly "tapas"
-(the counterpart of "lunch" for unbundled projects) and "m".
-
-```bash
-$ . build/envsetup.sh
-$ tapas droiddriver ManualDD
-$ m
-```
-
-ManualDD is an APK you can use to manually test DroidDriver.
diff --git a/droiddriver-android_support_test/Android.mk b/droiddriver-android_support_test/Android.mk
deleted file mode 100644
index 22c6c0a..0000000
--- a/droiddriver-android_support_test/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := droiddriver-android_support_test
-LOCAL_MODULE_TAGS := optional
-LOCAL_SDK_VERSION := 19
-
-LOCAL_JAVACFLAGS += -Xlint:deprecation -Xlint:unchecked
-
-# android-support-test requires /frameworks/testing, /external/junit, /external/hamcrest
-LOCAL_JAVA_LIBRARIES := droiddriver android-support-test
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/droiddriver-android_support_test/AndroidManifest.xml b/droiddriver-android_support_test/AndroidManifest.xml
deleted file mode 100644
index f9f47f8..0000000
--- a/droiddriver-android_support_test/AndroidManifest.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest
-    package="io.appium.droiddriver.android_support_test">
-
-</manifest>
diff --git a/droiddriver-android_support_test/build.gradle b/droiddriver-android_support_test/build.gradle
deleted file mode 100644
index 3f6120f..0000000
--- a/droiddriver-android_support_test/build.gradle
+++ /dev/null
@@ -1,70 +0,0 @@
-buildscript {
-    repositories {
-        jcenter()
-    }
-    dependencies {
-        // this requires Gradle 2
-        classpath 'com.android.tools.build:gradle:1.0.1'
-        classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
-    }
-}
-
-apply plugin: 'android-sdk-manager'
-apply plugin: 'com.android.library'
-
-ext.ddSnapshot = hasProperty('ddSnapshot')
-
-repositories {
-    jcenter()
-    if (ddSnapshot) {
-        // For development only - droiddriver SNAPSHOTs published here
-        maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' }
-    }
-}
-
-dependencies {
-    if (ddSnapshot) {
-        // For development only.
-        compile 'io.appium:droiddriver:1.0.0-SNAPSHOT'
-    } else {
-        // This is broken now b/c droiddriver-1.0.0 is not published yet
-        compile 'io.appium:droiddriver:1.0.0'
-    }
-
-    compile 'com.android.support.test:testing-support-lib:0.1'
-}
-
-tasks.withType(JavaCompile) {
-    options.compilerArgs << '-Xlint:deprecation'
-}
-
-android {
-    compileSdkVersion 21
-    buildToolsVersion '21.1.2'
-
-    defaultConfig {
-        minSdkVersion 8
-        targetSdkVersion 21
-        versionCode 1
-    }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_7
-        targetCompatibility JavaVersion.VERSION_1_7
-    }
-
-    sourceSets {
-        main {
-            manifest.srcFile 'AndroidManifest.xml'
-            java.srcDirs = ['src']
-        }
-    }
-
-    lintOptions {
-        // Aborting on lint errors prevents jenkins from processing the Lint output
-        // https://wiki.jenkins-ci.org/display/JENKINS/Android%20Lint%20Plugin
-        abortOnError false
-    }
-}
-
-//TODO: add script for publishing
diff --git a/droiddriver-android_support_test/readme.md b/droiddriver-android_support_test/readme.md
deleted file mode 100644
index f4d7ebb..0000000
--- a/droiddriver-android_support_test/readme.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# droiddriver-android_support_test
-
-An optional library that integrates DroidDriver with [the Android Support Test Library](https://code.google.com/p/android-test-kit/wiki/AndroidJUnitRunnerUserGuide).
-This is an experimental library because the Android Support Test Library is at early stage and many
-APIs are in internal packages.
\ No newline at end of file
diff --git a/droiddriver-android_support_test/src/io/appium/droiddriver/android_support_test/D2AndroidJUnitRunner.java b/droiddriver-android_support_test/src/io/appium/droiddriver/android_support_test/D2AndroidJUnitRunner.java
deleted file mode 100644
index 89e32d1..0000000
--- a/droiddriver-android_support_test/src/io/appium/droiddriver/android_support_test/D2AndroidJUnitRunner.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 DroidDriver committers
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.appium.droiddriver.android_support_test;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.os.Looper;
-import android.support.test.runner.AndroidJUnitRunner;
-import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
-import android.support.test.runner.lifecycle.Stage;
-import android.util.Log;
-
-import java.util.Iterator;
-import java.util.concurrent.Callable;
-
-import io.appium.droiddriver.util.ActivityUtils;
-import io.appium.droiddriver.util.InstrumentationUtils;
-import io.appium.droiddriver.util.Logs;
-
-/**
- * Integrates DroidDriver with AndroidJUnitRunner. <p> TODO: support DroidDriver test filter
- * annotations.
- */
-public class D2AndroidJUnitRunner extends AndroidJUnitRunner {
-  private static final Callable<Activity> GET_RUNNING_ACTIVITY = new Callable<Activity>() {
-    @Override
-    public Activity call() {
-      Iterator<Activity> activityIterator = ActivityLifecycleMonitorRegistry.getInstance()
-          .getActivitiesInStage(Stage.RESUMED).iterator();
-      return activityIterator.hasNext() ? activityIterator.next() : null;
-    }
-  };
-
-  /**
-   * {@inheritDoc} <p> Initializes {@link InstrumentationUtils}.
-   */
-  @Override
-  public void onCreate(Bundle arguments) {
-    InstrumentationUtils.init(this, arguments);
-    super.onCreate(arguments);
-  }
-
-  /**
-   * {@inheritDoc} <p> Hooks {@link ActivityUtils#setRunningActivitySupplier} to {@link
-   * ActivityLifecycleMonitorRegistry}.
-   */
-  @Override
-  public void onStart() {
-    ActivityUtils.setRunningActivitySupplier(new ActivityUtils.Supplier<Activity>() {
-      @Override
-      public Activity get() {
-        try {
-          // If this is called on main (UI) thread, don't call runOnMainSync
-          if (Looper.myLooper() == Looper.getMainLooper()) {
-            return GET_RUNNING_ACTIVITY.call();
-          }
-
-          return InstrumentationUtils.runOnMainSyncWithTimeout(GET_RUNNING_ACTIVITY);
-        } catch (Exception e) {
-          Logs.log(Log.WARN, e);
-          return null;
-        }
-      }
-    });
-
-    super.onStart();
-  }
-}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..c97a8bd
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..80b332a
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Mar 14 09:50:19 PDT 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/manualtest/Android.mk b/manualtest/Android.mk
deleted file mode 100644
index 6b52b73..0000000
--- a/manualtest/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_PACKAGE_NAME := ManualDD
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    droiddriver
-
-LOCAL_SDK_VERSION := 19
-
-include $(BUILD_PACKAGE)
-
diff --git a/manualtest/AndroidManifest.xml b/manualtest/AndroidManifest.xml
index 7e07f25..da5117a 100644
--- a/manualtest/AndroidManifest.xml
+++ b/manualtest/AndroidManifest.xml
@@ -4,7 +4,7 @@
     package="io.appium.droiddriver.manualtest">
 
     <instrumentation
-        android:name="io.appium.droiddriver.runner.TestRunner"
+        android:name="android.support.test.runner.AndroidJUnitRunner"
         android:targetPackage="io.appium.droiddriver.manualtest" />
 
     <!-- Needed for Android.mk -->
diff --git a/manualtest/build.gradle b/manualtest/build.gradle
index a732fe3..22731e4 100644
--- a/manualtest/build.gradle
+++ b/manualtest/build.gradle
@@ -3,8 +3,7 @@
         jcenter()
     }
     dependencies {
-        // this requires Gradle 2
-        classpath 'com.android.tools.build:gradle:1.0.1'
+        classpath 'com.android.tools.build:gradle:1.3.0'
     }
 }
 
@@ -12,17 +11,13 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 21
+    compileSdkVersion 23
     buildToolsVersion '21.1.2'
-
     defaultConfig {
         minSdkVersion 8
-        targetSdkVersion 21
-        // Force remove the suffix '.test'
-        testApplicationId 'io.appium.droiddriver.manualtest'
-        testInstrumentationRunner 'io.appium.droiddriver.runner.TestRunner'
+        targetSdkVersion 23
+        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
     }
-
     sourceSets {
         main {
             manifest.srcFile 'AndroidManifest.xml'
@@ -31,6 +26,8 @@
             java.srcDirs = ['src']
         }
     }
+    productFlavors {
+    }
 }
 
 // Building with droiddriver source. Common tests should use droiddriver from jcenter by having
@@ -39,7 +36,7 @@
 //     jcenter()
 // }
 // dependencies {
-//     androidTestCompile 'io.appium:droiddriver:0.9.1-BETA' // or another version
+//     androidTestCompile 'io.appium:droiddriver:1.0.0-BETA1' // or another version
 // }
 dependencies {
     androidTestCompile project(':droiddriver')
diff --git a/manualtest/src/io/appium/droiddriver/manualtest/ManualTest.java b/manualtest/src/io/appium/droiddriver/manualtest/ManualTest.java
index 83966f7..59beac4 100644
--- a/manualtest/src/io/appium/droiddriver/manualtest/ManualTest.java
+++ b/manualtest/src/io/appium/droiddriver/manualtest/ManualTest.java
@@ -16,10 +16,10 @@
  * {@link #testSetTextForPassword} assumes the password_edit field is displayed
  * on screen.
  * <p>
- * Run it as (optionally with -e debug true)
+ * Run it with
  *
  * <pre>
- * adb shell am instrument -w io.appium.droiddriver.manualtest/io.appium.droiddriver.runner.TestRunner
+ * ../gradlew :connectedAndroidTest
  * </pre>
  */
 public class ManualTest extends BaseDroidDriverTest<Activity> {
diff --git a/src/io/appium/droiddriver/UiElement.java b/src/io/appium/droiddriver/UiElement.java
index a003367..aa55d09 100644
--- a/src/io/appium/droiddriver/UiElement.java
+++ b/src/io/appium/droiddriver/UiElement.java
@@ -17,9 +17,7 @@
 package io.appium.droiddriver;
 
 import android.graphics.Rect;
-
-import java.util.List;
-
+import android.view.accessibility.AccessibilityNodeInfo;
 import io.appium.droiddriver.actions.Action;
 import io.appium.droiddriver.actions.InputInjector;
 import io.appium.droiddriver.finders.Attribute;
@@ -27,113 +25,114 @@
 import io.appium.droiddriver.instrumentation.InstrumentationDriver;
 import io.appium.droiddriver.scroll.Direction.PhysicalDirection;
 import io.appium.droiddriver.uiautomation.UiAutomationDriver;
+import java.util.List;
 
 /**
  * Represents an UI element within an Android App.
- * <p>
- * UI elements are generally views. Users can get attributes and perform
- * actions. Note that actions often update UiElement, so users are advised not
- * to store instances for later use -- the instances could become stale.
+ *
+ * <p>UI elements are generally views. Users can get attributes and perform actions. Note that
+ * actions often update UiElement, so users are advised not to store instances for later use -- the
+ * instances could become stale.
  */
 public interface UiElement {
-  /**
-   * Gets the text of this element.
-   */
+  /** Filters out invisible children. */
+  Predicate<UiElement> VISIBLE =
+      new Predicate<UiElement>() {
+        @Override
+        public boolean apply(UiElement element) {
+          return element.isVisible();
+        }
+
+        @Override
+        public String toString() {
+          return "VISIBLE";
+        }
+      };
+
+  /** Gets the text of this element. */
   String getText();
 
   /**
-   * Gets the content description of this element.
+   * Sets the text of this element. The implementation may not work on all UiElements if the
+   * underlying view is not EditText.
+   *
+   * <p>If this element already has text, it is cleared first if the device has API 11 or higher.
+   *
+   * <p>TODO: Support this behavior on older devices.
+   *
+   * <p>The soft keyboard may be shown after this call. If the {@code text} ends with {@code '\n'},
+   * the IME may be closed automatically. If the soft keyboard is open, you can call {@link
+   * UiDevice#pressBack()} to close it.
+   *
+   * <p>If you are using {@link io.appium.droiddriver.instrumentation.InstrumentationDriver}, you
+   * may use {@link io.appium.droiddriver.actions.view.CloseKeyboardAction} to close it. The
+   * advantage of {@code CloseKeyboardAction} is that it is a no-op if the soft keyboard is hidden.
+   * This is useful when the state of the soft keyboard cannot be determined.
+   *
+   * @param text the text to enter
    */
+  void setText(String text);
+
+  /** Gets the content description of this element. */
   String getContentDescription();
 
   /**
-   * Gets the class name of the underlying view. The actual name could be
-   * overridden.
-   *
-   * @see io.appium.droiddriver.instrumentation.ViewElement#overrideClassName
+   * Gets the class name of the underlying view. The actual name could be overridden if viewed with
+   * uiautomatorviewer, which gets the name from {@link AccessibilityNodeInfo#getClassName}. If the
+   * app uses custom View classes that do not call {@link AccessibilityNodeInfo#setClassName} with
+   * the actual class name, uiautomatorviewer will report the wrong name.
    */
   String getClassName();
 
-  /**
-   * Gets the resource id of this element.
-   */
+  /** Gets the resource id of this element. */
   String getResourceId();
 
-  /**
-   * Gets the package name of this element.
-   */
+  /** Gets the package name of this element. */
   String getPackageName();
 
-  /**
-   * @return whether or not this element is visible on the device's display.
-   */
+  /** @return whether or not this element is visible on the device's display. */
   boolean isVisible();
 
-  /**
-   * @return whether this element is checkable.
-   */
+  /** @return whether this element is checkable. */
   boolean isCheckable();
 
-  /**
-   * @return whether this element is checked.
-   */
+  /** @return whether this element is checked. */
   boolean isChecked();
 
-  /**
-   * @return whether this element is clickable.
-   */
+  /** @return whether this element is clickable. */
   boolean isClickable();
 
-  /**
-   * @return whether this element is enabled.
-   */
+  /** @return whether this element is enabled. */
   boolean isEnabled();
 
-  /**
-   * @return whether this element is focusable.
-   */
+  /** @return whether this element is focusable. */
   boolean isFocusable();
 
-  /**
-   * @return whether this element is focused.
-   */
+  /** @return whether this element is focused. */
   boolean isFocused();
 
-  /**
-   * @return whether this element is scrollable.
-   */
+  /** @return whether this element is scrollable. */
   boolean isScrollable();
 
-  /**
-   * @return whether this element is long-clickable.
-   */
+  /** @return whether this element is long-clickable. */
   boolean isLongClickable();
 
-  /**
-   * @return whether this element is password.
-   */
+  /** @return whether this element is password. */
   boolean isPassword();
 
-  /**
-   * @return whether this element is selected.
-   */
+  /** @return whether this element is selected. */
   boolean isSelected();
 
   /**
-   * Gets the UiElement bounds in screen coordinates. The coordinates may not be
-   * visible on screen.
+   * Gets the UiElement bounds in screen coordinates. The coordinates may not be visible on screen.
    */
   Rect getBounds();
 
-  /**
-   * Gets the UiElement bounds in screen coordinates. The coordinates will be
-   * visible on screen.
-   */
+  /** Gets the UiElement bounds in screen coordinates. The coordinates will be visible on screen. */
   Rect getVisibleBounds();
 
-  /**
-   * @return value of the given attribute.
-   */
+  /** @return value of the given attribute. */
+  @SuppressWarnings("TypeParameterUnusedInFormals")
   <T> T get(Attribute attribute);
 
   /**
@@ -144,37 +143,13 @@
    */
   boolean perform(Action action);
 
-  /**
-   * Sets the text of this element. The implementation may not work on all UiElements if the
-   * underlying view is not EditText. <p> If this element already has text, it is cleared first if
-   * the device has API 11 or higher. <p> TODO: Support this behavior on older devices. <p> The IME
-   * (soft keyboard) may be shown after this call. If the {@code text} ends with {@code '\n'}, the
-   * IME may be closed automatically. If the IME is open, you can call {@link UiDevice#pressBack()}
-   * to close it. <p> If you are using {@link io.appium.droiddriver.instrumentation.InstrumentationDriver},
-   * you may use {@link io.appium.droiddriver.actions.view.CloseKeyboardAction} to close it. The
-   * advantage of {@code CloseKeyboardAction} is that it is a no-op if the IME is hidden. This is
-   * useful when the state of the IME cannot be determined.
-   *
-   * @param text the text to enter
-   */
-  void setText(String text);
-
-  /**
-   * Clicks this element. The click will be at the center of the visible
-   * element.
-   */
+  /** Clicks this element. The click will be at the center of the visible element. */
   void click();
 
-  /**
-   * Long-clicks this element. The click will be at the center of the visible
-   * element.
-   */
+  /** Long-clicks this element. The click will be at the center of the visible element. */
   void longClick();
 
-  /**
-   * Double-clicks this element. The click will be at the center of the visible
-   * element.
-   */
+  /** Double-clicks this element. The click will be at the center of the visible element. */
   void doubleClick();
 
   /**
@@ -185,50 +160,27 @@
   void scroll(PhysicalDirection direction);
 
   /**
-   * Gets an immutable {@link List} of immediate children that satisfy
-   * {@code predicate}. It always filters children that are null. This gives a
-   * low level access to the underlying data. Do not use it unless you are sure
-   * about the subtle details. Note the count may not be what you expect. For
-   * instance, a dynamic list may show more items when scrolling beyond the end,
-   * varying the count. The count also depends on the driver implementation:
+   * Gets an immutable {@link List} of immediate children that satisfy {@code predicate}. It always
+   * filters children that are null. This gives a low level access to the underlying data. Do not
+   * use it unless you are sure about the subtle details. Note the count may not be what you expect.
+   * For instance, a dynamic list may show more items when scrolling beyond the end, varying the
+   * count. The count also depends on the driver implementation:
+   *
    * <ul>
-   * <li>{@link InstrumentationDriver} includes all.</li>
-   * <li>the Accessibility API (which {@link UiAutomationDriver} depends on)
-   * does not include off-screen children, but may include invisible on-screen
-   * children.</li>
+   *   <li>{@link InstrumentationDriver} includes all.
+   *   <li>the Accessibility API (which {@link UiAutomationDriver} depends on) does not include
+   *       off-screen children, but may include invisible on-screen children.
    * </ul>
-   * <p>
-   * Another discrepancy between {@link InstrumentationDriver}
-   * {@link UiAutomationDriver} is the order of children. The Accessibility API
-   * returns children in the order of layout (see
-   * {@link android.view.ViewGroup#addChildrenForAccessibility}, which is added
-   * in API16).
-   * </p>
+   *
+   * <p>Another discrepancy between {@link InstrumentationDriver} {@link UiAutomationDriver} is the
+   * order of children. The Accessibility API returns children in the order of layout (see {@link
+   * android.view.ViewGroup#addChildrenForAccessibility}, which is added in API16).
    */
   List<? extends UiElement> getChildren(Predicate<? super UiElement> predicate);
 
-  /**
-   * Filters out invisible children.
-   */
-  Predicate<UiElement> VISIBLE = new Predicate<UiElement>() {
-    @Override
-    public boolean apply(UiElement element) {
-      return element.isVisible();
-    }
-
-    @Override
-    public String toString() {
-      return "VISIBLE";
-    }
-  };
-
-  /**
-   * Gets the parent.
-   */
+  /** Gets the parent. */
   UiElement getParent();
 
-  /**
-   * Gets the {@link InputInjector} for injecting InputEvent.
-   */
+  /** Gets the {@link InputInjector} for injecting InputEvent. */
   InputInjector getInjector();
 }
diff --git a/src/io/appium/droiddriver/actions/TextAction.java b/src/io/appium/droiddriver/actions/TextAction.java
index b108b00..18b28e8 100644
--- a/src/io/appium/droiddriver/actions/TextAction.java
+++ b/src/io/appium/droiddriver/actions/TextAction.java
@@ -21,29 +21,26 @@
 import android.os.SystemClock;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-
+import android.view.ViewConfiguration;
 import io.appium.droiddriver.UiElement;
 import io.appium.droiddriver.exceptions.ActionException;
 import io.appium.droiddriver.util.Preconditions;
 import io.appium.droiddriver.util.Strings;
 
-/**
- * An action to type text.
- */
+/** An action to type text. */
 public class TextAction extends KeyAction {
 
   @SuppressLint("InlinedApi")
   @SuppressWarnings("deprecation")
   private static final KeyCharacterMap KEY_CHAR_MAP =
-      Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? KeyCharacterMap
-          .load(KeyCharacterMap.BUILT_IN_KEYBOARD) : KeyCharacterMap
-          .load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-
+      Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
+          ? KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD)
+          : KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+  // KeyRepeatDelay is a good heuristic for KeyInjectionDelay.
+  private static long keyInjectionDelayMillis = ViewConfiguration.getKeyRepeatDelay();
   private final String text;
 
-  /**
-   * Defaults timeoutMillis to 100.
-   */
+  /** Defaults timeoutMillis to 100. */
   public TextAction(String text) {
     this(text, 100L, false);
   }
@@ -53,13 +50,20 @@
     this.text = Preconditions.checkNotNull(text);
   }
 
+  public static long getKeyInjectionDelayMillis() {
+    return keyInjectionDelayMillis;
+  }
+
+  public static void setKeyInjectionDelayMillis(long keyInjectionDelayMillis) {
+    TextAction.keyInjectionDelayMillis = keyInjectionDelayMillis;
+  }
+
   @Override
   public boolean perform(InputInjector injector, UiElement element) {
     maybeCheckFocused(element);
 
     // TODO: recycle events?
     KeyEvent[] events = KEY_CHAR_MAP.getEvents(text.toCharArray());
-    boolean success = false;
 
     if (events != null) {
       for (KeyEvent event : events) {
@@ -69,15 +73,15 @@
         // possible for an event to become stale before it is injected if it
         // takes too long to inject the preceding ones.
         KeyEvent modifiedEvent = KeyEvent.changeTimeRepeat(event, SystemClock.uptimeMillis(), 0);
-        success = injector.injectInputEvent(modifiedEvent);
-        if (!success) {
-          break;
+        if (!injector.injectInputEvent(modifiedEvent)) {
+          throw new ActionException("Failed to inject " + event);
         }
+        SystemClock.sleep(keyInjectionDelayMillis);
       }
     } else {
       throw new ActionException("The given text is not supported: " + text);
     }
-    return success;
+    return true;
   }
 
   @Override
diff --git a/src/io/appium/droiddriver/actions/accessibility/AccessibilityClickAction.java b/src/io/appium/droiddriver/actions/accessibility/AccessibilityClickAction.java
index 8198059..5405d58 100644
--- a/src/io/appium/droiddriver/actions/accessibility/AccessibilityClickAction.java
+++ b/src/io/appium/droiddriver/actions/accessibility/AccessibilityClickAction.java
@@ -41,6 +41,7 @@
       super(timeoutMillis);
     }
 
+    @SuppressWarnings("IdentityBinaryExpression")
     @Override
     protected boolean perform(AccessibilityNodeInfo node, UiElement element) {
       return SINGLE.perform(element) && SINGLE.perform(element);
diff --git a/src/io/appium/droiddriver/base/AbstractDroidDriver.java b/src/io/appium/droiddriver/base/AbstractDroidDriver.java
new file mode 100644
index 0000000..bf0df4b
--- /dev/null
+++ b/src/io/appium/droiddriver/base/AbstractDroidDriver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 DroidDriver committers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.appium.droiddriver.base;
+
+import io.appium.droiddriver.DroidDriver;
+import io.appium.droiddriver.Poller;
+import io.appium.droiddriver.UiElement;
+import io.appium.droiddriver.actions.InputInjector;
+import io.appium.droiddriver.exceptions.ElementNotFoundException;
+import io.appium.droiddriver.exceptions.TimeoutException;
+import io.appium.droiddriver.finders.Finder;
+import io.appium.droiddriver.util.Logs;
+
+/**
+ * Base DroidDriver that implements the common operations.
+ */
+public abstract class AbstractDroidDriver implements DroidDriver {
+
+  private Poller poller = new DefaultPoller();
+
+  @Override
+  public boolean has(Finder finder) {
+    try {
+      refreshUiElementTree();
+      find(finder);
+      return true;
+    } catch (ElementNotFoundException enfe) {
+      return false;
+    }
+  }
+
+  @Override
+  public boolean has(Finder finder, long timeoutMillis) {
+    try {
+      getPoller().pollFor(this, finder, Poller.EXISTS, timeoutMillis);
+      return true;
+    } catch (TimeoutException e) {
+      return false;
+    }
+  }
+
+  @Override
+  public UiElement on(Finder finder) {
+    Logs.call(this, "on", finder);
+    return getPoller().pollFor(this, finder, Poller.EXISTS);
+  }
+
+  @Override
+  public void checkExists(Finder finder) {
+    Logs.call(this, "checkExists", finder);
+    getPoller().pollFor(this, finder, Poller.EXISTS);
+  }
+
+  @Override
+  public void checkGone(Finder finder) {
+    Logs.call(this, "checkGone", finder);
+    getPoller().pollFor(this, finder, Poller.GONE);
+  }
+
+  @Override
+  public Poller getPoller() {
+    return poller;
+  }
+
+  @Override
+  public void setPoller(Poller poller) {
+    this.poller = poller;
+  }
+
+  public abstract InputInjector getInjector();
+
+}
\ No newline at end of file
diff --git a/src/io/appium/droiddriver/base/BaseDroidDriver.java b/src/io/appium/droiddriver/base/BaseDroidDriver.java
index e985a38..d6114c6 100644
--- a/src/io/appium/droiddriver/base/BaseDroidDriver.java
+++ b/src/io/appium/droiddriver/base/BaseDroidDriver.java
@@ -18,22 +18,16 @@
 
 import android.util.Log;
 
-import io.appium.droiddriver.DroidDriver;
-import io.appium.droiddriver.Poller;
 import io.appium.droiddriver.UiElement;
-import io.appium.droiddriver.actions.InputInjector;
-import io.appium.droiddriver.exceptions.ElementNotFoundException;
-import io.appium.droiddriver.exceptions.TimeoutException;
 import io.appium.droiddriver.finders.ByXPath;
 import io.appium.droiddriver.finders.Finder;
 import io.appium.droiddriver.util.Logs;
 
 /**
- * Base DroidDriver that implements the common operations.
+ * Enhances AbstractDroidDriver to include basic element handling and matching operations.
  */
-public abstract class BaseDroidDriver<R, E extends BaseUiElement<R, E>> implements DroidDriver {
+public abstract class BaseDroidDriver<R, E extends BaseUiElement<R, E>> extends AbstractDroidDriver {
 
-  private Poller poller = new DefaultPoller();
   private E rootElement;
 
   @Override
@@ -42,57 +36,6 @@
     return finder.find(getRootElement());
   }
 
-  @Override
-  public boolean has(Finder finder) {
-    try {
-      refreshUiElementTree();
-      find(finder);
-      return true;
-    } catch (ElementNotFoundException enfe) {
-      return false;
-    }
-  }
-
-  @Override
-  public boolean has(Finder finder, long timeoutMillis) {
-    try {
-      getPoller().pollFor(this, finder, Poller.EXISTS, timeoutMillis);
-      return true;
-    } catch (TimeoutException e) {
-      return false;
-    }
-  }
-
-  @Override
-  public UiElement on(Finder finder) {
-    Logs.call(this, "on", finder);
-    return getPoller().pollFor(this, finder, Poller.EXISTS);
-  }
-
-  @Override
-  public void checkExists(Finder finder) {
-    Logs.call(this, "checkExists", finder);
-    getPoller().pollFor(this, finder, Poller.EXISTS);
-  }
-
-  @Override
-  public void checkGone(Finder finder) {
-    Logs.call(this, "checkGone", finder);
-    getPoller().pollFor(this, finder, Poller.GONE);
-  }
-
-  @Override
-  public Poller getPoller() {
-    return poller;
-  }
-
-  @Override
-  public void setPoller(Poller poller) {
-    this.poller = poller;
-  }
-
-  public abstract InputInjector getInjector();
-
   protected abstract E newRootElement();
 
   /**
diff --git a/src/io/appium/droiddriver/base/BaseUiDevice.java b/src/io/appium/droiddriver/base/BaseUiDevice.java
index 5b6d135..096de8b 100644
--- a/src/io/appium/droiddriver/base/BaseUiDevice.java
+++ b/src/io/appium/droiddriver/base/BaseUiDevice.java
@@ -22,14 +22,13 @@
 import android.os.PowerManager;
 import android.util.Log;
 import android.view.KeyEvent;
-
-import java.io.BufferedOutputStream;
-
 import io.appium.droiddriver.UiDevice;
 import io.appium.droiddriver.actions.Action;
 import io.appium.droiddriver.actions.SingleKeyAction;
 import io.appium.droiddriver.util.FileUtils;
+import io.appium.droiddriver.util.InstrumentationUtils;
 import io.appium.droiddriver.util.Logs;
+import java.io.BufferedOutputStream;
 
 /**
  * Base implementation of {@link UiDevice}.
@@ -54,7 +53,14 @@
   @Override
   public void wakeUp() {
     if (!isScreenOn()) {
-      perform(POWER_ON);
+      // Cannot call perform(POWER_ON) because perform() checks the UiElement is visible.
+      POWER_ON.perform(getContext().getDriver().getInjector(), null);
+      InstrumentationUtils.tryWaitForIdleSync(POWER_ON.getTimeoutMillis());
+
+      Logs.log(
+          Log.WARN,
+          "After wakeUp, root AccessibilityNodeInfo may not be available. This is seen"
+              + " on api 23 devices, but could also happen on earlier devices.");
     }
   }
 
diff --git a/src/io/appium/droiddriver/base/CompositeDroidDriver.java b/src/io/appium/droiddriver/base/CompositeDroidDriver.java
new file mode 100644
index 0000000..c92c5c3
--- /dev/null
+++ b/src/io/appium/droiddriver/base/CompositeDroidDriver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 DroidDriver committers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.appium.droiddriver.base;
+
+import io.appium.droiddriver.UiDevice;
+import io.appium.droiddriver.UiElement;
+import io.appium.droiddriver.actions.InputInjector;
+import io.appium.droiddriver.finders.Finder;
+
+/**
+ * Helper class to ease creation of drivers that defer actions to other drivers.
+ */
+public abstract class CompositeDroidDriver extends AbstractDroidDriver {
+  /**
+   * Determines which DroidDriver should handle the current situation.
+   *
+   * @return The DroidDriver instance to use
+   */
+  protected abstract AbstractDroidDriver getApplicableDriver();
+
+  @Override
+  public InputInjector getInjector() {
+    return getApplicableDriver().getInjector();
+  }
+
+  @Override
+  public UiDevice getUiDevice() {
+    return getApplicableDriver().getUiDevice();
+  }
+
+  @Override
+  public UiElement find(Finder finder) {
+    return getApplicableDriver().find(finder);
+  }
+
+  @Override
+  public void refreshUiElementTree() {
+    getApplicableDriver().refreshUiElementTree();
+  }
+
+  @Override
+  public boolean dumpUiElementTree(String path) {
+    return getApplicableDriver().dumpUiElementTree(path);
+  }
+}
diff --git a/src/io/appium/droiddriver/duo/DuoDriver.java b/src/io/appium/droiddriver/duo/DuoDriver.java
new file mode 100644
index 0000000..0ad84bf
--- /dev/null
+++ b/src/io/appium/droiddriver/duo/DuoDriver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 DroidDriver committers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.appium.droiddriver.duo;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.Instrumentation;
+
+import io.appium.droiddriver.base.AbstractDroidDriver;
+import io.appium.droiddriver.base.CompositeDroidDriver;
+import io.appium.droiddriver.instrumentation.InstrumentationDriver;
+import io.appium.droiddriver.uiautomation.UiAutomationDriver;
+import io.appium.droiddriver.util.ActivityUtils;
+import io.appium.droiddriver.util.InstrumentationUtils;
+
+/**
+ * Implementation of DroidDriver that attempts to use the best driver for the current activity.
+ * If the activity is part of the application under instrumentation, the InstrumentationDriver is
+ * used. Otherwise, the UiAutomationDriver is used.
+ */
+@TargetApi(18)
+public class DuoDriver extends CompositeDroidDriver {
+  private final String targetApkPackage;
+  private final UiAutomationDriver uiAutomationDriver;
+  private final InstrumentationDriver instrumentationDriver;
+
+  public DuoDriver() {
+    Instrumentation instrumentation = InstrumentationUtils.getInstrumentation();
+    targetApkPackage = InstrumentationUtils.getTargetContext().getPackageName();
+    uiAutomationDriver = new UiAutomationDriver(instrumentation);
+    instrumentationDriver = new InstrumentationDriver(instrumentation);
+  }
+
+  @Override
+  protected AbstractDroidDriver getApplicableDriver() {
+    Activity activity = ActivityUtils.getRunningActivity();
+    if (activity != null && targetApkPackage.equals(
+            activity.getApplicationContext().getPackageName())) {
+      return instrumentationDriver;
+    }
+    return uiAutomationDriver;
+  }
+}
diff --git a/src/io/appium/droiddriver/finders/Attribute.java b/src/io/appium/droiddriver/finders/Attribute.java
index 9dda497..c2aa83a 100644
--- a/src/io/appium/droiddriver/finders/Attribute.java
+++ b/src/io/appium/droiddriver/finders/Attribute.java
@@ -38,7 +38,7 @@
 
   private final String name;
 
-  private Attribute(String name) {
+  Attribute(String name) {
     this.name = name;
   }
 
diff --git a/src/io/appium/droiddriver/finders/By.java b/src/io/appium/droiddriver/finders/By.java
index f8ac924..9a38622 100644
--- a/src/io/appium/droiddriver/finders/By.java
+++ b/src/io/appium/droiddriver/finders/By.java
@@ -16,26 +16,34 @@
 
 package io.appium.droiddriver.finders;
 
+import static io.appium.droiddriver.util.Preconditions.checkNotNull;
+
 import android.content.Context;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import io.appium.droiddriver.UiElement;
 import io.appium.droiddriver.exceptions.ElementNotFoundException;
 import io.appium.droiddriver.util.InstrumentationUtils;
 
-import static io.appium.droiddriver.util.Preconditions.checkNotNull;
-
 /**
  * Convenience methods to create commonly used finders.
  */
 public class By {
+
   private static final MatchFinder ANY = new MatchFinder(null);
 
-  /** Matches any UiElement. */
+  /**
+   * Matches any UiElement.
+   */
   public static MatchFinder any() {
     return ANY;
   }
 
-  /** Matches a UiElement whose {@code attribute} is {@code true}. */
+  /**
+   * Matches a UiElement whose {@code attribute} is {@code true}.
+   */
   public static MatchFinder is(Attribute attribute) {
     return new MatchFinder(Predicates.attributeTrue(attribute));
   }
@@ -47,74 +55,91 @@
     return new MatchFinder(Predicates.attributeFalse(attribute));
   }
 
-  /** Matches a UiElement by a resource id defined in the AUT. */
+  /**
+   * Matches a UiElement by a resource id defined in the AUT.
+   */
   public static MatchFinder resourceId(int resourceId) {
     Context targetContext = InstrumentationUtils.getInstrumentation().getTargetContext();
     return resourceId(targetContext.getResources().getResourceName(resourceId));
   }
 
   /**
-   * Matches a UiElement by the string representation of a resource id. This works for resources
-   * not belonging to the AUT.
+   * Matches a UiElement by the string representation of a resource id. This works for resources not
+   * belonging to the AUT.
    */
   public static MatchFinder resourceId(String resourceId) {
     return new MatchFinder(Predicates.attributeEquals(Attribute.RESOURCE_ID, resourceId));
   }
 
-  /** Matches a UiElement by package name. */
+  /**
+   * Matches a UiElement by package name.
+   */
   public static MatchFinder packageName(String name) {
     return new MatchFinder(Predicates.attributeEquals(Attribute.PACKAGE, name));
   }
 
-  /** Matches a UiElement by the exact text. */
+  /**
+   * Matches a UiElement by the exact text.
+   */
   public static MatchFinder text(String text) {
     return new MatchFinder(Predicates.attributeEquals(Attribute.TEXT, text));
   }
 
-  /** Matches a UiElement whose text matches {@code regex}. */
+  /**
+   * Matches a UiElement whose text matches {@code regex}.
+   */
   public static MatchFinder textRegex(String regex) {
     return new MatchFinder(Predicates.attributeMatches(Attribute.TEXT, regex));
   }
 
-  /** Matches a UiElement whose text contains {@code substring}. */
+  /**
+   * Matches a UiElement whose text contains {@code substring}.
+   */
   public static MatchFinder textContains(String substring) {
     return new MatchFinder(Predicates.attributeContains(Attribute.TEXT, substring));
   }
 
-  /** Matches a UiElement by content description. */
+  /**
+   * Matches a UiElement by content description.
+   */
   public static MatchFinder contentDescription(String contentDescription) {
     return new MatchFinder(Predicates.attributeEquals(Attribute.CONTENT_DESC, contentDescription));
   }
 
-  /** Matches a UiElement whose content description contains {@code substring}. */
+  /**
+   * Matches a UiElement whose content description contains {@code substring}.
+   */
   public static MatchFinder contentDescriptionContains(String substring) {
     return new MatchFinder(Predicates.attributeContains(Attribute.CONTENT_DESC, substring));
   }
 
-  /** Matches a UiElement by class name. */
+  /**
+   * Matches a UiElement by class name.
+   */
   public static MatchFinder className(String className) {
     return new MatchFinder(Predicates.attributeEquals(Attribute.CLASS, className));
   }
 
-  /** Matches a UiElement by class name. */
+  /**
+   * Matches a UiElement by class name.
+   */
   public static MatchFinder className(Class<?> clazz) {
     return className(clazz.getName());
   }
 
-  /** Matches a UiElement that is selected. */
+  /**
+   * Matches a UiElement that is selected.
+   */
   public static MatchFinder selected() {
     return is(Attribute.SELECTED);
   }
 
   /**
-   * Matches by XPath. When applied on an non-root element, it will not evaluate
-   * above the context element.
-   * <p>
-   * XPath is the domain-specific-language for navigating a node tree. It is
-   * ideal if the UiElement to match has a complex relationship with surrounding
-   * nodes. For simple cases, {@link #withParent} or {@link #withAncestor} are
-   * preferred, which can combine with other {@link MatchFinder}s in
-   * {@link #allOf}. For complex cases like below, XPath is superior:
+   * Matches by XPath. When applied on an non-root element, it will not evaluate above the context
+   * element. <p> XPath is the domain-specific-language for navigating a node tree. It is ideal if
+   * the UiElement to match has a complex relationship with surrounding nodes. For simple cases,
+   * {@link #withParent} or {@link #withAncestor} are preferred, which can combine with other {@link
+   * MatchFinder}s in {@link #allOf}. For complex cases like below, XPath is superior:
    *
    * <pre>
    * {@code
@@ -132,8 +157,8 @@
    * }
    * </pre>
    *
-   * If we need to locate the RelativeLayout containing the album "Forever"
-   * instead of a song or an artist named "Forever", this XPath works:
+   * If we need to locate the RelativeLayout containing the album "Forever" instead of a song or an
+   * artist named "Forever", this XPath works:
    *
    * <pre>
    * {@code //*[LinearLayout/*[@text='Albums']]/RelativeLayout[*[@text='Forever']]}
@@ -147,34 +172,28 @@
   }
 
   /**
-   * Returns a finder that uses the UiElement returned by first Finder as
-   * context for the second Finder.
-   * <p>
-   * typically first Finder finds the ancestor, then second Finder finds the
-   * target UiElement, which is a descendant.
-   * </p>
-   * Note that if the first Finder matches multiple UiElements, only the first
-   * match is tried, which usually is not what callers expect. In this case,
-   * allOf(second, withAncesor(first)) may work.
+   * Returns a finder that uses the UiElement returned by first Finder as context for the second
+   * Finder. <p> typically first Finder finds the ancestor, then second Finder finds the target
+   * UiElement, which is a descendant. </p> Note that if the first Finder matches multiple
+   * UiElements, only the first match is tried, which usually is not what callers expect. In this
+   * case, allOf(second, withAncesor(first)) may work.
    */
   public static ChainFinder chain(Finder first, Finder second) {
     return new ChainFinder(first, second);
   }
 
-  private static Predicate<? super UiElement>[] getPredicates(MatchFinder... finders) {
-    @SuppressWarnings("unchecked")
-    Predicate<? super UiElement>[] predicates = new Predicate[finders.length];
+  private static List<Predicate<? super UiElement>> getPredicates(MatchFinder... finders) {
+    ArrayList<Predicate<? super UiElement>> predicates = new ArrayList<>(finders.length);
     for (int i = 0; i < finders.length; i++) {
-      predicates[i] = finders[i].predicate;
+      predicates.add(finders[i].predicate);
     }
     return predicates;
   }
 
   /**
-   * Evaluates given {@code finders} in short-circuit fashion in the order
-   * they are passed. Costly finders (for example those returned by with*
-   * methods that navigate the node tree) should be passed after cheap finders
-   * (for example the ByAttribute finders).
+   * Evaluates given {@code finders} in short-circuit fashion in the order they are passed. Costly
+   * finders (for example those returned by with* methods that navigate the node tree) should be
+   * passed after cheap finders (for example the ByAttribute finders).
    *
    * @return a finder that is the logical conjunction of given finders
    */
@@ -183,10 +202,9 @@
   }
 
   /**
-   * Evaluates given {@code finders} in short-circuit fashion in the order
-   * they are passed. Costly finders (for example those returned by with*
-   * methods that navigate the node tree) should be passed after cheap finders
-   * (for example the ByAttribute finders).
+   * Evaluates given {@code finders} in short-circuit fashion in the order they are passed. Costly
+   * finders (for example those returned by with* methods that navigate the node tree) should be
+   * passed after cheap finders (for example the ByAttribute finders).
    *
    * @return a finder that is the logical disjunction of given finders
    */
@@ -195,8 +213,8 @@
   }
 
   /**
-   * Matches a UiElement whose parent matches the given parentFinder. For
-   * complex cases, consider {@link #xpath}.
+   * Matches a UiElement whose parent matches the given parentFinder. For complex cases, consider
+   * {@link #xpath}.
    */
   public static MatchFinder withParent(MatchFinder parentFinder) {
     checkNotNull(parentFinder);
@@ -204,8 +222,8 @@
   }
 
   /**
-   * Matches a UiElement whose ancestor matches the given ancestorFinder. For
-   * complex cases, consider {@link #xpath}.
+   * Matches a UiElement whose ancestor matches the given ancestorFinder. For complex cases,
+   * consider {@link #xpath}.
    */
   public static MatchFinder withAncestor(MatchFinder ancestorFinder) {
     checkNotNull(ancestorFinder);
@@ -213,8 +231,8 @@
   }
 
   /**
-   * Matches a UiElement which has a visible sibling matching the given
-   * siblingFinder. This could be inefficient; consider {@link #xpath}.
+   * Matches a UiElement which has a visible sibling matching the given siblingFinder. This could be
+   * inefficient; consider {@link #xpath}.
    */
   public static MatchFinder withSibling(MatchFinder siblingFinder) {
     checkNotNull(siblingFinder);
@@ -222,8 +240,8 @@
   }
 
   /**
-   * Matches a UiElement which has a visible child matching the given
-   * childFinder. This could be inefficient; consider {@link #xpath}.
+   * Matches a UiElement which has a visible child matching the given childFinder. This could be
+   * inefficient; consider {@link #xpath}.
    */
   public static MatchFinder withChild(MatchFinder childFinder) {
     checkNotNull(childFinder);
@@ -231,8 +249,8 @@
   }
 
   /**
-   * Matches a UiElement whose descendant (including self) matches the given
-   * descendantFinder. This could be VERY inefficient; consider {@link #xpath}.
+   * Matches a UiElement whose descendant (including self) matches the given descendantFinder. This
+   * could be VERY inefficient; consider {@link #xpath}.
    */
   public static MatchFinder withDescendant(final MatchFinder descendantFinder) {
     checkNotNull(descendantFinder);
@@ -254,11 +272,14 @@
     });
   }
 
-  /** Matches a UiElement that does not match the provided {@code finder}. */
+  /**
+   * Matches a UiElement that does not match the provided {@code finder}.
+   */
   public static MatchFinder not(MatchFinder finder) {
     checkNotNull(finder);
     return new MatchFinder(Predicates.not(finder.predicate));
   }
 
-  private By() {}
+  private By() {
+  }
 }
diff --git a/src/io/appium/droiddriver/finders/Predicates.java b/src/io/appium/droiddriver/finders/Predicates.java
index 1b9ad80..0d2d9df 100644
--- a/src/io/appium/droiddriver/finders/Predicates.java
+++ b/src/io/appium/droiddriver/finders/Predicates.java
@@ -18,13 +18,17 @@
 
 import android.text.TextUtils;
 
+import java.util.Arrays;
+
 import io.appium.droiddriver.UiElement;
 
 /**
  * Static utility methods pertaining to {@code Predicate} instances.
  */
 public final class Predicates {
-  private Predicates() {}
+
+  private Predicates() {
+  }
 
   private static final Predicate<Object> ANY = new Predicate<Object>() {
     @Override
@@ -64,9 +68,9 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} if both arguments
-   * evaluate to {@code true}. The arguments are evaluated in order, and
-   * evaluation will be "short-circuited" as soon as a false predicate is found.
+   * Returns a predicate that evaluates to {@code true} if both arguments evaluate to {@code true}.
+   * The arguments are evaluated in order, and evaluation will be "short-circuited" as soon as a
+   * false predicate is found.
    */
   @SuppressWarnings("unchecked")
   public static <T> Predicate<T> allOf(final Predicate<? super T> first,
@@ -92,13 +96,11 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} if each of its
-   * components evaluates to {@code true}. The components are evaluated in
-   * order, and evaluation will be "short-circuited" as soon as a false
-   * predicate is found.
+   * Returns a predicate that evaluates to {@code true} if each of its components evaluates to
+   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
+   * as soon as a false predicate is found.
    */
-  @SuppressWarnings("unchecked")
-  public static <T> Predicate<T> allOf(final Predicate<? super T>... components) {
+  public static <T> Predicate<T> allOf(final Iterable<Predicate<? super T>> components) {
     return new Predicate<T>() {
       @Override
       public boolean apply(T input) {
@@ -118,13 +120,22 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} if any one of its
-   * components evaluates to {@code true}. The components are evaluated in
-   * order, and evaluation will be "short-circuited" as soon as a true predicate
-   * is found.
+   * Returns a predicate that evaluates to {@code true} if each of its components evaluates to
+   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
+   * as soon as a false predicate is found.
    */
-  @SuppressWarnings("unchecked")
-  public static <T> Predicate<T> anyOf(final Predicate<? super T>... components) {
+  @SuppressWarnings("RedundantTypeArguments") // Some compilers cannot infer <T>
+  @SafeVarargs
+  public static <T> Predicate<T> allOf(final Predicate<? super T>... components) {
+    return Predicates.<T>allOf(Arrays.asList(components));
+  }
+
+  /**
+   * Returns a predicate that evaluates to {@code true} if any one of its components evaluates to
+   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
+   * as soon as a true predicate is found.
+   */
+  public static <T> Predicate<T> anyOf(final Iterable<Predicate<? super T>> components) {
     return new Predicate<T>() {
       @Override
       public boolean apply(T input) {
@@ -144,8 +155,19 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
-   * if its {@code attribute} is {@code true}.
+   * Returns a predicate that evaluates to {@code true} if any one of its components evaluates to
+   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
+   * as soon as a true predicate is found.
+   */
+  @SuppressWarnings("RedundantTypeArguments") // Some compilers cannot infer <T>
+  @SafeVarargs
+  public static <T> Predicate<T> anyOf(final Predicate<? super T>... components) {
+    return Predicates.<T>anyOf(Arrays.asList(components));
+  }
+
+  /**
+   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
+   * attribute} is {@code true}.
    */
   public static Predicate<UiElement> attributeTrue(final Attribute attribute) {
     return new Predicate<UiElement>() {
@@ -163,8 +185,8 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
-   * if its {@code attribute} is {@code false}.
+   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
+   * attribute} is {@code false}.
    */
   public static Predicate<UiElement> attributeFalse(final Attribute attribute) {
     return new Predicate<UiElement>() {
@@ -182,8 +204,8 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
-   * if its {@code attribute} equals {@code expected}.
+   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
+   * attribute} equals {@code expected}.
    */
   public static Predicate<UiElement> attributeEquals(final Attribute attribute,
       final Object expected) {
@@ -202,10 +224,11 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
-   * if its {@code attribute} matches {@code regex}.
+   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
+   * attribute} matches {@code regex}.
    */
-  public static Predicate<UiElement> attributeMatches(final Attribute attribute, final String regex) {
+  public static Predicate<UiElement> attributeMatches(final Attribute attribute,
+      final String regex) {
     return new Predicate<UiElement>() {
       @Override
       public boolean apply(UiElement element) {
@@ -221,8 +244,8 @@
   }
 
   /**
-   * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
-   * if its {@code attribute} contains {@code substring}.
+   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
+   * attribute} contains {@code substring}.
    */
   public static Predicate<UiElement> attributeContains(final Attribute attribute,
       final String substring) {
@@ -240,7 +263,8 @@
     };
   }
 
-  public static Predicate<UiElement> withParent(final Predicate<? super UiElement> parentPredicate) {
+  public static Predicate<UiElement> withParent(
+      final Predicate<? super UiElement> parentPredicate) {
     return new Predicate<UiElement>() {
       @Override
       public boolean apply(UiElement element) {
@@ -277,7 +301,8 @@
     };
   }
 
-  public static Predicate<UiElement> withSibling(final Predicate<? super UiElement> siblingPredicate) {
+  public static Predicate<UiElement> withSibling(
+      final Predicate<? super UiElement> siblingPredicate) {
     return new Predicate<UiElement>() {
       @Override
       public boolean apply(UiElement element) {
@@ -318,4 +343,6 @@
       }
     };
   }
+
+
 }
diff --git a/src/io/appium/droiddriver/helpers/BaseDroidDriverTest.java b/src/io/appium/droiddriver/helpers/BaseDroidDriverTest.java
index 0b17dc5..607da95 100644
--- a/src/io/appium/droiddriver/helpers/BaseDroidDriverTest.java
+++ b/src/io/appium/droiddriver/helpers/BaseDroidDriverTest.java
@@ -91,9 +91,6 @@
    * behavior - if multiple subclasses override this method, only the first override is executed.
    * Other overrides are silently ignored. You can either use {@link SingleRun} in {@link #setUp},
    * or override this method, which is a simpler alternative with the aforementioned catch.
-   * <p>
-   * If an InstrumentationDriver is used, this is a good place to call {@link
-   * io.appium.droiddriver.instrumentation.ViewElement#overrideClassName}
    */
   protected void classSetUp() {
     DroidDriversInitializer.get(DroidDrivers.newDriver()).singleRun();
diff --git a/src/io/appium/droiddriver/helpers/DroidDrivers.java b/src/io/appium/droiddriver/helpers/DroidDrivers.java
index 7725bf5..e55d595 100644
--- a/src/io/appium/droiddriver/helpers/DroidDrivers.java
+++ b/src/io/appium/droiddriver/helpers/DroidDrivers.java
@@ -21,9 +21,9 @@
 import android.os.Build;
 
 import io.appium.droiddriver.DroidDriver;
+import io.appium.droiddriver.duo.DuoDriver;
 import io.appium.droiddriver.exceptions.DroidDriverException;
 import io.appium.droiddriver.instrumentation.InstrumentationDriver;
-import io.appium.droiddriver.uiautomation.UiAutomationDriver;
 import io.appium.droiddriver.util.InstrumentationUtils;
 
 /**
@@ -83,7 +83,7 @@
     // If "dd.driver" is not specified, return default.
     if (hasUiAutomation()) {
       checkUiAutomation();
-      return new UiAutomationDriver(instrumentation);
+      return new DuoDriver();
     }
     return new InstrumentationDriver(instrumentation);
   }
diff --git a/src/io/appium/droiddriver/helpers/SingleRun.java b/src/io/appium/droiddriver/helpers/SingleRun.java
index 5ffd21e..714c777 100644
--- a/src/io/appium/droiddriver/helpers/SingleRun.java
+++ b/src/io/appium/droiddriver/helpers/SingleRun.java
@@ -24,7 +24,7 @@
  * a class effect.
  */
 public abstract class SingleRun {
-  private AtomicBoolean hasRun = new AtomicBoolean();
+  private final AtomicBoolean hasRun = new AtomicBoolean();
 
   /**
    * Calls {@link #run()} if it is the first time this method is called upon this instance.
diff --git a/src/io/appium/droiddriver/instrumentation/InstrumentationDriver.java b/src/io/appium/droiddriver/instrumentation/InstrumentationDriver.java
index d3e5dd2..03b2123 100644
--- a/src/io/appium/droiddriver/instrumentation/InstrumentationDriver.java
+++ b/src/io/appium/droiddriver/instrumentation/InstrumentationDriver.java
@@ -16,14 +16,11 @@
 
 package io.appium.droiddriver.instrumentation;
 
+import android.app.Activity;
 import android.app.Instrumentation;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.View;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
 import io.appium.droiddriver.actions.InputInjector;
 import io.appium.droiddriver.base.BaseDroidDriver;
 import io.appium.droiddriver.base.DroidDriverContext;
@@ -31,11 +28,36 @@
 import io.appium.droiddriver.util.ActivityUtils;
 import io.appium.droiddriver.util.InstrumentationUtils;
 import io.appium.droiddriver.util.Logs;
+import java.util.List;
+import java.util.concurrent.Callable;
 
-/**
- * Implementation of DroidDriver that is driven via instrumentation.
- */
+/** Implementation of DroidDriver that is driven via instrumentation. */
 public class InstrumentationDriver extends BaseDroidDriver<View, ViewElement> {
+  private static final Callable<View> FIND_ROOT_VIEW =
+      new Callable<View>() {
+        @Override
+        public View call() {
+          InstrumentationUtils.checkMainThread();
+          Activity runningActivity = ActivityUtils.getRunningActivityNoWait();
+          if (runningActivity == null) {
+            // runningActivity changed since last call!
+            return null;
+          }
+
+          List<View> views = RootFinder.getRootViews();
+          if (views.size() > 1) {
+            Logs.log(Log.VERBOSE, "views.size()=" + views.size());
+            for (View view : views) {
+              if (view.hasWindowFocus()) {
+                return view;
+              }
+            }
+          }
+          // Fall back to DecorView.
+          // TODO(kjin): Should wait until a view hasWindowFocus?
+          return runningActivity.getWindow().getDecorView();
+        }
+      };
   private final DroidDriverContext<View, ViewElement> context;
   private final InputInjector injector;
   private final InstrumentationUiDevice uiDevice;
@@ -61,41 +83,22 @@
     return new ViewElement(context, rawElement, parent);
   }
 
-  private static final Callable<View> FIND_ROOT_VIEW = new Callable<View>() {
-    @Override
-    public View call() {
-      List<View> views = RootFinder.getRootViews();
-      if (views.size() > 1) {
-        Logs.log(Log.VERBOSE, "views.size()=" + views.size());
-        for (View view : views) {
-          if (view.hasWindowFocus()) {
-            return view;
-          }
-        }
-      }
-      // Fall back to DecorView.
-      return ActivityUtils.getRunningActivity().getWindow().getDecorView();
-    }
-  };
-
   private View findRootView() {
-    waitForRunningActivity();
-    return InstrumentationUtils.runOnMainSyncWithTimeout(FIND_ROOT_VIEW);
-  }
-
-  private void waitForRunningActivity() {
     long timeoutMillis = getPoller().getTimeoutMillis();
     long end = SystemClock.uptimeMillis() + timeoutMillis;
     while (true) {
-      if (ActivityUtils.getRunningActivity() != null) {
-        return;
-      }
       long remainingMillis = end - SystemClock.uptimeMillis();
       if (remainingMillis < 0) {
-        throw new NoRunningActivityException(String.format(
-            "Cannot find the running activity after %d milliseconds", timeoutMillis));
+        throw new NoRunningActivityException(
+            String.format("Cannot find the running activity after %d milliseconds", timeoutMillis));
       }
-      SystemClock.sleep(Math.min(250, remainingMillis));
+
+      if (ActivityUtils.getRunningActivity(remainingMillis) != null) {
+        View view = InstrumentationUtils.runOnMainSyncWithTimeout(FIND_ROOT_VIEW);
+        if (view != null) {
+          return view;
+        }
+      }
     }
   }
 
diff --git a/src/io/appium/droiddriver/instrumentation/ViewElement.java b/src/io/appium/droiddriver/instrumentation/ViewElement.java
index e706362..da7f2d0 100644
--- a/src/io/appium/droiddriver/instrumentation/ViewElement.java
+++ b/src/io/appium/droiddriver/instrumentation/ViewElement.java
@@ -16,39 +16,108 @@
 
 package io.appium.droiddriver.instrumentation;
 
+import static io.appium.droiddriver.util.Strings.charSequenceToString;
+
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Checkable;
 import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
-
 import io.appium.droiddriver.actions.InputInjector;
 import io.appium.droiddriver.base.BaseUiElement;
 import io.appium.droiddriver.base.DroidDriverContext;
 import io.appium.droiddriver.finders.Attribute;
 import io.appium.droiddriver.util.InstrumentationUtils;
 import io.appium.droiddriver.util.Preconditions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
 
-import static io.appium.droiddriver.util.Strings.charSequenceToString;
-
-/**
- * A UiElement that is backed by a View.
- */
+/** A UiElement that is backed by a View. */
 public class ViewElement extends BaseUiElement<View, ViewElement> {
+  private final DroidDriverContext<View, ViewElement> context;
+  private final View view;
+  private final Map<Attribute, Object> attributes;
+  private final boolean visible;
+  private final Rect visibleBounds;
+  private final ViewElement parent;
+  private final List<ViewElement> children;
+
+  /**
+   * A snapshot of all attributes is taken at construction. The attributes of a {@code ViewElement}
+   * instance are immutable. If the underlying view is updated, a new {@code ViewElement} instance
+   * will be created in {@link io.appium.droiddriver.DroidDriver#refreshUiElementTree}.
+   */
+  public ViewElement(DroidDriverContext<View, ViewElement> context, View view, ViewElement parent) {
+    this.context = Preconditions.checkNotNull(context);
+    this.view = Preconditions.checkNotNull(view);
+    this.parent = parent;
+    AttributesSnapshot attributesSnapshot = new AttributesSnapshot(view);
+    InstrumentationUtils.runOnMainSyncWithTimeout(attributesSnapshot);
+
+    attributes = Collections.unmodifiableMap(attributesSnapshot.attribs);
+    this.visibleBounds = attributesSnapshot.visibleBounds;
+    this.visible = attributesSnapshot.visible;
+    if (attributesSnapshot.childViews == null) {
+      this.children = null;
+    } else {
+      List<ViewElement> children = new ArrayList<>(attributesSnapshot.childViews.size());
+      for (View childView : attributesSnapshot.childViews) {
+        children.add(context.getElement(childView, this));
+      }
+      this.children = Collections.unmodifiableList(children);
+    }
+  }
+
+  @Override
+  public Rect getVisibleBounds() {
+    return visibleBounds;
+  }
+
+  @Override
+  public boolean isVisible() {
+    return visible;
+  }
+
+  @Override
+  public ViewElement getParent() {
+    return parent;
+  }
+
+  @Override
+  protected List<ViewElement> getChildren() {
+    return children;
+  }
+
+  @Override
+  protected Map<Attribute, Object> getAttributes() {
+    return attributes;
+  }
+
+  @Override
+  public InputInjector getInjector() {
+    return context.getDriver().getInjector();
+  }
+
+  @Override
+  protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) {
+    futureTask.run();
+    InstrumentationUtils.tryWaitForIdleSync(timeoutMillis);
+  }
+
+  @Override
+  public View getRawElement() {
+    return view;
+  }
+
   private static class AttributesSnapshot implements Callable<Void> {
+    final Map<Attribute, Object> attribs = new EnumMap<>(Attribute.class);
     private final View view;
-    final Map<Attribute, Object> attribs = new EnumMap<Attribute, Object>(Attribute.class);
     boolean visible;
     Rect visibleBounds;
     List<View> childViews;
@@ -107,9 +176,7 @@
     }
 
     private String getClassName() {
-      String className = view.getClass().getName();
-      return CLASS_NAME_OVERRIDES.containsKey(className) ? CLASS_NAME_OVERRIDES.get(className)
-          : className;
+      return view.getClass().getName();
     }
 
     private String getResourceId() {
@@ -168,7 +235,7 @@
       }
       ViewGroup group = (ViewGroup) view;
       int childCount = group.getChildCount();
-      childViews = new ArrayList<View>(childCount);
+      childViews = new ArrayList<>(childCount);
       for (int i = 0; i < childCount; i++) {
         View child = group.getChildAt(i);
         if (child != null) {
@@ -177,104 +244,4 @@
       }
     }
   }
-
-  private static final Map<String, String> CLASS_NAME_OVERRIDES = new HashMap<String, String>();
-
-  /**
-   * Typically users find the class name to use in tests using SDK tool
-   * uiautomatorviewer. This name is returned by
-   * {@link AccessibilityNodeInfo#getClassName}. If the app uses custom View
-   * classes that do not call {@link AccessibilityNodeInfo#setClassName} with
-   * the actual class name, different types of drivers see different class names
-   * (InstrumentationDriver sees the actual class name, while UiAutomationDriver
-   * sees {@link AccessibilityNodeInfo#getClassName}).
-   * <p>
-   * If tests fail with InstrumentationDriver, find the actual class name by
-   * examining app code or by calling
-   * {@link io.appium.droiddriver.DroidDriver#dumpUiElementTree}, then
-   * call this method in setUp to override it with the class name seen in
-   * uiautomatorviewer.
-   * </p>
-   * A better solution is to use resource-id instead of classname, which is an
-   * implementation detail and subject to change.
-   */
-  public static void overrideClassName(String actualClassName, String overridingClassName) {
-    CLASS_NAME_OVERRIDES.put(actualClassName, overridingClassName);
-  }
-
-  private final DroidDriverContext<View, ViewElement> context;
-  private final View view;
-  private final Map<Attribute, Object> attributes;
-  private final boolean visible;
-  private final Rect visibleBounds;
-  private final ViewElement parent;
-  private final List<ViewElement> children;
-
-  /**
-   * A snapshot of all attributes is taken at construction. The attributes of a
-   * {@code ViewElement} instance are immutable. If the underlying view is
-   * updated, a new {@code ViewElement} instance will be created in
-   * {@link io.appium.droiddriver.DroidDriver#refreshUiElementTree}.
-   */
-  public ViewElement(DroidDriverContext<View, ViewElement> context, View view, ViewElement parent) {
-    this.context = Preconditions.checkNotNull(context);
-    this.view = Preconditions.checkNotNull(view);
-    this.parent = parent;
-    AttributesSnapshot attributesSnapshot = new AttributesSnapshot(view);
-    InstrumentationUtils.runOnMainSyncWithTimeout(attributesSnapshot);
-
-    attributes = Collections.unmodifiableMap(attributesSnapshot.attribs);
-    this.visibleBounds = attributesSnapshot.visibleBounds;
-    this.visible = attributesSnapshot.visible;
-    if (attributesSnapshot.childViews == null) {
-      this.children = null;
-    } else {
-      List<ViewElement> children = new ArrayList<ViewElement>(attributesSnapshot.childViews.size());
-      for (View childView : attributesSnapshot.childViews) {
-        children.add(context.getElement(childView, this));
-      }
-      this.children = Collections.unmodifiableList(children);
-    }
-  }
-
-  @Override
-  public Rect getVisibleBounds() {
-    return visibleBounds;
-  }
-
-  @Override
-  public boolean isVisible() {
-    return visible;
-  }
-
-  @Override
-  public ViewElement getParent() {
-    return parent;
-  }
-
-  @Override
-  protected List<ViewElement> getChildren() {
-    return children;
-  }
-
-  @Override
-  protected Map<Attribute, Object> getAttributes() {
-    return attributes;
-  }
-
-  @Override
-  public InputInjector getInjector() {
-    return context.getDriver().getInjector();
-  }
-
-  @Override
-  protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) {
-    futureTask.run();
-    InstrumentationUtils.tryWaitForIdleSync(timeoutMillis);
-  }
-
-  @Override
-  public View getRawElement() {
-    return view;
-  }
 }
diff --git a/src/io/appium/droiddriver/runner/MinSdkVersion.java b/src/io/appium/droiddriver/runner/MinSdkVersion.java
index c1ea2e9..f560ad8 100644
--- a/src/io/appium/droiddriver/runner/MinSdkVersion.java
+++ b/src/io/appium/droiddriver/runner/MinSdkVersion.java
@@ -16,28 +16,26 @@
 
 package io.appium.droiddriver.runner;
 
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-
 /**
- * This annotation indicates that its target needs a minimum SDK version
- * specified as its value.
- * <p>
- * As any annotations, it is useful only if it is processed by tools.
- * {@link TestRunner} filters out tests with this annotation if the current
- * device has a lower SDK version.
+ * This annotation indicates that its target needs a minimum SDK version specified as its value.
+ *
+ * <p>As any annotations, it is useful only if it is processed by tools.
+ *
+ * @deprecated Use android.support.test.filters.SdkSuppress instead.
  */
 @Inherited
 @Target({TYPE, METHOD})
 @Retention(RetentionPolicy.RUNTIME)
+@Deprecated
 public @interface MinSdkVersion {
-  /**
-   * The minimum required SDK version.
-   */
+  /** The minimum required SDK version. */
   int value();
 }
diff --git a/src/io/appium/droiddriver/runner/TestRunner.java b/src/io/appium/droiddriver/runner/TestRunner.java
deleted file mode 100644
index 71bb744..0000000
--- a/src/io/appium/droiddriver/runner/TestRunner.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2013 DroidDriver committers
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.appium.droiddriver.runner;
-
-import android.app.Activity;
-import android.os.Build;
-import android.os.Bundle;
-import android.test.AndroidTestRunner;
-import android.test.InstrumentationTestRunner;
-import android.test.suitebuilder.TestMethod;
-import android.util.Log;
-
-import com.android.internal.util.Predicate;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.Test;
-import junit.framework.TestListener;
-
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import io.appium.droiddriver.helpers.DroidDrivers;
-import io.appium.droiddriver.util.ActivityUtils;
-import io.appium.droiddriver.util.ActivityUtils.Supplier;
-import io.appium.droiddriver.util.InstrumentationUtils;
-import io.appium.droiddriver.util.Logs;
-
-/**
- * Adds activity watcher to InstrumentationTestRunner.
- */
-public class TestRunner extends InstrumentationTestRunner {
-  private final Set<Activity> activities = new HashSet<Activity>();
-  private final AndroidTestRunner androidTestRunner = new AndroidTestRunner();
-  private volatile Activity runningActivity;
-
-  /**
-   * Returns an {@link AndroidTestRunner} that is shared by this and super, such
-   * that we can add custom {@link TestListener}s.
-   */
-  @Override
-  protected AndroidTestRunner getAndroidTestRunner() {
-    return androidTestRunner;
-  }
-
-  /**
-   * {@inheritDoc}
-   * <p>
-   * Initializes {@link InstrumentationUtils}.
-   */
-  @Override
-  public void onCreate(Bundle arguments) {
-    InstrumentationUtils.init(this, arguments);
-    super.onCreate(arguments);
-  }
-
-  /**
-   * {@inheritDoc}
-   * <p>
-   * Adds a {@link TestListener} that finishes all created activities.
-   */
-  @Override
-  public void onStart() {
-    getAndroidTestRunner().addTestListener(new TestListener() {
-      @Override
-      public void endTest(Test test) {
-        // Try to finish activity on best-effort basis - TestListener should
-        // not throw.
-        final Activity[] activitiesCopy;
-        synchronized (activities) {
-          if (activities.isEmpty()) {
-            return;
-          }
-          activitiesCopy = activities.toArray(new Activity[activities.size()]);
-        }
-
-        try {
-          InstrumentationUtils.runOnMainSyncWithTimeout(new Runnable() {
-            @Override
-            public void run() {
-              for (Activity activity : activitiesCopy) {
-                if (!activity.isFinishing()) {
-                  try {
-                    Logs.log(Log.INFO, "Stopping activity: " + activity);
-                    activity.finish();
-                  } catch (Throwable e) {
-                    Logs.log(Log.ERROR, e, "Failed to stop activity");
-                  }
-                }
-              }
-            }
-          });
-        } catch (Throwable e) {
-          Logs.log(Log.ERROR, e);
-        }
-
-        // We've done what we can. Clear activities if any are left.
-        synchronized (activities) {
-          activities.clear();
-          runningActivity = null;
-        }
-      }
-
-      @Override
-      public void addError(Test arg0, Throwable arg1) {}
-
-      @Override
-      public void addFailure(Test arg0, AssertionFailedError arg1) {}
-
-      @Override
-      public void startTest(Test arg0) {}
-    });
-
-    ActivityUtils.setRunningActivitySupplier(new Supplier<Activity>() {
-      @Override
-      public Activity get() {
-        return runningActivity;
-      }
-    });
-
-    super.onStart();
-  }
-
-  // Overrides InstrumentationTestRunner
-  List<Predicate<TestMethod>> getBuilderRequirements() {
-    List<Predicate<TestMethod>> requirements = new ArrayList<Predicate<TestMethod>>();
-    requirements.add(new Predicate<TestMethod>() {
-      @Override
-      public boolean apply(TestMethod arg0) {
-        MinSdkVersion minSdkVersion = getAnnotation(arg0, MinSdkVersion.class);
-        if (minSdkVersion != null && minSdkVersion.value() > Build.VERSION.SDK_INT) {
-          Logs.logfmt(Log.INFO, "filtered %s#%s: MinSdkVersion=%d", arg0.getEnclosingClassname(),
-              arg0.getName(), minSdkVersion.value());
-          return false;
-        }
-
-        UseUiAutomation useUiAutomation = getAnnotation(arg0, UseUiAutomation.class);
-        if (useUiAutomation != null && !DroidDrivers.hasUiAutomation()) {
-          Logs.logfmt(Log.INFO,
-              "filtered %s#%s: Has @UseUiAutomation, but ro.build.version.sdk=%d",
-              arg0.getEnclosingClassname(), arg0.getName(), Build.VERSION.SDK_INT);
-          return false;
-        }
-        return true;
-      }
-
-      private <T extends Annotation> T getAnnotation(TestMethod testMethod, Class<T> clazz) {
-        T annotation = testMethod.getAnnotation(clazz);
-        if (annotation == null) {
-          annotation = testMethod.getEnclosingClass().getAnnotation(clazz);
-        }
-        return annotation;
-      }
-    });
-    return requirements;
-  }
-
-  @Override
-  public void callActivityOnDestroy(Activity activity) {
-    super.callActivityOnDestroy(activity);
-    synchronized (activities) {
-      activities.remove(activity);
-    }
-  }
-
-  @Override
-  public void callActivityOnCreate(Activity activity, Bundle bundle) {
-    super.callActivityOnCreate(activity, bundle);
-    synchronized (activities) {
-      activities.add(activity);
-    }
-  }
-
-  @Override
-  public void callActivityOnResume(Activity activity) {
-    super.callActivityOnResume(activity);
-    runningActivity = activity;
-  }
-
-  @Override
-  public void callActivityOnPause(Activity activity) {
-    super.callActivityOnPause(activity);
-    if (activity == runningActivity) {
-      runningActivity = null;
-    }
-  }
-}
diff --git a/src/io/appium/droiddriver/runner/UseUiAutomation.java b/src/io/appium/droiddriver/runner/UseUiAutomation.java
index 316ac8f..e710238 100644
--- a/src/io/appium/droiddriver/runner/UseUiAutomation.java
+++ b/src/io/appium/droiddriver/runner/UseUiAutomation.java
@@ -16,26 +16,25 @@
 
 package io.appium.droiddriver.runner;
 
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-
 /**
- * This annotation indicates that its target needs
- * {@link android.app.UiAutomation}. It is effectively equivalent to
- * {@code @MinSdkVersion(Build.VERSION_CODES.JELLY_BEAN_MR2)}, just more
+ * This annotation indicates that its target needs {@link android.app.UiAutomation}. It is
+ * effectively equivalent to {@code @MinSdkVersion(Build.VERSION_CODES.JELLY_BEAN_MR2)}, just more
  * explicit.
- * <p>
- * As any annotations, it is useful only if it is processed by tools.
- * {@link TestRunner} filters out tests with this annotation if the current
- * device has SDK version below 18 (JELLY_BEAN_MR2).
+ *
+ * <p>As any annotations, it is useful only if it is processed by tools.
+ *
+ * @deprecated Use android.support.test.filters.SdkSuppress instead.
  */
 @Inherited
 @Target({TYPE, METHOD})
 @Retention(RetentionPolicy.RUNTIME)
-public @interface UseUiAutomation {
-}
+@Deprecated
+public @interface UseUiAutomation {}
diff --git a/src/io/appium/droiddriver/scroll/DynamicSentinelStrategy.java b/src/io/appium/droiddriver/scroll/DynamicSentinelStrategy.java
index 051cfa7..b42b60d 100644
--- a/src/io/appium/droiddriver/scroll/DynamicSentinelStrategy.java
+++ b/src/io/appium/droiddriver/scroll/DynamicSentinelStrategy.java
@@ -39,7 +39,7 @@
   /**
    * Interface for determining whether sentinel is updated.
    */
-  public static interface IsUpdatedStrategy {
+  public interface IsUpdatedStrategy {
     /**
      * Returns whether {@code newSentinel} is updated from {@code oldSentinel}.
      */
diff --git a/src/io/appium/droiddriver/scroll/StepBasedScroller.java b/src/io/appium/droiddriver/scroll/StepBasedScroller.java
index 6dbc79e..11c42f4 100644
--- a/src/io/appium/droiddriver/scroll/StepBasedScroller.java
+++ b/src/io/appium/droiddriver/scroll/StepBasedScroller.java
@@ -15,6 +15,8 @@
  */
 package io.appium.droiddriver.scroll;
 
+import static io.appium.droiddriver.scroll.Direction.LogicalDirection.BACKWARD;
+
 import android.util.Log;
 
 import io.appium.droiddriver.DroidDriver;
@@ -29,8 +31,6 @@
 import io.appium.droiddriver.scroll.Direction.PhysicalDirection;
 import io.appium.droiddriver.util.Logs;
 
-import static io.appium.droiddriver.scroll.Direction.LogicalDirection.BACKWARD;
-
 /**
  * A {@link Scroller} that looks for the desired item in the currently shown
  * content of the scrollable container, otherwise scrolls the container one step
diff --git a/src/io/appium/droiddriver/uiautomation/UiAutomationElement.java b/src/io/appium/droiddriver/uiautomation/UiAutomationElement.java
index cf7449e..5b99131 100644
--- a/src/io/appium/droiddriver/uiautomation/UiAutomationElement.java
+++ b/src/io/appium/droiddriver/uiautomation/UiAutomationElement.java
@@ -16,6 +16,8 @@
 
 package io.appium.droiddriver.uiautomation;
 
+import static io.appium.droiddriver.util.Strings.charSequenceToString;
+
 import android.annotation.TargetApi;
 import android.app.UiAutomation;
 import android.app.UiAutomation.AccessibilityEventFilter;
@@ -37,8 +39,6 @@
 import io.appium.droiddriver.uiautomation.UiAutomationContext.UiAutomationCallable;
 import io.appium.droiddriver.util.Preconditions;
 
-import static io.appium.droiddriver.util.Strings.charSequenceToString;
-
 /**
  * A UiElement that gets attributes via the Accessibility API.
  */
@@ -96,9 +96,9 @@
     put(attribs, Attribute.BOUNDS, getBounds(node));
     attributes = Collections.unmodifiableMap(attribs);
 
-    // Order matters as getVisibleBounds depends on visible
+    // Order matters as findVisibleBounds depends on visible
     visible = node.isVisibleToUser();
-    visibleBounds = getVisibleBounds(node);
+    visibleBounds = findVisibleBounds();
     List<UiAutomationElement> mutableChildren = buildChildren(node);
     this.children = mutableChildren == null ? null : Collections.unmodifiableList(mutableChildren);
   }
@@ -132,19 +132,19 @@
     return rect;
   }
 
-  private Rect getVisibleBounds(AccessibilityNodeInfo node) {
+  private Rect findVisibleBounds() {
     if (!visible) {
       return new Rect();
     }
-    Rect visibleBounds = getBounds();
+    Rect foundBounds = getBounds();
     UiAutomationElement parent = getParent();
-    Rect parentBounds;
     while (parent != null) {
-      parentBounds = parent.getBounds();
-      visibleBounds.intersect(parentBounds);
+      if (!foundBounds.intersect(parent.getBounds())) {
+        return new Rect();
+      }
       parent = parent.getParent();
     }
-    return visibleBounds;
+    return foundBounds;
   }
 
   @Override
diff --git a/src/io/appium/droiddriver/util/ActivityUtils.java b/src/io/appium/droiddriver/util/ActivityUtils.java
index 1e35de8..ff06ab5 100644
--- a/src/io/appium/droiddriver/util/ActivityUtils.java
+++ b/src/io/appium/droiddriver/util/ActivityUtils.java
@@ -17,47 +17,90 @@
 package io.appium.droiddriver.util;
 
 import android.app.Activity;
+import android.os.Looper;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Log;
+import java.util.Iterator;
+import java.util.concurrent.Callable;
 
-import io.appium.droiddriver.exceptions.UnrecoverableException;
-import io.appium.droiddriver.instrumentation.InstrumentationDriver;
-
-/**
- * Static helper methods for retrieving activities.
- */
+/** Static helper methods for retrieving activities. */
 public class ActivityUtils {
+  private static final Callable<Activity> GET_RUNNING_ACTIVITY =
+      new Callable<Activity>() {
+        @Override
+        public Activity call() {
+          Iterator<Activity> activityIterator =
+              ActivityLifecycleMonitorRegistry.getInstance()
+                  .getActivitiesInStage(Stage.RESUMED)
+                  .iterator();
+          return activityIterator.hasNext() ? activityIterator.next() : null;
+        }
+      };
+  private static Supplier<Activity> runningActivitySupplier =
+      new Supplier<Activity>() {
+        @Override
+        public Activity get() {
+          try {
+            // If this is called on main (UI) thread, don't call runOnMainSync
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+              return GET_RUNNING_ACTIVITY.call();
+            }
+
+            return InstrumentationUtils.runOnMainSyncWithTimeout(GET_RUNNING_ACTIVITY);
+          } catch (Exception e) {
+            Logs.log(Log.WARN, e);
+            return null;
+          }
+        }
+      };
+
+  /**
+   * Sets the Supplier for the running (a.k.a. resumed or foreground) activity. If a custom runner
+   * is used, this method must be called appropriately, otherwise {@link #getRunningActivity} won't
+   * work.
+   */
+  public static synchronized void setRunningActivitySupplier(Supplier<Activity> activitySupplier) {
+    runningActivitySupplier = Preconditions.checkNotNull(activitySupplier);
+  }
+
+  /** Shorthand to {@link #getRunningActivity(long)} with {@code timeoutMillis=30_000}. */
+  public static Activity getRunningActivity() {
+    return getRunningActivity(30_000L);
+  }
+
+  /**
+   * Waits for idle on main looper, then gets the running (a.k.a. resumed or foreground) activity.
+   *
+   * @return the currently running activity, or null if no activity has focus.
+   */
+  public static Activity getRunningActivity(long timeoutMillis) {
+    // It's safe to check running activity only when the main looper is idle.
+    // If the AUT is in background, its main looper should be idle already.
+    // If the AUT is in foreground, its main looper should be idle eventually.
+    if (InstrumentationUtils.tryWaitForIdleSync(timeoutMillis)) {
+      return getRunningActivityNoWait();
+    }
+    return null;
+  }
+
+  /**
+   * Gets the running (a.k.a. resumed or foreground) activity without waiting for idle on main
+   * looper.
+   *
+   * @return the currently running activity, or null if no activity has focus.
+   */
+  public static synchronized Activity getRunningActivityNoWait() {
+    return runningActivitySupplier.get();
+  }
+
   public interface Supplier<T> {
     /**
-     * Retrieves an instance of the appropriate type. The returned object may or
-     * may not be a new instance, depending on the implementation.
+     * Retrieves an instance of the appropriate type. The returned object may or may not be a new
+     * instance, depending on the implementation.
      *
      * @return an instance of the appropriate type
      */
     T get();
   }
-
-  private static Supplier<Activity> runningActivitySupplier;
-
-  /**
-   * Sets the Supplier for the running (a.k.a. resumed or foreground) activity.
-   * Called from {@link io.appium.droiddriver.runner.TestRunner}. If a
-   * custom runner is used, this method must be called appropriately, otherwise
-   * {@link #getRunningActivity} won't work.
-   */
-  public static synchronized void setRunningActivitySupplier(Supplier<Activity> activitySupplier) {
-    runningActivitySupplier = activitySupplier;
-  }
-
-  /**
-   * Gets the running (a.k.a. resumed or foreground) activity.
-   * {@link InstrumentationDriver} depends on this.
-   *
-   * @return the currently running activity, or null if no activity has focus.
-   */
-  public static synchronized Activity getRunningActivity() {
-    if (runningActivitySupplier == null) {
-      throw new UnrecoverableException("If you don't use DroidDriver TestRunner, you need to call" +
-          " ActivityUtils.setRunningActivitySupplier appropriately");
-    }
-    return runningActivitySupplier.get();
-  }
 }
diff --git a/src/io/appium/droiddriver/util/InstrumentationUtils.java b/src/io/appium/droiddriver/util/InstrumentationUtils.java
index c4f280d..06ac5ab 100644
--- a/src/io/appium/droiddriver/util/InstrumentationUtils.java
+++ b/src/io/appium/droiddriver/util/InstrumentationUtils.java
@@ -20,38 +20,34 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
-
+import io.appium.droiddriver.exceptions.DroidDriverException;
+import io.appium.droiddriver.exceptions.TimeoutException;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
-import io.appium.droiddriver.exceptions.DroidDriverException;
-import io.appium.droiddriver.exceptions.TimeoutException;
-import io.appium.droiddriver.exceptions.UnrecoverableException;
-
-/**
- * Static utility methods pertaining to {@link Instrumentation}.
- */
+/** Static utility methods pertaining to {@link Instrumentation}. */
 public class InstrumentationUtils {
+  private static final Runnable EMPTY_RUNNABLE =
+      new Runnable() {
+        @Override
+        public void run() {}
+      };
+  private static final Executor RUN_ON_MAIN_SYNC_EXECUTOR = Executors.newSingleThreadExecutor();
   private static Instrumentation instrumentation;
   private static Bundle options;
   private static long runOnMainSyncTimeoutMillis;
-  private static final Runnable EMPTY_RUNNABLE = new Runnable() {
-    @Override
-    public void run() {
-    }
-  };
-  private static final Executor RUN_ON_MAIN_SYNC_EXECUTOR = Executors.newSingleThreadExecutor();
 
   /**
-   * Initializes this class. If you use a runner that is not DroidDriver-aware, you need to call
-   * this method appropriately. See {@link io.appium.droiddriver.runner.TestRunner#onCreate} for
-   * example.
+   * Initializes this class. If you don't use android.support.test.runner.AndroidJUnitRunner or a
+   * runner that supports {link InstrumentationRegistry}, you need to call this method
+   * appropriately.
    */
-  public static void init(Instrumentation instrumentation, Bundle arguments) {
+  public static synchronized void init(Instrumentation instrumentation, Bundle arguments) {
     if (InstrumentationUtils.instrumentation != null) {
       throw new DroidDriverException("init() can only be called once");
     }
@@ -59,13 +55,13 @@
     options = arguments;
 
     String timeoutString = getD2Option("runOnMainSyncTimeout");
-    runOnMainSyncTimeoutMillis = timeoutString == null ? 10000L : Long.parseLong(timeoutString);
+    runOnMainSyncTimeoutMillis = timeoutString == null ? 10_000L : Long.parseLong(timeoutString);
   }
 
-  private static void checkInitialized() {
+  private static synchronized void checkInitialized() {
     if (instrumentation == null) {
-      throw new UnrecoverableException("If you use a runner that is not DroidDriver-aware, you" +
-          " need to call InstrumentationUtils.init appropriately");
+      // Assume android.support.test.runner.InstrumentationRegistry is valid
+      init(InstrumentationRegistry.getInstrumentation(), InstrumentationRegistry.getArguments());
     }
   }
 
@@ -79,19 +75,16 @@
   }
 
   /**
-   * Gets the <a href= "http://developer.android.com/tools/testing/testing_otheride.html#AMOptionsSyntax"
-   * >am instrument options</a>.
+   * Gets the <a href=
+   * "http://developer.android.com/tools/testing/testing_otheride.html#AMOptionsSyntax" >am
+   * instrument options</a>.
    */
   public static Bundle getOptions() {
     checkInitialized();
     return options;
   }
 
-  /**
-   * Gets the string value associated with the given key. This is preferred over using {@link
-   * #getOptions} because the returned {@link Bundle} contains only string values - am instrument
-   * options do not support value types other than string.
-   */
+  /** Gets the string value associated with the given key. */
   public static String getOption(String key) {
     return getOptions().getString(key);
   }
@@ -110,14 +103,15 @@
    * example, the ProgressBar.
    */
   public static boolean tryWaitForIdleSync(long timeoutMillis) {
-    validateNotAppThread();
+    checkNotMainThread();
     FutureTask<Void> emptyTask = new FutureTask<Void>(EMPTY_RUNNABLE, null);
-    instrumentation.waitForIdle(emptyTask);
+    getInstrumentation().waitForIdle(emptyTask);
 
     try {
       emptyTask.get(timeoutMillis, TimeUnit.MILLISECONDS);
     } catch (java.util.concurrent.TimeoutException e) {
-      Logs.log(Log.INFO,
+      Logs.log(
+          Log.INFO,
           "Timed out after " + timeoutMillis + " milliseconds waiting for idle on main looper");
       return false;
     } catch (Throwable t) {
@@ -127,44 +121,51 @@
   }
 
   public static void runOnMainSyncWithTimeout(final Runnable runnable) {
-    runOnMainSyncWithTimeout(new Callable<Void>() {
-      @Override
-      public Void call() throws Exception {
-        runnable.run();
-        return null;
-      }
-    });
+    runOnMainSyncWithTimeout(
+        new Callable<Void>() {
+          @Override
+          public Void call() throws Exception {
+            runnable.run();
+            return null;
+          }
+        });
   }
 
   /**
    * Runs {@code callable} on the main thread on best-effort basis up to a time limit, which
    * defaults to {@code 10000L} and can be set as an am instrument option under the key {@code
-   * dd.runOnMainSyncTimeout}. <p>This is a safer variation of {@link Instrumentation#runOnMainSync}
-   * because the latter may hang. You may turn off this behavior by setting {@code "-e
-   * dd.runOnMainSyncTimeout 0"} on the am command line.</p>The {@code callable} may never run, for
-   * example, if the main Looper has exited due to uncaught exception.
+   * dd.runOnMainSyncTimeout}.
+   *
+   * <p>This is a safer variation of {@link Instrumentation#runOnMainSync} because the latter may
+   * hang. You may turn off this behavior by setting {@code "-e dd.runOnMainSyncTimeout 0"} on the
+   * am command line.The {@code callable} may never run, for example, if the main Looper has exited
+   * due to uncaught exception.
    */
   public static <V> V runOnMainSyncWithTimeout(Callable<V> callable) {
-    validateNotAppThread();
+    checkNotMainThread();
     final RunOnMainSyncFutureTask<V> futureTask = new RunOnMainSyncFutureTask<>(callable);
 
     if (runOnMainSyncTimeoutMillis <= 0L) {
       // Call runOnMainSync on current thread without time limit.
       futureTask.runOnMainSyncNoThrow();
     } else {
-      RUN_ON_MAIN_SYNC_EXECUTOR.execute(new Runnable() {
-        @Override
-        public void run() {
-          futureTask.runOnMainSyncNoThrow();
-        }
-      });
+      RUN_ON_MAIN_SYNC_EXECUTOR.execute(
+          new Runnable() {
+            @Override
+            public void run() {
+              futureTask.runOnMainSyncNoThrow();
+            }
+          });
     }
 
     try {
       return futureTask.get(runOnMainSyncTimeoutMillis, TimeUnit.MILLISECONDS);
     } catch (java.util.concurrent.TimeoutException e) {
-      throw new TimeoutException("Timed out after " + runOnMainSyncTimeoutMillis
-          + " milliseconds waiting for Instrumentation.runOnMainSync", e);
+      throw new TimeoutException(
+          "Timed out after "
+              + runOnMainSyncTimeoutMillis
+              + " milliseconds waiting for Instrumentation.runOnMainSync",
+          e);
     } catch (Throwable t) {
       throw DroidDriverException.propagate(t);
     } finally {
@@ -172,6 +173,18 @@
     }
   }
 
+  public static void checkMainThread() {
+    if (Looper.myLooper() != Looper.getMainLooper()) {
+      throw new DroidDriverException("This method must be called on the main thread");
+    }
+  }
+
+  public static void checkNotMainThread() {
+    if (Looper.myLooper() == Looper.getMainLooper()) {
+      throw new DroidDriverException("This method cannot be called on the main thread");
+    }
+  }
+
   private static class RunOnMainSyncFutureTask<V> extends FutureTask<V> {
     public RunOnMainSyncFutureTask(Callable<V> callable) {
       super(callable);
@@ -185,11 +198,4 @@
       }
     }
   }
-
-  private static void validateNotAppThread() {
-    if (Looper.myLooper() == Looper.getMainLooper()) {
-      throw new DroidDriverException(
-          "This method can not be called from the main application thread");
-    }
-  }
 }
diff --git a/src/io/appium/droiddriver/validators/DefaultAccessibilityValidator.java b/src/io/appium/droiddriver/validators/DefaultAccessibilityValidator.java
index 1ce3649..b78d0a5 100644
--- a/src/io/appium/droiddriver/validators/DefaultAccessibilityValidator.java
+++ b/src/io/appium/droiddriver/validators/DefaultAccessibilityValidator.java
@@ -41,14 +41,8 @@
 
   // Logic from TalkBack
   private static boolean isAccessibilityFocusable(UiElement element) {
-    if (isActionableForAccessibility(element)) {
-      return true;
-    }
-
-    if (isTopLevelScrollItem(element) && (isSpeakingNode(element))) {
-      return true;
-    }
-    return false;
+    return isActionableForAccessibility(element)
+        || (isTopLevelScrollItem(element) && isSpeakingNode(element));
   }
 
   private static boolean isTopLevelScrollItem(UiElement element) {