Skip to content

fix(android): Samsung One UI stale cover-content filter + v0.1.8 - #5

Merged
amillez merged 2 commits into
mainfrom
fix/android-samsung-oneui-stale-host
Jun 1, 2026
Merged

amillez merged 2 commits into
mainfrom
fix/android-samsung-oneui-stale-host

Conversation

@amillez

@amillez amillez commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

v0.1.8 — completes the Samsung One UI Recents-cover fix from v0.1.7. v0.1.7 fixed the first host-walk wrong-pick (exclude2 = coverContent), but rapid setColor / setImage / enable JS calls during LockCover mount trigger 2–3 sequential attach cycles in ~280 ms. Each one creates a new FreezableFrameLayout; the previous instance lingers in WindowManagerGlobal.mViews briefly after teardown on Samsung One UI. v0.1.7's instance-only filter missed those stale entries; the walk picked one, addView threw BadTokenException: Unable to add window — token … is not valid; is your activity running?, and the cover never made it to the Recents thumbnail.

Fix

Add a class-based filter in CoverWindowAttachment.topmostHostViewFor:

if (v is FreezableFrameLayout) continue

Class match catches every cover-content instance regardless of which attach cycle created it. Instance-based exclude2 = coverContent (from v0.1.7) is preserved as the fast path; class filter is the safety net for the stale-instance window. FreezableFrameLayout widened from private to internal so CoverWindowAttachment can name the type (strictly broader visibility, no callers affected).

Safety review (done before merge)

  • Strictly subtractive — the filter narrows the candidate pool. It can never return an invalid candidate, only exclude one. Worst case: returns null → callers fall back to decor (every callsite has ?: decor and a decor.windowToken == null guard).
  • No interaction with v0.1.6 teardown defences — the filter is attach-side only. FreezableFrameLayout freeze, removeViewImmediate, deferredReleaseScvh, coverDetaching re-entrance guard, and the API 30 INTERNAL_SYSTEM_WINDOW pre-check are all unchanged. The original Invalid window token crash from WindowlessWindowManager.relayout remains fixed.
  • No-op on non-Samsung devices — on every other Android build we've tested, the SCVH-hosted content never appears in WindowManagerGlobal.mViews (it lives inside the SCVH's own ViewRootImpl). The new check matches zero times.
  • Modal coverage preserved — RN <Modal> Dialog windows are not FreezableFrameLayout, so cover still z-orders above them.
  • Blur source picker also benefits — CoverBlurRenderer.render uses the same walk and is now also protected from picking a stale cover bitmap as its source.

Verified end-to-end

Device Result
Samsung Galaxy A05 / SM-A057F (Android 14, One UI 6/7) — the regression repro ✅ Recents shows #5F8AFA cover with splash icon. Logcat: topmost=DecorView, 2 consecutive attachCover succeed (the exact case that hit BadTokenException before), broadcast: fast scvh=false dt=0mssetCoverVisibility: visible=true. Zero crash signatures in logcat *:E.
Pixel_9 emulator (Android 16, API 37) maestro suite ✅ 9/9 flows pass in 3m 28s — identical to v0.1.6 baseline. SCVH SurfaceFlinger-direct alpha toggle still wins the snapshot race.
Favvy_Android_30 (API 30, Android 11) stress ✅ App PID stable across 5× home/app-switcher + 10× rapid enable/disable. API 30 INTERNAL_SYSTEM_WINDOW pre-check still fires. Legacy attach path works. Zero Invalid window token / IllegalArgumentException.

Files changed

  • android/src/main/java/com/margelo/nitro/cover/CoverWindowAttachment.kt — +14 lines: class filter + comment explaining the stale-instance scenario.
  • android/src/main/java/com/margelo/nitro/cover/HybridCover.kt — 1 line: private classinternal class FreezableFrameLayout.
  • package.json0.1.70.1.8.
  • CHANGELOG.md — new 0.1.8 - 2026-06-01 entry.

amillez added 2 commits June 1, 2026 21:21
…on Samsung One UI

v0.1.7's `exclude2 = coverContent` only filtered the *current* SCVH-hosted
content instance from `topmostHostViewFor`. On Samsung Galaxy A05
(SM-A057F, Android 14, One UI), `LockCover`'s mount sequence triggers 2-3
rapid attach cycles in ~280ms — `enable()` runs `ensurePreMounted`, then
`refreshCoverContentIfMounted` fires when JS subsequently calls
`setColor` / `setImage` against the already-mounted cover. Each attach
creates a new `FreezableFrameLayout` instance; the previous one lingers
briefly in `WindowManagerGlobal.mViews` between `detachCoverView`
returning and Samsung's WindowManager finishing the underlying
`removeView`. The host walk picks the stale instance, `attachCover`
tries to attach as a sub-window of an invalidated token, and
`WindowManager.addView` throws

  BadTokenException: Unable to add window -- token
  android.view.ViewRootImpl$W@... is not valid; is your activity running?

The cover never appears in the Recents thumbnail.

Add a class-based filter in `CoverWindowAttachment.topmostHostViewFor`:
`if (v is FreezableFrameLayout) continue`. Class match catches every
cover-content instance, current or stale, regardless of which attach
cycle created it. `FreezableFrameLayout` is widened from `private` to
`internal` so `CoverWindowAttachment` can reference the type — strictly
broader visibility, no callers affected.

Existing instance-match `exclude2 = coverContent` is preserved as the
fast path. The class-based filter is the safety net for the stale-
instance window.

Safety: the filter is strictly subtractive. It can never return an
invalid candidate, only exclude one. On every non-Samsung device the
SCVH-hosted content never appears in `WindowManagerGlobal.mViews` to
begin with (it lives inside the SCVH's own ViewRootImpl), so the new
check is a no-op there. The original `Invalid window token` crash from
`WindowlessWindowManager.relayout` (v0.1.6) is unrelated to attach-side
target selection and remains fixed by the teardown defences:
`FreezableFrameLayout` freeze, `removeViewImmediate`,
`deferredReleaseScvh`, `coverDetaching` re-entrance guard, and the
API 30 `INTERNAL_SYSTEM_WINDOW` pre-check — all unchanged.

Verified:
- Samsung Galaxy A05 / SM-A057F (Android 14, One UI): cover paints in
  Recents with the configured `#5F8AFA` background and splash icon.
  Logcat shows clean `topmost=DecorView` host selection across multiple
  rapid `ensureCoverOnTopmost` cycles, two consecutive `attachCover`
  succeed, broadcast pipeline fires (`broadcast: fast scvh=false
  dt=0ms` → `setCoverVisibility: visible=true`), no `BadTokenException`
  or `Invalid window token` anywhere in `logcat *:E`.
- Pixel_9 emulator (Android 16, API 37): 9/9 maestro flows pass in
  3m 28s, identical to v0.1.6 baseline. SCVH SurfaceFlinger-direct
  alpha toggle still wins the Home-press snapshot race.
- Favvy_Android_30 / API 30 (Android 11): app PID stable through 5×
  home/app-switcher + 10× rapid enable/disable toggle stress, API 30
  `INTERNAL_SYSTEM_WINDOW` pre-check still fires, legacy attach path
  works, no `Invalid window token` in logcat.
@amillez
amillez merged commit 5d43f8c into main Jun 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant