Skip to content

feat(android): IME association fixes, learning, English typing and speed (stroke/English, rebased on Rime) - #1230

Open
DepengWang wants to merge 5 commits into
Open-Less:betafrom
DepengWang:feature/ime-candidates-learning-speed
Open

DepengWang wants to merge 5 commits into
Open-Less:betafrom
DepengWang:feature/ime-candidates-learning-speed

Conversation

@DepengWang

@DepengWang DepengWang commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Improvements to the Android IME's stroke and English input, in four groups: association fixes, learning, English typing conveniences, and speed. All of it is in openless-all/app/android/kotlin/ (plus one CI step for the emulator test); no Rust, desktop or frontend code changes.

Rebased onto beta with #1196 (Rime), #1187, #1188 and #1199. #1196 replaced only the pinyin backend, so the LitePinyin part of this PR (full-pinyin phrase index, two-commit combo learning) is dropped: its files no longer exist and Rime covers both. Everything else applies unchanged. The pinyin row keeps the pieces Rime still uses: the shared candidate-row view reuse, the association plumbing, and typed-out associations now recorded from RimePinyinController's commit callback (a small associationContext() accessor was added for that). #1199 conflicted only in StrokeUserFrequency.kt; see "Speed" below.

Associations

  • The row clears after the last association. Every phrase also sits in the trie node of its own last character, so once a whole phrase was on screen the lookup returned that phrase again, a candidate with nothing left to commit. Tapping it did nothing and it stayed in the row. Those self-matches are now dropped; when nothing else is left, the row is empty rather than falling back to phrases that only share the last character.
  • Associations clear when the host empties the field, e.g. a chat app's send button, which clears the field without restarting input. Detected in onUpdateSelection(): caret at 0 with nothing before or after it.
  • Picked associations rank first. Ranking was corpus weight plus the user score. Weights run from 1 to several hundred thousand, the user score stays in the hundreds, so a habit could only move rare phrases (after 天气, 天气图 at weight 7 could never pass 天气预报 at 2054). Picked phrases now come first by score, the rest by weight, the same rule the stroke candidate row already uses.
  • Typing an offered association out by hand counts as picking it. This replaces the record written on every stroke commit, whose key (the whole rolling context + the committed word) never matched the key an association lookup reads (the matched suffix + the phrase). Those records changed no ranking but used up the 3000-entry store that the stroke personal picks share.
  • This also works when the phrase is typed one character at a time (from @HKLHaoBin's review commit). TypedContinuationHistory keeps the commit boundaries of consecutive commits, so after 天, typing 气 and then 预报 as separate commits still trains 天 → 天气预报. The history resets on backspace, a candidate pick, a new input session, or any commit that doesn't continue the expected context.

Stroke

  • Marks, digits, space and Return drop the half-typed code. These keys committed text without clearing the stroke code, so the code row and its candidates stayed on screen. They now clear the code, the 分词 segments, the code row and the association context first, as pinyin already does on punctuation.
  • A field that the host emptied also clears the code row, and a new input session clears the code row and candidates on screen (the panel isn't rebuilt for it).
  • A learned character keeps its place. Picks were only recorded as corrections, so a character on top because of earlier picks stopped gaining score, decayed, and lost its place to the next one-off pick. Picking it is now recorded too. Picking a corpus-top character that was never learned still records nothing.

English

  • Sentence punctuation replaces the space a candidate pick added: word + , → word, . Only . , ! ? ; :, and only right after a candidate pick.
  • A quick double space after a word becomes . (within 500 ms, English mode only, never in a password field).
  • Both are bound to the caret (from @HKLHaoBin's review commit). The IME remembers the input connection and caret offset where it added the space; if the caret moved, the selection is non-empty, or the editor changed, the next mark or space is typed normally. The draft box's own selection changes are tracked the same way. The edits are also only applied when the deleteSurroundingText/commitText calls succeed.
  • Capitals at a sentence start: at the start of the field, of a line, or after . ! ? and whitespace. Not in email, URL, password or number fields. Tapping Shift while an automatic capital is on turns it off rather than going to caps lock; the user's own Shift and caps lock are never overridden.

Speed

  • Stroke queries stop at a full row. The first stroke matches up to ~48k dictionary entries (48,405 for 一). Every query scored and fully sorted all of them, with the score re-read from SharedPreferences and re-parsed on every comparison. The dictionary is now sorted by frequency once at load, so a query takes the user's picks first and then walks the frequency order until it has 36. The order is the same as before; InputLearningRulesTest checks it against the old full sort on random data. Measured on a OnePlus CPH2573 with the new OpenLessStroke debug log: 0–2 ms for strokes one to four, 8–10 ms from the fifth stroke on (past the 4-stroke prefix index). There is no measurement of the old code; the log is new.
  • Usage stores are cached in memory. FrequencyStampCache holds one parsed copy of each count,lastUsedMs store per process, kept current by a preference listener, so a write from any instance (the keyboard, the settings page clearing the store, an import) is seen everywhere. Before API 30 clear() doesn't notify listeners, so clear() and importAll() also invalidate it directly. In StrokeUserFrequency the cache reads and writes sit inside feat(android): add local configuration migration #1199's migration gate (OpenLessMigrationState.learn { } / settings { }), so a migration restore, which goes through importAll(), also resets the cache. This PR adds no new SharedPreferences writes, so nothing else needs the gate.
  • Shift no longer rebuilds the keyboard. A key already reads the shift state when it is tapped; only the letter labels and the Shift icon were stale. They are now updated in place. With sentence-start capitals this would otherwise have rebuilt the whole panel at every sentence start.
  • The English/pinyin candidate row reuses its views, as the stroke row already does. A reused view only swaps its actions, so keyboardKey()'s long-press handling (including swallowing the tap that ends a long press, which the English "forget this word" long press relies on) is unchanged.

Behavior changes worth a look

  • Typing a mark, digit or space in the middle of a stroke code discards the code instead of committing its first candidate. This matches pinyin; if committing the first candidate is preferred, it is a small change.
  • Sentence-start capitals and the double-space full stop are always on; there is no setting for them yet.
  • Clearing the field yourself with backspace also clears associations.

Relation to #1187, #1188 and #1199

All three are merged and this branch is rebased on top of them:

Testing

  • ./gradlew :app:compileArm64DebugKotlin and :app:testArm64DebugUnitTest on the current branch: 113 tests pass, including StrokePhraseSuffixTest, EnglishAutoTextTest, InputLearningRulesTest and feat(android): add local configuration migration #1199's OpenLessMigrationBundleContractTest.
  • node --test --test-concurrency=1 scripts/*.test.mjs: 92 of 92 pass. (Run in parallel, four of them can fail locally because one test rewrites linux-egui/assets/lucide/*.svg while others read git state.)
  • Emulator integration test ImeAutoTextIntegrationTest (from @HKLHaoBin's commit) runs in CI's connectedX86_64DebugAndroidTest step.
  • arm64 debug APK of the current branch (6468ebc7, with feat(android): add local configuration migration #1199 and the review commit) installed on a OnePlus CPH2573 and checked by hand: the caret-bound auto space and double-space full stop (moving the caret in between), associations learned from a phrase typed in separate commits, clearing / importing / exporting stroke learning data in settings, plus Rime pinyin and its associations, the stroke changes, sentence-start capitals and the Space swipe-up from feat(android): rearrange the English keyboard's swipe-up symbols #1188.

Not tested:

  • Hosts that clear the field without reporting a selection change would not trigger the field-emptied check.
  • The Xiaomi test phone has not run the rebased build.

🤖 Generated with Claude Code

@DepengWang

Copy link
Copy Markdown
Contributor Author

@HKLHaoBin 你好,这个 PR 包含 Android 输入法的联想修复、调频学习、英文输入便利和速度优化,改动全部在 openless-all/app/android/kotlin/,CI 已全绿、可合并。麻烦有空时审核确认一下,谢谢!

@HKLHaoBin

Copy link
Copy Markdown
Collaborator

你好,我关注到了你的 PR,但很遗憾它大概率无法合并,因为我们打算把它的后端改成 PR #1196:Android 与鸿蒙共用 Rime 输入法后端 。
你的 PR 本质上还是基于原来的旧后端(LitePinyin )做进一步改进。若合并了 #1196,你的 PR 将失去改进目标。不过,你的输入体验改进仍然是有价值的,接下来我打算合并 #1196 的新后端架构,同时有选择地移植你 PR 中仍然适用的安卓输入体验改进。

@DepengWang
DepengWang force-pushed the feature/ime-candidates-learning-speed branch from 840ef58 to 5429284 Compare October 19, 2026 07:17
@DepengWang DepengWang changed the title feat(android): IME association fixes, learning, English typing aids and faster stroke queries feat(android): IME association fixes, learning, English typing and speed (stroke/English, rebased on Rime) Oct 19, 2026
@DepengWang

Copy link
Copy Markdown
Contributor Author

@HKLHaoBin 感谢说明!我看了 #1196,它只替换了拼音后端,笔画、英文和联想部分都没动,所以我把这个 PR 变基到了最新 beta(已包含 #1196、#1187、#1188),这样您不用再另外移植:

变基后的分支在一加真机上测过,Rime 拼音、笔画、英文都正常;Kotlin 单元测试和脚本测试全部通过,PR 描述也已按变基后的内容更新。麻烦有空再看一下,谢谢!

@HKLHaoBin

Copy link
Copy Markdown
Collaborator

感谢您的贡献,我即刻审查您的变更

HKLHaoBin
HKLHaoBin previously approved these changes Oct 19, 2026
DepengWang and others added 3 commits October 19, 2026 19:19
…oke and English

Rebased onto the Rime pinyin backend (Open-Less#1196): the LitePinyin changes
(full-pinyin phrase index, combo learning) are dropped; what remains
applies to the stroke and English keyboards and to the pinyin row's
association and candidate plumbing that Rime still uses.

Associations
- Drop the phrase the context already completes from the association
  results, so the row clears once the last association is picked.
- Clear associations (and the stroke code row) when the host empties the
  field, e.g. a chat app's send button.
- Rank associations the user picked before ahead of unpicked ones; typing
  an offered association out by hand counts as picking it (stroke, and
  Rime commits through RimePinyinController's association context).
  Replaces the stroke commit record whose key never matched an
  association lookup.

Stroke
- Mark, digit, space and Return keys drop the half-typed code, its
  segments and the code row before committing; a new input session clears
  the code row and candidates on screen too.
- Keep recording picks of a character that is on top only because of
  earlier picks, so its score stays fresh.

English
- Sentence punctuation replaces the space a candidate pick added.
- A quick double space after a word becomes ". ".
- Capitalize at the start of a sentence, line or field (not in email,
  URL, password or number fields); Shift turns an automatic capital off.

Speed
- Stroke dictionary sorted by frequency once at load; a query puts the
  user's picks first and stops at 36 matches instead of scoring and
  sorting up to ~48k entries (first stroke now 0-2 ms on a OnePlus).
- Usage stores parsed into one process-wide in-memory cache, kept current
  by a preference listener.
- Shift changes relabel the English keys in place instead of rebuilding
  the keyboard; the English/pinyin candidate row reuses its views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… open

With Open-Less#1187's draft bar open, typing goes to the draft box, so an empty
host field must not clear the stroke code row or the associations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iations

Guard candidate spaces and double-space full stops with editor and selection anchors. Retain typed continuation boundaries so separately committed characters train the originally offered association. Add JVM and Android emulator regressions plus safe CI evidence collection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DepengWang
DepengWang force-pushed the feature/ime-candidates-learning-speed branch from d0781e2 to 6468ebc Compare October 19, 2026 11:33
@DepengWang

Copy link
Copy Markdown
Contributor Author

@HKLHaoBin 感谢审查,也感谢补的 bind automatic edits to caret 提交!

批准后 beta 合入了 #1199(本地配置迁移),这个 PR 和它在 StrokeUserFrequency.kt 上有冲突,所以我又变基了一次,强推后您的批准被自动撤销了,麻烦再看一下。这次变基只改了这一个文件的冲突:

  • 保留 feat(android): add local configuration migration #1199 的迁移闸门:record() 仍在 OpenLessMigrationState.learn { } 里,clear()/importAll() 仍在 OpenLessMigrationState.settings { } 里。
  • 本 PR 的内存缓存改动放进闸门内部:record() 里的 cache.get/put,clear()/importAll() 里的 cache.invalidate()。迁移恢复时走 importAll(),缓存会一并失效;FrequencyStampCache 本身也监听 preference 变化。
  • 本 PR 没有新增 SharedPreferences 写入,英文学习存储也不在迁移包里,所以没有别处需要加闸门。

git range-diff:第 2 个提交与之前完全相同;您的提交(现 6468ebc7)增删行完全相同,只是上下文变了;第 1 个提交只多了上面这处冲突解决。

本地验证::app:compileArm64DebugKotlin、:app:testArm64DebugUnitTest(113 个,含 #1199 的 OpenLessMigrationBundleContractTest)通过;node --test --test-concurrency=1 scripts/*.test.mjs 92 个通过。模拟器集成测试 ImeAutoTextIntegrationTest 本机跑不了,看 CI。当前分支的 APK 已在一加 CPH2573 上手动测过,包括您补的光标绑定和分段联想学习,以及设置页里笔画学习数据的清除和导入导出,都正常。PR 描述已同步更新。

Invalidate confirmed association text and continuation history when Pinyin Backspace deletes editor text without an active composition. Add an asynchronous service regression for stale-context learning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complete cold dictionary deployment on the instrumentation test thread before exercising the editor fallback, matching the existing Rime integration test setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@H-Chris233 H-Chris233 added area:ime IME area area:input Input area area:ux UX area bug Something isn't working enhancement New feature or request large-change Large or risky change needs-review Needs review P2 Medium priority platform:android Android-specific labels Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ime IME area area:input Input area area:ux UX area bug Something isn't working enhancement New feature or request large-change Large or risky change needs-review Needs review P2 Medium priority platform:android Android-specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants