Skip to content

Commit d83ddde

Browse files
committed
feat: wire Android 14+ stylus handwriting through recognizer facade
Replaced the logging-only onStartStylusHandwriting stub with a full session flow that uses StrokeRecognizerRegistry: - onPrepareStylusHandwriting: clears stroke buffers - onStartStylusHandwriting: checks recognizer readiness for the active locale; shows a toast and returns false when no addon is installed - onStylusHandwritingMotionEvent: collects MotionEvent points into Stroke objects, including historical samples for smooth ink - onFinishStylusHandwriting: runs recognition, commits the top candidate to the InputConnection When no recognizer addon is installed, the system falls back to the standard touch input path. The preference toggle still gates the entire flow.
1 parent f0cf154 commit d83ddde

2 files changed

Lines changed: 85 additions & 31 deletions

File tree

app/src/main/kotlin/dev/patrickgold/florisboard/FlorisImeService.kt

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import android.os.Trace
2929
import android.util.Log
3030
import android.util.Size
3131
import android.view.KeyEvent
32+
import android.view.MotionEvent
3233
import android.view.View
3334
import android.view.ViewGroup
3435
import android.view.inputmethod.EditorInfo
@@ -569,39 +570,91 @@ class FlorisImeService : LifecycleInputMethodService() {
569570
voiceInputManager.refreshAvailability()
570571
}
571572

572-
/**
573-
* ROADMAP §7 Next-4.1 — stylus handwriting entry point. Android 14+
574-
* routes a stylus motion-event landing on an editor with
575-
* `setAutoHandwritingEnabled(true)` (the default on Android 14+) into
576-
* this callback. The IME has the choice of:
577-
*
578-
* - returning without action (default — the stylus event falls through
579-
* to the standard input path, which is exactly the current SwiftFloris
580-
* behaviour while a real on-device recognizer is being scoped under
581-
* Next-4.2);
582-
* - invoking a real stroke recogniser (Google ML Kit Digital Ink,
583-
* custom ICU-LM model, etc.) and `currentInputConnection.commitText`
584-
* the recognised result; or
585-
* - showing a handwriting overlay UI via `setInkWindow`.
586-
*
587-
* Logging-only for v1.7.x; the recogniser slot lands as Next-4.2.
588-
* Wiring this override now reserves the surface so language-pack /
589-
* preference plumbing can ship ahead of the recogniser bring-up.
590-
*/
573+
private val pendingStrokes = mutableListOf<dev.patrickgold.florisboard.ime.handwriting.Stroke>()
574+
private val pendingPoints = mutableListOf<dev.patrickgold.florisboard.ime.handwriting.StrokePoint>()
575+
private var handwritingSessionActive = false
576+
577+
@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
578+
override fun onPrepareStylusHandwriting(): Unit {
579+
if (!prefs.keyboard.stylusHandwritingEnabled.get()) return
580+
pendingStrokes.clear()
581+
pendingPoints.clear()
582+
}
583+
591584
@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
592585
override fun onStartStylusHandwriting(): Boolean {
593-
// ROADMAP §7 Next-4.3 — gate on the user's stylus-handwriting toggle.
594-
// When the toggle is off (default), short-circuit so the system
595-
// falls back to standard touch input without ever calling into our
596-
// recogniser stub.
597586
if (!prefs.keyboard.stylusHandwritingEnabled.get()) return false
598-
flogInfo { "Stylus handwriting session started (Next-4.1 stub; recogniser pending Next-4.2)" }
599-
// Return false: we acknowledge the stylus event but don't yet have a
600-
// recogniser running, so the system falls back to the standard
601-
// touch-input path. Once Next-4.2 (Google ML Kit Digital Ink) lands,
602-
// flip to `super.onStartStylusHandwriting()` and start a real
603-
// recognition session.
604-
return false
587+
val locale = subtypeManager.activeSubtype.primaryLocale.localeTag()
588+
val recognizer = dev.patrickgold.florisboard.ime.handwriting.StrokeRecognizerRegistry.active
589+
if (!recognizer.isReady(locale)) {
590+
flogInfo { "Stylus handwriting: no recognizer addon installed for $locale" }
591+
android.widget.Toast.makeText(
592+
this, getString(R.string.handwriting__no_recognizer), android.widget.Toast.LENGTH_SHORT
593+
).show()
594+
return false
595+
}
596+
handwritingSessionActive = true
597+
pendingStrokes.clear()
598+
pendingPoints.clear()
599+
flogInfo { "Stylus handwriting session started for locale=$locale" }
600+
return true
601+
}
602+
603+
@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
604+
override fun onStylusHandwritingMotionEvent(event: MotionEvent) {
605+
if (!handwritingSessionActive) return
606+
when (event.actionMasked) {
607+
MotionEvent.ACTION_DOWN -> {
608+
pendingPoints.clear()
609+
pendingPoints.add(dev.patrickgold.florisboard.ime.handwriting.StrokePoint(
610+
event.x, event.y, event.eventTime,
611+
))
612+
}
613+
MotionEvent.ACTION_MOVE -> {
614+
for (i in 0 until event.historySize) {
615+
pendingPoints.add(dev.patrickgold.florisboard.ime.handwriting.StrokePoint(
616+
event.getHistoricalX(i), event.getHistoricalY(i), event.getHistoricalEventTime(i),
617+
))
618+
}
619+
pendingPoints.add(dev.patrickgold.florisboard.ime.handwriting.StrokePoint(
620+
event.x, event.y, event.eventTime,
621+
))
622+
}
623+
MotionEvent.ACTION_UP -> {
624+
pendingPoints.add(dev.patrickgold.florisboard.ime.handwriting.StrokePoint(
625+
event.x, event.y, event.eventTime,
626+
))
627+
if (pendingPoints.size >= 2) {
628+
pendingStrokes.add(dev.patrickgold.florisboard.ime.handwriting.Stroke(
629+
pendingPoints.toList(),
630+
))
631+
}
632+
pendingPoints.clear()
633+
}
634+
}
635+
}
636+
637+
@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
638+
override fun onFinishStylusHandwriting() {
639+
if (!handwritingSessionActive) return
640+
handwritingSessionActive = false
641+
if (pendingStrokes.isEmpty()) return
642+
val locale = subtypeManager.activeSubtype.primaryLocale.localeTag()
643+
val recognizer = dev.patrickgold.florisboard.ime.handwriting.StrokeRecognizerRegistry.active
644+
val result = recognizer.recognize(pendingStrokes.toList(), locale)
645+
pendingStrokes.clear()
646+
when (result) {
647+
is dev.patrickgold.florisboard.ime.handwriting.StrokeRecognitionResult.Candidates -> {
648+
val best = result.candidates.firstOrNull()
649+
if (best != null) {
650+
currentInputConnection?.commitText(best.text, 1)
651+
flogInfo { "Stylus handwriting committed: '${best.text}' (confidence=${best.confidence})" }
652+
}
653+
}
654+
is dev.patrickgold.florisboard.ime.handwriting.StrokeRecognitionResult.NoRecognition -> {
655+
flogInfo { "Stylus handwriting: recognizer returned NoRecognition" }
656+
}
657+
}
605658
}
606659

