Skip to content

feat(android-ime): keyboard data export, fuzzy pinyin and separate learning stores (depends on #1230) - #1236

Draft
DepengWang wants to merge 6 commits into
Open-Less:betafrom
DepengWang:feature/ime-pinyin-learning-portability
Draft

DepengWang wants to merge 6 commits into
Open-Less:betafrom
DepengWang:feature/ime-pinyin-learning-portability

Conversation

@DepengWang

@DepengWang DepengWang commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #1230. This branch is stacked on #1230's head (272be3ba), so until #1230 merges the diff here also shows its commits. Only the last commit (abb0969e) belongs to this PR. I'll rebase onto beta and mark it ready once #1230 is in.

Summary

Android keyboard only (openless-all/app/android/kotlin/ and the shared mobile-ime/ Rime wrapper); no Rust core, desktop or frontend changes, and the migration bundle format is unchanged.

  1. Keyboard data export/import on the keyboard settings page (the haptics page), independent of the migration bundle and cloud sync.
  2. Fuzzy pinyin switches.
  3. Separate stores for stroke picks and association picks, with larger caps and batch trimming.
  4. LitePinyin's leftover backup adapter removed.

1. Keyboard data export / import

Under "个人偏好数据", two actions write and read one JSON file (openlessKeyboardDataVersion: 1). A checklist picks the parts:

Part Content On import
Keyboard settings keyboard height and raise, haptics (on/amplitude/duration), debug features, cloud pinyin, weekly public dictionary, fuzzy pinyin, English suggestions replaced
Pinyin user dictionary Rime pinyin_simp snapshot (.userdb.txt text) merged by Rime
Stroke ranking stroke code → picked character merged
Association learning text before → picked association merged
Cloud notes webhook URL and token (plaintext, labelled) replaced; unchecked by default on import
  • Merging keeps the higher count and the later use per entry and never deletes. Rime's own restore_user_dict merge does the same for the pinyin dictionary.
  • Every part is validated before anything is written: known keys only, value types and ranges (e.g. height 220–420 dp, amplitude 1–255), stroke keys only in the stroke part, a snapshot whose header says pinyin_simp / userdb. One invalid item rejects the file.
  • Settings that are also in the migration bundle (haptics, English suggestions, debug) are written through OpenLessMigrationState.settings. A changed fuzzy-pinyin set triggers a redeploy.
  • This is deliberately not part of the .omb bundle or encrypted sync; app settings keep travelling through those.

2. Fuzzy pinyin

Seven switches under "拼音云候选": z c s = zh ch sh, n = l, f = h, r = l, an = ang (covers ian/uan), en = eng, in = ing. They are rime-ice's own commented-out rules, appended to speller/algebra through openless_pinyin_{learning,private}.custom.yaml in the user data dir. Both schemas get the same patch because they share one compiled spelling index. Toggles made while a redeploy runs are folded into the next one.

Native Rime (mobile-ime/rime_engine.cpp, shared with HarmonyOS)

  • user_dict_export / user_dict_import: librime levers backup_user_dict / restore_user_dict on pinyin_simp.
  • redeploy: removes the compiled pinyin schemas from build/ (so a same-second rewrite isn't seen as up to date) and deploys both again.
  • All three first destroy every session. That commits the user dictionary transaction and releases the LevelDB lock that the levers tools need. OpenLessRime.sessionGeneration is bumped, and RimePinyinController starts a new session when its generation is stale instead of failing with "session is stale".
  • A second installation_update run at initialization: on a fresh profile the first run only writes installation.yaml and leaves Rime's sync dir as the relative "sync" (the read-only process cwd on Android), so a backup failed until the next process start. Export also refuses a non-absolute sync dir with a clear error. Found by CI's fresh emulator.
  • rime_require_module_levers() keeps the statically linked levers module from being dropped by the linker.
  • rime_levers_api.h is copied unchanged from librime 1.17.0, because its install step only ships rime_api.h.

3. Separate learning stores

StrokeUserFrequency used to hold stroke-code picks and association picks in one store with a 3000 cap, so heavy use of one evicted the other. Now:

  • openless_stroke_frequency keeps stroke picks (cap 5000), and the new openless_association_frequency keeps association picks (cap 10000).
  • Existing data is split once by key shape (stroke contexts are spelled only with hspnz*), inside the migration fence.
  • Past the cap a store trims to 90% in one pass instead of a full copy and sort for every new entry once full. Lookups go through FrequencyStampCache, so query cost doesn't depend on store size.
  • The migration bundle and legacy JSON keep one combined strokeFrequency map (15,000 at most, within the 20,000 limit). On import it is split by key.

4. LitePinyin leftovers removed

LegacyPinyinBackup, its settings row and its legacy-JSON import category are gone. Its two SharedPreferences stores are deleted on first Rime initialization. Bundle schema 1 requires pinyinLearnedPhrases, so it is kept, always written empty and ignored on import. That keeps bundles readable by both older and newer apps without a schema change. local-bundle-contract.test.mjs, ImeLearningMigrationTest and OpenLessMigrationBundleInstrumentedTest are updated to match.

Testing

  • :app:compileArm64DebugKotlin, :app:testArm64DebugUnitTest: 123 pass, including 10 new in RimePinyinDataTest (fuzzy patch, snapshot validation, key classification, entry validation and merging, trim target, setting ranges, webhook URLs, default checklist).
  • :app:compileArm64DebugAndroidTestKotlin.
  • node --test --test-concurrency=1 scripts/*.test.mjs: 92 pass.
  • arm64 debug APK installed on a OnePlus CPH2573 and checked by hand: export and import of every part, fuzzy pinyin on and off, the split counters, and normal pinyin/stroke/English typing afterwards.
  • New instrumented tests in RimeEngineIntegrationTest, not run locally (CI emulator): userDictionaryExportsAndMergesSnapshots, fuzzyPinyinPatchIsDeployedAndRemoved, controllerStartsANewSessionAfterSessionsWereClosed.
  • HarmonyOS build with the rime_engine.cpp changes.

Worth a look

  • Opening the keyboard settings page reads the dictionary size through an export, which closes the open Rime sessions; a half-typed pinyin composition is dropped (the keyboard is hidden at that point).
  • The keyboard data file is plaintext and contains learned words, plus the token when that part is exported.

🤖 Generated with Claude Code

DepengWang and others added 5 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>
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>
@DepengWang
DepengWang force-pushed the feature/ime-pinyin-learning-portability branch 2 times, most recently from d83910a to 25933f4 Compare October 19, 2026 14:51
…arning stores

Keyboard settings page:
- "Export / Import keyboard data": one JSON file with this page's
  preferences (keyboard size, haptics, switches, fuzzy pinyin), the Rime
  pinyin user dictionary, stroke ranking, association learning and the
  cloud notes webhook. Each part is chosen in a checklist; settings are
  replaced, learning data is merged (higher count and later use win).
  It is independent of the migration bundle and cloud sync.
- Fuzzy pinyin: seven pairs from rime-ice's own rules, applied to both
  pinyin schemas through a .custom.yaml patch and a redeploy.

Native Rime (shared with HarmonyOS): user_dict_export / user_dict_import
through librime levers backup/restore, and redeploy. Each closes every
session first; controllers start a new one after a session generation
bump. rime_levers_api.h is copied from librime 1.17.0, whose install step
only ships rime_api.h.

Stroke picks and association picks get separate stores (caps 5000 and
10000, trimmed to 90% in one pass), split once from the shared store.
The migration bundle keeps one combined "strokeFrequency" map.

LitePinyin's backup adapter and stores are removed. The bundle keeps
"pinyinLearnedPhrases" (schema 1 requires it), always empty and ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DepengWang
DepengWang force-pushed the feature/ime-pinyin-learning-portability branch from 25933f4 to abb0969 Compare October 19, 2026 15:04
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.

2 participants