/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.appium.android.bootstrap; import android.graphics.Rect; import android.view.MotionEvent.PointerCoords; import android.view.accessibility.AccessibilityNodeInfo; import com.android.uiautomator.core.Configurator; import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiSelector; import io.appium.android.bootstrap.exceptions.InvalidCoordinatesException; import io.appium.android.bootstrap.exceptions.NoAttributeFoundException; import io.appium.android.bootstrap.utils.Point; import io.appium.android.bootstrap.utils.UnicodeEncoder; import static io.appium.android.bootstrap.utils.ReflectionUtils.invoke; import static io.appium.android.bootstrap.utils.ReflectionUtils.method; import static io.appium.android.bootstrap.utils.API.API_18; /** * Proxy class for UiObject. * */ public class AndroidElement { private final UiObject el; private String id; AndroidElement(final String id, final UiObject el) { this.el = el; this.id = id; } public AndroidElement(final UiObject uiObj) { el = uiObj; } public void clearText() throws UiObjectNotFoundException { el.clearTextField(); } public boolean click() throws UiObjectNotFoundException { return el.click(); } public boolean exists() { return el.exists(); } public boolean dragTo(final int destX, final int destY, final int steps) throws UiObjectNotFoundException { if (API_18) { return el.dragTo(destX, destY, steps); } else { Logger.error("Device does not support API >= 18!"); return false; } } public boolean dragTo(final UiObject destObj, final int steps) throws UiObjectNotFoundException { if (API_18) { return el.dragTo(destObj, steps); } else { Logger.error("Device does not support API >= 18!"); return false; } } public Point getAbsolutePosition(final Point point) throws UiObjectNotFoundException, InvalidCoordinatesException { final Rect rect = this.getBounds(); Logger.debug("Element bounds: " + rect.toShortString()); return PositionHelper.getAbsolutePosition(point, rect, new Point(rect.left, rect.top), false); } public boolean getBoolAttribute(final String attr) throws UiObjectNotFoundException, NoAttributeFoundException { boolean res; if (attr.equals("enabled")) { res = el.isEnabled(); } else if (attr.equals("checkable")) { res = el.isCheckable(); } else if (attr.equals("checked")) { res = el.isChecked(); } else if (attr.equals("clickable")) { res = el.isClickable(); } else if (attr.equals("focusable")) { res = el.isFocusable(); } else if (attr.equals("focused")) { res = el.isFocused(); } else if (attr.equals("longClickable")) { res = el.isLongClickable(); } else if (attr.equals("scrollable")) { res = el.isScrollable(); } else if (attr.equals("selected")) { res = el.isSelected(); } else if (attr.equals("displayed")) { res = el.exists(); } else { throw new NoAttributeFoundException(attr); } return res; } public Rect getBounds() throws UiObjectNotFoundException { return el.getBounds(); } public UiObject getChild(final UiSelector sel) throws UiObjectNotFoundException { return el.getChild(sel); } public String getClassName() throws UiObjectNotFoundException { if (API_18) { return el.getClassName(); } else { Logger.error("Device does not support API >= 18!"); return ""; } } public String getResourceId() throws UiObjectNotFoundException { String resourceId = ""; if (!API_18) { Logger.error("Device does not support API >= 18!"); return resourceId; } try { /* * Unfortunately UiObject does not implement a getResourceId method. * There is currently no way to determine the resource-id of a given * element represented by UiObject. Until this support is added to * UiAutomater, we try to match the implementation pattern that is * already used by UiObject for getting attributes using reflection. * The returned string matches exactly what is displayed in the * UiAutomater inspector. */ AccessibilityNodeInfo node = (AccessibilityNodeInfo) invoke( method(el.getClass(), "findAccessibilityNodeInfo", long.class), el, Configurator.getInstance().getWaitForSelectorTimeout()); if (node == null) { throw new UiObjectNotFoundException(el.getSelector().toString()); } resourceId = node.getViewIdResourceName(); } catch (final Exception e) { Logger.error("Exception: " + e + " (" + e.getMessage() + ")"); } return resourceId; } public String getContentDesc() throws UiObjectNotFoundException { return el.getContentDescription(); } public String getId() { return id; } public String getStringAttribute(final String attr) throws UiObjectNotFoundException, NoAttributeFoundException { String res; if (attr.equals("name")) { res = getContentDesc(); if (res.equals("")) { res = getText(); } } else if (attr.equals("contentDescription")) { res = getContentDesc(); } else if (attr.equals("text")) { res = getText(); } else if (attr.equals("className")) { res = getClassName(); } else if (attr.equals("resourceId")) { res = getResourceId(); } else { throw new NoAttributeFoundException(attr); } return res; } public String getText() throws UiObjectNotFoundException { return el.getText(); } public UiObject getUiObject() { return el; } public Rect getVisibleBounds() throws UiObjectNotFoundException { return el.getVisibleBounds(); } public boolean longClick() throws UiObjectNotFoundException { return el.longClick(); } public boolean pinchIn(final int percent, final int steps) throws UiObjectNotFoundException { if (API_18) { return el.pinchIn(percent, steps); } else { Logger.error("Device does not support API >= 18!"); return false; } } public boolean pinchOut(final int percent, final int steps) throws UiObjectNotFoundException { if (API_18) { return el.pinchOut(percent, steps); } else { Logger.error("Device does not support API >= 18!"); return false; } } public void setId(final String id) { this.id = id; } public boolean setText(final String text) throws UiObjectNotFoundException { return setText(text, false); } public boolean setText(final String text, boolean unicodeKeyboard) throws UiObjectNotFoundException { if (unicodeKeyboard && UnicodeEncoder.needsEncoding(text)) { Logger.debug("Sending Unicode text to element: " + text); String encodedText = UnicodeEncoder.encode(text); Logger.debug("Encoded text: " + encodedText); return el.setText(encodedText); } else { Logger.debug("Sending plain text to element: " + text); return el.setText(text); } } public boolean performMultiPointerGesture(PointerCoords[] ...touches) { try { if (API_18) { // The compile-time SDK expects the wrong arguments, but the runtime // version in the emulator is correct. So we cannot do: // `return el.performMultiPointerGesture(touches);` // Instead we need to use Reflection to do it all at runtime. return (Boolean) invoke(method(el.getClass(), "performMultiPointerGesture", PointerCoords[][].class), el, (Object)touches); } else { Logger.error("Device does not support API < 18!"); return false; } } catch (final Exception e) { Logger.error("Exception: " + e + " (" + e.getMessage() + ")"); return false; } } }