1 /* <lambda>null2 * Copyright 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.test.uiautomator.internal 18 19 import android.graphics.Rect 20 import android.os.Build 21 import android.view.accessibility.AccessibilityNodeInfo 22 import androidx.test.uiautomator.boundsInScreen 23 import androidx.test.uiautomator.children 24 25 /** A snapshot of an accessibility node info at a certain moment in time. */ 26 internal data class ViewNode( 27 val depth: Int, 28 val text: String, 29 val viewIdResourceName: String, 30 val className: String, 31 val packageName: String, 32 val contentDescription: String, 33 val isCheckable: Boolean, 34 val isChecked: Boolean, 35 val isClickable: Boolean, 36 val isEnabled: Boolean, 37 val isFocusable: Boolean, 38 val isFocused: Boolean, 39 val isScrollable: Boolean, 40 val isLongClickable: Boolean, 41 val isPassword: Boolean, 42 val isSelected: Boolean, 43 val bounds: Rect, 44 val childCount: Int, 45 val children: Set<ViewNode>, 46 val hintText: String, 47 val isLeaf: Boolean, 48 val drawingOrderInParent: Int, 49 val accessibilityNodeInfo: AccessibilityNodeInfo, 50 ) { 51 companion object { 52 53 fun fromAccessibilityNodeInfo( 54 depth: Int, 55 node: AccessibilityNodeInfo, 56 displayRect: Rect, 57 ): ViewNode { 58 59 val children = node.children() 60 61 val childrenViewNodes = 62 children 63 .map { child -> 64 val childNode = 65 fromAccessibilityNodeInfo( 66 node = child, 67 displayRect = displayRect, 68 depth = depth + 1 69 ) 70 @Suppress("DEPRECATION") child.recycle() 71 childNode 72 } 73 .sortedBy { it.drawingOrderInParent } 74 75 val hintText = 76 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 77 node.hintText?.toString() ?: "" 78 } else "" 79 val drawingOrderInParent = 80 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 81 node.drawingOrder 82 } else 0 83 84 fun CharSequence?.orBlank() = this.toString() 85 return ViewNode( 86 depth = depth, 87 text = node.text.orBlank(), 88 viewIdResourceName = node.viewIdResourceName.orBlank(), 89 className = node.className.orBlank(), 90 packageName = node.packageName.orBlank(), 91 contentDescription = node.contentDescription.orBlank(), 92 hintText = hintText, 93 isCheckable = node.isCheckable, 94 isChecked = node.isChecked, 95 isClickable = node.isClickable, 96 isEnabled = node.isEnabled, 97 isFocusable = node.isFocusable, 98 isFocused = node.isFocused, 99 isScrollable = node.isScrollable, 100 isLongClickable = node.isLongClickable, 101 isPassword = node.isPassword, 102 isSelected = node.isSelected, 103 childCount = node.childCount, 104 bounds = node.boundsInScreen(), 105 children = childrenViewNodes.toSet(), 106 isLeaf = children.isEmpty(), 107 drawingOrderInParent = drawingOrderInParent, 108 accessibilityNodeInfo = node, 109 ) 110 } 111 } 112 113 override fun equals(other: Any?): Boolean { 114 if (this === other) return true 115 if (javaClass != other?.javaClass) return false 116 117 other as ViewNode 118 119 if (depth != other.depth) return false 120 if (text != other.text) return false 121 if (viewIdResourceName != other.viewIdResourceName) return false 122 if (className != other.className) return false 123 if (packageName != other.packageName) return false 124 if (contentDescription != other.contentDescription) return false 125 if (isCheckable != other.isCheckable) return false 126 if (isChecked != other.isChecked) return false 127 if (isClickable != other.isClickable) return false 128 if (isEnabled != other.isEnabled) return false 129 if (isFocusable != other.isFocusable) return false 130 if (isFocused != other.isFocused) return false 131 if (isScrollable != other.isScrollable) return false 132 if (isLongClickable != other.isLongClickable) return false 133 if (isPassword != other.isPassword) return false 134 if (isSelected != other.isSelected) return false 135 if (bounds != other.bounds) return false 136 if (childCount != other.childCount) return false 137 if (children != other.children) return false 138 if (hintText != other.hintText) return false 139 if (isLeaf != other.isLeaf) return false 140 if (drawingOrderInParent != other.drawingOrderInParent) return false 141 142 return true 143 } 144 145 override fun hashCode(): Int { 146 var result = depth 147 result = 31 * result + text.hashCode() 148 result = 31 * result + viewIdResourceName.hashCode() 149 result = 31 * result + className.hashCode() 150 result = 31 * result + packageName.hashCode() 151 result = 31 * result + contentDescription.hashCode() 152 result = 31 * result + isCheckable.hashCode() 153 result = 31 * result + isChecked.hashCode() 154 result = 31 * result + isClickable.hashCode() 155 result = 31 * result + isEnabled.hashCode() 156 result = 31 * result + isFocusable.hashCode() 157 result = 31 * result + isFocused.hashCode() 158 result = 31 * result + isScrollable.hashCode() 159 result = 31 * result + isLongClickable.hashCode() 160 result = 31 * result + isPassword.hashCode() 161 result = 31 * result + isSelected.hashCode() 162 result = 31 * result + bounds.hashCode() 163 result = 31 * result + childCount 164 result = 31 * result + children.hashCode() 165 result = 31 * result + hintText.hashCode() 166 result = 31 * result + isLeaf.hashCode() 167 result = 31 * result + drawingOrderInParent 168 return result 169 } 170 } 171