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