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 * < {@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