aboutsummaryrefslogtreecommitdiff
path: root/src/com
diff options
context:
space:
mode:
authorKevin Jin <kjin@google.com>2013-08-07 16:16:45 -0700
committerKevin Jin <kjin@google.com>2013-08-08 10:40:10 -0700
commit70e34108e0fc19277e642aef3b36b65b8e254899 (patch)
tree7738804f314cf84a5bdc54606f47657d2c56c530 /src/com
parent21a0001e2426644dd68e6140b5873ebaeafcc3dc (diff)
downloaddroiddriver-70e34108e0fc19277e642aef3b36b65b8e254899.tar.gz
add UiDevice for global actions
add UiAutomationDriver#clearAccessibilityNodeInfoCache to work around an Accessibility bug Change-Id: I42db1d61944240520cc34f1ccb4537f572adecf9
Diffstat (limited to 'src/com')
-rw-r--r--src/com/google/android/droiddriver/DroidDriver.java8
-rw-r--r--src/com/google/android/droiddriver/UiDevice.java43
-rw-r--r--src/com/google/android/droiddriver/actions/KeyAction.java14
-rw-r--r--src/com/google/android/droiddriver/actions/PressKeyAction.java24
-rw-r--r--src/com/google/android/droiddriver/actions/SwipeAction.java36
-rw-r--r--src/com/google/android/droiddriver/actions/TypeAction.java10
-rw-r--r--src/com/google/android/droiddriver/base/AbstractContext.java17
-rw-r--r--src/com/google/android/droiddriver/base/AbstractDroidDriver.java7
-rw-r--r--src/com/google/android/droiddriver/base/AbstractUiElement.java3
-rw-r--r--src/com/google/android/droiddriver/base/BaseUiDevice.java70
-rw-r--r--src/com/google/android/droiddriver/finders/XPaths.java5
-rw-r--r--src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java4
-rw-r--r--src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java15
-rw-r--r--src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java9
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java9
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java41
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java7
17 files changed, 289 insertions, 33 deletions
diff --git a/src/com/google/android/droiddriver/DroidDriver.java b/src/com/google/android/droiddriver/DroidDriver.java
index 6daed71..d43e982 100644
--- a/src/com/google/android/droiddriver/DroidDriver.java
+++ b/src/com/google/android/droiddriver/DroidDriver.java
@@ -20,6 +20,9 @@ import com.google.android.droiddriver.exceptions.ElementNotFoundException;
import com.google.android.droiddriver.exceptions.TimeoutException;
import com.google.android.droiddriver.finders.Finder;
+/**
+ * The entry interface for using droiddriver.
+ */
public interface DroidDriver {
/**
* Returns whether a matching element exists without polling. Use this if the
@@ -106,6 +109,11 @@ public interface DroidDriver {
void setPoller(Poller poller);
/**
+ * Returns a {@link UiDevice} for device-wide interaction.
+ */
+ UiDevice getUiDevice();
+
+ /**
* Dumps the UiElement tree to a file to help debug. The tree is based on the
* last used root UiElement if it exists. Screenshot is always current. If
* they do not match, the UiElement tree must be stale, indicating that you
diff --git a/src/com/google/android/droiddriver/UiDevice.java b/src/com/google/android/droiddriver/UiDevice.java
new file mode 100644
index 0000000..381aeab
--- /dev/null
+++ b/src/com/google/android/droiddriver/UiDevice.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.google.android.droiddriver;
+
+import com.google.android.droiddriver.actions.Action;
+
+/**
+ * Interface for device-wide interaction.
+ */
+public interface UiDevice {
+ /**
+ * Returns whether the screen is on.
+ */
+ boolean isScreenOn();
+
+ /** Wakes up device if the screen is off */
+ void wakeUp();
+
+ /** Puts device to sleep if the screen is on */
+ void sleep();
+
+ /**
+ * Executes a global action without the context of a certain UiElement.
+ *
+ * @param action The action to execute
+ * @return true if the action is successful
+ */
+ boolean perform(Action action);
+}
diff --git a/src/com/google/android/droiddriver/actions/KeyAction.java b/src/com/google/android/droiddriver/actions/KeyAction.java
index 1cafe0e..1855b21 100644
--- a/src/com/google/android/droiddriver/actions/KeyAction.java
+++ b/src/com/google/android/droiddriver/actions/KeyAction.java
@@ -16,11 +16,23 @@
package com.google.android.droiddriver.actions;
+import com.google.android.droiddriver.UiElement;
+import com.google.android.droiddriver.exceptions.ActionException;
+
/**
* Base class for {@link Action} that injects key events.
*/
public abstract class KeyAction extends BaseAction {
- protected KeyAction(long timeoutMillis) {
+ private final boolean checkFocused;
+
+ protected KeyAction(long timeoutMillis, boolean checkFocused) {
super(timeoutMillis);
+ this.checkFocused = checkFocused;
+ }
+
+ protected void maybeCheckFocused(UiElement element) {
+ if (checkFocused && element != null && !element.isFocused()) {
+ throw new ActionException(element + " is not focused");
+ }
}
}
diff --git a/src/com/google/android/droiddriver/actions/PressKeyAction.java b/src/com/google/android/droiddriver/actions/PressKeyAction.java
index 14fd060..a08dbef 100644
--- a/src/com/google/android/droiddriver/actions/PressKeyAction.java
+++ b/src/com/google/android/droiddriver/actions/PressKeyAction.java
@@ -25,25 +25,39 @@ import com.google.android.droiddriver.util.Events;
import com.google.common.base.Objects;
/**
- * An action to press a single key. TODO: rename to SingleKeyAction
+ * An action to press a single key. While it is convenient for navigating the
+ * UI, do not overuse it -- the application may interpret key codes in a custom
+ * way and, more importantly, application users may not have access to it
+ * because the device (physical or virtual keyboard) may not support all key
+ * codes. TODO: rename to SingleKeyAction
*/
public class PressKeyAction extends KeyAction {
+ /**
+ * Common instances for convenience and memory preservation.
+ */
+ public static final PressKeyAction MENU = new PressKeyAction(KeyEvent.KEYCODE_MENU);
+ public static final PressKeyAction SEARCH = new PressKeyAction(KeyEvent.KEYCODE_SEARCH);
+ public static final PressKeyAction BACK = new PressKeyAction(KeyEvent.KEYCODE_BACK);
+ public static final PressKeyAction DELETE = new PressKeyAction(KeyEvent.KEYCODE_DEL);
+
private final int keyCode;
/**
- * Defaults timeoutMillis to 0.
+ * Defaults timeoutMillis to 100.
*/
public PressKeyAction(int keyCode) {
- this(keyCode, 0L);
+ this(keyCode, 100L, false);
}
- public PressKeyAction(int keyCode, long timeoutMillis) {
- super(timeoutMillis);
+ public PressKeyAction(int keyCode, long timeoutMillis, boolean checkFocused) {
+ super(timeoutMillis, checkFocused);
this.keyCode = keyCode;
}
@Override
public boolean perform(InputInjector injector, UiElement element) {
+ maybeCheckFocused(element);
+
final long downTime = SystemClock.uptimeMillis();
KeyEvent downEvent = Events.newKeyEvent(downTime, KeyEvent.ACTION_DOWN, keyCode);
KeyEvent upEvent = Events.newKeyEvent(downTime, KeyEvent.ACTION_UP, keyCode);
diff --git a/src/com/google/android/droiddriver/actions/SwipeAction.java b/src/com/google/android/droiddriver/actions/SwipeAction.java
index 433f6de..a7ea7ea 100644
--- a/src/com/google/android/droiddriver/actions/SwipeAction.java
+++ b/src/com/google/android/droiddriver/actions/SwipeAction.java
@@ -24,20 +24,43 @@ import com.google.android.droiddriver.InputInjector;
import com.google.android.droiddriver.UiElement;
import com.google.android.droiddriver.exceptions.ActionException;
import com.google.android.droiddriver.util.Events;
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
/**
* A {@link ScrollAction} that swipes the touch screen.
*/
public class SwipeAction extends ScrollAction {
+ /** Common instances for convenience */
+ public static final SwipeAction SCROLL_UP = new SwipeAction(ScrollDirection.UP, false);
+ public static final SwipeAction SCROLL_DOWN = new SwipeAction(ScrollDirection.DOWN, false);
+ public static final SwipeAction SCROLL_LEFT = new SwipeAction(ScrollDirection.LEFT, false);
+ public static final SwipeAction SCROLL_RIGHT = new SwipeAction(ScrollDirection.RIGHT, false);
+
+ /** Gets canned common instances */
+ public static SwipeAction toScroll(ScrollDirection direction) {
+ switch (direction) {
+ case UP:
+ return SCROLL_UP;
+ case DOWN:
+ return SCROLL_DOWN;
+ case LEFT:
+ return SCROLL_LEFT;
+ case RIGHT:
+ return SCROLL_RIGHT;
+ default:
+ throw new ActionException("Unknown scroll direction: " + direction);
+ }
+ }
private final ScrollDirection direction;
private final boolean drag;
/**
- * Defaults timeoutMillis to 0.
+ * Defaults timeoutMillis to 1000.
*/
public SwipeAction(ScrollDirection direction, boolean drag) {
- this(direction, drag, 0L);
+ this(direction, drag, 1000L);
}
public SwipeAction(ScrollDirection direction, boolean drag, long timeoutMillis) {
@@ -106,4 +129,13 @@ public class SwipeAction extends ScrollAction {
Events.touchUp(injector, downTime, endX, endY);
return true;
}
+
+ @Override
+ public String toString() {
+ ToStringHelper toStringHelper = Objects.toStringHelper(this).addValue(direction);
+ if (drag) {
+ toStringHelper.addValue("drag");
+ }
+ return toStringHelper.toString();
+ }
}
diff --git a/src/com/google/android/droiddriver/actions/TypeAction.java b/src/com/google/android/droiddriver/actions/TypeAction.java
index 55e8dea..912b58f 100644
--- a/src/com/google/android/droiddriver/actions/TypeAction.java
+++ b/src/com/google/android/droiddriver/actions/TypeAction.java
@@ -37,19 +37,21 @@ public class TypeAction extends KeyAction {
private final String text;
/**
- * Defaults timeoutMillis to 0.
+ * Defaults timeoutMillis to 100.
*/
public TypeAction(String text) {
- this(text, 0L);
+ this(text, 100L, false);
}
- public TypeAction(String text, long timeoutMillis) {
- super(timeoutMillis);
+ public TypeAction(String text, long timeoutMillis, boolean checkFocused) {
+ super(timeoutMillis, checkFocused);
this.text = Preconditions.checkNotNull(text);
}
@Override
public boolean perform(InputInjector injector, UiElement element) {
+ maybeCheckFocused(element);
+
// TODO: recycle events?
KeyEvent[] events = KEY_CHAR_MAP.getEvents(text.toCharArray());
boolean success = false;
diff --git a/src/com/google/android/droiddriver/base/AbstractContext.java b/src/com/google/android/droiddriver/base/AbstractContext.java
index 2c831ee..a0b8b4a 100644
--- a/src/com/google/android/droiddriver/base/AbstractContext.java
+++ b/src/com/google/android/droiddriver/base/AbstractContext.java
@@ -16,18 +16,33 @@
package com.google.android.droiddriver.base;
+import android.app.Instrumentation;
+
import com.google.android.droiddriver.InputInjector;
/**
* Internal helper for managing all instances.
*/
public abstract class AbstractContext {
+ protected final Instrumentation instrumentation;
+ protected final AbstractDroidDriver driver;
protected final InputInjector injector;
- protected AbstractContext(InputInjector injector) {
+ protected AbstractContext(Instrumentation instrumentation, AbstractDroidDriver driver,
+ InputInjector injector) {
+ this.instrumentation = instrumentation;
+ this.driver = driver;
this.injector = injector;
}
+ public Instrumentation getInstrumentation() {
+ return instrumentation;
+ }
+
+ public AbstractDroidDriver getDriver() {
+ return driver;
+ }
+
public InputInjector getInjector() {
return injector;
}
diff --git a/src/com/google/android/droiddriver/base/AbstractDroidDriver.java b/src/com/google/android/droiddriver/base/AbstractDroidDriver.java
index ed78b46..0b0f9c6 100644
--- a/src/com/google/android/droiddriver/base/AbstractDroidDriver.java
+++ b/src/com/google/android/droiddriver/base/AbstractDroidDriver.java
@@ -16,7 +16,6 @@
package com.google.android.droiddriver.base;
-import android.app.Instrumentation;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.util.Log;
@@ -32,7 +31,6 @@ import com.google.android.droiddriver.finders.Finder;
import com.google.android.droiddriver.util.DefaultPoller;
import com.google.android.droiddriver.util.FileUtils;
import com.google.android.droiddriver.util.Logs;
-import com.google.common.base.Preconditions;
import java.io.BufferedOutputStream;
@@ -42,14 +40,9 @@ import java.io.BufferedOutputStream;
*/
public abstract class AbstractDroidDriver implements DroidDriver, Screenshotter {
- protected final Instrumentation instrumentation;
private Poller poller = new DefaultPoller();
private AbstractUiElement rootElement;
- protected AbstractDroidDriver(Instrumentation instrumentation) {
- this.instrumentation = Preconditions.checkNotNull(instrumentation);
- }
-
@Override
public UiElement find(Finder finder) {
Logs.call(this, "find", finder);
diff --git a/src/com/google/android/droiddriver/base/AbstractUiElement.java b/src/com/google/android/droiddriver/base/AbstractUiElement.java
index 65048f2..780d4ac 100644
--- a/src/com/google/android/droiddriver/base/AbstractUiElement.java
+++ b/src/com/google/android/droiddriver/base/AbstractUiElement.java
@@ -90,7 +90,6 @@ public abstract class AbstractUiElement implements UiElement {
@Override
public void setText(String text) {
- // TODO: Define common actions as a const.
perform(new TypeAction(text));
// TypeAction may not be effective immediately and reflected bygetText(),
// so the following will fail.
@@ -120,7 +119,7 @@ public abstract class AbstractUiElement implements UiElement {
@Override
public void scroll(ScrollDirection direction) {
- perform(new SwipeAction(direction, false));
+ perform(SwipeAction.toScroll(direction));
}
@Override
diff --git a/src/com/google/android/droiddriver/base/BaseUiDevice.java b/src/com/google/android/droiddriver/base/BaseUiDevice.java
new file mode 100644
index 0000000..29320b2
--- /dev/null
+++ b/src/com/google/android/droiddriver/base/BaseUiDevice.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.google.android.droiddriver.base;
+
+import android.app.Service;
+import android.os.PowerManager;
+import android.view.KeyEvent;
+
+import com.google.android.droiddriver.UiDevice;
+import com.google.android.droiddriver.actions.Action;
+import com.google.android.droiddriver.actions.PressKeyAction;
+
+/**
+ * Base implementation of {@link UiDevice}.
+ */
+public class BaseUiDevice implements UiDevice {
+ // power off may not trigger new events
+ private static final PressKeyAction POWER_OFF = new PressKeyAction(KeyEvent.KEYCODE_POWER, 0,
+ false);
+ // power on should always trigger new events
+ private static final PressKeyAction POWER_ON = new PressKeyAction(KeyEvent.KEYCODE_POWER, 1000L,
+ false);
+
+ private final AbstractContext abstractContext;
+
+ public BaseUiDevice(AbstractContext abstractContext) {
+ this.abstractContext = abstractContext;
+ }
+
+ @Override
+ public boolean isScreenOn() {
+ PowerManager pm =
+ (PowerManager) abstractContext.instrumentation.getTargetContext().getSystemService(
+ Service.POWER_SERVICE);
+ return pm.isScreenOn();
+ }
+
+ @Override
+ public void wakeUp() {
+ if (!isScreenOn()) {
+ perform(POWER_ON);
+ }
+ }
+
+ @Override
+ public void sleep() {
+ if (isScreenOn()) {
+ perform(POWER_OFF);
+ }
+ }
+
+ @Override
+ public boolean perform(Action action) {
+ return abstractContext.driver.getRootElement().perform(action);
+ }
+}
diff --git a/src/com/google/android/droiddriver/finders/XPaths.java b/src/com/google/android/droiddriver/finders/XPaths.java
index c401582..b671aca 100644
--- a/src/com/google/android/droiddriver/finders/XPaths.java
+++ b/src/com/google/android/droiddriver/finders/XPaths.java
@@ -95,6 +95,11 @@ public class XPaths {
return attr(Attribute.TEXT, value);
}
+ /** Shorthand for {@link #attr}{@code (Attribute.RESOURCE_ID, value)} */
+ public static String resourceId(String value) {
+ return attr(Attribute.RESOURCE_ID, value);
+ }
+
/**
* Adapted from http://stackoverflow.com/questions/1341847/.
* <p>
diff --git a/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java b/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
index 0c4e9dc..3ed7e20 100644
--- a/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
+++ b/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
@@ -35,8 +35,8 @@ import java.util.Map;
public class InstrumentationContext extends AbstractContext {
private final Map<View, ViewElement> map = new MapMaker().weakKeys().weakValues().makeMap();
- InstrumentationContext(final Instrumentation instrumentation) {
- super(new InputInjector() {
+ InstrumentationContext(final Instrumentation instrumentation, InstrumentationDriver driver) {
+ super(instrumentation, driver, new InputInjector() {
@Override
public boolean injectInputEvent(InputEvent event) {
if (event instanceof MotionEvent) {
diff --git a/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java b/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
index c4f1fbb..078c4bf 100644
--- a/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
+++ b/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
@@ -22,9 +22,12 @@ import android.graphics.Bitmap;
import android.os.SystemClock;
import android.view.View;
+import com.google.android.droiddriver.UiDevice;
import com.google.android.droiddriver.base.AbstractDroidDriver;
+import com.google.android.droiddriver.base.BaseUiDevice;
import com.google.android.droiddriver.exceptions.TimeoutException;
import com.google.android.droiddriver.util.ActivityUtils;
+import com.google.common.base.Preconditions;
import com.google.common.primitives.Longs;
/**
@@ -32,10 +35,13 @@ import com.google.common.primitives.Longs;
*/
public class InstrumentationDriver extends AbstractDroidDriver {
private final InstrumentationContext context;
+ private final Instrumentation instrumentation;
+ private final BaseUiDevice uiDevice;
public InstrumentationDriver(Instrumentation instrumentation) {
- super(instrumentation);
- this.context = new InstrumentationContext(instrumentation);
+ this.instrumentation = Preconditions.checkNotNull(instrumentation);
+ this.context = new InstrumentationContext(instrumentation, this);
+ uiDevice = new BaseUiDevice(context);
}
@Override
@@ -102,4 +108,9 @@ public class InstrumentationDriver extends AbstractDroidDriver {
instrumentation.runOnMainSync(screenshotRunnable);
return screenshotRunnable.screenshot;
}
+
+ @Override
+ public UiDevice getUiDevice() {
+ return uiDevice;
+ }
}
diff --git a/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
index c166c1e..779a41f 100644
--- a/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
@@ -88,6 +88,15 @@ public class DynamicSentinelStrategy extends AbstractSentinelStrategy {
@Override
public boolean isSentinelUpdated(UiElement newSentinel, UiElement oldSentinel) {
+ // If the sentinel moved, scrolling has some effect. This is both an
+ // optimization - getBounds is cheaper than find - and necessary in
+ // certain cases, e.g. user is looking for a sibling of the unique string;
+ // the scroll is close to the end therefore the unique string does not
+ // change, but the target could be revealed.
+ if (!newSentinel.getBounds().equals(oldSentinel.getBounds())) {
+ return true;
+ }
+
String newString = getUniqueStringFromSentinel(newSentinel);
// If newString is null, newSentinel must be partially shown. In this case
// we return true to allow further scrolling. But program error could also
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
index 7efa48e..57a64d5 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
@@ -16,6 +16,7 @@
package com.google.android.droiddriver.uiautomation;
+import android.app.Instrumentation;
import android.app.UiAutomation;
import android.view.InputEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -35,14 +36,14 @@ public class UiAutomationContext extends AbstractContext {
.weakValues().makeMap();
private final UiAutomation uiAutomation;
- UiAutomationContext(final UiAutomation uiAutomation) {
- super(new InputInjector() {
+ UiAutomationContext(final Instrumentation instrumentation, UiAutomationDriver driver) {
+ super(instrumentation, driver, new InputInjector() {
@Override
public boolean injectInputEvent(InputEvent event) {
- return uiAutomation.injectInputEvent(event, true /* sync */);
+ return instrumentation.getUiAutomation().injectInputEvent(event, true /* sync */);
}
});
- this.uiAutomation = uiAutomation;
+ this.uiAutomation = instrumentation.getUiAutomation();
}
public UiAutomationElement getUiElement(AccessibilityNodeInfo node) {
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java
index 7d21233..8b2baaf 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationDriver.java
@@ -16,13 +16,18 @@
package com.google.android.droiddriver.uiautomation;
-import android.app.UiAutomation;
import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
import android.graphics.Bitmap;
import android.os.SystemClock;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.google.android.droiddriver.UiDevice;
import com.google.android.droiddriver.base.AbstractDroidDriver;
+import com.google.android.droiddriver.base.BaseUiDevice;
import com.google.android.droiddriver.exceptions.TimeoutException;
import com.google.common.primitives.Longs;
@@ -40,11 +45,12 @@ public class UiAutomationDriver extends AbstractDroidDriver {
private final UiAutomationContext context;
private final UiAutomation uiAutomation;
+ private final BaseUiDevice uiDevice;
public UiAutomationDriver(Instrumentation instrumentation) {
- super(instrumentation);
this.uiAutomation = instrumentation.getUiAutomation();
- this.context = new UiAutomationContext(uiAutomation);
+ this.context = new UiAutomationContext(instrumentation, this);
+ uiDevice = new BaseUiDevice(context);
}
@Override
@@ -84,4 +90,33 @@ public class UiAutomationDriver extends AbstractDroidDriver {
protected Bitmap takeScreenshot() {
return uiAutomation.takeScreenshot();
}
+
+ /**
+ * Some widgets fail to trigger some AccessibilityEvent's after actions,
+ * resulting in stale AccessibilityNodeInfo's. As a work-around, force to
+ * clear the AccessibilityNodeInfoCache.
+ */
+ public void clearAccessibilityNodeInfoCache() {
+ uiDevice.sleep();
+ uiDevice.wakeUp();
+ }
+
+ /**
+ * {@link #clearAccessibilityNodeInfoCache} causes the screen to blink. This
+ * method clears the cache without blinking by employing an implementation
+ * detail of AccessibilityNodeInfoCache. This is a hack; use it at your own
+ * discretion.
+ */
+ public void clearAccessibilityNodeInfoCacheHack() {
+ AccessibilityManager accessibilityManager =
+ (AccessibilityManager) context.getInstrumentation().getTargetContext()
+ .getSystemService(Context.ACCESSIBILITY_SERVICE);
+ accessibilityManager.sendAccessibilityEvent(AccessibilityEvent
+ .obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
+ }
+
+ @Override
+ public UiDevice getUiDevice() {
+ return uiDevice;
+ }
}
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
index a399360..a28498c 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
@@ -189,6 +189,13 @@ public class UiAutomationElement extends AbstractUiElement {
// it is not reliable.
// Exception is ignored here. Tests will fail anyways if this is
// critical.
+ // Actions should usually trigger some AccessibilityEvent's, but some
+ // widgets fail to do so, resulting in stale AccessibilityNodeInfo's. As a
+ // work-around, force to clear the AccessibilityNodeInfoCache.
+ // A legitimate case of no AccessibilityEvent is when scrolling has
+ // reached the end, but we cannot tell whether it's legitimate or the
+ // widget has bugs, so clearAccessibilityNodeInfoCache anyways.
+ ((UiAutomationDriver) context.getDriver()).clearAccessibilityNodeInfoCacheHack();
}
}
}