Repository navigation
fix(ime): docked choice survives keyboard switches — self-healing mode restore, engine re-entry guard, unbind cleanup - #74
Merged
Conversation
…e restore, engine re-entry guard, unbind cleanup (#73) Heide (v0.1.21): "The IME still starts in float mode, then you have to tap on dock. Then it's fine unless you switch keyboards." Not a v0.1.22 regression — v0.1.22's IME changes are output batching + force-show; this is the #64 mode-remember machinery, which has three lifecycle holes: - onCreateInputView can run again on a LIVE instance (configuration change; some OEM IME-switch flows keep the service alive). On re-entry createEngine() built a SECOND engine (the first kept its dasher_frame loop — both submitting into the same canvas), and a stale floating=true made the posted restore early-return inside enterFloatingMode(): a dead overlay canvas presented as floating over a full-height docked root. Tap Dock → clean; switch keyboards → the cycle repeated. - No onUnbindInput cleanup: a surviving service left the overlay hovering over the next keyboard. - exitFloatingMode early-returned when floatingView was null — a swallowed removeView failure (Samsung overlay quirks, cf. #46) stranded a dead overlay over every subsequent show. Fixes: - createEngine: re-entry guard — the engine is per-process by design (#53/#56); a second one leaked the first. - Posted restore: self-heal — if floating, exitFloatingMode(persist= false) FIRST, then evaluate the saved preference, so every keyboard show converges to the persisted mode regardless of view recreation. - onUnbindInput: exit floating without persisting (the saved mode stays the user's deliberate choice; it is re-evaluated on the next show). - exitFloatingMode: never early-return on a missing overlay window — mode state and docked layout are restored regardless; removeView failures now log. Manual verification still needed on Heide's device (Samsung/Android 16) to confirm which path she hits; all three are hardened. Signed-off-by: will wade <willwade@gmail.com>
willwade
added a commit
that referenced
this pull request
Oct 24, 2026
…round 3) (#75) Round 3 for Heide's float/dock report, after her v0.1.23 test: "It still starts in float mode. And this time, it doesn't stay in docked mode when you bring the keyboard up again. But one thing that's fixed though, is the letters are not right on top of your cursor… better than the last version." The v0.1.23 fix (#74) was insufficient. A full reproduction attempt on an API 35 emulator (edge-to-edge enforced, IME switch cycles, hide/show, pref manipulation via run-as) **could not reproduce the float restart** — the docked keyboard lays out correctly, `mode restore` breadcrumbs show the expected state machine, and the full preference matrix behaves as designed (unset→docked; floating→float restore; floating+always-docked→docked). The remaining trigger is Samsung/OneUI-specific (Heide's Android 16 tablet) and needs data from her device. This PR therefore ships three things: 1. **Escape hatch** — new Settings → Input → "Keyboard always starts docked" (`dasher_ime/always_docked`). Heide has never wanted float (three consecutive reports); pinning docked makes the underlying bug irrelevant for her. Float users who want restore keep it when the toggle is off. 2. **Diagnostics** — `ime_mode_restore` analytics event with every decision input (opt-in, no-op otherwise), `ime_float_enter`/`ime_float_exit` with reasons (`button`/`restore`/`selfheal`/`unbind`), plus Log.d lifecycle breadcrumbs. If float still appears for her after this build, the next session shows exactly which path took it there. 3. **Hardening** from the repro session — `setDockedHeight` now sets `Gravity.BOTTOM` explicitly (a no-gravity FrameLayout child defaults to TOP|START in the IME decor — never the intent, even where platform layout happens to re-dock it). #74's fixes (engine re-entry guard, self-healing restore, unbind cleanup) remain — they closed real holes by inspection; they just weren't her hole. ## Testing - [x] `:app:assembleDebug` + `:app:testDebugUnitTest` green - [x] Emulator (API 35): full pref matrix verified via `run-as` state injection + logcat breadcrumbs (docked default / pinned docked / float restore) - [x] Emulator: IME switch cycles with the service alive AND destroyed — breadcrumbs show clean state convergence - [ ] Heide's device: enable Settings → Input → "Keyboard always starts docked"; float should never reappear - [ ] If it still does: the `ime_mode_restore` event from her session identifies the path Fixes #73 (as far as it can be fixed without her device; diagnostics close the loop either way). --------- Signed-off-by: will wade <willwade@gmail.com>
willwade
added a commit
that referenced
this pull request
Oct 25, 2026
…e 'float-like dead window' (#76) (#77) Fixes #76. Heide's v0.1.24 report: "It starts with a big float LIKE window, but you can't type on it. You have to tap on Float first and then Dock." **Root cause — a regression from #74's engine re-entry guard.** When `onCreateInputView` runs again on a LIVE instance (configuration change: rotation, fold, MuMu's freely-resizable window; OEM IME-switch flows that keep the service alive), the recreated view tree builds a fresh loading overlay (visible by default) — but `createEngine()` returns at the guard BEFORE the line that hides it. The opaque overlay covers the canvas forever: a big, dead, overlay-looking panel. The Float/Dock buttons sit outside the overlay, which is why Float→Dock appeared to revive it. This also re-explains the whole #73 timeline: "still starts in float mode / fine unless you switch keyboards / doesn't stay docked" were all this dead-canvas state, never the mode preference. The timing matches — v0.1.23 shipped the guard, exactly when her "doesn't stay docked" report arrived. ## The fix (single guard-path change, +22/−7) The engine-guard path now hides the fresh overlay and re-pushes the canvas geometry to the surviving engine. Plus an output-event `Log.d` breadcrumb (non-empty events only) for support diagnostics. ## Repro & verification (API 33 emulator — MuMu's Android generation) Rotate with the keyboard up → same-instance view recreation: | Measurement | Before | After | |---|---|---| | Guard-path restore latency | 0.3s (guard hit) | 0.07s (guard hit) | | Colorful canvas pixels | **1** (dead overlay) | **500** | | Pixels changed during a zoom drag | 0 | **106** | First-show, IME-switch (fresh instance), and hide/show paths re-verified unaffected. ## Testing - [x] Emulator repro matrix (above), strict step-by-step with pixel verification - [x] `:app:assembleDebug` green - [ ] Heide's device: rotate/resize the MuMu window mid-typing — keyboard stays alive - [ ] Her video (Google Drive, pending) to confirm the visual matches the overlay state Signed-off-by: will wade <willwade@gmail.com>
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.
Fixes #73.
Heide (testing v0.1.21, before v0.1.22): "The IME still starts in float mode, then you have to tap on dock. Then it's fine unless you switch keyboards."
Not a v0.1.22 regression — v0.1.22's IME changes are output batching + force-show. This hardens #64's mode-remember machinery, which has three lifecycle holes found by inspection:
onCreateInputViewre-entry on a live instance (config change; OEM IME-switch flows that keep the service alive):createEngine()built a second engine — the first kept itsdasher_frameloop, both submitting into the same canvas — and a stalefloating=truemade the posted restore early-return insideenterFloatingMode(), presenting a dead overlay canvas as "floating" over a full-height docked root. Tap Dock → clean; switch keyboards → repeats. This matches the report verbatim.onUnbindInputcleanup — a surviving service left the overlay hovering over the next keyboard.exitFloatingModeearly-returned onfloatingView == null— a swallowedremoveViewfailure (Samsung overlay quirks, cf. Floating window drag: horizontal moves opposite on Samsung (Galaxy Fold 5) — gravity + y-axis inversion #46) stranded a dead overlay over every subsequent show.Changes (single file, +51/−4)
createEngine(): re-entry guard — the engine is per-process by design (IME can't type after visiting the main app — static output listener nulled by MainActivity #53/Per-engine-instance listeners instead of process-wide statics — the recurring #53 class #56)if (floating) exitFloatingMode(persist = false)before evaluating the saved preference, so every keyboard show converges to the persisted mode regardless of view recreationonUnbindInput(): exit floating without persisting (saved mode stays the user's deliberate choice)exitFloatingMode(persist): never early-returns on a missing overlay window;removeViewfailures now logTesting
:app:assembleDebug+:app:testDebugUnitTestgreen locallyGiven #71's human review is pending, happy to fold this into the same review session.