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