Commit 14d0cef
committed
fix(android): eliminate
Production crash from downstream apps' Play Console (NEAR Mobile and others):
java.lang.IllegalArgumentException: Invalid window token (never added or removed already)
at android.view.WindowlessWindowManager.relayout
at android.view.ViewRootImpl.relayoutWindow / performTraversals
at android.view.Choreographer.doFrame
The SCVH path introduced in v0.1.3 (snapshot-race fix) creates a
`WindowlessWindowManager`-backed window through `SurfaceControlViewHost`.
Teardown is racy: `host.release()` removes the WWM token via an
asynchronously-dispatched `doDie()` (`MSG_DIE`), and any
`TraversalRunnable` already in the SCVH ViewRootImpl's Choreographer
queue can fire after the token is removed, throwing the crash from
`Looper.loop` — outside any try/catch.
v0.1.5 added reflective `unscheduleScvhTraversals` before release,
which cancels queued runnables when reflection is available. Android
14+ hidden-API enforcement blocks the probe on a growing share of
devices, leaving the crash unfixed there.
This commit closes the crash class deterministically while preserving
the SurfaceFlinger-direct alpha toggle that wins the Home-press
snapshot race on every device that supports SCVH. Layered defences in
`detachCoverView` + `tryAttachCoverViaScvh`:
- `FreezableFrameLayout` cover-content root whose `requestLayout()`
and `invalidate()` no-op while a `frozen` flag is set. Set first
in `detachCoverView`; from that point no `requestLayout` reaches
`ViewRootImpl.scheduleTraversals`, so no new TraversalRunnable can
be queued during teardown.
- Snapshot-then-null-out of shared state at the top of
`detachCoverView`, plus a `coverDetaching` re-entrance guard. Any
synchronous re-entrant call (animation cancel, dispatchDetached)
sees cleared fields and bails before double-removing or
double-releasing.
- Cancel SCVH traversals BEFORE `removeView` via
`unscheduleScvhTraversals` (best-effort; latches off on permanent
reflection failure).
- `WindowManager.removeViewImmediate` so `dispatchDetachedFromWindow`
runs inline while the freeze is active. Falls back to async
`removeView` on OEM impls that reject immediate removal in
transitional states.
- `setCoverVisibility` validity-checks `view.windowToken != null`
before `updateViewLayout` — same WMS/WWM path that throws when the
token is gone.
- `deferredReleaseScvh` schedules `safeReleaseScvh` to run AFTER the
next Choreographer frame's traversal callbacks complete. Uses
`Choreographer.postFrameCallback` (animation phase) → nested
`mainHandler.post`. `ViewRootImpl.mTraversalScheduled` guarantees at
most one queued TraversalRunnable per ViewRootImpl; a single frame's
wait drains the queue. Any pre-queued runnable fires with the WWM
token still valid; release happens after, removing the token only
when nothing is left to relayout. Wired into all 6 release call
sites (5 in `tryAttachCoverViaScvh` recovery branches +
`detachCoverView`).
- API 30 + `INTERNAL_SYSTEM_WINDOW` pre-check. On Android 11,
`WindowManagerService.addWindow` enforces this signature-level
permission for the SCVH path. `host.setView` throws
`SecurityException` AFTER `ViewRootImpl.setView` has already called
`requestLayout()`, queuing a TraversalRunnable that fires the same
vsync and crashes because the token was never registered with the
WWM. The deferred release can't help — the runnable fires in the
same vsync's TRAVERSAL phase, before our queued Handler message.
Skipping SCVH entirely when the permission isn't granted is the
only safe path. Scoped to API 30 only; Android 12+ dropped the
check, so SCVH and the snapshot-race fix run unmodified on every
modern device.
Also fixes `CoverBlurRenderer` on API < 31: `RenderEffect` doesn't
exist, and the previous fallback dropped the captured bitmap and
painted a flat ~80% white tint, leaving underlying app content fully
readable through the cover (broken privacy on every Android 11
host). Now captures at 1/12 in each dimension (1/144 pixels) — small
enough that ImageView's bilinear filter at draw time produces a
frosted-glass smudge — and layers the style tint as foreground,
matching the visual contract of the API >= S path.
Verified on Favvy_Android_30 (API 30, reflection-blocked,
permission-restricted SCVH) and Pixel_9 (API 34+, reflection-blocked,
SCVH-friendly): no `IllegalArgumentException: Invalid window token`
in `logcat *:E` across 9/9 maestro flows + rapid Home/app-switcher
cycles + rapid enable/disable toggles. App PID stable. SCVH fast
alpha (`broadcast: fast scvh=true dt=0ms`) persists across detaches
on devices that support it. Android 11 BLUR mode shows real smudged
content with style tint in recents-thumbnail view.Invalid window token crash on SCVH teardown + fix Android 11 BLUR fallback1 parent 1650639 commit 14d0cef
2 files changed
Lines changed: 502 additions & 36 deletions
Lines changed: 43 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
21 | 34 | | |
22 | 35 | | |
23 | 36 | | |
| |||
49 | 62 | | |
50 | 63 | | |
51 | 64 | | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | 65 | | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
56 | 69 | | |
57 | 70 | | |
58 | 71 | | |
59 | 72 | | |
60 | 73 | | |
61 | | - | |
62 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
63 | 101 | | |
64 | 102 | | |
65 | 103 | | |
| |||
0 commit comments