1/* 2 * Copyright (C) 2013 DroidDriver committers 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.android.droiddriver.base; 18 19import com.google.android.droiddriver.InputInjector; 20import com.google.android.droiddriver.UiElement; 21import com.google.android.droiddriver.actions.Action; 22import com.google.android.droiddriver.actions.ClickAction; 23import com.google.android.droiddriver.actions.ScrollDirection; 24import com.google.android.droiddriver.actions.SwipeAction; 25import com.google.android.droiddriver.actions.TypeAction; 26import com.google.android.droiddriver.exceptions.ElementNotVisibleException; 27import com.google.android.droiddriver.finders.Attribute; 28import com.google.android.droiddriver.finders.ByXPath; 29import com.google.android.droiddriver.util.Logs; 30import com.google.common.base.Objects; 31import com.google.common.base.Objects.ToStringHelper; 32import com.google.common.base.Predicate; 33import com.google.common.base.Predicates; 34import com.google.common.collect.ImmutableList; 35 36import org.w3c.dom.Element; 37 38import java.lang.ref.WeakReference; 39import java.util.List; 40import java.util.concurrent.Callable; 41import java.util.concurrent.FutureTask; 42 43/** 44 * Abstract implementation with common methods already implemented. 45 */ 46public abstract class AbstractUiElement implements UiElement { 47 private WeakReference<Element> domNode; 48 49 @Override 50 public <T> T get(Attribute attribute) { 51 return attribute.getValue(this); 52 } 53 54 @Override 55 public boolean perform(Action action) { 56 Logs.call(this, "perform", action); 57 checkVisible(); 58 return performAndWait(action); 59 } 60 61 protected boolean doPerform(Action action) { 62 return action.perform(getInjector(), this); 63 } 64 65 protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) { 66 // ignores timeoutMillis; subclasses can override this behavior 67 futureTask.run(); 68 } 69 70 private boolean performAndWait(final Action action) { 71 // timeoutMillis <= 0 means no need to wait 72 if (action.getTimeoutMillis() <= 0) { 73 return doPerform(action); 74 } 75 76 FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() { 77 @Override 78 public Boolean call() { 79 return doPerform(action); 80 } 81 }); 82 doPerformAndWait(futureTask, action.getTimeoutMillis()); 83 try { 84 return futureTask.get(); 85 } catch (Exception e) { 86 // should not reach here b/c futureTask has run 87 return false; 88 } 89 } 90 91 @Override 92 public void setText(String text) { 93 // TODO: Define common actions as a const. 94 perform(new TypeAction(text)); 95 // TypeAction may not be effective immediately and reflected bygetText(), 96 // so the following will fail. 97 // if (Logs.DEBUG) { 98 // String actual = getText(); 99 // if (!text.equals(actual)) { 100 // throw new DroidDriverException(String.format( 101 // "setText failed: expected=\"%s\", actual=\"%s\"", text, actual)); 102 // } 103 // } 104 } 105 106 @Override 107 public void click() { 108 perform(ClickAction.SINGLE); 109 } 110 111 @Override 112 public void longClick() { 113 perform(ClickAction.LONG); 114 } 115 116 @Override 117 public void doubleClick() { 118 perform(ClickAction.DOUBLE); 119 } 120 121 @Override 122 public void scroll(ScrollDirection direction) { 123 perform(new SwipeAction(direction, false)); 124 } 125 126 @Override 127 public abstract AbstractUiElement getChild(int index); 128 129 protected abstract InputInjector getInjector(); 130 131 private void checkVisible() { 132 if (!isVisible()) { 133 throw new ElementNotVisibleException(this); 134 } 135 } 136 137 @Override 138 public List<UiElement> getChildren(Predicate<? super UiElement> predicate) { 139 predicate = Predicates.and(Predicates.notNull(), predicate); 140 // TODO: Use internal data when we take snapshot of current node tree. 141 ImmutableList.Builder<UiElement> builder = ImmutableList.builder(); 142 for (int i = 0; i < getChildCount(); i++) { 143 UiElement child = getChild(i); 144 if (predicate.apply(child)) { 145 builder.add(child); 146 } 147 } 148 return builder.build(); 149 } 150 151 @Override 152 public String toString() { 153 ToStringHelper toStringHelper = Objects.toStringHelper(this); 154 for (Attribute attr : Attribute.values()) { 155 addAttribute(toStringHelper, attr, get(attr)); 156 } 157 return toStringHelper.toString(); 158 } 159 160 private static void addAttribute(ToStringHelper toStringHelper, Attribute attr, Object value) { 161 if (value != null) { 162 if (value instanceof Boolean) { 163 if ((Boolean) value) { 164 toStringHelper.addValue(attr.getName()); 165 } 166 } else { 167 toStringHelper.add(attr.getName(), value); 168 } 169 } 170 } 171 172 /** 173 * Used internally in {@link ByXPath}. Returns the DOM node representing this 174 * UiElement. The DOM is constructed from the UiElement tree. 175 * <p> 176 * TODO: move this to {@link ByXPath}. This requires a BiMap using 177 * WeakReference for both keys and values, which is error-prone. This will be 178 * deferred until we decide whether to clear cache upon getRootElement. 179 */ 180 public Element getDomNode() { 181 if (domNode == null || domNode.get() == null) { 182 domNode = new WeakReference<Element>(ByXPath.buildDomNode(this)); 183 } 184 return domNode.get(); 185 } 186} 187