blob: 65048f2048097cc319f9594953c942ce3d9f687b [file] [log] [blame]
/*
* 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 com.google.android.droiddriver.InputInjector;
import com.google.android.droiddriver.UiElement;
import com.google.android.droiddriver.actions.Action;
import com.google.android.droiddriver.actions.ClickAction;
import com.google.android.droiddriver.actions.ScrollDirection;
import com.google.android.droiddriver.actions.SwipeAction;
import com.google.android.droiddriver.actions.TypeAction;
import com.google.android.droiddriver.exceptions.ElementNotVisibleException;
import com.google.android.droiddriver.finders.Attribute;
import com.google.android.droiddriver.finders.ByXPath;
import com.google.android.droiddriver.util.Logs;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import org.w3c.dom.Element;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Abstract implementation with common methods already implemented.
*/
public abstract class AbstractUiElement implements UiElement {
private WeakReference<Element> domNode;
@Override
public <T> T get(Attribute attribute) {
return attribute.getValue(this);
}
@Override
public boolean perform(Action action) {
Logs.call(this, "perform", action);
checkVisible();
return performAndWait(action);
}
protected boolean doPerform(Action action) {
return action.perform(getInjector(), this);
}
protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) {
// ignores timeoutMillis; subclasses can override this behavior
futureTask.run();
}
private boolean performAndWait(final Action action) {
// timeoutMillis <= 0 means no need to wait
if (action.getTimeoutMillis() <= 0) {
return doPerform(action);
}
FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() {
return doPerform(action);
}
});
doPerformAndWait(futureTask, action.getTimeoutMillis());
try {
return futureTask.get();
} catch (Exception e) {
// should not reach here b/c futureTask has run
return false;
}
}
@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.
// if (Logs.DEBUG) {
// String actual = getText();
// if (!text.equals(actual)) {
// throw new DroidDriverException(String.format(
// "setText failed: expected=\"%s\", actual=\"%s\"", text, actual));
// }
// }
}
@Override
public void click() {
perform(ClickAction.SINGLE);
}
@Override
public void longClick() {
perform(ClickAction.LONG);
}
@Override
public void doubleClick() {
perform(ClickAction.DOUBLE);
}
@Override
public void scroll(ScrollDirection direction) {
perform(new SwipeAction(direction, false));
}
@Override
public abstract AbstractUiElement getChild(int index);
protected abstract InputInjector getInjector();
private void checkVisible() {
if (!isVisible()) {
throw new ElementNotVisibleException(this);
}
}
@Override
public List<UiElement> getChildren(Predicate<? super UiElement> predicate) {
predicate = Predicates.and(Predicates.notNull(), predicate);
// TODO: Use internal data when we take snapshot of current node tree.
ImmutableList.Builder<UiElement> builder = ImmutableList.builder();
for (int i = 0; i < getChildCount(); i++) {
UiElement child = getChild(i);
if (predicate.apply(child)) {
builder.add(child);
}
}
return builder.build();
}
@Override
public String toString() {
ToStringHelper toStringHelper = Objects.toStringHelper(this);
for (Attribute attr : Attribute.values()) {
addAttribute(toStringHelper, attr, get(attr));
}
return toStringHelper.toString();
}
private static void addAttribute(ToStringHelper toStringHelper, Attribute attr, Object value) {
if (value != null) {
if (value instanceof Boolean) {
if ((Boolean) value) {
toStringHelper.addValue(attr.getName());
}
} else {
toStringHelper.add(attr.getName(), value);
}
}
}
/**
* Used internally in {@link ByXPath}. Returns the DOM node representing this
* UiElement. The DOM is constructed from the UiElement tree.
* <p>
* TODO: move this to {@link ByXPath}. This requires a BiMap using
* WeakReference for both keys and values, which is error-prone. This will be
* deferred until we decide whether to clear cache upon getRootElement.
*/
public Element getDomNode() {
if (domNode == null || domNode.get() == null) {
domNode = new WeakReference<Element>(ByXPath.buildDomNode(this));
}
return domNode.get();
}
}