1cddda72410c992a12db61cef26713b498e31fea4Thanh Le/*
2cddda72410c992a12db61cef26713b498e31fea4Thanh Le * Copyright (C) 2013 DroidDriver committers
3cddda72410c992a12db61cef26713b498e31fea4Thanh Le *
4cddda72410c992a12db61cef26713b498e31fea4Thanh Le * Licensed under the Apache License, Version 2.0 (the "License");
5cddda72410c992a12db61cef26713b498e31fea4Thanh Le * you may not use this file except in compliance with the License.
6cddda72410c992a12db61cef26713b498e31fea4Thanh Le * You may obtain a copy of the License at
7cddda72410c992a12db61cef26713b498e31fea4Thanh Le *
8cddda72410c992a12db61cef26713b498e31fea4Thanh Le *      http://www.apache.org/licenses/LICENSE-2.0
9cddda72410c992a12db61cef26713b498e31fea4Thanh Le *
10cddda72410c992a12db61cef26713b498e31fea4Thanh Le * Unless required by applicable law or agreed to in writing, software
11cddda72410c992a12db61cef26713b498e31fea4Thanh Le * distributed under the License is distributed on an "AS IS" BASIS,
12cddda72410c992a12db61cef26713b498e31fea4Thanh Le * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cddda72410c992a12db61cef26713b498e31fea4Thanh Le * See the License for the specific language governing permissions and
14cddda72410c992a12db61cef26713b498e31fea4Thanh Le * limitations under the License.
15cddda72410c992a12db61cef26713b498e31fea4Thanh Le */
16cddda72410c992a12db61cef26713b498e31fea4Thanh Le
17cddda72410c992a12db61cef26713b498e31fea4Thanh Lepackage com.google.android.droiddriver.instrumentation;
18cddda72410c992a12db61cef26713b498e31fea4Thanh Le
1917342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport static com.google.android.droiddriver.util.Strings.charSequenceToString;
2007704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin
2107704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jinimport android.content.res.Resources;
22cddda72410c992a12db61cef26713b498e31fea4Thanh Leimport android.graphics.Rect;
23cddda72410c992a12db61cef26713b498e31fea4Thanh Leimport android.view.View;
24cddda72410c992a12db61cef26713b498e31fea4Thanh Leimport android.view.ViewGroup;
25394b364bfa602c0bbe813229ad099e8075a20c9dKevin Jinimport android.view.accessibility.AccessibilityNodeInfo;
2607704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jinimport android.widget.Checkable;
27cddda72410c992a12db61cef26713b498e31fea4Thanh Leimport android.widget.TextView;
28cddda72410c992a12db61cef26713b498e31fea4Thanh Le
29f50519233078e65a056cff49d7b4989d57c3e750Kevin Jinimport com.google.android.droiddriver.actions.InputInjector;
30f50519233078e65a056cff49d7b4989d57c3e750Kevin Jinimport com.google.android.droiddriver.base.BaseUiElement;
3174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinimport com.google.android.droiddriver.base.DroidDriverContext;
32dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jinimport com.google.android.droiddriver.exceptions.DroidDriverException;
33dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jinimport com.google.android.droiddriver.finders.Attribute;
3417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport com.google.android.droiddriver.util.Preconditions;
359f81086b618594cb080adf33548edd0c999e388eKevin Jin
3617342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport java.util.ArrayList;
3717342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport java.util.Collections;
3817342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport java.util.EnumMap;
3917342a5115d7575d44a99fed9c7032e3ab316dccKevin Jinimport java.util.HashMap;
40dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jinimport java.util.List;
419f81086b618594cb080adf33548edd0c999e388eKevin Jinimport java.util.Map;
428d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jinimport java.util.concurrent.FutureTask;
4352107c27b6b0f2b0fdfec995784c73746bb95c4eKevin Jin
44cddda72410c992a12db61cef26713b498e31fea4Thanh Le/**
4507401c162d1957a497ab08937f1188cc602f29c6Kevin Jin * A UiElement that is backed by a View.
46cddda72410c992a12db61cef26713b498e31fea4Thanh Le */
4774676fdd3c8a9e599eddd13bea56898674d9916aKevin Jinpublic class ViewElement extends BaseUiElement<View, ViewElement> {
48dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  private static class SnapshotViewAttributesRunnable implements Runnable {
49dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private final View view;
5017342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    final Map<Attribute, Object> attribs = new EnumMap<Attribute, Object>(Attribute.class);
51dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    boolean visible;
52dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    Rect visibleBounds;
53dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    List<View> childViews;
54dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    Throwable exception;
559f81086b618594cb080adf33548edd0c999e388eKevin Jin
56dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private SnapshotViewAttributesRunnable(View view) {
57dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      this.view = view;
58dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
59dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
60dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    @Override
61dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    public void run() {
62dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      try {
63dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.PACKAGE, view.getContext().getPackageName());
64dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.CLASS, getClassName());
65dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.TEXT, getText());
66dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.CONTENT_DESC, charSequenceToString(view.getContentDescription()));
67dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.RESOURCE_ID, getResourceId());
68dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.CHECKABLE, view instanceof Checkable);
69dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.CHECKED, isChecked());
70dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.CLICKABLE, view.isClickable());
71dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.ENABLED, view.isEnabled());
72dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.FOCUSABLE, view.isFocusable());
73dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.FOCUSED, view.isFocused());
74dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.LONG_CLICKABLE, view.isLongClickable());
75dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.PASSWORD, isPassword());
76dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.SCROLLABLE, isScrollable());
775c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin        if (view instanceof TextView) {
785c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin          TextView textView = (TextView) view;
795c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin          if (textView.hasSelection()) {
805c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin            attribs.put(Attribute.SELECTION_START, textView.getSelectionStart());
815c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin            attribs.put(Attribute.SELECTION_END, textView.getSelectionEnd());
825c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin          }
835c9d7f7e2db9c7dd52543e455abff0449e21a90bKevin Jin        }
84dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.SELECTED, view.isSelected());
85dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        put(Attribute.BOUNDS, getBounds());
86dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
87dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        // Order matters as setVisible() depends on setVisibleBounds().
88dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        this.visibleBounds = getVisibleBounds();
89dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        // isShown() checks the visibility flag of this view and ancestors; it
90dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        // needs to have the VISIBLE flag as well as non-empty bounds to be
91dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        // visible.
92dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        this.visible = view.isShown() && !visibleBounds.isEmpty();
93dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        setChildViews();
94dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      } catch (Throwable e) {
95dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        exception = e;
96dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
97dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
98dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
99dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private void put(Attribute key, Object value) {
100dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      if (value != null) {
101dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        attribs.put(key, value);
102dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
103dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
104dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
105dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private String getText() {
106dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      if (!(view instanceof TextView)) {
107dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        return null;
108dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
109dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return charSequenceToString(((TextView) view).getText());
110dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
111dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
112dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private String getClassName() {
113dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      String className = view.getClass().getName();
114dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return CLASS_NAME_OVERRIDES.containsKey(className) ? CLASS_NAME_OVERRIDES.get(className)
115dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin          : className;
116dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
117dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
118dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private String getResourceId() {
119dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      if (view.getId() != View.NO_ID && view.getResources() != null) {
120dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        try {
121dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin          return charSequenceToString(view.getResources().getResourceName(view.getId()));
122dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        } catch (Resources.NotFoundException nfe) {
123dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin          /* ignore */
124dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        }
125dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
126dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return null;
127dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
128dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
129dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private boolean isChecked() {
130dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return view instanceof Checkable && ((Checkable) view).isChecked();
131dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
132dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
133dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private boolean isScrollable() {
134dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      // TODO: find a meaningful implementation
135dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return true;
136dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
137dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
138dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private boolean isPassword() {
139dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      // TODO: find a meaningful implementation
140dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return false;
141dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
142dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
143dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private Rect getBounds() {
144dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      Rect rect = new Rect();
145dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      int[] xy = new int[2];
146dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      view.getLocationOnScreen(xy);
147dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      rect.set(xy[0], xy[1], xy[0] + view.getWidth(), xy[1] + view.getHeight());
148dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return rect;
149dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
150dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
151dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private Rect getVisibleBounds() {
152dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      Rect visibleBounds = new Rect();
153dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      if (!view.isShown() || !view.getGlobalVisibleRect(visibleBounds)) {
154dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        visibleBounds.setEmpty();
155dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
156dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      int[] xyScreen = new int[2];
157dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      view.getLocationOnScreen(xyScreen);
158dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      int[] xyWindow = new int[2];
159dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      view.getLocationInWindow(xyWindow);
160dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      int windowLeft = xyScreen[0] - xyWindow[0];
161dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      int windowTop = xyScreen[1] - xyWindow[1];
162dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
163dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      // Bounds are relative to root view; adjust to screen coordinates.
164dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      visibleBounds.offset(windowLeft, windowTop);
165dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      return visibleBounds;
166dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
167dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
168dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    private void setChildViews() {
169dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      if (!(view instanceof ViewGroup)) {
170dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        return;
171dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
172dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      ViewGroup group = (ViewGroup) view;
173dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      int childCount = group.getChildCount();
17417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin      childViews = new ArrayList<View>(childCount);
175dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      for (int i = 0; i < childCount; i++) {
176dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        View child = group.getChildAt(i);
177dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        if (child != null) {
178dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin          childViews.add(child);
179dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin        }
180dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      }
181dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    }
182dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  }
183dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin
18417342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin  private static final Map<String, String> CLASS_NAME_OVERRIDES = new HashMap<String, String>();
1859f81086b618594cb080adf33548edd0c999e388eKevin Jin
1869f81086b618594cb080adf33548edd0c999e388eKevin Jin  /**
1879f81086b618594cb080adf33548edd0c999e388eKevin Jin   * Typically users find the class name to use in tests using SDK tool
1889f81086b618594cb080adf33548edd0c999e388eKevin Jin   * uiautomatorviewer. This name is returned by
1899f81086b618594cb080adf33548edd0c999e388eKevin Jin   * {@link AccessibilityNodeInfo#getClassName}. If the app uses custom View
1909f81086b618594cb080adf33548edd0c999e388eKevin Jin   * classes that do not call {@link AccessibilityNodeInfo#setClassName} with
1919f81086b618594cb080adf33548edd0c999e388eKevin Jin   * the actual class name, different types of drivers see different class names
1929f81086b618594cb080adf33548edd0c999e388eKevin Jin   * (InstrumentationDriver sees the actual class name, while UiAutomationDriver
1939f81086b618594cb080adf33548edd0c999e388eKevin Jin   * sees {@link AccessibilityNodeInfo#getClassName}).
1949f81086b618594cb080adf33548edd0c999e388eKevin Jin   * <p>
1959f81086b618594cb080adf33548edd0c999e388eKevin Jin   * If tests fail with InstrumentationDriver, find the actual class name by
1969f81086b618594cb080adf33548edd0c999e388eKevin Jin   * examining app code or by calling
1979f81086b618594cb080adf33548edd0c999e388eKevin Jin   * {@link com.google.android.droiddriver.DroidDriver#dumpUiElementTree}, then
1989f81086b618594cb080adf33548edd0c999e388eKevin Jin   * call this method in setUp to override it with the class name seen in
1999f81086b618594cb080adf33548edd0c999e388eKevin Jin   * uiautomatorviewer.
2005cf5f03c64b65b1f1ecd2140b8d6605ac05b6199Kevin Jin   * </p>
2015cf5f03c64b65b1f1ecd2140b8d6605ac05b6199Kevin Jin   * A better solution is to use resource-id instead of classname, which is an
2025cf5f03c64b65b1f1ecd2140b8d6605ac05b6199Kevin Jin   * implementation detail and subject to change.
2039f81086b618594cb080adf33548edd0c999e388eKevin Jin   */
2049f81086b618594cb080adf33548edd0c999e388eKevin Jin  public static void overrideClassName(String actualClassName, String overridingClassName) {
2059f81086b618594cb080adf33548edd0c999e388eKevin Jin    CLASS_NAME_OVERRIDES.put(actualClassName, overridingClassName);
2069f81086b618594cb080adf33548edd0c999e388eKevin Jin  }
207cddda72410c992a12db61cef26713b498e31fea4Thanh Le
20874676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private final DroidDriverContext<View, ViewElement> context;
20974676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  private final View view;
210dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  private final Map<Attribute, Object> attributes;
211dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  private final boolean visible;
212dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  private final Rect visibleBounds;
213dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  private final ViewElement parent;
214dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  private final List<ViewElement> children;
215cddda72410c992a12db61cef26713b498e31fea4Thanh Le
21607401c162d1957a497ab08937f1188cc602f29c6Kevin Jin  /**
21707401c162d1957a497ab08937f1188cc602f29c6Kevin Jin   * A snapshot of all attributes is taken at construction. The attributes of a
21807401c162d1957a497ab08937f1188cc602f29c6Kevin Jin   * {@code ViewElement} instance are immutable. If the underlying view is
21907401c162d1957a497ab08937f1188cc602f29c6Kevin Jin   * updated, a new {@code ViewElement} instance will be created in
22007401c162d1957a497ab08937f1188cc602f29c6Kevin Jin   * {@link com.google.android.droiddriver.DroidDriver#refreshUiElementTree}.
22107401c162d1957a497ab08937f1188cc602f29c6Kevin Jin   */
22274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  public ViewElement(DroidDriverContext<View, ViewElement> context, View view, ViewElement parent) {
223dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    this.context = Preconditions.checkNotNull(context);
22474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    this.view = Preconditions.checkNotNull(view);
225dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    this.parent = parent;
226dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    SnapshotViewAttributesRunnable attributesSnapshot = new SnapshotViewAttributesRunnable(view);
227eacc6c8c1f05ad4c6d9ca4c612204240b9dc1d4eKevin Jin    context.runOnMainSync(attributesSnapshot);
228dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    if (attributesSnapshot.exception != null) {
229dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin      throw new DroidDriverException(attributesSnapshot.exception);
23007704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin    }
23107704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin
23217342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    attributes = Collections.unmodifiableMap(attributesSnapshot.attribs);
233dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    this.visibleBounds = attributesSnapshot.visibleBounds;
234dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    this.visible = attributesSnapshot.visible;
23517342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    if (attributesSnapshot.childViews == null) {
23617342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin      this.children = null;
23717342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    } else {
23817342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin      List<ViewElement> children = new ArrayList<ViewElement>(attributesSnapshot.childViews.size());
23917342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin      for (View childView : attributesSnapshot.childViews) {
24074676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin        children.add(context.getElement(childView, this));
24117342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin      }
24217342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin      this.children = Collections.unmodifiableList(children);
24317342a5115d7575d44a99fed9c7032e3ab316dccKevin Jin    }
24407704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin  }
24507704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin
24607704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin  @Override
247dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  public Rect getVisibleBounds() {
248dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    return visibleBounds;
249cddda72410c992a12db61cef26713b498e31fea4Thanh Le  }
250cddda72410c992a12db61cef26713b498e31fea4Thanh Le
251cddda72410c992a12db61cef26713b498e31fea4Thanh Le  @Override
252cddda72410c992a12db61cef26713b498e31fea4Thanh Le  public boolean isVisible() {
253dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    return visible;
25407704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin  }
25507704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin
25607704975f91b729d9be3a13d6a2d3dfdbbd7d426Kevin Jin  @Override
257dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  public ViewElement getParent() {
258dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    return parent;
2597dde4b200c490587409e0301e58261210e7a5896Tony Wickham  }
2607dde4b200c490587409e0301e58261210e7a5896Tony Wickham
2617dde4b200c490587409e0301e58261210e7a5896Tony Wickham  @Override
262dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  protected List<ViewElement> getChildren() {
263dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    return children;
26452107c27b6b0f2b0fdfec995784c73746bb95c4eKevin Jin  }
26552107c27b6b0f2b0fdfec995784c73746bb95c4eKevin Jin
26652107c27b6b0f2b0fdfec995784c73746bb95c4eKevin Jin  @Override
267dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin  protected Map<Attribute, Object> getAttributes() {
268dfc316e1bfb37148c50947c46f5aaed5cb2e708aKevin Jin    return attributes;
26952107c27b6b0f2b0fdfec995784c73746bb95c4eKevin Jin  }
2700d7b67b43f83536708a6ae0330e739744049a48eKevin Jin
2710d7b67b43f83536708a6ae0330e739744049a48eKevin Jin  @Override
2729c92f46280cf3943701e75349833c68b584992e2Kevin Jin  public InputInjector getInjector() {
27374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    return context.getDriver().getInjector();
2740d7b67b43f83536708a6ae0330e739744049a48eKevin Jin  }
2758d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jin
2768d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jin  @Override
2778d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jin  protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) {
2788d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jin    futureTask.run();
279a6749c6913f014416419850a9fb5235a745fdeb8Kevin Jin    context.tryWaitForIdleSync(timeoutMillis);
2808d19bb634c670a49f7a58636a2a535c86b57d538Kevin Jin  }
28174676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin
28274676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  @Override
28374676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  public View getRawElement() {
28474676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin    return view;
28574676fdd3c8a9e599eddd13bea56898674d9916aKevin Jin  }
286cddda72410c992a12db61cef26713b498e31fea4Thanh Le}
287