@onekeyfe/react-native-native-overlay 3.0.162-alpha.270 → 6.7.0-11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/android/build.gradle +6 -0
  2. package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolation.kt +37 -0
  3. package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAnimation.kt +5 -10
  4. package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayEntryRootView.kt +23 -2
  5. package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayHost.kt +31 -32
  6. package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostView.kt +21 -0
  7. package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayView.kt +10 -3
  8. package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolationTest.kt +112 -0
  9. package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayBackTest.kt +295 -0
  10. package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayMotionTest.kt +101 -0
  11. package/docs/SPEC.md +198 -30
  12. package/ios/NativeOverlayContainerView.swift +20 -7
  13. package/ios/NativeOverlaySheetController.swift +37 -1
  14. package/lib/module/OverlayPageHost.web.js +19 -6
  15. package/lib/module/OverlayPageHost.web.js.map +1 -1
  16. package/lib/module/OverlayStore.js +13 -1
  17. package/lib/module/OverlayStore.js.map +1 -1
  18. package/lib/module/OverlayView.web.js +107 -25
  19. package/lib/module/OverlayView.web.js.map +1 -1
  20. package/lib/module/useOverlayController.js +11 -3
  21. package/lib/module/useOverlayController.js.map +1 -1
  22. package/lib/module/web/animateTransition.js +20 -12
  23. package/lib/module/web/animateTransition.js.map +1 -1
  24. package/lib/module/web/overlayLayers.js +27 -9
  25. package/lib/module/web/overlayLayers.js.map +1 -1
  26. package/lib/module/web/useKeyboardShift.js +1 -1
  27. package/lib/module/web/useKeyboardShift.js.map +1 -1
  28. package/lib/module/web/useSheetDrag.js +22 -2
  29. package/lib/module/web/useSheetDrag.js.map +1 -1
  30. package/lib/typescript/src/OverlayPageHost.web.d.ts.map +1 -1
  31. package/lib/typescript/src/OverlayStore.d.ts +2 -0
  32. package/lib/typescript/src/OverlayStore.d.ts.map +1 -1
  33. package/lib/typescript/src/OverlayView.web.d.ts.map +1 -1
  34. package/lib/typescript/src/useOverlayController.d.ts.map +1 -1
  35. package/lib/typescript/src/web/animateTransition.d.ts +2 -2
  36. package/lib/typescript/src/web/animateTransition.d.ts.map +1 -1
  37. package/lib/typescript/src/web/overlayLayers.d.ts +3 -0
  38. package/lib/typescript/src/web/overlayLayers.d.ts.map +1 -1
  39. package/lib/typescript/src/web/useSheetDrag.d.ts.map +1 -1
  40. package/package.json +2 -1
  41. package/src/OverlayPageHost.web.tsx +24 -9
  42. package/src/OverlayStore.ts +13 -1
  43. package/src/OverlayView.web.tsx +197 -56
  44. package/src/useOverlayController.ts +11 -4
  45. package/src/web/animateTransition.ts +30 -11
  46. package/src/web/overlayLayers.ts +48 -10
  47. package/src/web/useKeyboardShift.ts +1 -1
  48. package/src/web/useSheetDrag.ts +23 -2
@@ -60,6 +60,10 @@ android {
60
60
  ]
61
61
  }
62
62
  }
63
+
64
+ testOptions {
65
+ unitTests.includeAndroidResources = true
66
+ }
63
67
  }
64
68
 
