| /* |
| * 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(); |
| } |
| } |