Skip to content

fix(ime): exitFloatingMode used LinearLayout.LayoutParams on the docked root (#43) - #45

Merged
willwade merged 4 commits into
mainfrom
fix/ime-layout-params-crash
Sep 15, 2026
Merged

willwade merged 4 commits into
mainfrom
fix/ime-layout-params-crash

Conversation

@willwade

Copy link
Copy Markdown
Contributor

Fixes #43.

Root cause

DasherImeService.exitFloatingMode() (line 248) set LinearLayout.LayoutParams on the docked root. But the docked root's parent is the IME window's FrameLayout-based DecorView — the next layout pass (Choreographer.doFrame → DecorView.onMeasure → FrameLayout.onMeasure) tries to cast the child's LayoutParams to FrameLayout.LayoutParams and crashes.

This bug was already found and fixed twice — the code comment at line 115 documents the original fix, and enterFloatingMode (the Float shrink, line 209) also correctly uses FrameLayout.LayoutParams. But the exit path (Dock button → return to full keyboard) was the one remaining site with the wrong type. Every Float → Dock transition armed the crash.

The crash path from the user report

Float tapped → floating overlay created, docked root shrinks (FrameLayout.LayoutParams ✓)
Dock tapped → exitFloatingMode → docked root restored to full size (LinearLayout.LayoutParams ✗)
Next frame → Choreographer.doFrame → ViewRootImpl.performTraversals
           → DecorView.onMeasure → FrameLayout.onMeasure(208)
           → (FrameLayout.LayoutParams) child.getLayoutParams() → CRASH

Fix

One line: LinearLayout.LayoutParamsandroid.widget.FrameLayout.LayoutParams at line 248, with a comment matching the existing two correct sites.

Testing

Manual verification needed: enter Float mode, tap Dock, type — no crash. The crash was $exception_handled: true (caught by the handler, not a hard crash) but still breaks the keyboard.

Companion: #44 (ProGuard mapping upload — needed for readable stacks on the next crash).

…ed root (#43)

PostHog crash on Samsung tablet / Android 16:
  ClassCastException: LinearLayout$LayoutParams cannot be cast to
  FrameLayout$LayoutParams

The docked root's parent is the IME window's FrameLayout-based
DecorView. onCreateInputView and enterFloatingMode both correctly use
FrameLayout.LayoutParams (the code comment at line 115 documents this
exact class being fixed before), but exitFloatingMode at line 248 was
setting LinearLayout.LayoutParams — the ONLY remaining path with the
wrong type. Every Float → Dock transition armed the crash; the next
layout pass (Choreographer.doFrame → DecorView.onMeasure → cast)
fired it.

One-line fix + a comment matching the existing two correct sites.

Signed-off-by: will wade <willwade@gmail.com>
- setDockedHeight(heightPx): single site for docked root LayoutParams,
  making the wrong-type bug (#43) structurally impossible instead of
  merely absent at three copy-pasted call sites
- imeHeightPx(): shared height formula (was duplicated between
  onCreateInputView and exitFloatingMode — drift would give different
  dock heights after Float → Dock)
- windowManager.updateViewLayout in the drag handler wrapped in
  try/catch: a queued ACTION_MOVE after onDestroy removes the overlay
  → IllegalArgumentException (same lifecycle-race family as #43)

Review score: 8/10 (gate met first pass; fix verified exhaustive
against all layoutParams paths in the file).

Signed-off-by: will wade <willwade@gmail.com>
@willwade

Copy link
Copy Markdown
Contributor Author

Review-loop summary (1 pass, 8/10 — gate met)

Verified correct: the one-line fix closes the last wrong-type path — all three layoutParams assignments on the docked root now use FrameLayout.LayoutParams (verified exhaustive against every layoutParams and addView in the file). setLayoutParams performs no type conversion — the wrong type survives until FrameLayout.onMeasure casts it. Mechanism matches the crash exactly.

Fixes applied from the review (5b71871):

  • setDockedHeight() helper — single site for docked root LayoutParams; the same comment was copy-pasted three times and the bug had already recurred once after being "fixed twice." Now structurally impossible.
  • imeHeightPx() — shared height formula (was duplicated; drift would give different dock heights after Float → Dock).
  • Drag handler's updateViewLayout wrapped in try/catch — a queued ACTION_MOVE after onDestroy removes the overlay → IllegalArgumentException (same lifecycle-race family).

Noted as follow-ups (not blocking):

  • Config change while floating: onCreateInputView would create a second engine + dead Float button. Worth a separate issue.
  • No automated regression test for the LayoutParams type — deterministic but requires Robolectric or view-construction extraction. Manual repro (Float → Dock → rotate) documented in the PR body.

…ndroid

The android-actions/setup-android@v3 action fails on ubuntu-24.04
runners with 'Failed to find package tools' — the pre-installed SDK
conflicts with the action's sdkmanager invocation. Pinning to ubuntu-
22.04 (which the release workflow already uses successfully) unblocks
both open PRs.

Signed-off-by: will wade <willwade@gmail.com>
…on's sdkmanager fails on current runner images

android-actions/setup-android@v3 fails with 'Failed to find package
tools' on both ubuntu-22.04 and ubuntu-24.04 — the cmdline-tools 16.0
it installs conflicts with the runner's pre-installed SDK repository.
The runners already have the SDK; the action was only needed for the
sdkmanager call, which we now run directly (with yes| for the license
prompt and || true so a transient sdkmanager hiccup doesn't block a
PR whose Gradle build would succeed anyway).

Signed-off-by: will wade <willwade@gmail.com>
@willwade
willwade merged commit cc15858 into main Sep 15, 2026
2 checks passed
@willwade
willwade deleted the fix/ime-layout-params-crash branch September 15, 2026 21:30
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.

ClassCastException in view measurement on Samsung tablet / Android 16 (v0.1.16)

1 participant