Fix Shizuku double-bind cascade that orphaned virtual displays#49
Merged
Conversation
VirtualDisplayManager and ShizukuSetup were both calling Shizuku.bindUserService, and VDM treated every onServiceConnected callback after the first as a binder death — so duplicate connects spawned 6+ orphan VDs per session and apps launched from the web launcher targeted a different display than the one being encoded. Consolidate bind ownership in ShizukuSetup; VDM becomes a passive helper that mirrors the privileged service via attachPrivilegedService. Add BinderConnectionTracker (pure logic, unit-tested) so reconnect is only triggered after a real disconnect event. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
VirtualDisplayManagerandShizukuSetupwere both callingShizuku.bindUserService. VDM also treated everyonServiceConnectedafter the first as a binder death and recreated the VD — so duplicate connects spawned 6+ orphan VirtualDisplays per session and zombie:privilegedprocesses.Changes
VirtualDisplayManager: strip the embeddedShizuku.bindUserServiceServiceConnection. NewattachPrivilegedService(svc: IPrivilegedService?)API receives the binder from outside.release()is now local-state only.MirrorForegroundService:ensureShizukuSetup()— lazy singleton creation at first browser connect;ShizukuSetupnow lives for the foreground-service lifetime.startReconnectObserver(setup)— singleton coroutine (reconnectJob) collectssetup.serviceConnectedand classifies transitions viaBinderConnectionTracker. OnlyReconnectrecreates a VD;FirstConnectis owned bytrySetupVirtualDisplay.trySetupVirtualDisplayrewritten: waits forserviceConnected.first { it }with timeout, reuses long-lived setup, gated byshizukuSetupInProgressagainst concurrent rebuild races. Single-delivery semantics viasafeResult.releaseShizukuSession→tearDownVdSession— no longer releases ShizukuSetup mid-session.performCleanupcancelsreconnectJobbeforeshizukuSetup.release().displayId < 0guards onlaunchExternalBrowserTargetandlaunchSplitExternalBrowserTarget.ShizukuSetup: removed deadattachPrivilegedService(svc)API (had no callers after the refactor).BinderConnectionTracker(new, pure logic, 7 unit tests): state machine that distinguishes a fresh connect from a reconnect after a real disconnect. Key correctness rule:onDisconnectedfromNewstays inNew(so MutableStateFlow's initialfalseemission to a fresh collector doesn't misclassify the next real connect asReconnect).On-device verification (Samsung Z Flip 6, open state)
Before:
(6 VDs in 1s; 14+ zombie
:privilegedprocesses accumulated across sessions)After:
(1 VD per session; canvas renders the launched app in browser as expected.)
Out of scope
When Z Flip is closed, Samsung's
FlipLargeCoverScreenSizeCompatModeinterceptsam start --display <vd>for non-whitelisted apps (e.g. Naver Map) and reroutes them to the cover screen, bypassing the virtual display. This is per-app OS policy — Samsung Calculator launches on the Castla VD under the same code path. Not addressed in this patch; tracked separately.Test plan
./gradlew :app:testDebugUnitTest --tests "com.castla.mirror.shizuku.*"— greentransition=FirstConnect, single Shizuku bind, app launches render in browser canvas🤖 Generated with Claude Code