aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Jin <kjin@google.com>2013-11-05 18:05:39 -0800
committerKevin Jin <kjin@google.com>2013-11-06 10:06:48 -0800
commit9c92f46280cf3943701e75349833c68b584992e2 (patch)
treedab04f0fd3b4f9951d066066e4e3dde08161f6f4 /src
parent2131c9b43a72432c5c2ac6636433c12050141221 (diff)
downloaddroiddriver-9c92f46280cf3943701e75349833c68b584992e2.tar.gz
introduce AccessibilityEventScrollStepStrategy which is
a simple ScrollStepStrategy for UiAutomation and behaves like UiScrollable. rename SentinelScroller to StepBasedScroller Change-Id: I424140817d53c63165a66a5fffb5cae24c47288b
Diffstat (limited to 'src')
-rw-r--r--src/com/google/android/droiddriver/UiElement.java6
-rw-r--r--src/com/google/android/droiddriver/base/BaseUiElement.java2
-rw-r--r--src/com/google/android/droiddriver/helpers/ScrollerHelper.java51
-rw-r--r--src/com/google/android/droiddriver/instrumentation/ViewElement.java2
-rw-r--r--src/com/google/android/droiddriver/scroll/AbstractSentinelStrategy.java202
-rw-r--r--src/com/google/android/droiddriver/scroll/AccessibilityEventScrollStepStrategy.java91
-rw-r--r--src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java96
-rw-r--r--src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java34
-rw-r--r--src/com/google/android/droiddriver/scroll/ScrollStepStrategy.java52
-rw-r--r--src/com/google/android/droiddriver/scroll/Scrollers.java48
-rw-r--r--src/com/google/android/droiddriver/scroll/SentinelStrategy.java114
-rw-r--r--src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java11
-rw-r--r--src/com/google/android/droiddriver/scroll/StepBasedScroller.java (renamed from src/com/google/android/droiddriver/scroll/SentinelScroller.java)45
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java2
14 files changed, 474 insertions, 282 deletions
diff --git a/src/com/google/android/droiddriver/UiElement.java b/src/com/google/android/droiddriver/UiElement.java
index 1ae6254..924197f 100644
--- a/src/com/google/android/droiddriver/UiElement.java
+++ b/src/com/google/android/droiddriver/UiElement.java
@@ -19,6 +19,7 @@ package com.google.android.droiddriver;
import android.graphics.Rect;
import com.google.android.droiddriver.actions.Action;
+import com.google.android.droiddriver.actions.InputInjector;
import com.google.android.droiddriver.exceptions.ElementNotVisibleException;
import com.google.android.droiddriver.finders.Attribute;
import com.google.android.droiddriver.instrumentation.InstrumentationDriver;
@@ -225,4 +226,9 @@ public interface UiElement {
* Gets the parent.
*/
UiElement getParent();
+
+ /**
+ * Gets the {@link InputInjector} for injecting InputEvent.
+ */
+ InputInjector getInjector();
}
diff --git a/src/com/google/android/droiddriver/base/BaseUiElement.java b/src/com/google/android/droiddriver/base/BaseUiElement.java
index 6a2f88c..9cddc71 100644
--- a/src/com/google/android/droiddriver/base/BaseUiElement.java
+++ b/src/com/google/android/droiddriver/base/BaseUiElement.java
@@ -211,8 +211,6 @@ public abstract class BaseUiElement implements UiElement {
protected abstract List<? extends BaseUiElement> getChildren();
- protected abstract InputInjector getInjector();
-
private void checkVisible() {
if (!isVisible()) {
throw new ElementNotVisibleException(this);
diff --git a/src/com/google/android/droiddriver/helpers/ScrollerHelper.java b/src/com/google/android/droiddriver/helpers/ScrollerHelper.java
new file mode 100644
index 0000000..dc946e7
--- /dev/null
+++ b/src/com/google/android/droiddriver/helpers/ScrollerHelper.java
@@ -0,0 +1,51 @@
+/*
+ * 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.helpers;
+
+import com.google.android.droiddriver.DroidDriver;
+import com.google.android.droiddriver.UiElement;
+import com.google.android.droiddriver.exceptions.ElementNotFoundException;
+import com.google.android.droiddriver.finders.Finder;
+import com.google.android.droiddriver.scroll.Scroller;
+
+/**
+ * Helper for Scroller.
+ */
+public class ScrollerHelper {
+ private final DroidDriver driver;
+ private final Finder containerFinder;
+ private final Scroller scroller;
+
+ public ScrollerHelper(Scroller scroller, DroidDriver driver, Finder containerFinder) {
+ this.scroller = scroller;
+ this.driver = driver;
+ this.containerFinder = containerFinder;
+ }
+
+ public UiElement scrollTo(Finder itemFinder) {
+ return scroller.scrollTo(driver, containerFinder, itemFinder);
+ }
+
+ public boolean canScrollTo(Finder itemFinder) {
+ try {
+ scrollTo(itemFinder);
+ return true;
+ } catch (ElementNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/com/google/android/droiddriver/instrumentation/ViewElement.java b/src/com/google/android/droiddriver/instrumentation/ViewElement.java
index 186681f..070d14f 100644
--- a/src/com/google/android/droiddriver/instrumentation/ViewElement.java
+++ b/src/com/google/android/droiddriver/instrumentation/ViewElement.java
@@ -255,7 +255,7 @@ public class ViewElement extends BaseUiElement {
}
@Override
- protected InputInjector getInjector() {
+ public InputInjector getInjector() {
return context.getInjector();
}
}
diff --git a/src/com/google/android/droiddriver/scroll/AbstractSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/AbstractSentinelStrategy.java
deleted file mode 100644
index 2c339ad..0000000
--- a/src/com/google/android/droiddriver/scroll/AbstractSentinelStrategy.java
+++ /dev/null
@@ -1,202 +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 com.google.android.droiddriver.scroll;
-
-import android.util.Log;
-
-import com.google.android.droiddriver.DroidDriver;
-import com.google.android.droiddriver.UiElement;
-import com.google.android.droiddriver.exceptions.ElementNotFoundException;
-import com.google.android.droiddriver.finders.By;
-import com.google.android.droiddriver.finders.Finder;
-import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
-import com.google.android.droiddriver.scroll.Direction.LogicalDirection;
-import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
-import com.google.android.droiddriver.util.Logs;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-
-import java.util.List;
-
-/**
- * Base {@link SentinelStrategy} for common code.
- */
-public abstract class AbstractSentinelStrategy implements SentinelStrategy {
-
- /**
- * Gets sentinel based on {@link Predicate}.
- */
- public static abstract class GetStrategy {
- protected final Predicate<? super UiElement> predicate;
- protected final String description;
-
- protected GetStrategy(Predicate<? super UiElement> predicate, String description) {
- this.predicate = predicate;
- this.description = description;
- }
-
- /**
- * Gets the sentinel, which must be an immediate child of {@code container}
- * -- not a descendant. Note this could be null if {@code container} has not
- * finished updating.
- */
- public UiElement getSentinel(UiElement container) {
- return getSentinel(container.getChildren(predicate));
- }
-
- protected abstract UiElement getSentinel(List<? extends UiElement> children);
-
- @Override
- public String toString() {
- return description;
- }
- }
-
- /**
- * Decorates an existing {@link GetStrategy} by adding another
- * {@link Predicate}.
- */
- public static class MorePredicateGetStrategy extends GetStrategy {
- private final GetStrategy original;
-
- public MorePredicateGetStrategy(GetStrategy original,
- Predicate<? super UiElement> extraPredicate, String extraDescription) {
- super(Predicates.and(original.predicate, extraPredicate), extraDescription
- + original.description);
- this.original = original;
- }
-
- @Override
- protected UiElement getSentinel(List<? extends UiElement> children) {
- return original.getSentinel(children);
- }
- }
-
- /**
- * Returns the first child as the sentinel.
- */
- public static final GetStrategy FIRST_CHILD_GETTER = new GetStrategy(Predicates.alwaysTrue(),
- "FIRST_CHILD") {
- @Override
- protected UiElement getSentinel(List<? extends UiElement> children) {
- return children.isEmpty() ? null : children.get(0);
- }
- };
-
- /**
- * Returns the last child as the sentinel.
- */
- public static final GetStrategy LAST_CHILD_GETTER = new GetStrategy(Predicates.alwaysTrue(),
- "LAST_CHILD") {
- @Override
- protected UiElement getSentinel(List<? extends UiElement> children) {
- return children.isEmpty() ? null : children.get(children.size() - 1);
- }
- };
-
- /**
- * Returns the second last child as the sentinel. Useful when the activity
- * always shows the last child as an anchor (for example a footer).
- * <p>
- * Sometimes uiautomatorviewer may not show the anchor as the last child, due
- * to the reordering by layout described in {@link UiElement#getChildren}.
- * This is not a problem with UiAutomationDriver because it sees the same as
- * uiautomatorviewer does, but could be a problem with InstrumentationDriver.
- * </p>
- */
- public static final GetStrategy SECOND_LAST_CHILD_GETTER = new GetStrategy(
- Predicates.alwaysTrue(), "SECOND_LAST_CHILD") {
- @Override
- protected UiElement getSentinel(List<? extends UiElement> children) {
- return children.size() < 2 ? null : children.get(children.size() - 2);
- }
- };
-
- /**
- * Returns the second child as the sentinel. Useful when the activity shows a
- * fixed first child.
- */
- public static final GetStrategy SECOND_CHILD_GETTER = new GetStrategy(Predicates.alwaysTrue(),
- "SECOND_CHILD") {
- @Override
- protected UiElement getSentinel(List<? extends UiElement> children) {
- return children.size() <= 1 ? null : children.get(1);
- }
- };
-
- // Make sure sentinel exists in container
- private static class SentinelFinder implements Finder {
- private final GetStrategy getStrategy;
-
- public SentinelFinder(GetStrategy getStrategy) {
- this.getStrategy = getStrategy;
- }
-
- @Override
- public UiElement find(UiElement container) {
- UiElement sentinel = getStrategy.getSentinel(container);
- if (sentinel == null) {
- throw new ElementNotFoundException(this);
- }
- Logs.log(Log.INFO, "Found match: " + sentinel);
- return sentinel;
- }
-
- @Override
- public String toString() {
- return String.format("SentinelFinder{%s}", getStrategy);
- }
- }
-
- private final GetStrategy backwardGetStrategy;
- private final GetStrategy forwardGetStrategy;
- private final DirectionConverter directionConverter;
- private final SentinelFinder backwardSentinelFinder;
- private final SentinelFinder forwardSentinelFinder;
-
- public AbstractSentinelStrategy(GetStrategy backwardGetStrategy, GetStrategy forwardGetStrategy,
- DirectionConverter directionConverter) {
- this.backwardGetStrategy = backwardGetStrategy;
- this.forwardGetStrategy = forwardGetStrategy;
- this.directionConverter = directionConverter;
- this.backwardSentinelFinder = new SentinelFinder(backwardGetStrategy);
- this.forwardSentinelFinder = new SentinelFinder(forwardGetStrategy);
- }
-
- protected UiElement getSentinel(DroidDriver driver, Finder containerFinder,
- PhysicalDirection direction) {
- Logs.call(this, "getSentinel", driver, containerFinder, direction);
- Finder sentinelFinder;
- LogicalDirection logicalDirection = directionConverter.toLogicalDirection(direction);
- if (logicalDirection == LogicalDirection.BACKWARD) {
- sentinelFinder = By.chain(containerFinder, backwardSentinelFinder);
- } else {
- sentinelFinder = By.chain(containerFinder, forwardSentinelFinder);
- }
- return driver.on(sentinelFinder);
- }
-
- @Override
- public final DirectionConverter getDirectionConverter() {
- return directionConverter;
- }
-
- @Override
- public String toString() {
- return String.format("{backwardGetStrategy=%s, forwardGetStrategy=%s}", backwardGetStrategy,
- forwardGetStrategy);
- }
-}
diff --git a/src/com/google/android/droiddriver/scroll/AccessibilityEventScrollStepStrategy.java b/src/com/google/android/droiddriver/scroll/AccessibilityEventScrollStepStrategy.java
new file mode 100644
index 0000000..0333d77
--- /dev/null
+++ b/src/com/google/android/droiddriver/scroll/AccessibilityEventScrollStepStrategy.java
@@ -0,0 +1,91 @@
+/*
+ * 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.scroll;
+
+import android.app.UiAutomation;
+import android.app.UiAutomation.AccessibilityEventFilter;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.google.android.droiddriver.DroidDriver;
+import com.google.android.droiddriver.UiElement;
+import com.google.android.droiddriver.actions.SwipeAction;
+import com.google.android.droiddriver.finders.Finder;
+import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
+import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A {@link ScrollStepStrategy} that determines whether more scrolling is
+ * possible by checking the {@link AccessibilityEvent} returned by
+ * {@link android.app.UiAutomation}.
+ * <p>
+ * This implementation behaves just like the <a href=
+ * "http://developer.android.com/tools/help/uiautomator/UiScrollable.html"
+ * >UiScrollable</a> class. It may not work in all cases. For instance,
+ * sometimes {@link android.support.v4.widget.DrawerLayout} does not send
+ * correct {@link AccessibilityEvent}s after scrolling.
+ * </p>
+ */
+public class AccessibilityEventScrollStepStrategy implements ScrollStepStrategy {
+ private static final AccessibilityEventFilter SCROLL_EVENT_FILTER =
+ new AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent arg0) {
+ return (arg0.getEventType() & AccessibilityEvent.TYPE_VIEW_SCROLLED) != 0;
+ }
+ };
+
+ private final UiAutomation uiAutomation;
+ private final long scrollEventTimeoutMillis;
+ private final DirectionConverter directionConverter;
+
+ public AccessibilityEventScrollStepStrategy(UiAutomation uiAutomation,
+ long scrollEventTimeoutMillis, DirectionConverter converter) {
+ this.uiAutomation = uiAutomation;
+ this.scrollEventTimeoutMillis = scrollEventTimeoutMillis;
+ this.directionConverter = converter;
+ }
+
+ @Override
+ public boolean scroll(DroidDriver driver, Finder containerFinder,
+ final PhysicalDirection direction) {
+ final UiElement container = driver.on(containerFinder);
+ try {
+ uiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ SwipeAction.toScroll(direction).perform(container.getInjector(), container);
+ }
+ }, SCROLL_EVENT_FILTER, scrollEventTimeoutMillis);
+ } catch (TimeoutException e) {
+ // If no TYPE_VIEW_SCROLLED event, no more scrolling is possible
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public final DirectionConverter getDirectionConverter() {
+ return directionConverter;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("AccessibilityEventScrollStepStrategy{scrollEventTimeoutMillis=%d}",
+ scrollEventTimeoutMillis);
+ }
+}
diff --git a/src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java
new file mode 100644
index 0000000..709a068
--- /dev/null
+++ b/src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java
@@ -0,0 +1,96 @@
+/*
+ * 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.scroll;
+
+import android.util.Log;
+
+import com.google.android.droiddriver.DroidDriver;
+import com.google.android.droiddriver.UiElement;
+import com.google.android.droiddriver.exceptions.ElementNotFoundException;
+import com.google.android.droiddriver.finders.By;
+import com.google.android.droiddriver.finders.Finder;
+import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
+import com.google.android.droiddriver.scroll.Direction.LogicalDirection;
+import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
+import com.google.android.droiddriver.util.Logs;
+
+/**
+ * Base class for {@link SentinelStrategy}.
+ */
+public abstract class BaseSentinelStrategy implements SentinelStrategy {
+
+ // Make sure sentinel exists in container
+ private static class SentinelFinder implements Finder {
+ private final Getter getter;
+
+ public SentinelFinder(Getter getter) {
+ this.getter = getter;
+ }
+
+ @Override
+ public UiElement find(UiElement container) {
+ UiElement sentinel = getter.getSentinel(container);
+ if (sentinel == null) {
+ throw new ElementNotFoundException(this);
+ }
+ Logs.log(Log.INFO, "Found match: " + sentinel);
+ return sentinel;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SentinelFinder{%s}", getter);
+ }
+ }
+
+ private final Getter backwardGetter;
+ private final Getter forwardGetter;
+ private final DirectionConverter directionConverter;
+ private final SentinelFinder backwardSentinelFinder;
+ private final SentinelFinder forwardSentinelFinder;
+
+ protected BaseSentinelStrategy(Getter backwardGetter, Getter forwardGetter,
+ DirectionConverter directionConverter) {
+ this.backwardGetter = backwardGetter;
+ this.forwardGetter = forwardGetter;
+ this.directionConverter = directionConverter;
+ this.backwardSentinelFinder = new SentinelFinder(backwardGetter);
+ this.forwardSentinelFinder = new SentinelFinder(forwardGetter);
+ }
+
+ protected UiElement getSentinel(DroidDriver driver, Finder containerFinder,
+ PhysicalDirection direction) {
+ Logs.call(this, "getSentinel", driver, containerFinder, direction);
+ Finder sentinelFinder;
+ LogicalDirection logicalDirection = directionConverter.toLogicalDirection(direction);
+ if (logicalDirection == LogicalDirection.BACKWARD) {
+ sentinelFinder = By.chain(containerFinder, backwardSentinelFinder);
+ } else {
+ sentinelFinder = By.chain(containerFinder, forwardSentinelFinder);
+ }
+ return driver.on(sentinelFinder);
+ }
+
+ @Override
+ public final DirectionConverter getDirectionConverter() {
+ return directionConverter;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{backwardGetter=%s, forwardGetter=%s}", backwardGetter, forwardGetter);
+ }
+}
diff --git a/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
index 3ae0e50..bf8c6d3 100644
--- a/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
@@ -34,7 +34,7 @@ import com.google.common.base.Objects;
* used, which skips invisible children, or in the case of dynamic list, which
* shows more items when scrolling beyond the end.
*/
-public class DynamicSentinelStrategy extends AbstractSentinelStrategy {
+public class DynamicSentinelStrategy extends BaseSentinelStrategy {
/**
* Interface for determining whether sentinel is updated.
@@ -100,7 +100,7 @@ public class DynamicSentinelStrategy extends AbstractSentinelStrategy {
String newString = getUniqueStringFromSentinel(newSentinel);
// A legitimate case for newString being null is when newSentinel is
// partially shown. We return true to allow further scrolling. But program
- // error could also cause this, e.g. a bad choice of GetStrategy, which
+ // error could also cause this, e.g. a bad choice of Getter, which
// results in unnecessary scroll actions that have no visual effect. This
// log helps troubleshooting in the latter case.
if (newString == null) {
@@ -176,36 +176,32 @@ public class DynamicSentinelStrategy extends AbstractSentinelStrategy {
private final IsUpdatedStrategy isUpdatedStrategy;
/**
- * Constructs with {@code GetStrategy}s that decorate the given
- * {@code GetStrategy}s with {@link UiElement#VISIBLE}, and the given
- * {@code isUpdatedStrategy} and {@code directionConverter}. Be careful with
- * {@code GetStrategy}s: the sentinel after each scroll should be unique.
+ * Constructs with {@code Getter}s that decorate the given {@code Getter}s
+ * with {@link UiElement#VISIBLE}, and the given {@code isUpdatedStrategy} and
+ * {@code directionConverter}. Be careful with {@code Getter}s: the sentinel
+ * after each scroll should be unique.
*/
- public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy,
- GetStrategy backwardGetStrategy, GetStrategy forwardGetStrategy,
- DirectionConverter directionConverter) {
- super(new MorePredicateGetStrategy(backwardGetStrategy, UiElement.VISIBLE, "VISIBLE_"),
- new MorePredicateGetStrategy(forwardGetStrategy, UiElement.VISIBLE, "VISIBLE_"),
- directionConverter);
+ public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter,
+ Getter forwardGetter, DirectionConverter directionConverter) {
+ super(new MorePredicateGetter(backwardGetter, UiElement.VISIBLE, "VISIBLE_"),
+ new MorePredicateGetter(forwardGetter, UiElement.VISIBLE, "VISIBLE_"), directionConverter);
this.isUpdatedStrategy = isUpdatedStrategy;
}
/**
* Defaults to the standard {@link DirectionConverter}.
*/
- public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy,
- GetStrategy backwardGetStrategy, GetStrategy forwardGetStrategy) {
- this(isUpdatedStrategy, backwardGetStrategy, forwardGetStrategy,
- DirectionConverter.STANDARD_CONVERTER);
+ public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter,
+ Getter forwardGetter) {
+ this(isUpdatedStrategy, backwardGetter, forwardGetter, DirectionConverter.STANDARD_CONVERTER);
}
/**
* Defaults to LAST_CHILD_GETTER for forward scrolling, and the standard
* {@link DirectionConverter}.
*/
- public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy,
- GetStrategy backwardGetStrategy) {
- this(isUpdatedStrategy, backwardGetStrategy, LAST_CHILD_GETTER,
+ public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter) {
+ this(isUpdatedStrategy, backwardGetter, LAST_CHILD_GETTER,
DirectionConverter.STANDARD_CONVERTER);
}
diff --git a/src/com/google/android/droiddriver/scroll/ScrollStepStrategy.java b/src/com/google/android/droiddriver/scroll/ScrollStepStrategy.java
new file mode 100644
index 0000000..5bdeb83
--- /dev/null
+++ b/src/com/google/android/droiddriver/scroll/ScrollStepStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * 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.scroll;
+
+import com.google.android.droiddriver.DroidDriver;
+import com.google.android.droiddriver.finders.Finder;
+import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
+import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
+
+/**
+ * Interface for determining whether scrolling is possible.
+ */
+public interface ScrollStepStrategy {
+ /**
+ * Tries to scroll {@code containerFinder} in {@code direction}. Returns
+ * whether scrolling is effective.
+ *
+ * @param driver
+ * @param containerFinder Finder for the container that can scroll, for
+ * instance a ListView
+ * @param direction
+ * @return whether scrolling is effective
+ */
+ boolean scroll(DroidDriver driver, Finder containerFinder, PhysicalDirection direction);
+
+ /**
+ * Returns the {@link DirectionConverter}.
+ */
+ DirectionConverter getDirectionConverter();
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * It is recommended that this method return a description to help debugging.
+ */
+ @Override
+ String toString();
+}
diff --git a/src/com/google/android/droiddriver/scroll/Scrollers.java b/src/com/google/android/droiddriver/scroll/Scrollers.java
new file mode 100644
index 0000000..ddf9610
--- /dev/null
+++ b/src/com/google/android/droiddriver/scroll/Scrollers.java
@@ -0,0 +1,48 @@
+/*
+ * 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.scroll;
+
+import android.app.UiAutomation;
+
+import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
+
+/**
+ * Static utility methods pertaining to {@link Scroller} instances.
+ */
+public class Scrollers {
+ /**
+ * Returns a new default Scroller that works in simple cases. In complex cases
+ * you may try a {@link StepBasedScroller} with a custom
+ * {@link ScrollStepStrategy}:
+ * <ul>
+ * <li>If the Scroller is used with InstrumentationDriver,
+ * StaticSentinelStrategy may work and it's the simplest.</li>
+ * <li>Otherwise, DynamicSentinelStrategy should work in all cases, including
+ * the case of dynamic list, which shows more items when scrolling beyond the
+ * end. On the other hand, it's complex and needs more configuration.</li>
+ * </ul>
+ */
+ public static Scroller newScroller(UiAutomation uiAutomation) {
+ if (uiAutomation != null) {
+ return new StepBasedScroller(new AccessibilityEventScrollStepStrategy(uiAutomation, 1000L,
+ DirectionConverter.STANDARD_CONVERTER));
+ }
+ // TODO: A {@link Scroller} that directly jumps to the view if an
+ // InstrumentationDriver is used.
+ return new StepBasedScroller(StaticSentinelStrategy.DEFAULT);
+ }
+}
diff --git a/src/com/google/android/droiddriver/scroll/SentinelStrategy.java b/src/com/google/android/droiddriver/scroll/SentinelStrategy.java
index 33c19e3..0330844 100644
--- a/src/com/google/android/droiddriver/scroll/SentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/SentinelStrategy.java
@@ -15,38 +15,110 @@
*/
package com.google.android.droiddriver.scroll;
-import com.google.android.droiddriver.DroidDriver;
-import com.google.android.droiddriver.finders.Finder;
-import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
-import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
+import com.google.android.droiddriver.UiElement;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+import java.util.List;
/**
* Interface for determining whether scrolling is possible based on a sentinel.
*/
-public interface SentinelStrategy {
+public interface SentinelStrategy extends ScrollStepStrategy {
+
/**
- * Tries to scroll {@code containerFinder} in {@code direction}. Returns
- * whether scrolling is effective.
- *
- * @param driver
- * @param containerFinder Finder for the container that can scroll, for
- * instance a ListView
- * @param direction
- * @return whether scrolling is effective
+ * Gets sentinel based on {@link Predicate}.
*/
- boolean scroll(DroidDriver driver, Finder containerFinder, PhysicalDirection direction);
+ public static abstract class Getter {
+ protected final Predicate<? super UiElement> predicate;
+ protected final String description;
+
+ protected Getter(Predicate<? super UiElement> predicate, String description) {
+ this.predicate = predicate;
+ this.description = description;
+ }
+
+ /**
+ * Gets the sentinel, which must be an immediate child of {@code container}
+ * -- not a descendant. Note this could be null if {@code container} has not
+ * finished updating.
+ */
+ public UiElement getSentinel(UiElement container) {
+ return getSentinel(container.getChildren(predicate));
+ }
+
+ protected abstract UiElement getSentinel(List<? extends UiElement> children);
+
+ @Override
+ public String toString() {
+ return description;
+ }
+ }
/**
- * Returns the {@link DirectionConverter}.
+ * Decorates an existing {@link Getter} by adding another {@link Predicate}.
*/
- DirectionConverter getDirectionConverter();
+ public static class MorePredicateGetter extends Getter {
+ private final Getter original;
+
+ public MorePredicateGetter(Getter original, Predicate<? super UiElement> extraPredicate,
+ String extraDescription) {
+ super(Predicates.and(original.predicate, extraPredicate), extraDescription
+ + original.description);
+ this.original = original;
+ }
+ @Override
+ protected UiElement getSentinel(List<? extends UiElement> children) {
+ return original.getSentinel(children);
+ }
+ }
+
+ /**
+ * Returns the first child as the sentinel.
+ */
+ public static final Getter FIRST_CHILD_GETTER =
+ new Getter(Predicates.alwaysTrue(), "FIRST_CHILD") {
+ @Override
+ protected UiElement getSentinel(List<? extends UiElement> children) {
+ return children.isEmpty() ? null : children.get(0);
+ }
+ };
/**
- * {@inheritDoc}
- *
+ * Returns the last child as the sentinel.
+ */
+ public static final Getter LAST_CHILD_GETTER = new Getter(Predicates.alwaysTrue(), "LAST_CHILD") {
+ @Override
+ protected UiElement getSentinel(List<? extends UiElement> children) {
+ return children.isEmpty() ? null : children.get(children.size() - 1);
+ }
+ };
+ /**
+ * Returns the second last child as the sentinel. Useful when the activity
+ * always shows the last child as an anchor (for example a footer).
* <p>
- * It is recommended that this method return a description to help debugging.
+ * Sometimes uiautomatorviewer may not show the anchor as the last child, due
+ * to the reordering by layout described in {@link UiElement#getChildren}.
+ * This is not a problem with UiAutomationDriver because it sees the same as
+ * uiautomatorviewer does, but could be a problem with InstrumentationDriver.
+ * </p>
+ */
+ public static final Getter SECOND_LAST_CHILD_GETTER = new Getter(Predicates.alwaysTrue(),
+ "SECOND_LAST_CHILD") {
+ @Override
+ protected UiElement getSentinel(List<? extends UiElement> children) {
+ return children.size() < 2 ? null : children.get(children.size() - 2);
+ }
+ };
+ /**
+ * Returns the second child as the sentinel. Useful when the activity shows a
+ * fixed first child.
*/
- @Override
- String toString();
+ public static final Getter SECOND_CHILD_GETTER = new Getter(Predicates.alwaysTrue(),
+ "SECOND_CHILD") {
+ @Override
+ protected UiElement getSentinel(List<? extends UiElement> children) {
+ return children.size() <= 1 ? null : children.get(1);
+ }
+ };
}
diff --git a/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java
index 35e6f29..3393e43 100644
--- a/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java
@@ -34,18 +34,17 @@ import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
* This does not work if a child is larger than the physical size of the
* container.
*/
-public class StaticSentinelStrategy extends AbstractSentinelStrategy {
+public class StaticSentinelStrategy extends BaseSentinelStrategy {
/**
* Defaults to FIRST_CHILD_GETTER for backward scrolling, LAST_CHILD_GETTER
* for forward scrolling, and the standard {@link DirectionConverter}.
*/
- public StaticSentinelStrategy() {
- super(FIRST_CHILD_GETTER, LAST_CHILD_GETTER, DirectionConverter.STANDARD_CONVERTER);
- }
+ public static final StaticSentinelStrategy DEFAULT = new StaticSentinelStrategy(
+ FIRST_CHILD_GETTER, LAST_CHILD_GETTER, DirectionConverter.STANDARD_CONVERTER);
- public StaticSentinelStrategy(GetStrategy backwardGetStrategy, GetStrategy forwardGetStrategy,
+ public StaticSentinelStrategy(Getter backwardGetter, Getter forwardGetter,
DirectionConverter directionConverter) {
- super(backwardGetStrategy, forwardGetStrategy, directionConverter);
+ super(backwardGetter, forwardGetter, directionConverter);
}
@Override
diff --git a/src/com/google/android/droiddriver/scroll/SentinelScroller.java b/src/com/google/android/droiddriver/scroll/StepBasedScroller.java
index 273d6fa..07d7fb6 100644
--- a/src/com/google/android/droiddriver/scroll/SentinelScroller.java
+++ b/src/com/google/android/droiddriver/scroll/StepBasedScroller.java
@@ -37,19 +37,11 @@ import com.google.android.droiddriver.util.Logs;
/**
* A {@link Scroller} that looks for the desired item in the currently shown
* content of the scrollable container, otherwise scrolls the container one step
- * at a time and look again, until we cannot scroll any more. A
- * {@link SentinelStrategy} is used to determine whether more scrolling is
+ * at a time and looks again, until we cannot scroll any more. A
+ * {@link ScrollStepStrategy} is used to determine whether more scrolling is
* possible.
- * <p>
- * This implementation may not work well with InstrumentationDriver if
- * {@link UiElement#getChildren} returns children in an order different from the
- * Accessibility API. In this case you may try
- * {@link AbstractSentinelStrategy#SECOND_LAST_CHILD_GETTER}.
- * </p>
- * TODO: A {@link Scroller} that directly jumps to the item if an
- * InstrumentationDriver is used.
*/
-public class SentinelScroller implements Scroller {
+public class StepBasedScroller implements Scroller {
private static final SingleKeyAction MOVE_HOME = new SingleKeyAction(KeyEvent.KEYCODE_MOVE_HOME,
1000L, false);
@@ -57,7 +49,7 @@ public class SentinelScroller implements Scroller {
private final int maxScrolls;
private final long perScrollTimeoutMillis;
private final Axis axis;
- private final SentinelStrategy sentinelStrategy;
+ private final ScrollStepStrategy scrollStepStrategy;
private final boolean startFromBeginning;
/**
@@ -72,29 +64,21 @@ public class SentinelScroller implements Scroller {
* location and scrolling in both directions. It may not always work,
* but when it works, it is faster.
*/
- public SentinelScroller(int maxScrolls, long perScrollTimeoutMillis, Axis axis,
- SentinelStrategy sentinelStrategy, boolean startFromBeginning) {
+ public StepBasedScroller(int maxScrolls, long perScrollTimeoutMillis, Axis axis,
+ ScrollStepStrategy scrollStepStrategy, boolean startFromBeginning) {
this.maxScrolls = maxScrolls;
this.perScrollTimeoutMillis = perScrollTimeoutMillis;
this.axis = axis;
- this.sentinelStrategy = sentinelStrategy;
+ this.scrollStepStrategy = scrollStepStrategy;
this.startFromBeginning = startFromBeginning;
}
/**
- * Constructs with default not startFromBegining.
- */
- public SentinelScroller(int maxScrolls, long perScrollTimeoutMillis, Axis axis,
- SentinelStrategy sentinelStrategy) {
- this(maxScrolls, perScrollTimeoutMillis, axis, sentinelStrategy, false);
- }
-
- /**
* Constructs with default 100 maxScrolls, 1 second for
* perScrollTimeoutMillis, vertical axis, not startFromBegining.
*/
- public SentinelScroller(SentinelStrategy sentinelStrategy) {
- this(100, 1000L, Axis.VERTICAL, sentinelStrategy, false);
+ public StepBasedScroller(ScrollStepStrategy scrollStepStrategy) {
+ this(100, 1000L, Axis.VERTICAL, scrollStepStrategy, false);
}
// if scrollBack is true, scrolls back to starting location if not found, so
@@ -113,7 +97,7 @@ public class SentinelScroller implements Scroller {
return driver.getPoller()
.pollFor(driver, itemFinder, Poller.EXISTS, perScrollTimeoutMillis);
} catch (TimeoutException e) {
- if (i < maxScrolls && !sentinelStrategy.scroll(driver, containerFinder, direction)) {
+ if (i < maxScrolls && !scrollStepStrategy.scroll(driver, containerFinder, direction)) {
break;
}
}
@@ -123,9 +107,10 @@ public class SentinelScroller implements Scroller {
if (i == maxScrolls) {
// This is often a program error -- maxScrolls is a safety net; we should
// have either found itemFinder, or stopped to scroll b/c of reaching the
- // end. If maxScrolls is reasonably large, sentinelStrategy must be wrong.
- Logs.logfmt(Log.WARN, exception, "Scrolled %s %d times; sentinelStrategy=%s",
- containerFinder, maxScrolls, sentinelStrategy);
+ // end. If maxScrolls is reasonably large, ScrollStepStrategy must be
+ // wrong.
+ Logs.logfmt(Log.WARN, exception, "Scrolled %s %d times; ScrollStepStrategy=%s",
+ containerFinder, maxScrolls, scrollStepStrategy);
}
if (scrollBack) {
@@ -145,7 +130,7 @@ public class SentinelScroller implements Scroller {
@Override
public UiElement scrollTo(DroidDriver driver, Finder containerFinder, Finder itemFinder) {
Logs.call(this, "scrollTo", driver, containerFinder, itemFinder);
- DirectionConverter converter = sentinelStrategy.getDirectionConverter();
+ DirectionConverter converter = scrollStepStrategy.getDirectionConverter();
PhysicalDirection backwardDirection = converter.toPhysicalDirection(axis, BACKWARD);
if (startFromBeginning) {
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
index f80f33d..7fc5056 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
@@ -168,7 +168,7 @@ public class UiAutomationElement extends BaseUiElement {
}
@Override
- protected InputInjector getInjector() {
+ public InputInjector getInjector() {
return context.getInjector();
}