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