Skip to content

Commit 668ef96

Browse files
committed
fix(android): cover never appears on Samsung One UI (BadTokenException on host walk)
Reported on Samsung Galaxy A05 (SM-A057F) running Android 14 with One UI: pressing Home left the Recents thumbnail showing the underlying React Native UI instead of the configured privacy cover. Logcat from the affected device: Cover : ensureCoverOnTopmost: reparent (topmost=FreezableFrameLayout) ^^^^^^^^^^^^^^^^^^^^ Cover : attachCover: addView failed: android.view.WindowManager$BadTokenException: Unable to add window -- token android.view.ViewRootImpl$W@d70be2d is not valid; is your activity running? The host-window walk (`CoverWindowAttachment.topmostHostViewFor`) excluded `coverView` (the cover Window's root — `SurfaceView` on the SCVH path, `FrameLayout` on legacy) but not `coverContent` (the SCVH-hosted `FreezableFrameLayout`). On Samsung One UI's customised WindowManager the SCVH-hosted content appears in `WindowManagerGlobal.mViews` even though it lives inside the SCVH's own `ViewRootImpl`. The walk found our own content view as the "topmost host", `addCover` tried to attach the cover as a sub-window of itself, and `addView` rejected the token. The cover never reached the Recents thumbnail. Fix: pass `exclude2 = coverContent` at all three `topmostHostViewFor` callsites in `HybridCover.kt`. `CoverWindowAttachment` already supported the parameter (the blur source picker uses it for the same reason on the SCVH path). Verified on Samsung Galaxy A05 (Android 14, One UI): Cover : ensureCoverOnTopmost: reparent (topmost=DecorView) Cover : attachCover: preferredRefreshRate=90.0 Cover : broadcast: reason=homekey isEnabled=true isVisible=false sc=false Cover : broadcast: fast scvh=false refl=false dt=1ms Cover : performUserLeaveMount Cover : setCoverVisibility: visible=true animated=false scvh=false Recents thumbnail shows the #5F8AFA splash background with the icon, exactly the iOS App Switcher parity the library was designed to deliver.
1 parent 404d989 commit 668ef96

1 file changed

Lines changed: 48 additions & 3 deletions

File tree

android/src/main/java/com/margelo/nitro/cover/HybridCover.kt

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,22 @@ class HybridCover : HybridCoverSpec() {
564564
val decor = activity.window?.decorView ?: return
565565
if (decor.windowToken == null) return
566566

567-
val topmost = CoverWindowAttachment.topmostHostViewFor(activity, exclude = coverView) ?: decor
567+
// Exclude both the cover Window's root (`coverView` — SurfaceView
568+
// on SCVH path, FrameLayout on legacy) AND the SCVH-hosted content
569+
// (`coverContent`). On some OEM builds (Samsung One UI on Android
570+
// 14, observed on the Galaxy A05 / SM-A057F) the SCVH's content
571+
// view appears in `WindowManagerGlobal.mViews` even though it
572+
// lives inside the SCVH's own ViewRootImpl. Without excluding it
573+
// the host walk picks our own content view as the "topmost", we
574+
// then try to attach the cover as a sub-window of itself, and
575+
// `addView` throws `BadTokenException: Unable to add window —
576+
// token … is not valid; is your activity running?`. The cover
577+
// never appears in the Recents thumbnail on those devices.
578+
val topmost = CoverWindowAttachment.topmostHostViewFor(
579+
activity,
580+
exclude = coverView,
581+
exclude2 = coverContent,
582+
) ?: decor
568583
val targetToken = topmost.windowToken ?: decor.windowToken!!
569584

570585
val current = coverView
@@ -639,7 +654,22 @@ class HybridCover : HybridCoverSpec() {
639654
// live: while a Dialog is in front, the activity decor's window
640655
// never sees the focus regain that signals "user came back via
641656
// Recents tap" — but the Dialog's decor does.
642-
val topmost = CoverWindowAttachment.topmostHostViewFor(activity, exclude = coverView) ?: decor
657+
// Exclude both the cover Window's root (`coverView` — SurfaceView
658+
// on SCVH path, FrameLayout on legacy) AND the SCVH-hosted content
659+
// (`coverContent`). On some OEM builds (Samsung One UI on Android
660+
// 14, observed on the Galaxy A05 / SM-A057F) the SCVH's content
661+
// view appears in `WindowManagerGlobal.mViews` even though it
662+
// lives inside the SCVH's own ViewRootImpl. Without excluding it
663+
// the host walk picks our own content view as the "topmost", we
664+
// then try to attach the cover as a sub-window of itself, and
665+
// `addView` throws `BadTokenException: Unable to add window —
666+
// token … is not valid; is your activity running?`. The cover
667+
// never appears in the Recents thumbnail on those devices.
668+
val topmost = CoverWindowAttachment.topmostHostViewFor(
669+
activity,
670+
exclude = coverView,
671+
exclude2 = coverContent,
672+
) ?: decor
643673
val targetToken = topmost.windowToken ?: decor.windowToken!!
644674

645675
if (coverView != null && coverAttachedToken === targetToken
@@ -1740,7 +1770,22 @@ class HybridCover : HybridCoverSpec() {
17401770
// destroyed (e.g. a Modal that the cover was attached to has
17411771
// since dismissed), in which case re-attaching to the stale
17421772
// token would throw and silently leave the cover unmounted.
1743-
val topmost = CoverWindowAttachment.topmostHostViewFor(activity, exclude = coverView) ?: decor
1773+
// Exclude both the cover Window's root (`coverView` — SurfaceView
1774+
// on SCVH path, FrameLayout on legacy) AND the SCVH-hosted content
1775+
// (`coverContent`). On some OEM builds (Samsung One UI on Android
1776+
// 14, observed on the Galaxy A05 / SM-A057F) the SCVH's content
1777+
// view appears in `WindowManagerGlobal.mViews` even though it
1778+
// lives inside the SCVH's own ViewRootImpl. Without excluding it
1779+
// the host walk picks our own content view as the "topmost", we
1780+
// then try to attach the cover as a sub-window of itself, and
1781+
// `addView` throws `BadTokenException: Unable to add window —
1782+
// token … is not valid; is your activity running?`. The cover
1783+
// never appears in the Recents thumbnail on those devices.
1784+
val topmost = CoverWindowAttachment.topmostHostViewFor(
1785+
activity,
1786+
exclude = coverView,
1787+
exclude2 = coverContent,
1788+
) ?: decor
17441789
val targetToken = topmost.windowToken ?: decor.windowToken ?: return
17451790
attachCover(activity, targetToken = targetToken, visible = isVisible, animated = false)
17461791
}

0 commit comments

Comments
 (0)