-
-
Notifications
You must be signed in to change notification settings - Fork 475
Expand file tree
/
Copy pathSentryLayoutNodeHelper.kt
More file actions
87 lines (77 loc) · 2.83 KB
/
Copy pathSentryLayoutNodeHelper.kt
File metadata and controls
87 lines (77 loc) · 2.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@file:Suppress(
"INVISIBLE_MEMBER",
"INVISIBLE_REFERENCE",
"EXPOSED_PARAMETER_TYPE",
"EXPOSED_RETURN_TYPE",
"EXPOSED_FUNCTION_RETURN_TYPE",
)
package io.sentry.android.replay.viewhierarchy
import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.node.NodeCoordinator
import io.sentry.util.CompileOnlyCompat.CompileOnlyCall
import java.lang.reflect.Method
/**
* Provides access to internal LayoutNode members that are subject to Kotlin name-mangling.
*
* This class is not thread-safe, as Compose UI operations are expected to be performed on the main
* thread.
*
* Compiled against Compose >= 1.10 where the mangled names use the "ui" module suffix (e.g.
* getChildren$ui()). For apps still on Compose < 1.10 (where the suffix is "$ui_release"), the
* direct call will throw [NoSuchMethodError] and we fall back to reflection-based accessors that
* are resolved and cached on first use.
*/
internal object SentryLayoutNodeHelper {
private class Fallback(val getChildren: Method?, val getOuterCoordinator: Method?)
private var useFallback: Boolean? = null
private var fallback: Fallback? = null
private fun tryResolve(clazz: Class<*>, name: String): Method? {
return try {
clazz.getDeclaredMethod(name).apply { isAccessible = true }
} catch (_: NoSuchMethodException) {
null
}
}
@Suppress("UNCHECKED_CAST")
fun getChildren(node: LayoutNode): List<LayoutNode> {
when (useFallback) {
false -> return node.children
true -> return getFallback().getChildren!!.invoke(node) as List<LayoutNode>
null ->
return CompileOnlyCall { node.children.also { useFallback = false } }
.ifAbsent {
useFallback = true
getFallback().getChildren!!.invoke(node) as List<LayoutNode>
}
}
}
fun isTransparent(node: LayoutNode): Boolean {
when (useFallback) {
false -> return node.outerCoordinator.isTransparent()
true -> {
val fb = getFallback()
val coordinator = fb.getOuterCoordinator!!.invoke(node) as NodeCoordinator
return coordinator.isTransparent()
}
null ->
return CompileOnlyCall {
node.outerCoordinator.isTransparent().also { useFallback = false }
}
.ifAbsent {
useFallback = true
val fb = getFallback()
val coordinator = fb.getOuterCoordinator!!.invoke(node) as NodeCoordinator
coordinator.isTransparent()
}
}
}
private fun getFallback(): Fallback {
fallback?.let {
return it
}
val layoutNodeClass = LayoutNode::class.java
val getChildren = tryResolve(layoutNodeClass, "getChildren\$ui_release")
val getOuterCoordinator = tryResolve(layoutNodeClass, "getOuterCoordinator\$ui_release")
return Fallback(getChildren, getOuterCoordinator).also { fallback = it }
}
}