• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package io.appium.droiddriver.base;
18 
19 import android.graphics.Rect;
20 import android.os.Build;
21 import android.text.TextUtils;
22 import android.view.KeyEvent;
23 
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.FutureTask;
30 
31 import io.appium.droiddriver.UiElement;
32 import io.appium.droiddriver.actions.Action;
33 import io.appium.droiddriver.actions.EventUiElementActor;
34 import io.appium.droiddriver.actions.InputInjector;
35 import io.appium.droiddriver.actions.SingleKeyAction;
36 import io.appium.droiddriver.actions.TextAction;
37 import io.appium.droiddriver.actions.UiElementActor;
38 import io.appium.droiddriver.exceptions.DroidDriverException;
39 import io.appium.droiddriver.finders.Attribute;
40 import io.appium.droiddriver.finders.Predicate;
41 import io.appium.droiddriver.finders.Predicates;
42 import io.appium.droiddriver.scroll.Direction.PhysicalDirection;
43 import io.appium.droiddriver.util.Events;
44 import io.appium.droiddriver.util.Logs;
45 import io.appium.droiddriver.util.Strings;
46 import io.appium.droiddriver.util.Strings.ToStringHelper;
47 import io.appium.droiddriver.validators.Validator;
48 
49 /**
50  * Base UiElement that implements the common operations.
51  *
52  * @param <R> the type of the raw element this class wraps, for example, View or
53  *        AccessibilityNodeInfo
54  * @param <E> the type of the concrete subclass of BaseUiElement
55  */
56 public abstract class BaseUiElement<R, E extends BaseUiElement<R, E>> implements UiElement {
57   // These two attribute names are used for debugging only.
58   // The two constants are used internally and must match to-uiautomator.xsl.
59   public static final String ATTRIB_VISIBLE_BOUNDS = "VisibleBounds";
60   public static final String ATTRIB_NOT_VISIBLE = "NotVisible";
61 
62   private UiElementActor uiElementActor = EventUiElementActor.INSTANCE;
63   private Validator validator = null;
64 
65   @SuppressWarnings("unchecked")
66   @Override
get(Attribute attribute)67   public <T> T get(Attribute attribute) {
68     return (T) getAttributes().get(attribute);
69   }
70 
71   @Override
getText()72   public String getText() {
73     return get(Attribute.TEXT);
74   }
75 
76   @Override
getContentDescription()77   public String getContentDescription() {
78     return get(Attribute.CONTENT_DESC);
79   }
80 
81   @Override
getClassName()82   public String getClassName() {
83     return get(Attribute.CLASS);
84   }
85 
86   @Override
getResourceId()87   public String getResourceId() {
88     return get(Attribute.RESOURCE_ID);
89   }
90 
91   @Override
getPackageName()92   public String getPackageName() {
93     return get(Attribute.PACKAGE);
94   }
95 
96   @Override
isCheckable()97   public boolean isCheckable() {
98     return (Boolean) get(Attribute.CHECKABLE);
99   }
100 
101   @Override
isChecked()102   public boolean isChecked() {
103     return (Boolean) get(Attribute.CHECKED);
104   }
105 
106   @Override
isClickable()107   public boolean isClickable() {
108     return (Boolean) get(Attribute.CLICKABLE);
109   }
110 
111   @Override
isEnabled()112   public boolean isEnabled() {
113     return (Boolean) get(Attribute.ENABLED);
114   }
115 
116   @Override
isFocusable()117   public boolean isFocusable() {
118     return (Boolean) get(Attribute.FOCUSABLE);
119   }
120 
121   @Override
isFocused()122   public boolean isFocused() {
123     return (Boolean) get(Attribute.FOCUSED);
124   }
125 
126   @Override
isScrollable()127   public boolean isScrollable() {
128     return (Boolean) get(Attribute.SCROLLABLE);
129   }
130 
131   @Override
isLongClickable()132   public boolean isLongClickable() {
133     return (Boolean) get(Attribute.LONG_CLICKABLE);
134   }
135 
136   @Override
isPassword()137   public boolean isPassword() {
138     return (Boolean) get(Attribute.PASSWORD);
139   }
140 
141   @Override
isSelected()142   public boolean isSelected() {
143     return (Boolean) get(Attribute.SELECTED);
144   }
145 
146   @Override
getBounds()147   public Rect getBounds() {
148     return get(Attribute.BOUNDS);
149   }
150 
151   // TODO: expose these 3 methods in UiElement?
getSelectionStart()152   public int getSelectionStart() {
153     Integer value = get(Attribute.SELECTION_START);
154     return value == null ? 0 : value;
155   }
156 
getSelectionEnd()157   public int getSelectionEnd() {
158     Integer value = get(Attribute.SELECTION_END);
159     return value == null ? 0 : value;
160   }
161 
hasSelection()162   public boolean hasSelection() {
163     final int selectionStart = getSelectionStart();
164     final int selectionEnd = getSelectionEnd();
165 
166     return selectionStart >= 0 && selectionStart != selectionEnd;
167   }
168 
169   @Override
perform(Action action)170   public boolean perform(Action action) {
171     Logs.call(this, "perform", action);
172     if (validator != null && validator.isApplicable(this, action)) {
173       String failure = validator.validate(this, action);
174       if (failure != null) {
175         throw new DroidDriverException(toString() + " failed validation: " + failure);
176       }
177     }
178 
179     // timeoutMillis <= 0 means no need to wait
180     if (action.getTimeoutMillis() <= 0) {
181       return doPerform(action);
182     }
183     return performAndWait(action);
184   }
185 
doPerform(Action action)186   protected boolean doPerform(Action action) {
187     return action.perform(this);
188   }
189 
doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis)190   protected abstract void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis);
191 
performAndWait(final Action action)192   private boolean performAndWait(final Action action) {
193     FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
194       @Override
195       public Boolean call() {
196         return doPerform(action);
197       }
198     });
199     doPerformAndWait(futureTask, action.getTimeoutMillis());
200 
201     try {
202       return futureTask.get();
203     } catch (Throwable t) {
204       throw DroidDriverException.propagate(t);
205     }
206   }
207 
208   @Override
setText(String text)209   public void setText(String text) {
210     Logs.call(this, "setText", text);
211     longClick(); // Gain focus; single click always activates IME.
212     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
213       clearText();
214     }
215 
216     if (TextUtils.isEmpty(text)) {
217       return;
218     }
219 
220     perform(new TextAction(text));
221   }
222 
clearText()223   private void clearText() {
224     String text = getText();
225     if (TextUtils.isEmpty(text)) {
226       return;
227     }
228 
229     InputInjector injector = getInjector();
230     SingleKeyAction.CTRL_MOVE_HOME.perform(injector, this);
231 
232     final long shiftDownTime = Events.keyDown(injector, KeyEvent.KEYCODE_SHIFT_LEFT, 0);
233     SingleKeyAction.CTRL_MOVE_END.perform(injector, this);
234     Events.keyUp(injector, shiftDownTime, KeyEvent.KEYCODE_SHIFT_LEFT, 0);
235     SingleKeyAction.DELETE.perform(injector, this);
236   }
237 
238   @Override
click()239   public void click() {
240     uiElementActor.click(this);
241   }
242 
243   @Override
longClick()244   public void longClick() {
245     uiElementActor.longClick(this);
246   }
247 
248   @Override
doubleClick()249   public void doubleClick() {
250     uiElementActor.doubleClick(this);
251   }
252 
253   @Override
scroll(PhysicalDirection direction)254   public void scroll(PhysicalDirection direction) {
255     uiElementActor.scroll(this, direction);
256   }
257 
getAttributes()258   protected abstract Map<Attribute, Object> getAttributes();
259 
getChildren()260   protected abstract List<E> getChildren();
261 
262   @Override
getChildren(Predicate<? super UiElement> predicate)263   public List<E> getChildren(Predicate<? super UiElement> predicate) {
264     List<E> children = getChildren();
265     if (children == null) {
266       return Collections.emptyList();
267     }
268     if (predicate == null || predicate.equals(Predicates.any())) {
269       return children;
270     }
271 
272     List<E> filteredChildren = new ArrayList<E>(children.size());
273     for (E child : children) {
274       if (predicate.apply(child)) {
275         filteredChildren.add(child);
276       }
277     }
278     return Collections.unmodifiableList(filteredChildren);
279   }
280 
281   @Override
toString()282   public String toString() {
283     ToStringHelper toStringHelper = Strings.toStringHelper(this);
284     for (Map.Entry<Attribute, Object> entry : getAttributes().entrySet()) {
285       addAttribute(toStringHelper, entry.getKey(), entry.getValue());
286     }
287     if (!isVisible()) {
288       toStringHelper.addValue(ATTRIB_NOT_VISIBLE);
289     } else if (!getVisibleBounds().equals(getBounds())) {
290       toStringHelper.add(ATTRIB_VISIBLE_BOUNDS, getVisibleBounds().toShortString());
291     }
292     return toStringHelper.toString();
293   }
294 
addAttribute(ToStringHelper toStringHelper, Attribute attr, Object value)295   private static void addAttribute(ToStringHelper toStringHelper, Attribute attr, Object value) {
296     if (value != null) {
297       if (value instanceof Boolean) {
298         if ((Boolean) value) {
299           toStringHelper.addValue(attr.getName());
300         }
301       } else if (value instanceof Rect) {
302         toStringHelper.add(attr.getName(), ((Rect) value).toShortString());
303       } else {
304         toStringHelper.add(attr.getName(), value);
305       }
306     }
307   }
308 
309   /**
310    * Gets the raw element used to create this UiElement. The attributes of this
311    * UiElement are based on a snapshot of the raw element at construction time.
312    * If the raw element is updated later, the attributes may not match.
313    */
314   // TODO: expose in UiElement?
getRawElement()315   public abstract R getRawElement();
316 
setUiElementActor(UiElementActor uiElementActor)317   public void setUiElementActor(UiElementActor uiElementActor) {
318     this.uiElementActor = uiElementActor;
319   }
320 
321   /**
322    * Sets the validator to check when {@link #perform(Action)} is called.
323    */
setValidator(Validator validator)324   public void setValidator(Validator validator) {
325     this.validator = validator;
326   }
327 }
328