• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.android.apps.common.testing.accessibility.framework.uielement;
16 
17 import static com.google.common.base.Preconditions.checkNotNull;
18 import static java.lang.Boolean.TRUE;
19 
20 import com.google.android.apps.common.testing.accessibility.framework.ViewHierarchyElementUtils;
21 import com.google.android.apps.common.testing.accessibility.framework.replacements.LayoutParams;
22 import com.google.android.apps.common.testing.accessibility.framework.replacements.Rect;
23 import com.google.android.apps.common.testing.accessibility.framework.replacements.SpannableString;
24 import com.google.android.apps.common.testing.accessibility.framework.replacements.TextUtils;
25 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AccessibilityHierarchyProtos.ViewHierarchyActionProto;
26 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AccessibilityHierarchyProtos.ViewHierarchyElementProto;
27 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AndroidFrameworkProtos.RectProto;
28 import com.google.common.base.Preconditions;
29 import com.google.common.collect.ImmutableList;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.NoSuchElementException;
34 import java.util.Objects;
35 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
36 import org.checkerframework.checker.nullness.qual.Nullable;
37 import org.checkerframework.dataflow.qual.Pure;
38 
39 /**
40  * Representation of a {@link android.view.View} hierarchy for accessibility checking
41  *
42  * <p>These views hold references to surrounding {@link ViewHierarchyElement}s in its local view
43  * hierarchy and the containing {@link WindowHierarchyElement}. An individual view may be uniquely
44  * identified in the context of its containing {@link WindowHierarchyElement} by the {@code id}
45  * value returned by {@link #getId()}, or it may be uniquely identified in the context of its
46  * containing {@link AccessibilityHierarchy} by the {@code long} returned by {@link
47  * #getCondensedUniqueId()}.
48  */
49 public class ViewHierarchyElement {
50   /** className on an element that holds View content from a Compose AndroidView. */
51   private static final String VIEW_FACTORY_HOLDER_CLASS_NAME =
52       "androidx.compose.ui.viewinterop.ViewFactoryHolder";
53 
54   protected final int id;
55   protected final @Nullable Integer parentId;
56 
57   // Created lazily, because many views are leafs.
58   protected @MonotonicNonNull List<Integer> childIds;
59 
60   // This field is set to a non-null value after construction.
61   private @MonotonicNonNull WindowHierarchyElement windowElement;
62 
63   protected final @Nullable CharSequence packageName;
64   protected final @Nullable CharSequence className;
65   protected final @Nullable CharSequence accessibilityClassName;
66   private ViewHierarchyElementOrigin origin;
67   protected final @Nullable String resourceName;
68   protected final @Nullable CharSequence testTag;
69   protected final @Nullable SpannableString contentDescription;
70   protected final @Nullable SpannableString text;
71   protected final @Nullable SpannableString stateDescription;
72   protected final boolean importantForAccessibility;
73   protected final @Nullable Boolean visibleToUser;
74   protected final boolean clickable;
75   protected final boolean longClickable;
76   protected final boolean focusable;
77   protected final @Nullable Boolean editable;
78   protected final @Nullable Boolean scrollable;
79   protected final @Nullable Boolean canScrollForward;
80   protected final @Nullable Boolean canScrollBackward;
81   protected final @Nullable Boolean checkable;
82   protected final @Nullable Boolean checked;
83   protected final @Nullable Boolean hasTouchDelegate;
84   protected final boolean isScreenReaderFocusable;
85   protected final @Nullable Rect boundsInScreen;
86   protected final @Nullable Integer nonclippedHeight;
87   protected final @Nullable Integer nonclippedWidth;
88   protected final @Nullable Float textSize;
89   protected final @Nullable Integer textSizeUnit;
90   protected final @Nullable Integer textColor;
91   protected final @Nullable Integer backgroundDrawableColor;
92   protected final @Nullable Integer typefaceStyle;
93   protected final boolean enabled;
94   protected final @Nullable Integer drawingOrder;
95   protected final ImmutableList<ViewHierarchyAction> actionList;
96   protected final @Nullable LayoutParams layoutParams;
97   protected final @Nullable SpannableString hintText; // only for TextView
98   protected final @Nullable Integer hintTextColor; // only for TextView
99   protected final ImmutableList<Rect> textCharacterLocations;
100 
101   // Populated only after a hierarchy is constructed
102   protected @Nullable Long labeledById;
103   protected @Nullable Long accessibilityTraversalBeforeId;
104   protected @Nullable Long accessibilityTraversalAfterId;
105   protected List<Rect> touchDelegateBounds;
106 
107   // A list of identifiers that represents all the superclasses of the corresponding view element.
108   protected final List<Integer> superclassViews;
109 
ViewHierarchyElement( int id, @Nullable Integer parentId, List<Integer> childIds, @Nullable CharSequence packageName, @Nullable CharSequence className, @Nullable CharSequence accessibilityClassName, ViewHierarchyElementOrigin origin, @Nullable String resourceName, @Nullable CharSequence testTag, @Nullable SpannableString contentDescription, @Nullable SpannableString text, @Nullable SpannableString stateDescription, boolean importantForAccessibility, @Nullable Boolean visibleToUser, boolean clickable, boolean longClickable, boolean focusable, @Nullable Boolean editable, @Nullable Boolean scrollable, @Nullable Boolean canScrollForward, @Nullable Boolean canScrollBackward, @Nullable Boolean checkable, @Nullable Boolean checked, @Nullable Boolean hasTouchDelegate, boolean isScreenReaderFocusable, List<Rect> touchDelegateBounds, @Nullable Rect boundsInScreen, @Nullable Integer nonclippedHeight, @Nullable Integer nonclippedWidth, @Nullable Float textSize, @Nullable Integer textSizeUnit, @Nullable Integer textColor, @Nullable Integer backgroundDrawableColor, @Nullable Integer typefaceStyle, boolean enabled, @Nullable Long labeledById, @Nullable Long accessibilityTraversalBeforeId, @Nullable Long accessibilityTraversalAfterId, @Nullable Integer drawingOrder, List<Integer> superclassViews, List<? extends ViewHierarchyAction> actionList, @Nullable LayoutParams layoutParams, @Nullable SpannableString hintText, @Nullable Integer hintTextColor, List<Rect> textCharacterLocations)110   protected ViewHierarchyElement(
111       int id,
112       @Nullable Integer parentId,
113       List<Integer> childIds,
114       @Nullable CharSequence packageName,
115       @Nullable CharSequence className,
116       @Nullable CharSequence accessibilityClassName,
117       ViewHierarchyElementOrigin origin,
118       @Nullable String resourceName,
119       @Nullable CharSequence testTag,
120       @Nullable SpannableString contentDescription,
121       @Nullable SpannableString text,
122       @Nullable SpannableString stateDescription,
123       boolean importantForAccessibility,
124       @Nullable Boolean visibleToUser,
125       boolean clickable,
126       boolean longClickable,
127       boolean focusable,
128       @Nullable Boolean editable,
129       @Nullable Boolean scrollable,
130       @Nullable Boolean canScrollForward,
131       @Nullable Boolean canScrollBackward,
132       @Nullable Boolean checkable,
133       @Nullable Boolean checked,
134       @Nullable Boolean hasTouchDelegate,
135       boolean isScreenReaderFocusable,
136       List<Rect> touchDelegateBounds,
137       @Nullable Rect boundsInScreen,
138       @Nullable Integer nonclippedHeight,
139       @Nullable Integer nonclippedWidth,
140       @Nullable Float textSize,
141       @Nullable Integer textSizeUnit,
142       @Nullable Integer textColor,
143       @Nullable Integer backgroundDrawableColor,
144       @Nullable Integer typefaceStyle,
145       boolean enabled,
146       @Nullable Long labeledById,
147       @Nullable Long accessibilityTraversalBeforeId,
148       @Nullable Long accessibilityTraversalAfterId,
149       @Nullable Integer drawingOrder,
150       List<Integer> superclassViews,
151       List<? extends ViewHierarchyAction> actionList,
152       @Nullable LayoutParams layoutParams,
153       @Nullable SpannableString hintText,
154       @Nullable Integer hintTextColor,
155       List<Rect> textCharacterLocations) {
156     this.id = id;
157     this.parentId = parentId;
158     if (!childIds.isEmpty()) {
159       this.childIds = new ArrayList<>(childIds.size());
160       this.childIds.addAll(childIds);
161     }
162     this.packageName = packageName;
163     this.className = className;
164     this.accessibilityClassName = accessibilityClassName;
165     this.origin = origin;
166     this.resourceName = resourceName;
167     this.testTag = testTag;
168     this.contentDescription = contentDescription;
169     this.text = text;
170     this.stateDescription = stateDescription;
171     this.importantForAccessibility = importantForAccessibility;
172     this.visibleToUser = visibleToUser;
173     this.clickable = clickable;
174     this.longClickable = longClickable;
175     this.focusable = focusable;
176     this.editable = editable;
177     this.scrollable = scrollable;
178     this.canScrollForward = canScrollForward;
179     this.canScrollBackward = canScrollBackward;
180     this.checkable = checkable;
181     this.checked = checked;
182     this.hasTouchDelegate = hasTouchDelegate;
183     this.isScreenReaderFocusable = isScreenReaderFocusable;
184     this.touchDelegateBounds = touchDelegateBounds;
185     this.boundsInScreen = boundsInScreen;
186     this.nonclippedHeight = nonclippedHeight;
187     this.nonclippedWidth = nonclippedWidth;
188     this.textSize = textSize;
189     this.textSizeUnit = textSizeUnit;
190     this.textColor = textColor;
191     this.backgroundDrawableColor = backgroundDrawableColor;
192     this.typefaceStyle = typefaceStyle;
193     this.enabled = enabled;
194     this.labeledById = labeledById;
195     this.accessibilityTraversalBeforeId = accessibilityTraversalBeforeId;
196     this.accessibilityTraversalAfterId = accessibilityTraversalAfterId;
197     this.drawingOrder = drawingOrder;
198     this.superclassViews = superclassViews;
199     if (actionList != null && !actionList.isEmpty()) {
200       this.actionList = ImmutableList.copyOf(actionList);
201     } else {
202       this.actionList = ImmutableList.of();
203     }
204     this.layoutParams = layoutParams;
205     this.hintText = hintText;
206     this.hintTextColor = hintTextColor;
207     this.textCharacterLocations = ImmutableList.copyOf(textCharacterLocations);
208   }
209 
ViewHierarchyElement(ViewHierarchyElementProto proto)210   ViewHierarchyElement(ViewHierarchyElementProto proto) {
211     checkNotNull(proto);
212 
213     // Bookkeeping
214     this.id = proto.getId();
215     this.parentId = (proto.getParentId() != -1) ? proto.getParentId() : null;
216     if (proto.getChildIdsCount() > 0) {
217       this.childIds = new ArrayList<>(proto.getChildIdsCount());
218       this.childIds.addAll(proto.getChildIdsList());
219     }
220 
221     packageName = proto.hasPackageName() ? proto.getPackageName() : null;
222     className = proto.hasClassName() ? proto.getClassName() : null;
223     accessibilityClassName =
224         proto.hasAccessibilityClassName() ? proto.getAccessibilityClassName() : null;
225     resourceName = proto.hasResourceName() ? proto.getResourceName() : null;
226     testTag = proto.hasTestTag() ? proto.getTestTag() : null;
227     contentDescription =
228         proto.hasContentDescription() ? new SpannableString(proto.getContentDescription()) : null;
229     text = proto.hasText() ? new SpannableString(proto.getText()) : null;
230     stateDescription =
231         proto.hasStateDescription() ? new SpannableString(proto.getStateDescription()) : null;
232     importantForAccessibility = proto.getImportantForAccessibility();
233     visibleToUser = proto.hasVisibleToUser() ? proto.getVisibleToUser() : null;
234     clickable = proto.getClickable();
235     longClickable = proto.getLongClickable();
236     focusable = proto.getFocusable();
237     editable = proto.hasEditable() ? proto.getEditable() : null;
238     scrollable = proto.hasScrollable() ? proto.getScrollable() : null;
239     canScrollForward = proto.hasCanScrollForward() ? proto.getCanScrollForward() : null;
240     canScrollBackward = proto.hasCanScrollBackward() ? proto.getCanScrollBackward() : null;
241     checkable = proto.hasCheckable() ? proto.getCheckable() : null;
242     checked = proto.hasChecked() ? proto.getChecked() : null;
243     hasTouchDelegate = proto.hasHasTouchDelegate() ? proto.getHasTouchDelegate() : null;
244     isScreenReaderFocusable = proto.getScreenReaderFocusable();
245     if (proto.getTouchDelegateBoundsCount() > 0) {
246       ImmutableList.Builder<Rect> builder = ImmutableList.<Rect>builder();
247       for (int i = 0; i < proto.getTouchDelegateBoundsCount(); ++i) {
248         builder.add(new Rect(proto.getTouchDelegateBounds(i)));
249       }
250       touchDelegateBounds = builder.build();
251     } else {
252       touchDelegateBounds = ImmutableList.of();
253     }
254     boundsInScreen = proto.hasBoundsInScreen() ? new Rect(proto.getBoundsInScreen()) : null;
255     nonclippedHeight = proto.hasNonclippedHeight() ? proto.getNonclippedHeight() : null;
256     nonclippedWidth = proto.hasNonclippedWidth() ? proto.getNonclippedWidth() : null;
257     textSize = proto.hasTextSize() ? proto.getTextSize() : null;
258     textSizeUnit = proto.hasTextSizeUnit() ? proto.getTextSizeUnit() : null;
259     textColor = proto.hasTextColor() ? proto.getTextColor() : null;
260     backgroundDrawableColor =
261         proto.hasBackgroundDrawableColor() ? proto.getBackgroundDrawableColor() : null;
262     typefaceStyle = proto.hasTypefaceStyle() ? proto.getTypefaceStyle() : null;
263     enabled = proto.getEnabled();
264     labeledById = proto.hasLabeledById() ? proto.getLabeledById() : null;
265     accessibilityTraversalBeforeId =
266         proto.hasAccessibilityTraversalBeforeId()
267             ? proto.getAccessibilityTraversalBeforeId()
268             : null;
269     accessibilityTraversalAfterId =
270         proto.hasAccessibilityTraversalAfterId() ? proto.getAccessibilityTraversalAfterId() : null;
271     superclassViews = proto.getSuperclassesList();
272     drawingOrder = proto.hasDrawingOrder() ? proto.getDrawingOrder() : null;
273     ImmutableList.Builder<ViewHierarchyAction> actionBuilder = new ImmutableList.Builder<>();
274     for (ViewHierarchyActionProto actionProto : proto.getActionsList()) {
275       actionBuilder.add(new ViewHierarchyAction(actionProto));
276     }
277     actionList = actionBuilder.build();
278     layoutParams = proto.hasLayoutParams() ? new LayoutParams(proto.getLayoutParams()) : null;
279     hintText = proto.hasHintText() ? new SpannableString(proto.getHintText()) : null;
280     hintTextColor = proto.hasHintTextColor() ? proto.getHintTextColor() : null;
281     ImmutableList.Builder<Rect> characterLocations = ImmutableList.<Rect>builder();
282     for (RectProto rectProto : proto.getTextCharacterLocationsList()) {
283       characterLocations.add(new Rect(rectProto));
284     }
285     textCharacterLocations = characterLocations.build();
286 
287     // The origin cannot be determined without the element's parents, so it will be set later.
288     origin = ViewHierarchyElementOrigin.UNKNOWN;
289   }
290 
291   /**
292    * Returns the value uniquely identifying this element within the context of its containing {@link
293    * WindowHierarchyElement}.
294    */
295   @Pure
getId()296   public int getId() {
297     return id;
298   }
299 
300   /**
301    * @return a value uniquely representing this {@link ViewHierarchyElement} and its containing
302    *     {@link WindowHierarchyElement} in the context of its containing {@link
303    *     AccessibilityHierarchy}.
304    */
getCondensedUniqueId()305   public long getCondensedUniqueId() {
306     return (((long) getWindow().getId() << 32) | getId());
307   }
308 
309   /**
310    * @return The parent {@link ViewHierarchyElement} of this view, or {@code null} if this is a root
311    *     view.
312    * @see android.view.accessibility.AccessibilityNodeInfo#getParent()
313    * @see android.view.View#getParent()
314    */
315   @Pure
getParentView()316   public @Nullable ViewHierarchyElement getParentView() {
317     Integer parentIdtmp = parentId;
318     return (parentIdtmp != null) ? getWindow().getViewById(parentIdtmp) : null;
319   }
320 
321   /**
322    * @return The number of child {@link ViewHierarchyElement}s rooted at this view
323    * @see android.view.accessibility.AccessibilityNodeInfo#getChildCount()
324    * @see android.view.ViewGroup#getChildCount()
325    */
getChildViewCount()326   public int getChildViewCount() {
327     return (childIds == null) ? 0 : childIds.size();
328   }
329 
330   /**
331    * @param atIndex The index of the child {@link ViewHierarchyElement} to obtain. Must be &ge 0 and
332    *     &lt {@link #getChildViewCount()}.
333    * @return The requested child, or {@code null} if no such child exists at the given {@code
334    *     atIndex}
335    * @throws NoSuchElementException if {@code atIndex} is less than 0 or greater than {@code
336    *     getChildViewCount() - 1}
337    */
getChildView(int atIndex)338   public ViewHierarchyElement getChildView(int atIndex) {
339     if ((atIndex < 0) || (childIds == null) || (atIndex >= childIds.size())) {
340       throw new NoSuchElementException();
341     }
342     return getWindow().getViewById(childIds.get(atIndex));
343   }
344 
345   /**
346    * @return an unmodifiable {@link List} containing this {@link ViewHierarchyElement} and any
347    *     descendants, direct or indirect, in depth-first ordering.
348    */
getSelfAndAllDescendants()349   public List<? extends ViewHierarchyElement> getSelfAndAllDescendants() {
350     List<ViewHierarchyElement> listToPopulate = new ArrayList<>();
351     listToPopulate.add(this);
352     for (int i = 0; i < getChildViewCount(); ++i) {
353       listToPopulate.addAll(getChildView(i).getSelfAndAllDescendants());
354     }
355 
356     return Collections.unmodifiableList(listToPopulate);
357   }
358 
359   /**
360    * @return The containing {@link WindowHierarchyElement} of this view.
361    */
getWindow()362   public WindowHierarchyElement getWindow() {
363 
364     // The type is explicit because the @MonotonicNonNull field is not read as @Nullable.
365     return Preconditions.<@Nullable WindowHierarchyElement>checkNotNull(windowElement);
366   }
367 
368   /**
369    * @return The package name to which this view belongs, or {@code null} if one cannot be
370    *     determined
371    * @see android.view.accessibility.AccessibilityNodeInfo#getPackageName()
372    * @see android.content.Context#getPackageName()
373    */
getPackageName()374   public @Nullable CharSequence getPackageName() {
375     return packageName;
376   }
377 
378   /**
379    * @return The class name to which this view belongs, or {@code null} if one cannot be determined
380    * @see android.view.accessibility.AccessibilityNodeInfo#getPackageName()
381    * @see android.view.View#getClass()
382    */
getClassName()383   public @Nullable CharSequence getClassName() {
384     return className;
385   }
386 
387   /**
388    * @return The view id's associated resource name, or {@code null} if one cannot be determined or
389    *     is not available
390    * @see android.view.accessibility.AccessibilityNodeInfo#getViewIdResourceName()
391    * @see android.view.View#getId()
392    * @see android.content.res.Resources#getResourceName(int)
393    */
394   @Pure
getResourceName()395   public @Nullable String getResourceName() {
396     return resourceName;
397   }
398 
399   /**
400    * Gets the Compose testTag.
401    *
402    * @see androidx.compose.ui.Modifier#testTag(String)
403    */
404   @Pure
getTestTag()405   public @Nullable CharSequence getTestTag() {
406     return testTag;
407   }
408 
409   /**
410    * Check if the {@link android.view.View} this element represents matches a particular class using
411    * its class name and accessibility class name if available.
412    *
413    * @param referenceClassName the name of the class to check against the class of this element.
414    * @return true if the {@code android.view.View} this element represents is an instance of the
415    *     class whose name is {@code referenceClassName}. False if it does not.
416    */
checkInstanceOf(String referenceClassName)417   public boolean checkInstanceOf(String referenceClassName) {
418     AccessibilityHierarchy hierarchy = getWindow().getAccessibilityHierarchy();
419     Integer id = hierarchy.getViewElementClassNames().getIdentifierForClassName(referenceClassName);
420     if (id == null) {
421       return false;
422     }
423     return superclassViews.contains(id);
424   }
425 
426   /**
427    * Check if the {@link android.view.View} this element represents matches one of the classes.
428    *
429    * @param referenceClassNameList the list of names of classes to check against the class of this
430    *     element.
431    * @return true if the {@code android.view.View} this element represents is an instance of at
432    *     least one of the class names in {@code referenceClassNameList}. False if it does not.
433    */
checkInstanceOfAny(List<String> referenceClassNameList)434   public boolean checkInstanceOfAny(List<String> referenceClassNameList) {
435     for (String referenceClassName : referenceClassNameList) {
436       if (checkInstanceOf(referenceClassName)) {
437         return true;
438       }
439     }
440     return false;
441   }
442 
443   /**
444    * @return This view's content description, or {@code null} if one is not present
445    * @see android.view.accessibility.AccessibilityNodeInfo#getContentDescription()
446    * @see android.view.View#getContentDescription()
447    */
getContentDescription()448   public @Nullable SpannableString getContentDescription() {
449     return contentDescription;
450   }
451 
452   /**
453    * Indicates whether the element is important for accessibility and would be reported to
454    * accessibility services.
455    *
456    * @see android.view.View#isImportantForAccessibility()
457    */
458   @Pure
isImportantForAccessibility()459   public boolean isImportantForAccessibility() {
460     return importantForAccessibility;
461   }
462 
463   /**
464    * @return This view's text content, or {@code null} if none is present
465    * @see android.view.accessibility.AccessibilityNodeInfo#getText()
466    * @see android.widget.TextView#getText()
467    * @see android.widget.TextView#getHint()
468    */
getText()469   public @Nullable SpannableString getText() {
470     return text;
471   }
472 
473   /**
474    * Returns the View's state description.
475    *
476    * @see android.view.getStateDescription()
477    * @see android.view.accessibility.AccessibilityNodeInfo#getStateDescription()
478    */
479   @Pure
getStateDescription()480   public @Nullable SpannableString getStateDescription() {
481     return stateDescription;
482   }
483 
484   /**
485    * @return {@link Boolean#TRUE} if the element is visible to the user, {@link Boolean#FALSE} if
486    *     not, or {@code null} if this cannot be determined.
487    * @see android.view.accessibility.AccessibilityNodeInfo#isVisibleToUser()
488    */
489   @Pure
isVisibleToUser()490   public @Nullable Boolean isVisibleToUser() {
491     return visibleToUser;
492   }
493 
494   /**
495    * Indicates whether this view reports that it reacts to click events or not.
496    *
497    * @see android.view.accessibility.AccessibilityNodeInfo#isClickable()
498    * @see android.view.View#isClickable()
499    */
500   @Pure
isClickable()501   public boolean isClickable() {
502     return clickable;
503   }
504 
505   /**
506    * Indicates whether this view reports that it reacts to long click events or not.
507    *
508    * @see android.view.accessibility.AccessibilityNodeInfo#isLongClickable()
509    * @see android.view.View#isLongClickable()
510    */
511   @Pure
isLongClickable()512   public boolean isLongClickable() {
513     return longClickable;
514   }
515 
516   /**
517    * Indicates whether this view reports that it is currently able to take focus.
518    *
519    * @see android.view.accessibility.AccessibilityNodeInfo#isFocusable()
520    * @see android.view.View#isFocusable()
521    */
522   @Pure
isFocusable()523   public boolean isFocusable() {
524     return focusable;
525   }
526 
527   /**
528    * @return {@link Boolean#TRUE} if the element is editable, {@link Boolean#FALSE} if not, or
529    *     {@code null} if this cannot be determined.
530    */
531   @Pure
isEditable()532   public @Nullable Boolean isEditable() {
533     return editable;
534   }
535 
536   /**
537    * @return {@link Boolean#TRUE} if the element is potentially scrollable or indicated as a
538    *     scrollable container, {@link Boolean#FALSE} if not, or {@code null} if this cannot be
539    *     determined. Scrollable in this context refers only to a element's potential for being
540    *     scrolled, and doesn't indicate if the container holds enough wrapped content to scroll. To
541    *     determine if an element is actually scrollable based on contents use {@link
542    *     #canScrollForward} or {@link #canScrollBackward}.
543    */
544   @Pure
isScrollable()545   public @Nullable Boolean isScrollable() {
546     return scrollable;
547   }
548 
549   /**
550    * @return {@link Boolean#TRUE} if the element is scrollable in the "forward" direction, typically
551    *     meaning either vertically downward or horizontally to the right (in left-to-right locales),
552    *     {@link Boolean#FALSE} if not, or {@code null} if this cannot be determined.
553    */
554   @Pure
canScrollForward()555   public @Nullable Boolean canScrollForward() {
556     return canScrollForward;
557   }
558 
559   /**
560    * @return {@link Boolean#TRUE} if the element is scrollable in the "backward" direction,
561    *     typically meaning either vertically downward or horizontally to the right (in left-to-right
562    *     locales), {@link Boolean#FALSE} if not, or {@code null} if this cannot be determined.
563    */
564   @Pure
canScrollBackward()565   public @Nullable Boolean canScrollBackward() {
566     return canScrollBackward;
567   }
568 
569   /**
570    * @return {@link Boolean#TRUE} if the element is checkable, {@link Boolean#FALSE} if not, or
571    *     {@code null} if this cannot be determined.
572    */
573   @Pure
isCheckable()574   public @Nullable Boolean isCheckable() {
575     return checkable;
576   }
577 
578   /**
579    * @return {@link Boolean#TRUE} if the element is checked, {@link Boolean#FALSE} if not, or {@code
580    *     null} if this cannot be determined.
581    */
582   @Pure
isChecked()583   public @Nullable Boolean isChecked() {
584     return checked;
585   }
586 
587   /**
588    * Returns {@link Boolean#TRUE} if the element has a {@link android.view.TouchDelegate}, {@link
589    * Boolean#FALSE} if not, or {@code null} if this cannot be determined. This indicates only that
590    * this element may be responsible for delegating its touches to another element.
591    */
592   @Pure
hasTouchDelegate()593   public @Nullable Boolean hasTouchDelegate() {
594     return hasTouchDelegate;
595   }
596 
597   /**
598    * Returns whether the view should be treated as a focusable unit by screenreaders.
599    *
600    * @see android.view.accessibility.AccessibilityNodeInfo#isScreenReaderFocusable()
601    * @see android.view.View#isScreenReaderFocusable()
602    */
603   @Pure
isScreenReaderFocusable()604   public boolean isScreenReaderFocusable() {
605     return isScreenReaderFocusable;
606   }
607 
608   /**
609    * Returns a list of the touchable bounds of this element if rectangular {@link
610    * android.view.TouchDelegate}s are used to modify this delegatee's hit region from another
611    * delegating element.
612    *
613    * <p>NOTE: This is distinct from {@link #hasTouchDelegate()}, that indicates whether the element
614    * may be a delegator of touches.
615    */
getTouchDelegateBounds()616   public List<Rect> getTouchDelegateBounds() {
617     return touchDelegateBounds;
618   }
619 
620   /** Returns a list of character locations in screen coordinates. */
621   @Pure
getTextCharacterLocations()622   public List<Rect> getTextCharacterLocations() {
623     return textCharacterLocations;
624   }
625 
626   /**
627    * Retrieves the visible bounds of this element in absolute screen coordinates.
628    *
629    * <p>NOTE: This method provides dimensions that may be reduced in size due to clipping effects
630    * from parent elements. To determine nonclipped dimensions, consider using {@link
631    * #getNonclippedHeight()} and {@link #getNonclippedWidth}.
632    *
633    * @return the view's bounds, or {@link Rect#EMPTY} if the view's bounds are unavailable, such as
634    *     when it is positioned off-screen.
635    * @see android.view.accessibility.AccessibilityNodeInfo#getBoundsInScreen(android.graphics.Rect)
636    * @see android.view.View#getGlobalVisibleRect(android.graphics.Rect)
637    */
638   @Pure
getBoundsInScreen()639   public Rect getBoundsInScreen() {
640     return (boundsInScreen != null) ? boundsInScreen : Rect.EMPTY;
641   }
642 
643   /**
644    * @return the height of this element (in raw pixels) not taking into account clipping effects
645    *     applied by parent elements.
646    * @see android.view.View#getHeight()
647    */
648   @Pure
getNonclippedHeight()649   public @Nullable Integer getNonclippedHeight() {
650     return nonclippedHeight;
651   }
652 
653   /**
654    * @return the width of this element (in raw pixels) not taking into account clipping effects
655    *     applied by parent elements.
656    * @see android.view.View#getWidth()
657    */
658   @Pure
getNonclippedWidth()659   public @Nullable Integer getNonclippedWidth() {
660     return nonclippedWidth;
661   }
662 
663   /**
664    * @return The size (in raw pixels) of the default text appearing in this view, or {@code null} if
665    *     this cannot be determined
666    * @see android.widget.TextView#getTextSize()
667    */
668   @Pure
getTextSize()669   public @Nullable Float getTextSize() {
670     return textSize;
671   }
672 
673   /*
674    * @return the dimension type of the text size unit originally defined.
675    * @see android.util.TypedValue#TYPE_DIMENSION
676    */
677   @Pure
getTextSizeUnit()678   public @Nullable Integer getTextSizeUnit() {
679     return textSizeUnit;
680   }
681 
682   /**
683    * @return The color of the default text appearing in this view, or {@code null} if this cannot be
684    *     determined
685    * @see android.widget.TextView#getCurrentTextColor()
686    */
687   @Pure
getTextColor()688   public @Nullable Integer getTextColor() {
689     return textColor;
690   }
691 
692   /**
693    * @return The color of this View's background drawable, or {@code null} if the view does not have
694    *     a {@link android.graphics.drawable.ColorDrawable} background
695    * @see android.view.View#getBackground()
696    * @see android.graphics.drawable.ColorDrawable#getColor()
697    */
698   @Pure
getBackgroundDrawableColor()699   public @Nullable Integer getBackgroundDrawableColor() {
700     return backgroundDrawableColor;
701   }
702 
703   /**
704    * Returns The style attributes of the {@link android.graphics.Typeface} of the default text
705    * appearing in this view, or @code null} if this cannot be determined.
706    *
707    * @see android.widget.TextView#getTypeface()
708    * @see android.graphics.Typeface#getStyle()
709    * @see android.graphics.Typeface#NORMAL
710    * @see android.graphics.Typeface#BOLD
711    * @see android.graphics.Typeface#ITALIC
712    * @see android.graphics.Typeface#BOLD_ITALIC
713    */
714   @Pure
getTypefaceStyle()715   public @Nullable Integer getTypefaceStyle() {
716     return typefaceStyle;
717   }
718 
719   /**
720    * Returns the enabled status for this view.
721    *
722    * @see android.view.View#isEnabled()
723    * @see android.view.accessibility.AccessibilityNodeInfo#isEnabled()
724    */
725   @Pure
isEnabled()726   public boolean isEnabled() {
727     return enabled;
728   }
729 
730   /**
731    * @return The class name as reported to accessibility services, or {@code null} if this cannot be
732    *     determined
733    *     <p>NOTE: Unavailable for instances originally created from a {@link android.view.View} on
734    *     API < 23.
735    * @see android.view.accessibility.AccessibilityNodeInfo#getClassName()
736    */
737   @Pure
getAccessibilityClassName()738   public @Nullable CharSequence getAccessibilityClassName() {
739     return accessibilityClassName;
740   }
741 
742   /** Returns the type of node from which the element was originally constructed. */
getOrigin()743   public ViewHierarchyElementOrigin getOrigin() {
744     return origin;
745   }
746 
747   /**
748    * Computes and sets the origin of the element. This is used when the origin cannot be determined
749    * during construction of the instance.
750    */
computeAndSetOrigin()751   /* package */ void computeAndSetOrigin() {
752     this.origin = computeOrigin(getClassName(), getParentView());
753   }
754 
755   /**
756    * Returns the {@link ViewHierarchyElement} which acts as a label for this element, or {@code
757    * null} if this element is not labeled by another
758    *
759    * @see android.view.accessibility.AccessibilityNodeInfo#getLabeledBy()
760    * @see android.view.accessibility.AccessibilityNodeInfo#getLabelFor()
761    * @see android.view.View#getLabelFor()
762    */
getLabeledBy()763   public @Nullable ViewHierarchyElement getLabeledBy() {
764     return getViewHierarchyElementById(labeledById);
765   }
766 
767   /**
768    * @return a view before which this one is visited in accessibility traversal
769    * @see android.view.accessibility.AccessibilityNodeInfo#getTraversalBefore()
770    * @see android.view.View#getAccessibilityTraversalBefore()
771    */
getAccessibilityTraversalBefore()772   public @Nullable ViewHierarchyElement getAccessibilityTraversalBefore() {
773     return getViewHierarchyElementById(accessibilityTraversalBeforeId);
774   }
775 
776   /**
777    * @return a view after which this one is visited in accessibility traversal
778    * @see android.view.accessibility.AccessibilityNodeInfo#getTraversalAfter()
779    * @see android.view.View#getAccessibilityTraversalAfter()
780    */
getAccessibilityTraversalAfter()781   public @Nullable ViewHierarchyElement getAccessibilityTraversalAfter() {
782     return getViewHierarchyElementById(accessibilityTraversalAfterId);
783   }
784 
785   /**
786    * Returns this element's drawing order within its parent, or {@code null} if the element's
787    * drawing order is not available.
788    *
789    * @see android.view.accessibility.AccessibilityNodeInfo#getDrawingOrder()
790    */
791   @Pure
getDrawingOrder()792   public @Nullable Integer getDrawingOrder() {
793     return drawingOrder;
794   }
795 
796   /**
797    * Returns how the element wants to be laid out in its parent, or {@code null} if the info is not
798    * available.
799    *
800    * @see android.view.ViewGroup.LayoutParams
801    */
802   @Pure
getLayoutParams()803   public @Nullable LayoutParams getLayoutParams() {
804     return layoutParams;
805   }
806 
807   /**
808    * Returns the hint that is displayed when this view is a TextView and the text of the TextView is
809    * empty, or {@code null} if the view is not a TextView, or if the info in not available.
810    *
811    * @see android.widget.TextView#getHint()
812    */
813   @Pure
getHintText()814   public @Nullable SpannableString getHintText() {
815     return hintText;
816   }
817 
818   /**
819    * Returns the current color selected to paint the hint text, or {@code null} if this cannot be
820    * determined.
821    *
822    * @see android.widget.TextView#getCurrentHintTextColor()
823    */
824   @Pure
getHintTextColor()825   public @Nullable Integer getHintTextColor() {
826     return hintTextColor;
827   }
828 
829   /**
830    * Returns {@code true} if this element {@link #isVisibleToUser} and its visible bounds are
831    * adjacent to the scrollable edge of a scrollable container. This would indicate that the element
832    * may be partially obscured by the container.
833    */
isAgainstScrollableEdge()834   public boolean isAgainstScrollableEdge() {
835     return TRUE.equals(isVisibleToUser()) && isAgaistScrollableEdgeOfAncestor(this);
836   }
837 
isAgaistScrollableEdgeOfAncestor(ViewHierarchyElement view)838   private boolean isAgaistScrollableEdgeOfAncestor(ViewHierarchyElement view) {
839     ViewHierarchyElement ancestor = view.getParentView();
840     if (ancestor == null) {
841       return false;
842     }
843 
844     // See if this element is at the top or left edge of a scrollable container that can be scrolled
845     // backward.
846     if (TRUE.equals(ancestor.canScrollBackward())) {
847       Rect scrollableBounds = ancestor.getBoundsInScreen();
848       Rect descendantBounds = this.getBoundsInScreen();
849 
850       if ((descendantBounds.getTop() <= scrollableBounds.getTop())
851           || (descendantBounds.getLeft() <= scrollableBounds.getLeft())) {
852         return true;
853       }
854     }
855 
856     // See if this element is at the bottom or right edge of a scrollable container that can be
857     // scrolled forward.
858     if (TRUE.equals(ancestor.canScrollForward())) {
859       Rect scrollableBounds = ancestor.getBoundsInScreen();
860       Rect descendantBounds = this.getBoundsInScreen();
861 
862       if ((descendantBounds.getBottom() >= scrollableBounds.getBottom())
863           || (descendantBounds.getRight() >= scrollableBounds.getRight())) {
864         return true;
865       }
866     }
867 
868     // Recurse for ancestors.
869     return isAgaistScrollableEdgeOfAncestor(ancestor);
870   }
871 
872   @Override
hashCode()873   public int hashCode() {
874     return getId();
875   }
876 
877   @Override
equals(@ullable Object object)878   public boolean equals(@Nullable Object object) {
879     if (object == this) {
880       return true;
881     }
882     if (!(object instanceof ViewHierarchyElement)) {
883       return false;
884     }
885 
886     ViewHierarchyElement element = (ViewHierarchyElement) object;
887     if (!propertiesEquals((ViewHierarchyElement) object)) {
888       return false;
889     }
890     for (int i = 0; i < getChildViewCount(); i++) {
891       if (!getChildView(i).equals(element.getChildView(i))) {
892         return false;
893       }
894     }
895     return true;
896   }
897 
898   // For debugging
899   @Override
toString()900   public String toString() {
901     StringBuilder sb = new StringBuilder("[ViewHierarchyElement");
902     if (!TextUtils.isEmpty(className)) {
903       sb.append(" class=").append(className);
904     }
905     if (!TextUtils.isEmpty(resourceName)) {
906       sb.append(" resource=").append(resourceName);
907     }
908     if (!TextUtils.isEmpty(testTag)) {
909       sb.append(" testTag=").append(testTag);
910     }
911     if (!TextUtils.isEmpty(text)) {
912       sb.append(" text=").append(text);
913     }
914     if (boundsInScreen != null) {
915       sb.append(" bounds=").append(boundsInScreen);
916     }
917     return sb.append("]").toString();
918   }
919 
920   /**
921    * Returns a list of identifiers that represents all the superclasses of the corresponding view
922    * element.
923    */
getSuperclassList()924   List<Integer> getSuperclassList() {
925     return superclassViews;
926   }
927 
928   /** Add a view class id to superclass list. */
addIdToSuperclassViewList(int id)929   void addIdToSuperclassViewList(int id) {
930     this.superclassViews.add(id);
931   }
932 
933   /** Returns a list of actions exposed by this view element. */
934   @Pure
getActionList()935   ImmutableList<ViewHierarchyAction> getActionList() {
936     return actionList;
937   }
938 
toProto()939   ViewHierarchyElementProto toProto() {
940     ViewHierarchyElementProto.Builder builder = ViewHierarchyElementProto.newBuilder();
941     // Bookkeeping
942     builder.setId(id);
943     if (parentId != null) {
944       builder.setParentId(parentId);
945     }
946     if ((childIds != null) && !childIds.isEmpty()) {
947       builder.addAllChildIds(childIds);
948     }
949 
950     // View properties
951     if (!TextUtils.isEmpty(packageName)) {
952       builder.setPackageName(packageName.toString());
953     }
954     if (!TextUtils.isEmpty(className)) {
955       builder.setClassName(className.toString());
956     }
957     if (!TextUtils.isEmpty(resourceName)) {
958       builder.setResourceName(resourceName);
959     }
960     if (!TextUtils.isEmpty(testTag)) {
961       builder.setTestTag(testTag.toString());
962     }
963     if (!TextUtils.isEmpty(contentDescription)) {
964       builder.setContentDescription(contentDescription.toProto());
965     }
966     if (!TextUtils.isEmpty(text)) {
967       builder.setText(text.toProto());
968     }
969     if (!TextUtils.isEmpty(stateDescription)) {
970       builder.setStateDescription(stateDescription.toProto());
971     }
972     builder.setImportantForAccessibility(importantForAccessibility);
973     if (visibleToUser != null) {
974       builder.setVisibleToUser(visibleToUser);
975     }
976     builder.setClickable(clickable).setLongClickable(longClickable).setFocusable(focusable);
977     if (editable != null) {
978       builder.setEditable(editable);
979     }
980     if (scrollable != null) {
981       builder.setScrollable(scrollable);
982     }
983     if (canScrollForward != null) {
984       builder.setCanScrollForward(canScrollForward);
985     }
986     if (canScrollBackward != null) {
987       builder.setCanScrollBackward(canScrollBackward);
988     }
989     if (checkable != null) {
990       builder.setCheckable(checkable);
991     }
992     if (checked != null) {
993       builder.setChecked(checked);
994     }
995     if (hasTouchDelegate != null) {
996       builder.setHasTouchDelegate(hasTouchDelegate);
997     }
998     builder.setScreenReaderFocusable(isScreenReaderFocusable);
999     for (Rect bounds : touchDelegateBounds) {
1000       builder.addTouchDelegateBounds(bounds.toProto());
1001     }
1002     if (boundsInScreen != null) {
1003       builder.setBoundsInScreen(boundsInScreen.toProto());
1004     }
1005     if (nonclippedHeight != null) {
1006       builder.setNonclippedHeight(nonclippedHeight);
1007     }
1008     if (nonclippedWidth != null) {
1009       builder.setNonclippedWidth(nonclippedWidth);
1010     }
1011     if (textSize != null) {
1012       builder.setTextSize(textSize);
1013     }
1014     if (textSizeUnit != null) {
1015       builder.setTextSizeUnit(textSizeUnit);
1016     }
1017     if (textColor != null) {
1018       builder.setTextColor(textColor);
1019     }
1020     if (backgroundDrawableColor != null) {
1021       builder.setBackgroundDrawableColor(backgroundDrawableColor);
1022     }
1023     if (typefaceStyle != null) {
1024       builder.setTypefaceStyle(typefaceStyle);
1025     }
1026     builder.setEnabled(enabled);
1027     if (labeledById != null) {
1028       builder.setLabeledById(labeledById);
1029     }
1030     if (accessibilityClassName != null) {
1031       builder.setAccessibilityClassName(accessibilityClassName.toString());
1032     }
1033     if (accessibilityTraversalBeforeId != null) {
1034       builder.setAccessibilityTraversalBeforeId(accessibilityTraversalBeforeId);
1035     }
1036     if (accessibilityTraversalAfterId != null) {
1037       builder.setAccessibilityTraversalAfterId(accessibilityTraversalAfterId);
1038     }
1039     if (drawingOrder != null) {
1040       builder.setDrawingOrder(drawingOrder);
1041     }
1042     if (layoutParams != null) {
1043       builder.setLayoutParams(layoutParams.toProto());
1044     }
1045     if (!TextUtils.isEmpty(hintText)) {
1046       builder.setHintText(hintText.toProto());
1047     }
1048     if (hintTextColor != null) {
1049       builder.setHintTextColor(hintTextColor);
1050     }
1051 
1052     builder.addAllSuperclasses(superclassViews);
1053     for (ViewHierarchyAction action : actionList) {
1054       builder.addActions(action.toProto());
1055     }
1056     for (Rect rect : textCharacterLocations) {
1057       builder.addTextCharacterLocations(rect.toProto());
1058     }
1059     return builder.build();
1060   }
1061 
1062   /** Set the containing {@link WindowHierarchyElement} of this view. */
setWindow(WindowHierarchyElement window)1063   void setWindow(WindowHierarchyElement window) {
1064     this.windowElement = window;
1065   }
1066 
1067   /**
1068    * @param child The child {@link ViewHierarchyElement} to add as a child of this view
1069    */
addChild(ViewHierarchyElement child)1070   void addChild(ViewHierarchyElement child) {
1071     if (childIds == null) {
1072       childIds = new ArrayList<>();
1073     }
1074     childIds.add(child.id);
1075   }
1076 
1077   /**
1078    * Denotes that {@code labelingElement} acts as a label for this element
1079    *
1080    * @param labelingElement The element that labels this element, or {@code null} if this element is
1081    *     not labeled by another
1082    */
setLabeledBy(ViewHierarchyElement labelingElement)1083   void setLabeledBy(ViewHierarchyElement labelingElement) {
1084     labeledById = (labelingElement != null) ? labelingElement.getCondensedUniqueId() : null;
1085   }
1086 
1087   /**
1088    * Sets a view before which this one is visited in accessibility traversal. A screen-reader must
1089    * visit the content of this view before the content of the one it precedes.
1090    */
setAccessibilityTraversalBefore(ViewHierarchyElement element)1091   void setAccessibilityTraversalBefore(ViewHierarchyElement element) {
1092     accessibilityTraversalBeforeId = element.getCondensedUniqueId();
1093   }
1094 
1095   /**
1096    * Sets a view after which this one is visited in accessibility traversal. A screen-reader must
1097    * visit the content of the other view before the content of this one.
1098    */
setAccessibilityTraversalAfter(ViewHierarchyElement element)1099   void setAccessibilityTraversalAfter(ViewHierarchyElement element) {
1100     accessibilityTraversalAfterId = element.getCondensedUniqueId();
1101   }
1102 
1103   /**
1104    * Adds a Rect to this element's list of {@code TouchDelegate} hit-Rect bounds, indicating it may
1105    * receive touches via another element within these bounds.
1106    *
1107    * <p>NOTE: This should be invoked only on instances from an AccessibilityHierarchy built from
1108    * {@link android.view.accessibility.AccessibilityNodeInfo}. Hierarchies built from other
1109    * structures expect this field to be Immutable after construction.
1110    */
addTouchDelegateBounds(Rect bounds)1111   void addTouchDelegateBounds(Rect bounds) {
1112     touchDelegateBounds.add(bounds);
1113   }
1114 
getViewHierarchyElementById(@ullable Long id)1115   private @Nullable ViewHierarchyElement getViewHierarchyElementById(@Nullable Long id) {
1116     return (id != null) ? getWindow().getAccessibilityHierarchy().getViewById(id) : null;
1117   }
1118 
1119   /** Determines the type of content that produced a ViewHierarchyElement. */
computeOrigin( @ullable CharSequence className, @Nullable ViewHierarchyElement parent)1120   protected static ViewHierarchyElementOrigin computeOrigin(
1121       @Nullable CharSequence className, @Nullable ViewHierarchyElement parent) {
1122     if ((parent == null) || TextUtils.equals(className, VIEW_FACTORY_HOLDER_CLASS_NAME)) {
1123       return ViewHierarchyElementOrigin.VIEW;
1124     }
1125     CharSequence parentClassName = parent.getClassName();
1126     if (TextUtils.equals(
1127         parentClassName, ViewHierarchyElementUtils.ANDROID_COMPOSE_VIEW_CLASS_NAME)) {
1128       return ViewHierarchyElementOrigin.COMPOSE;
1129     }
1130     if (TextUtils.equals(parentClassName, ViewHierarchyElementUtils.FLUTTER_VIEW_CLASS_NAME)) {
1131       return ViewHierarchyElementOrigin.FLUTTER;
1132     }
1133     if (TextUtils.equals(parentClassName, ViewHierarchyElementUtils.WEB_VIEW_CLASS_NAME)) {
1134       return ViewHierarchyElementOrigin.WEB;
1135     }
1136     return parent.getOrigin();
1137   }
1138 
propertiesEquals(ViewHierarchyElement element)1139   private boolean propertiesEquals(ViewHierarchyElement element) {
1140     return (getCondensedUniqueId() == element.getCondensedUniqueId())
1141         && (getChildViewCount() == element.getChildViewCount())
1142         && TextUtils.equals(getPackageName(), element.getPackageName())
1143         && TextUtils.equals(getClassName(), element.getClassName())
1144         && TextUtils.equals(getResourceName(), element.getResourceName())
1145         && TextUtils.equals(getTestTag(), element.getTestTag())
1146         && isImportantForAccessibility() == element.isImportantForAccessibility()
1147         && TextUtils.equals(getContentDescription(), element.getContentDescription())
1148         && TextUtils.equals(getText(), element.getText())
1149         && TextUtils.equals(getStateDescription(), element.getStateDescription())
1150         && Objects.equals(getTextColor(), element.getTextColor())
1151         && Objects.equals(getBackgroundDrawableColor(), element.getBackgroundDrawableColor())
1152         && Objects.equals(isVisibleToUser(), element.isVisibleToUser())
1153         && (isClickable() == element.isClickable())
1154         && (isLongClickable() == element.isLongClickable())
1155         && (isFocusable() == element.isFocusable())
1156         && Objects.equals(isEditable(), element.isEditable())
1157         && Objects.equals(isScrollable(), element.isScrollable())
1158         && Objects.equals(canScrollForward(), element.canScrollForward())
1159         && Objects.equals(canScrollBackward(), element.canScrollBackward())
1160         && Objects.equals(isCheckable(), element.isCheckable())
1161         && Objects.equals(isChecked(), element.isChecked())
1162         && Objects.equals(hasTouchDelegate(), element.hasTouchDelegate())
1163         && (isScreenReaderFocusable() == element.isScreenReaderFocusable())
1164         && Objects.equals(getTouchDelegateBounds(), element.getTouchDelegateBounds())
1165         && Objects.equals(getBoundsInScreen(), element.getBoundsInScreen())
1166         && Objects.equals(getNonclippedWidth(), element.getNonclippedWidth())
1167         && Objects.equals(getNonclippedHeight(), element.getNonclippedHeight())
1168         && Objects.equals(getTextSize(), element.getTextSize())
1169         && Objects.equals(getTextSizeUnit(), element.getTextSizeUnit())
1170         && Objects.equals(getTypefaceStyle(), element.getTypefaceStyle())
1171         && (isEnabled() == element.isEnabled())
1172         && condensedUniqueIdEquals(getLabeledBy(), element.getLabeledBy())
1173         && TextUtils.equals(getAccessibilityClassName(), element.getAccessibilityClassName())
1174         && condensedUniqueIdEquals(
1175             getAccessibilityTraversalAfter(), element.getAccessibilityTraversalAfter())
1176         && condensedUniqueIdEquals(
1177             getAccessibilityTraversalBefore(), element.getAccessibilityTraversalBefore())
1178         && Objects.equals(getDrawingOrder(), element.getDrawingOrder())
1179         && Objects.equals(getLayoutParams(), element.getLayoutParams())
1180         && TextUtils.equals(getHintText(), element.getHintText())
1181         && Objects.equals(getHintTextColor(), element.getHintTextColor())
1182         && Objects.equals(getTextCharacterLocations(), element.getTextCharacterLocations());
1183   }
1184 
condensedUniqueIdEquals( @ullable ViewHierarchyElement ve1, @Nullable ViewHierarchyElement ve2)1185   private static boolean condensedUniqueIdEquals(
1186       @Nullable ViewHierarchyElement ve1, @Nullable ViewHierarchyElement ve2) {
1187     return (ve1 == null)
1188         ? (ve2 == null)
1189         : ((ve2 != null) && (ve1.getCondensedUniqueId() == ve2.getCondensedUniqueId()));
1190   }
1191 }
1192