157e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le/* 21194ec356a16f3c6dcf408289e36e42c149d6dc8Kevin Jin * Copyright (C) 2013 DroidDriver committers 357e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * 457e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * Licensed under the Apache License, Version 2.0 (the "License"); 557e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * you may not use this file except in compliance with the License. 657e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * You may obtain a copy of the License at 757e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * 857e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * http://www.apache.org/licenses/LICENSE-2.0 957e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * 1057e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * Unless required by applicable law or agreed to in writing, software 1157e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * distributed under the License is distributed on an "AS IS" BASIS, 1257e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1357e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * See the License for the specific language governing permissions and 1457e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le * limitations under the License. 1557e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le */ 1657e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le 174b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinpackage io.appium.droiddriver.uiautomation; 1874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 192721cd91087f866e6a4c9d1d62221a36c31c4cd6Kevin Jinimport android.annotation.TargetApi; 2074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.app.UiAutomation; 2174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.app.UiAutomation.AccessibilityEventFilter; 2274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.graphics.Rect; 2374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport android.view.accessibility.AccessibilityEvent; 24646e91a139ecd447d23c7d604aed96ee306ce7edKevin Jinimport android.view.accessibility.AccessibilityNodeInfo; 25646e91a139ecd447d23c7d604aed96ee306ce7edKevin Jin 2674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.ArrayList; 2774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.Collections; 2874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.EnumMap; 2974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.List; 3074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.Map; 3174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.concurrent.FutureTask; 3274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport java.util.concurrent.TimeoutException; 3321a0001e2426644dd68e6140b5873ebaeafcc3dcKevin Jin 344b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.actions.InputInjector; 354b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.base.BaseUiElement; 364b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.finders.Attribute; 374b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.uiautomation.UiAutomationContext.UiAutomationCallable; 384b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport io.appium.droiddriver.util.Preconditions; 394b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jin 404b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jinimport static io.appium.droiddriver.util.Strings.charSequenceToString; 414b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jin 4257e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le/** 4374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * A UiElement that gets attributes via the Accessibility API. 4457e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le */ 452721cd91087f866e6a4c9d1d62221a36c31c4cd6Kevin Jin@TargetApi(18) 4674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinpublic class UiAutomationElement extends BaseUiElement<AccessibilityNodeInfo, UiAutomationElement> { 4774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private static final AccessibilityEventFilter ANY_EVENT_FILTER = new AccessibilityEventFilter() { 4874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 4974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public boolean accept(AccessibilityEvent arg0) { 5074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return true; 5174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 5274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin }; 5374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 5474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final AccessibilityNodeInfo node; 5574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final UiAutomationContext context; 5674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final Map<Attribute, Object> attributes; 5774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final boolean visible; 5874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final Rect visibleBounds; 5974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final UiAutomationElement parent; 6074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private final List<UiAutomationElement> children; 6174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 6274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin /** 6374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * A snapshot of all attributes is taken at construction. The attributes of a 6474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * {@code UiAutomationElement} instance are immutable. If the underlying 6574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * {@link AccessibilityNodeInfo} is updated, a new {@code UiAutomationElement} 6674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * instance will be created in 674b31201b5a2dbf8036da5a8d089a68a39cc1dc44Kevin Jin * {@link io.appium.droiddriver.DroidDriver#refreshUiElementTree}. 6874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin */ 6974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin protected UiAutomationElement(UiAutomationContext context, AccessibilityNodeInfo node, 70dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin UiAutomationElement parent) { 7174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin this.node = Preconditions.checkNotNull(node); 7274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin this.context = Preconditions.checkNotNull(context); 7374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin this.parent = parent; 7474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 7574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin Map<Attribute, Object> attribs = new EnumMap<Attribute, Object>(Attribute.class); 7674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.PACKAGE, charSequenceToString(node.getPackageName())); 7774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.CLASS, charSequenceToString(node.getClassName())); 7874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.TEXT, charSequenceToString(node.getText())); 7974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.CONTENT_DESC, charSequenceToString(node.getContentDescription())); 8074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.RESOURCE_ID, charSequenceToString(node.getViewIdResourceName())); 8174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.CHECKABLE, node.isCheckable()); 8274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.CHECKED, node.isChecked()); 8374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.CLICKABLE, node.isClickable()); 8474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.ENABLED, node.isEnabled()); 8574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.FOCUSABLE, node.isFocusable()); 8674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.FOCUSED, node.isFocused()); 8774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.LONG_CLICKABLE, node.isLongClickable()); 8874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.PASSWORD, node.isPassword()); 8974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.SCROLLABLE, node.isScrollable()); 9074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin if (node.getTextSelectionStart() >= 0 9174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin && node.getTextSelectionStart() != node.getTextSelectionEnd()) { 9274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin attribs.put(Attribute.SELECTION_START, node.getTextSelectionStart()); 9374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin attribs.put(Attribute.SELECTION_END, node.getTextSelectionEnd()); 9474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 9574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.SELECTED, node.isSelected()); 9674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin put(attribs, Attribute.BOUNDS, getBounds(node)); 9774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin attributes = Collections.unmodifiableMap(attribs); 9874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 9974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // Order matters as getVisibleBounds depends on visible 10074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin visible = node.isVisibleToUser(); 10174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin visibleBounds = getVisibleBounds(node); 10274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin List<UiAutomationElement> mutableChildren = buildChildren(node); 10374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin this.children = mutableChildren == null ? null : Collections.unmodifiableList(mutableChildren); 10474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 10574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 10674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private void put(Map<Attribute, Object> attribs, Attribute key, Object value) { 10774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin if (value != null) { 10874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin attribs.put(key, value); 10974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 11074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 11174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 11274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private List<UiAutomationElement> buildChildren(AccessibilityNodeInfo node) { 11374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin List<UiAutomationElement> children; 11474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin int childCount = node.getChildCount(); 11574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin if (childCount == 0) { 11674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin children = null; 11774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } else { 11874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin children = new ArrayList<UiAutomationElement>(childCount); 11974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin for (int i = 0; i < childCount; i++) { 12074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin AccessibilityNodeInfo child = node.getChild(i); 12174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin if (child != null) { 12274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin children.add(context.getElement(child, this)); 12374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 12474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 12574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 12674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return children; 12774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 12874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 12974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private Rect getBounds(AccessibilityNodeInfo node) { 13074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin Rect rect = new Rect(); 13174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin node.getBoundsInScreen(rect); 13274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return rect; 13374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 13474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 13574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin private Rect getVisibleBounds(AccessibilityNodeInfo node) { 13674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin if (!visible) { 13774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return new Rect(); 13874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 13974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin Rect visibleBounds = getBounds(); 14074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin UiAutomationElement parent = getParent(); 14174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin Rect parentBounds; 14274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin while (parent != null) { 14374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin parentBounds = parent.getBounds(); 14474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin visibleBounds.intersect(parentBounds); 14574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin parent = parent.getParent(); 14674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 14774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return visibleBounds; 14874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 14974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 15074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 15174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public Rect getVisibleBounds() { 15274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return visibleBounds; 15374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 15474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 15574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 15674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public boolean isVisible() { 15774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return visible; 15874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 15974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 16074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 16174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public UiAutomationElement getParent() { 16274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return parent; 16374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 16474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 16574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 16674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin protected List<UiAutomationElement> getChildren() { 16774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return children; 16874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 16974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 17074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 17174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin protected Map<Attribute, Object> getAttributes() { 17274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return attributes; 17374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 17474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 17574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 17674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public InputInjector getInjector() { 17774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return context.getDriver().getInjector(); 17874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 17974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 18074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin /** 18174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * Note: This implementation of {@code doPerformAndWait} clears the 18274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin * {@code AccessibilityEvent} queue. 18374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin */ 18474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 18574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin protected void doPerformAndWait(final FutureTask<Boolean> futureTask, final long timeoutMillis) { 18674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin context.callUiAutomation(new UiAutomationCallable<Void>() { 18774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 18874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 18974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public Void call(UiAutomation uiAutomation) { 19074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin try { 19174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin uiAutomation.executeAndWaitForEvent(futureTask, ANY_EVENT_FILTER, timeoutMillis); 19274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } catch (TimeoutException e) { 19374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // This is for sync'ing with Accessibility API on best-effort because 19474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // it is not reliable. 19574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // Exception is ignored here. Tests will fail anyways if this is 19674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // critical. 19774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // Actions should usually trigger some AccessibilityEvent's, but some 19874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // widgets fail to do so, resulting in stale AccessibilityNodeInfo's. 19974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // As a work-around, force to clear the AccessibilityNodeInfoCache. 20074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // A legitimate case of no AccessibilityEvent is when scrolling has 20174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // reached the end, but we cannot tell whether it's legitimate or the 20274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin // widget has bugs, so clearAccessibilityNodeInfoCache anyways. 203988386cd9cc46bf5399846a414c09e0af48b1e5aKevin Jin context.getDriver().clearAccessibilityNodeInfoCache(); 20474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 20574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return null; 20674676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 20774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 20874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin }); 20974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin } 21074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin 21174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin @Override 21274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin public AccessibilityNodeInfo getRawElement() { 21374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin return node; 21421a0001e2426644dd68e6140b5873ebaeafcc3dcKevin Jin } 21557e46577852ffa1dde4662f6018f7fbcfacb6148Thanh Le} 216