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