65
69
  repositories {
@@ -73,6 +77,8 @@ dependencies {
73
77
  implementation "com.facebook.react:react-android"
74
78
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
75
79
  implementation "com.google.android.material:material:1.14.0-alpha05"
80
+ testImplementation "junit:junit:4.13.2"
81
+ testImplementation "org.robolectric:robolectric:4.17"
76
82
  }
77
83
 
78
84
  react {
@@ -0,0 +1,37 @@
1
+ package com.onekey.nativeoverlay
2
+
3
+ import android.view.View
4
+ import android.view.ViewGroup
5
+
6
+ /** Restores the caller's flags when a blocking overlay no longer covers it. */
7
+ internal class NativeOverlayAccessibilityIsolation {
8
+ private val hidden = mutableMapOf<View, Int>()
9
+
10
+ fun update(
11
+ root: ViewGroup,
12
+ activeContainer: View?,
13
+ entriesBelow: List<View>,
14
+ globalHost: View,
15
+ ) {
16
+ val below = mutableSetOf<View>()
17
+ if (activeContainer != null) {
18
+ var node: View = activeContainer
19
+ while (node !== root && node.parent is ViewGroup) {
20
+ val parent = node.parent as ViewGroup
21
+ for (index in 0 until parent.childCount) {
22
+ val sibling = parent.getChildAt(index)
23
+ if (sibling !== node && sibling !== globalHost) below.add(sibling)
24
+ }
25
+ node = parent
26
+ }
27
+ if (node === root) below.addAll(entriesBelow) else below.clear()
28
+ }
29
+ hidden.keys.filter { it !in below }.forEach { view ->
30
+ view.importantForAccessibility = hidden.remove(view) ?: View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
31
+ }
32
+ below.forEach { view ->
33
+ if (view !in hidden) hidden[view] = view.importantForAccessibility
34
+ view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
35
+ }
36
+ }
37
+ }
@@ -69,8 +69,9 @@ internal sealed class OverlayMotion {
69
69
  }
70
70
 
71
71
  /**
72
- * Analytic step response of `m·x'' + c·x' + k·x = 0`, identical to
73
- * Reanimated and `springProgressAt` in JS. Driving one ValueAnimator with it
72
+ * Analytic step response of `m·x'' + c·x' + k·x = 0`, with overdamped
73
+ * responses capped at critical damping to match UIKit's sheet response.
74
+ * Driving one ValueAnimator with it
74
75
  * keeps alpha, translation, scale and backdrop on the same clock; androidx
75
76
  * SpringAnimation would need one animation per property.
76
77
  */
@@ -89,19 +90,13 @@ internal class SpringInterpolator(private val spring: OverlayMotion.Spring) : Ti
89
90
 
90
91
  fun progressAt(spring: OverlayMotion.Spring, t: Double): Double {
91
92
  val w0 = sqrt(spring.stiffness / spring.mass)
92
- val zeta = spring.damping / (2 * sqrt(spring.stiffness * spring.mass))
93
+ val zeta = minOf(1.0, spring.damping / (2 * sqrt(spring.stiffness * spring.mass)))
93
94
  return when {
94
95
  zeta < 1 -> {
95
96
  val wd = w0 * sqrt(1 - zeta * zeta)
96
97
  1 - exp(-zeta * w0 * t) * (cos(wd * t) + (zeta * w0 / wd) * sin(wd * t))
97
98
  }
98
- zeta == 1.0 -> 1 - exp(-w0 * t) * (1 + w0 * t)
99
- else -> {
100
- val root = sqrt(zeta * zeta - 1)
101
- val r1 = -w0 * (zeta - root)
102
- val r2 = -w0 * (zeta + root)
103
- 1 - (r2 * exp(r1 * t) - r1 * exp(r2 * t)) / (r2 - r1)
104
- }
99
+ else -> 1 - exp(-w0 * t) * (1 + w0 * t)
105
100
  }
106
101
  }
107
102
 
@@ -60,7 +60,12 @@ internal class NativeOverlayEntryRootView(
60
60
  * below), non-blocking ones let it continue past the empty area.
61
61
  */
62
62
  override val pointerEvents: PointerEvents
63
- get() = if (blocking && isShownForInput) PointerEvents.AUTO else PointerEvents.BOX_NONE
63
+ get() = when {
64
+ blocking && isClosing -> PointerEvents.BOX_ONLY
65
+ isClosing -> PointerEvents.NONE
66
+ blocking && isShownForInput -> PointerEvents.AUTO
67
+ else -> PointerEvents.BOX_NONE
68
+ }
64
69
  var stackOrder = 0
65
70
  var levelOrder = 0
66
71
  /** Page scope: the owning page, used by the page host to hide the entry. */
@@ -74,6 +79,19 @@ internal class NativeOverlayEntryRootView(
74
79
  var dismissOnBackPress = true
75
80
  var dismissOnBackdropPress = false
76
81
  var isShownForInput = false
82
+ private var contentAccessibilityBeforeClose: Int? = null
83
+ var isClosing = false
84
+ set(value) {
85
+ if (field == value) return
86
+ field = value
87
+ if (value) {
88
+ contentAccessibilityBeforeClose = contentView.importantForAccessibility
89
+ contentView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
90
+ } else {
91
+ contentAccessibilityBeforeClose?.let { contentView.importantForAccessibility = it }
92
+ contentAccessibilityBeforeClose = null
93
+ }
94
+ }
77
95
  var onRequestDismiss: ((String) -> Unit)? = null
78
96
 
79
97
  val backdropView = View(reactContext).apply { alpha = 0f }
@@ -90,12 +108,13 @@ internal class NativeOverlayEntryRootView(
90
108
  /** `sheet` presentation: the draggable surface; otherwise null. */
91
109
  var sheetView: FrameLayout? = null
92
110
  private set
111
+ private var sheetAnimationView: CoordinatorLayout? = null
93
112
  private var sheetBehavior: BottomSheetBehavior<FrameLayout>? = null
94
113
  private var handleView: View? = null
95
114
  var onSheetHiddenByPan: (() -> Unit)? = null
96
115
 
97
116
  /** The view that enter / exit animations move. */
98
- val animatedView: View get() = sheetView ?: contentView
117
+ val animatedView: View get() = sheetAnimationView ?: contentView
99
118
 
100
119
  private val touchDispatcher = JSTouchDispatcher(this)
101
120
  private val pointerDispatcher = if (ReactFeatureFlags.dispatchPointerEvents) {
@@ -220,6 +239,7 @@ internal class NativeOverlayEntryRootView(
220
239
  },
221
240
  )
222
241
  sheetView = sheet
242
+ sheetAnimationView = coordinator
223
243
  sheetBehavior = behavior
224
244
  handleView = handle
225
245
  return sheet
@@ -350,6 +370,7 @@ internal class NativeOverlayEntryRootView(
350
370
  }
351
371
 
352
372
  override fun dispatchTouchEvent(event: MotionEvent): Boolean {
373
+ if (isClosing) return blocking
353
374
  if (!isShownForInput) return false
354
375
  if (event.actionMasked == MotionEvent.ACTION_DOWN) {
355
376
  // Non-blocking overlays (toasts) only take touches that land on content.
@@ -2,13 +2,13 @@ package com.onekey.nativeoverlay
2
2
 
3
3
  import android.app.Activity
4
4
  import android.view.KeyEvent
5
- import android.view.View
6
5
  import android.view.ViewGroup
7
6
  import android.view.Window
8
7
  import android.widget.FrameLayout
9
8
  import androidx.activity.ComponentActivity
10
9
  import androidx.activity.OnBackPressedCallback
11
10
  import java.util.WeakHashMap
11
+ import java.lang.ref.WeakReference
12
12
 
13
13
  /** Mirrors `OVERLAY_LEVEL_ORDER`. */
14
14
  internal enum class NativeOverlayLevel(val order: Int) {
@@ -49,12 +49,12 @@ internal class NativeOverlayHost private constructor(
49
49
  )
50
50
  private val backCallback = object : OnBackPressedCallback(false) {
51
51
  override fun handleOnBackPressed() {
52
- topBlockingEntry()?.let { entry ->
53
- if (entry.dismissOnBackPress) entry.onRequestDismiss?.invoke("back")
52
+ topBackEntry()?.let { entry ->
53
+ if (!entry.isClosing && entry.dismissOnBackPress) entry.onRequestDismiss?.invoke("back")
54
54
  }
55
55
  }
56
56
  }
57
- private val hiddenForAccessibility = mutableMapOf<View, Int>()
57
+ private val accessibilityIsolation = NativeOverlayAccessibilityIsolation()
58
58
 
59
59
  init {
60
60
  clipChildren = false
@@ -67,8 +67,8 @@ internal class NativeOverlayHost private constructor(
67
67
  * predictive back, so the dispatcher alone never runs first.
68
68
  */
69
69
  fun handleBackKey(event: KeyEvent): Boolean {
70
- val entry = topBlockingEntry() ?: return false
71
- if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && entry.dismissOnBackPress) {
70
+ val entry = topBackEntry() ?: return false
71
+ if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && !entry.isClosing && entry.dismissOnBackPress) {
72
72
  entry.onRequestDismiss?.invoke("back")
73
73
  }
74
74
  return true
@@ -109,14 +109,14 @@ internal class NativeOverlayHost private constructor(
109
109
  }
110
110
 
111
111
  fun onEntriesChanged() {
112
- val hasBlocking = topBlockingEntry() != null
113
- if (hasBlocking && !backCallback.isEnabled) {
112
+ val hasBackEntry = topBackEntry() != null
113
+ if (hasBackEntry && !backCallback.isEnabled) {
114
114
  // The most recently added callback wins; re-add so ours runs before
115
115
  // ReactActivity's and react-native-screens' callbacks.
116
116
  backCallback.remove()
117
117
  (activity as? ComponentActivity)?.onBackPressedDispatcher?.addCallback(backCallback)
118
118
  }
119
- backCallback.isEnabled = hasBlocking
119
+ backCallback.isEnabled = hasBackEntry
120
120
  updateAccessibility()
121
121
  }
122
122
 
@@ -134,41 +134,40 @@ internal class NativeOverlayHost private constructor(
134
134
  }
135
135
 
136
136
  /** Global overlays win; otherwise the topmost visible page overlay. */
137
- private fun topBlockingEntry(): NativeOverlayEntryRootView? =
138
- orderedEntries().lastOrNull { it.blocking && it.isShownForInput }
139
- ?: NativeOverlayPageHostView.allHosts()
140
- .filter { it.isAttachedToWindow && it.isShown }
137
+ private fun topBackEntry(): NativeOverlayEntryRootView? =
138
+ orderedEntries().lastOrNull {
139
+ (it.blocking || it.dismissOnBackPress) && it.isShownForInput && (!it.isClosing || it.blocking)
140
+ }
141
+ ?: shownPageHosts()
141
142
  .flatMap { it.shownEntries() }
142
- .lastOrNull { it.blocking }
143
+ .lastOrNull { (it.blocking || it.dismissOnBackPress) && (!it.isClosing || it.blocking) }
144
+
145
+ private fun shownPageHosts(): List<NativeOverlayPageHostView> =
146
+ NativeOverlayPageHostView.allHosts().filter {
147
+ it.isAttachedToWindow && it.isShown && it.rootView === activity.window.decorView
148
+ }
143
149
 
144
150
  /** TalkBack only reads the topmost blocking overlay and what is above it. */
145
151
  private fun updateAccessibility() {
146
152
  val entries = orderedEntries()
147
153
  val topIndex = entries.indexOfLast { it.blocking && it.isShownForInput }
148
- val content = parent as? ViewGroup
149
- val below = mutableListOf<View>()
154
+ val content = activity.findViewById<ViewGroup>(android.R.id.content) ?: return
150
155
  if (topIndex >= 0) {
151
- content?.let { group ->
152
- (0 until group.childCount).map(group::getChildAt).filter { it !== this }.forEach(below::add)
153
- }
154
- below.addAll(entries.subList(0, topIndex))
155
- }
156
- val restore = hiddenForAccessibility.keys.filter { it !in below }
157
- restore.forEach { view ->
158
- view.importantForAccessibility = hiddenForAccessibility.remove(view) ?: IMPORTANT_FOR_ACCESSIBILITY_AUTO
159
- }
160
- below.forEach { view ->
161
- if (view !in hiddenForAccessibility) {
162
- hiddenForAccessibility[view] = view.importantForAccessibility
163
- view.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
164
- }
156
+ accessibilityIsolation.update(content, this, entries.take(topIndex), this)
157
+ return
165
158
  }
159
+ val pageEntry = shownPageHosts().flatMap { it.shownEntries() }.lastOrNull { it.blocking }
160
+ val pageHost = pageEntry?.parent as? NativeOverlayPageHostView
161
+ val below = pageHost?.shownEntries()?.takeWhile { it !== pageEntry }.orEmpty()
162
+ accessibilityIsolation.update(content, pageHost, below, this)
166
163
  }
167
164
 
168
165
  companion object {
169
- private val hosts = WeakHashMap<Activity, NativeOverlayHost>()
166
+ private val hosts = WeakHashMap<Activity, WeakReference<NativeOverlayHost>>()
170
167
 
171
168
  fun of(activity: Activity): NativeOverlayHost =
172
- hosts.getOrPut(activity) { NativeOverlayHost(activity) }
169
+ hosts[activity]?.get() ?: NativeOverlayHost(activity).also {
170
+ hosts[activity] = WeakReference(it)
171
+ }
173
172
  }
174
173
  }
@@ -3,6 +3,7 @@ package com.onekey.nativeoverlay
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
5
  import android.view.ViewGroup
6
+ import com.facebook.react.bridge.ReactContext
6
7
  import com.facebook.react.uimanager.PointerEvents
7
8
  import com.facebook.react.uimanager.ReactPointerEventsView
8
9
  import java.lang.ref.WeakReference
@@ -33,6 +34,7 @@ class NativeOverlayPageHostView(context: Context) : ViewGroup(context), ReactPoi
33
34
  if (owners == suspendedOwners) return
34
35
  suspendedOwners = owners
35
36
  entries().forEach(::applySuspension)
37
+ onEntriesChanged()
36
38
  }
37
39
 
38
40
  internal fun attach(entry: NativeOverlayEntryRootView) {
@@ -44,10 +46,12 @@ class NativeOverlayPageHostView(context: Context) : ViewGroup(context), ReactPoi
44
46
  }
45
47
  layoutEntries()
46
48
  applySuspension(entry)
49
+ onEntriesChanged()
47
50
  }
48
51
 
49
52
  internal fun detach(entry: NativeOverlayEntryRootView) {
50
53
  removeView(entry)
54
+ onEntriesChanged()
51
55
  }
52
56
 
53
57
  /** Visible, input-ready entries in bottom-to-top order. */
@@ -56,6 +60,23 @@ class NativeOverlayPageHostView(context: Context) : ViewGroup(context), ReactPoi
56
60
 
57
61
  fun invalidateHost() {
58
62
  if (hostKey.isNotEmpty() && registry[hostKey]?.get() === this) registry.remove(hostKey)
63
+ onEntriesChanged()
64
+ }
65
+
66
+ override fun onAttachedToWindow() {
67
+ super.onAttachedToWindow()
68
+ onEntriesChanged()
69
+ }
70
+
71
+ override fun onDetachedFromWindow() {
72
+ super.onDetachedFromWindow()
73
+ onEntriesChanged()
74
+ }
75
+
76
+ private fun onEntriesChanged() {
77
+ (context as? ReactContext)?.currentActivity?.let {
78
+ NativeOverlayHost.of(it).onEntriesChanged()
79
+ }
59
80
  }
60
81
 
61
82
  private var layoutScheduled = false
@@ -135,6 +135,7 @@ class NativeOverlayView(
135
135
  config = OverlayAnimationConfig.parse(animationConfig)
136
136
  }
137
137
  entry?.let(::applyEntryConfiguration)
138
+ notifyHostEntriesChanged()
138
139
  if (visible) {
139
140
  cycleOpen = true
140
141
  if (phase == Phase.HIDDEN || phase == Phase.EXITING) present()
@@ -199,8 +200,9 @@ class NativeOverlayView(
199
200
  }
200
201
  cancelAnimators()
201
202
  phase = Phase.ENTERING
203
+ target.isClosing = false
202
204
  target.isShownForInput = true
203
- host?.onEntriesChanged()
205
+ notifyHostEntriesChanged()
204
206
  if (blocking) {
205
207
  target.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
206
208
  }
@@ -225,8 +227,8 @@ class NativeOverlayView(
225
227
  val target = entry ?: return finishDismiss("programmatic")
226
228
  cancelAnimators()
227
229
  phase = Phase.EXITING
228
- target.isShownForInput = false
229
- host?.onEntriesChanged()
230
+ target.isClosing = true
231
+ notifyHostEntriesChanged()
230
232
  val transition = config.exit.reduceMotionAdjusted(isReduceMotionEnabled())
231
233
  if (transition.kind == "none") return finishDismiss("programmatic")
232
234
  animate(target, hiddenState(target, transition), 0f, transition.motion) {
@@ -240,6 +242,7 @@ class NativeOverlayView(
240
242
  phase = Phase.HIDDEN
241
243
  entry?.let { target ->
242
244
  target.isShownForInput = false
245
+ target.isClosing = false
243
246
  stageContent()
244
247
  host?.detach(target)
245
248
  pageHost?.detach(target)
@@ -389,6 +392,10 @@ class NativeOverlayView(
389
392
  }
390
393
  }
391
394
 
395
+ private fun notifyHostEntriesChanged() {
396
+ reactContext.currentActivity?.let { NativeOverlayHost.of(it).onEntriesChanged() }
397
+ }
398
+
392
399
  /**
393
400
  * While hidden, keep the React child inside this invisible view. Fabric only
394
401
  * reports onLayout for attached views, and fitted sheets size themselves
@@ -0,0 +1,112 @@
1
+ package com.onekey.nativeoverlay
2
+
3
+ import android.view.View
4
+ import android.widget.FrameLayout
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Test
7
+ import org.junit.runner.RunWith
8
+ import org.robolectric.RobolectricTestRunner
9
+ import org.robolectric.RuntimeEnvironment
10
+ import org.robolectric.annotation.Config
11
+
12
+ @RunWith(RobolectricTestRunner::class)
13
+ @Config(sdk = [34])
14
+ class NativeOverlayAccessibilityIsolationTest {
15
+ private val context get() = RuntimeEnvironment.getApplication()
16
+
17
+ private class Tree(context: android.content.Context) {
18
+ val root = FrameLayout(context)
19
+ val reactRoot = FrameLayout(context)
20
+ val card = FrameLayout(context)
21
+ val content = View(context).apply { importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES }
22
+ val chrome = View(context)
23
+ val pageHost = FrameLayout(context)
24
+ val lower = View(context)
25
+ val blocker = View(context)
26
+ val above = View(context)
27
+ val globalHost = FrameLayout(context)
28
+
29
+ init {
30
+ root.addView(reactRoot)
31
+ root.addView(globalHost)
32
+ reactRoot.addView(card)
33
+ reactRoot.addView(chrome)
34
+ card.addView(content)
35
+ card.addView(pageHost)
36
+ pageHost.addView(lower)
37
+ pageHost.addView(blocker)
38
+ pageHost.addView(above)
39
+ }
40
+ }
41
+
42
+ @Test
43
+ fun pageBlockerHidesPageAndNavigationWithoutHidingTheHost() {
44
+ val tree = Tree(context)
45
+ val isolation = NativeOverlayAccessibilityIsolation()
46
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
47
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.content.importantForAccessibility)
48
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.chrome.importantForAccessibility)
49
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.pageHost.importantForAccessibility)
50
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.blocker.importantForAccessibility)
51
+ }
52
+
53
+ @Test
54
+ fun entriesAbovePageBlockerAndGlobalHostStayAccessible() {
55
+ val tree = Tree(context)
56
+ NativeOverlayAccessibilityIsolation().update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
57
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.lower.importantForAccessibility)
58
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.above.importantForAccessibility)
59
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.globalHost.importantForAccessibility)
60
+ }
61
+
62
+ @Test
63
+ fun dismissalOrSuspensionRestoresOriginalFlags() {
64
+ val tree = Tree(context)
65
+ tree.lower.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
66
+ val isolation = NativeOverlayAccessibilityIsolation()
67
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
68
+ isolation.update(tree.root, null, emptyList(), tree.globalHost)
69
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_YES, tree.content.importantForAccessibility)
70
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO, tree.lower.importantForAccessibility)
71
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.chrome.importantForAccessibility)
72
+ }
73
+
74
+ @Test
75
+ fun globalBlockerHidesReactRootThenPageIsolationReturns() {
76
+ val tree = Tree(context)
77
+ val globalLower = View(context)
78
+ tree.globalHost.addView(globalLower)
79
+ val isolation = NativeOverlayAccessibilityIsolation()
80
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
81
+ isolation.update(tree.root, tree.globalHost, listOf(globalLower), tree.globalHost)
82
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.reactRoot.importantForAccessibility)
83
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, globalLower.importantForAccessibility)
84
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_YES, tree.content.importantForAccessibility)
85
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
86
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.reactRoot.importantForAccessibility)
87
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, globalLower.importantForAccessibility)
88
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.content.importantForAccessibility)
89
+ }
90
+
91
+ @Test
92
+ fun detachedHostRestoresIsolationInsteadOfHidingAnotherTree() {
93
+ val tree = Tree(context)
94
+ val isolation = NativeOverlayAccessibilityIsolation()
95
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
96
+ tree.card.removeView(tree.pageHost)
97
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
98
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_YES, tree.content.importantForAccessibility)
99
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.lower.importantForAccessibility)
100
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.chrome.importantForAccessibility)
101
+ }
102
+
103
+ @Test
104
+ fun changingTheBlockingEntryRestoresEntriesNoLongerBelowIt() {
105
+ val tree = Tree(context)
106
+ val isolation = NativeOverlayAccessibilityIsolation()
107
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower, tree.blocker), tree.globalHost)
108
+ isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
109
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.blocker.importantForAccessibility)
110
+ assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.lower.importantForAccessibility)
111
+ }
112
+ }