aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Jin <kjin@google.com>2014-02-24 12:59:14 -0800
committerKevin Jin <kjin@google.com>2014-02-25 09:54:06 -0800
commita6749c6913f014416419850a9fb5235a745fdeb8 (patch)
tree24117ef299270a0068c388acf0d8975b18107946 /src
parent39b609194aea07e7f1d8ead084d48d1171198f02 (diff)
downloaddroiddriver-a6749c6913f014416419850a9fb5235a745fdeb8.tar.gz
fix the hanging when the app is constantly busy (no idle)
The main thread may not enter the idle state when animation is playing, for example, the ProgressBar. Do not call waitForIdleSync in UiAutomationDriver. Change-Id: If3c355c8aa302a076895fd62a6428874b24f5d39
Diffstat (limited to 'src')
-rw-r--r--src/com/google/android/droiddriver/base/DroidDriverContext.java62
-rw-r--r--src/com/google/android/droiddriver/finders/By.java8
-rw-r--r--src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java10
-rw-r--r--src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java6
-rw-r--r--src/com/google/android/droiddriver/instrumentation/ViewElement.java3
-rw-r--r--src/com/google/android/droiddriver/scroll/Scrollers.java2
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java12
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java1
8 files changed, 70 insertions, 34 deletions
diff --git a/src/com/google/android/droiddriver/base/DroidDriverContext.java b/src/com/google/android/droiddriver/base/DroidDriverContext.java
index f5dbf68..17d681c 100644
--- a/src/com/google/android/droiddriver/base/DroidDriverContext.java
+++ b/src/com/google/android/droiddriver/base/DroidDriverContext.java
@@ -17,19 +17,69 @@
package com.google.android.droiddriver.base;
import android.app.Instrumentation;
+import android.os.Looper;
+import android.util.Log;
import com.google.android.droiddriver.actions.InputInjector;
+import com.google.android.droiddriver.exceptions.DroidDriverException;
+import com.google.android.droiddriver.util.Logs;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
/**
- * Internal helper for managing all UiElement instances.
+ * Internal helper for DroidDriver implementation.
*/
-public interface DroidDriverContext {
- Instrumentation getInstrumentation();
+public abstract class DroidDriverContext {
+ private final Instrumentation instrumentation;
+
+ protected DroidDriverContext(Instrumentation instrumentation) {
+ this.instrumentation = instrumentation;
+ }
+
+ public Instrumentation getInstrumentation() {
+ return instrumentation;
+ }
- BaseDroidDriver getDriver();
+ public abstract BaseDroidDriver getDriver();
- InputInjector getInjector();
+ public abstract InputInjector getInjector();
/** Clears UiElement instances in the context */
- void clearData();
+ public abstract void clearData();
+
+ /**
+ * Tries to wait for an idle state on the main thread on best-effort basis up
+ * to {@code timeoutMillis}. The main thread may not enter the idle state when
+ * animation is playing, for example, the ProgressBar.
+ */
+ public boolean tryWaitForIdleSync(long timeoutMillis) {
+ validateNotAppThread();
+ FutureTask<?> futureTask = new FutureTask<Void>(new Runnable() {
+ @Override
+ public void run() {}
+ }, null);
+ instrumentation.waitForIdle(futureTask);
+
+ try {
+ futureTask.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new DroidDriverException(e);
+ } catch (ExecutionException e) {
+ throw new DroidDriverException(e);
+ } catch (java.util.concurrent.TimeoutException e) {
+ Logs.log(Log.DEBUG, String.format(
+ "Timed out after %d milliseconds waiting for idle on main looper", timeoutMillis));
+ return false;
+ }
+ return true;
+ }
+
+ private 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/com/google/android/droiddriver/finders/By.java b/src/com/google/android/droiddriver/finders/By.java
index 0f1592b..5e7f322 100644
--- a/src/com/google/android/droiddriver/finders/By.java
+++ b/src/com/google/android/droiddriver/finders/By.java
@@ -235,8 +235,12 @@ public class By {
* Returns a finder that uses the UiElement returned by first Finder as
* context for the second Finder.
* <p>
- * Note typically first Finder finds the ancestor, then second Finder finds
- * the target UiElement, which is a descendant.
+ * 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);
diff --git a/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java b/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
index f535ccb..a549a0f 100644
--- a/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
+++ b/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
@@ -30,14 +30,13 @@ import com.google.common.collect.MapMaker;
import java.util.Map;
-class InstrumentationContext implements DroidDriverContext {
+class InstrumentationContext extends DroidDriverContext {
private final Map<View, ViewElement> map = new MapMaker().weakKeys().weakValues().makeMap();
- private final Instrumentation instrumentation;
private final InstrumentationDriver driver;
private final InputInjector injector;
InstrumentationContext(final Instrumentation instrumentation, InstrumentationDriver driver) {
- this.instrumentation = instrumentation;
+ super(instrumentation);
this.driver = driver;
this.injector = new InputInjector() {
@Override
@@ -55,11 +54,6 @@ class InstrumentationContext implements DroidDriverContext {
}
@Override
- public Instrumentation getInstrumentation() {
- return instrumentation;
- }
-
- @Override
public InstrumentationDriver getDriver() {
return driver;
}
diff --git a/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java b/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
index 460817c..7684530 100644
--- a/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
+++ b/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
@@ -31,7 +31,6 @@ import com.google.android.droiddriver.base.BaseDroidDriver;
import com.google.android.droiddriver.exceptions.TimeoutException;
import com.google.android.droiddriver.util.ActivityUtils;
import com.google.android.droiddriver.util.Logs;
-import com.google.common.base.Preconditions;
import com.google.common.primitives.Longs;
/**
@@ -39,11 +38,9 @@ import com.google.common.primitives.Longs;
*/
public class InstrumentationDriver extends BaseDroidDriver {
private final InstrumentationContext context;
- private final Instrumentation instrumentation;
private final InstrumentationUiDevice uiDevice;
public InstrumentationDriver(Instrumentation instrumentation) {
- this.instrumentation = Preconditions.checkNotNull(instrumentation);
this.context = new InstrumentationContext(instrumentation, this);
uiDevice = new InstrumentationUiDevice(context);
}
@@ -76,7 +73,6 @@ public class InstrumentationDriver extends BaseDroidDriver {
long timeoutMillis = getPoller().getTimeoutMillis();
long end = SystemClock.uptimeMillis() + timeoutMillis;
while (true) {
- instrumentation.waitForIdleSync();
Activity runningActivity = ActivityUtils.getRunningActivity();
if (runningActivity != null) {
return runningActivity;
@@ -127,7 +123,7 @@ public class InstrumentationDriver extends BaseDroidDriver {
Bitmap takeScreenshot() {
ScreenshotRunnable screenshotRunnable = new ScreenshotRunnable(findRootView());
- instrumentation.runOnMainSync(screenshotRunnable);
+ context.getInstrumentation().runOnMainSync(screenshotRunnable);
return screenshotRunnable.screenshot;
}
diff --git a/src/com/google/android/droiddriver/instrumentation/ViewElement.java b/src/com/google/android/droiddriver/instrumentation/ViewElement.java
index 1823194..55f9819 100644
--- a/src/com/google/android/droiddriver/instrumentation/ViewElement.java
+++ b/src/com/google/android/droiddriver/instrumentation/ViewElement.java
@@ -266,7 +266,6 @@ public class ViewElement extends BaseUiElement {
@Override
protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) {
futureTask.run();
- // Instead of specific timeoutMillis, wait for idle
- context.getInstrumentation().waitForIdleSync();
+ context.tryWaitForIdleSync(timeoutMillis);
}
}
diff --git a/src/com/google/android/droiddriver/scroll/Scrollers.java b/src/com/google/android/droiddriver/scroll/Scrollers.java
index 6272955..9834c71 100644
--- a/src/com/google/android/droiddriver/scroll/Scrollers.java
+++ b/src/com/google/android/droiddriver/scroll/Scrollers.java
@@ -40,7 +40,7 @@ public class Scrollers {
* arguments that apply to typical cases. You may want to customize them for
* specific cases. For instance, {@code perScrollTimeoutMillis} can be 0L if
* there are no asynchronously updated views. To that extent, this method
- * serves as an example of how to construct rather {@link Scroller}s than
+ * serves as an example of how to construct {@link Scroller}s rather than
* providing the "official" {@link Scroller}.
*/
public static Scroller newScroller(UiAutomation uiAutomation) {
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
index b85803b..ac114cd 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
@@ -28,16 +28,15 @@ import com.google.common.collect.MapMaker;
import java.util.Map;
-class UiAutomationContext implements DroidDriverContext {
+class UiAutomationContext extends DroidDriverContext {
private final Map<AccessibilityNodeInfo, UiAutomationElement> map = new MapMaker().weakKeys()
.weakValues().makeMap();
private final UiAutomation uiAutomation;
- private final Instrumentation instrumentation;
- private final UiAutomationDriver driver;
private final InputInjector injector;
+ private final UiAutomationDriver driver;
UiAutomationContext(final Instrumentation instrumentation, UiAutomationDriver driver) {
- this.instrumentation = instrumentation;
+ super(instrumentation);
this.uiAutomation = instrumentation.getUiAutomation();
this.driver = driver;
this.injector = new InputInjector() {
@@ -49,11 +48,6 @@ class UiAutomationContext implements DroidDriverContext {
}
@Override
- public Instrumentation getInstrumentation() {
- return instrumentation;
- }
-
- @Override
public UiAutomationDriver getDriver() {
return driver;
}
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java
index f6c6c62..5089acf 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java
@@ -64,7 +64,6 @@ public class UiAutomationDriver extends BaseDroidDriver {
private AccessibilityNodeInfo getRootNode() {
long timeoutMillis = getPoller().getTimeoutMillis();
try {
- context.getInstrumentation().waitForIdleSync();
uiAutomation.waitForIdle(QUIET_TIME_TO_BE_CONSIDERD_IDLE_STATE, timeoutMillis);
} catch (java.util.concurrent.TimeoutException e) {
throw new TimeoutException(e);