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