aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--Android.mk16
-rw-r--r--build.gradle17
-rw-r--r--contributing.md9
-rw-r--r--contributing_aosp.md20
-rw-r--r--droiddriver-android_support_test/Android.mk19
-rw-r--r--droiddriver-android_support_test/AndroidManifest.xml5
-rw-r--r--droiddriver-android_support_test/build.gradle70
-rw-r--r--droiddriver-android_support_test/readme.md5
-rw-r--r--droiddriver-android_support_test/src/io/appium/droiddriver/android_support_test/D2AndroidJUnitRunner.java82
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 51018 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xgradlew164
-rw-r--r--gradlew.bat90
-rw-r--r--manualtest/Android.mk17
-rw-r--r--manualtest/AndroidManifest.xml2
-rw-r--r--manualtest/build.gradle17
-rw-r--r--manualtest/src/io/appium/droiddriver/manualtest/ManualTest.java4
-rw-r--r--src/io/appium/droiddriver/UiElement.java208
-rw-r--r--src/io/appium/droiddriver/actions/TextAction.java36
-rw-r--r--src/io/appium/droiddriver/actions/accessibility/AccessibilityClickAction.java1
-rw-r--r--src/io/appium/droiddriver/base/AbstractDroidDriver.java86
-rw-r--r--src/io/appium/droiddriver/base/BaseDroidDriver.java61
-rw-r--r--src/io/appium/droiddriver/base/BaseUiDevice.java14
-rw-r--r--src/io/appium/droiddriver/base/CompositeDroidDriver.java59
-rw-r--r--src/io/appium/droiddriver/duo/DuoDriver.java57
-rw-r--r--src/io/appium/droiddriver/finders/Attribute.java2
-rw-r--r--src/io/appium/droiddriver/finders/By.java139
-rw-r--r--src/io/appium/droiddriver/finders/Predicates.java85
-rw-r--r--src/io/appium/droiddriver/helpers/BaseDroidDriverTest.java3
-rw-r--r--src/io/appium/droiddriver/helpers/DroidDrivers.java4
-rw-r--r--src/io/appium/droiddriver/helpers/SingleRun.java2
-rw-r--r--src/io/appium/droiddriver/instrumentation/InstrumentationDriver.java73
-rw-r--r--src/io/appium/droiddriver/instrumentation/ViewElement.java205
-rw-r--r--src/io/appium/droiddriver/runner/MinSdkVersion.java22
-rw-r--r--src/io/appium/droiddriver/runner/TestRunner.java203
-rw-r--r--src/io/appium/droiddriver/runner/UseUiAutomation.java23
-rw-r--r--src/io/appium/droiddriver/scroll/DynamicSentinelStrategy.java2
-rw-r--r--src/io/appium/droiddriver/scroll/StepBasedScroller.java4
-rw-r--r--src/io/appium/droiddriver/uiautomation/UiAutomationElement.java20
-rw-r--r--src/io/appium/droiddriver/util/ActivityUtils.java97
-rw-r--r--src/io/appium/droiddriver/util/InstrumentationUtils.java124
-rw-r--r--src/io/appium/droiddriver/validators/DefaultAccessibilityValidator.java10
43 files changed, 1041 insertions, 1049 deletions
diff --git a/.gitignore b/.gitignore
index 3d0ebbd..93237bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,17 +3,14 @@ gen/
# 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 @@ buildscript {
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 @@ version = ddVersion
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 @@ The [`master` branch](https://github.com/appium/droiddriver/tree/master) on GitH
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 @@ $ repo sync
```
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 @@ When commenting on the code, posts will show up as drafts. Drafts are not visibl
- `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 @@ buildscript {
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 @@ buildscript {
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 @@ android {
java.srcDirs = ['src']
}
}
+ productFlavors {
+ }
}
// Building with droiddriver source. Common tests should use droiddriver from jcenter by having
@@ -39,7 +36,7 @@ android {
// 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 @@ import io.appium.droiddriver.uiautomation.UiAutomationDriver;
* {@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.finders.Predicate;
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 @@ public interface UiElement {
*/
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 @@ public interface UiElement {
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.Build;
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 @@ public class TextAction extends KeyAction {
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 @@ public class TextAction extends KeyAction {
// 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 @@ public abstract class AccessibilityClickAction extends AccessibilityAction {
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 @@ package io.appium.droiddriver.base;
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 @@ public abstract class BaseDroidDriver<R, E extends BaseUiElement<R, E>> implemen
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.graphics.Bitmap.CompressFormat;
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 @@ public abstract class BaseUiDevice implements UiDevice {
@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 @@ public enum Attribute {
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 @@ public class By {
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 @@ public class By {
* }
* </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 @@ public class By {
}
/**
- * 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 @@ public class By {
}
/**
- * 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 @@ public class By {
}
/**
- * 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 @@ public class By {
}
/**
- * 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 @@ public class By {
}
/**
- * 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 @@ public class By {
}
/**
- * 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 @@ public class By {
}
/**
- * 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 @@ public class By {
});
}
- /** 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 @@ package io.appium.droiddriver.finders;
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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 @@ public final class Predicates {
}
/**
- * 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 final class Predicates {
};
}
- 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 final class Predicates {
};
}
- 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 @@ public final class Predicates {
}
};
}
+
+
}
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 @@ public abstract class BaseDroidDriverTest<T extends Activity> extends
* 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.app.Instrumentation;
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 @@ public class DroidDrivers {
// 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 @@ import java.util.concurrent.atomic.AtomicBoolean;
* 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.exceptions.NoRunningActivityException;
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 @@ public class InstrumentationDriver extends BaseDroidDriver<View, ViewElement> {
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));
+ }
+
+ if (ActivityUtils.getRunningActivity(remainingMillis) != null) {
+ View view = InstrumentationUtils.runOnMainSyncWithTimeout(FIND_ROOT_VIEW);
+ if (view != null) {
+ return view;
+ }
}
- SystemClock.sleep(Math.min(250, remainingMillis));
}
}
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 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.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;
+/** 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;
-import static io.appium.droiddriver.util.Strings.charSequenceToString;
+ /**
+ * 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;
+ }
-/**
- * A UiElement that is backed by a View.
- */
-public class ViewElement extends BaseUiElement<View, ViewElement> {
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 @@ public class ViewElement extends BaseUiElement<View, ViewElement> {
}
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 @@ public class ViewElement extends BaseUiElement<View, ViewElement> {
}
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 @@ public class ViewElement extends BaseUiElement<View, ViewElement> {
}
}
}
-
- 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 @@ public class DynamicSentinelStrategy extends SentinelStrategy {
/**
* 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.DirectionConverter;
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.finders.Attribute;
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 @@ public class UiAutomationElement extends BaseUiElement<AccessibilityNodeInfo, Ui
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 @@ public class UiAutomationElement extends BaseUiElement<AccessibilityNodeInfo, Ui
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 {
- 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.
- *
- * @return an instance of the appropriate type
- */
- T get();
- }
+ 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();
+ }
- private static Supplier<Activity> runningActivitySupplier;
+ 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.
- * 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.
+ * 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 = activitySupplier;
+ runningActivitySupplier = Preconditions.checkNotNull(activitySupplier);
+ }
+
+ /** Shorthand to {@link #getRunningActivity(long)} with {@code timeoutMillis=30_000}. */
+ public static Activity getRunningActivity() {
+ return getRunningActivity(30_000L);
}
/**
- * Gets the running (a.k.a. resumed or foreground) activity.
- * {@link InstrumentationDriver} depends on this.
+ * 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 synchronized Activity getRunningActivity() {
- if (runningActivitySupplier == null) {
- throw new UnrecoverableException("If you don't use DroidDriver TestRunner, you need to call" +
- " ActivityUtils.setRunningActivitySupplier appropriately");
+ 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.
+ *
+ * @return an instance of the appropriate type
+ */
+ T 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.app.Instrumentation;
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 @@ public class InstrumentationUtils {
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 @@ public class InstrumentationUtils {
}
/**
- * 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 @@ public class InstrumentationUtils {
* 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 class InstrumentationUtils {
}
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 class InstrumentationUtils {
}
}
+ 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 @@ public class InstrumentationUtils {
}
}
}
-
- 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 @@ public class DefaultAccessibilityValidator implements Validator {
// 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) {