607660
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,8 @@
846846
<string name="pref__keyboard__bottom_row_preset__programmer" comment="Bottom row preset label">Programmer</string>
847847
<string name="pref__keyboard__bottom_row_preset__navigation" comment="Bottom row preset label">Arrow keys</string>
848848
<string name="pref__keyboard__stylus_handwriting__label" comment="Preference title">Stylus handwriting</string>
849-
<string name="pref__keyboard__stylus_handwriting__summary" comment="Preference summary">Android 14+ devices with a compatible stylus can write directly into text fields. Recognizer support is staged; this toggle reserves the system entry point.</string>
849+
<string name="pref__keyboard__stylus_handwriting__summary" comment="Preference summary">Android 14+ devices with a compatible stylus can write directly into text fields. Install a handwriting recognizer addon for recognition support.</string>
850+
<string name="handwriting__no_recognizer" comment="Toast shown when stylus handwriting starts but no recognizer addon is installed">No handwriting recognizer installed. Install a recognizer addon from Settings → Addons.</string>
850851
<string name="pref__keyboard__utility_key_enabled__label" comment="Preference title">Show utility key</string>
851852
<string name="pref__keyboard__utility_key_enabled__summary" comment="Preference summary">Shows a configurable utility key next to space bar</string>
852853
<string name="pref__keyboard__utility_key_action__label" comment="Preference title">Utility key action</string>

0 commit comments

Comments
 (0)