Skip to content

fix: include floating keyboard prefs in backup, use synchronous commit on restore#115

Merged
LeanBitLab merged 3 commits into
LeanBitLab:mainfrom
AsafMah:fix-export-import
May 18, 2026
Merged

fix: include floating keyboard prefs in backup, use synchronous commit on restore#115
LeanBitLab merged 3 commits into
LeanBitLab:mainfrom
AsafMah:fix-export-import

Conversation

@AsafMah
Copy link
Copy Markdown
Contributor

@AsafMah AsafMah commented May 18, 2026

The settings export/import flow had two bugs that caused user-visible data loss after restoring on a fresh install.

1. floating_keyboard_prefs was never backed up

FloatingKeyboardManager stores the floating-window position (floating_x, floating_y) in its own SharedPreferences file (floating_keyboard_prefs) via DeviceProtectedUtils.getSharedPreferences(ctx, ""floating_keyboard_prefs""). The backup code only serialized the main prefs and the protected prefs — that auxiliary file was silently ignored on both export and import, so users would lose their floating-window position whenever they restored a backup.

This PR introduces a small auxiliaryPrefsToBackUp(ctx) map and wires both the export and import loops through it. Today it has one entry (floating_keyboard_prefsfloating_keyboard_preferences.json); it's structured this way so future feature-local SharedPreferences files can be added by adding one map entry.

gemini_prefs (ProofreadService's EncryptedSharedPreferences) is intentionally not in the map, with a comment explaining why: the values are encrypted under a device-bound master key and would be unreadable on any other device, and they hold API credentials we don't want in a plaintext backup zip.

2. Restore used apply() instead of commit()

After clearing and refilling the SharedPreferences on restore, the code immediately runs AppUpgrade.checkVersionUpgrade(ctx), Settings.startListener(), broadcasts the new-dictionary intent, etc., and the user may also close the settings activity as soon as the ""Backup restored"" feedback shows. apply() writes asynchronously, so there was a real race where the upgrade/listener code (or the next launch of the IME) could read partially-applied prefs, and a process kill right after the toast could lose writes entirely. Switched to e.commit().

Testing

  • ./gradlew :app:compileStandardDebugKotlin — clean build.
  • Built and installed on a real device; restore flow works as before for everything else, plus the floating-window position now survives a fresh-install restore.
  • Round-tripped representative boolean / int / long / float / string / string-set values through the actual settingsToJsonStream / readJsonLinesToSettings pair in a standalone harness to confirm there's no regression in the existing serialization.

Note for users seeing ""my toggles flipped after restore""

Some users (myself initially) thought the boolean remember_toolbar_state / var_toolbar_direction weren't being restored. They actually are — what's happening is that Android's SharedPreferences.getAll() only returns explicitly-written keys, and if the source device left a switch at its default it isn't in the backup at all. After restore, the new install simply uses the default value, which happens to differ from the displayed value on the source (e.g. var_toolbar_direction defaults to true). This is a separate UX issue and isn't fixed here; the right fix would be a full enumeration of known prefs at export time and is more invasive.

AsafMah and others added 3 commits May 18, 2026 07:49
When long-pressing a letter, the popup keyboard now includes all
symbols defined at the corresponding position in the symbols layout,
not just the primary label. Previously a symbols-layout entry such as
`% per` or `( < { [` only contributed its first label to the
letter's popup; the symbol key's own popup keys were discarded.

addSymbolPopupKeys (and the number-row-in-symbols branch of
addNumberRowOrPopupKeys) now collect both the symbol key's label and
its popup labels via PopupSet.getPopupKeyLabels. PopupSet.symbol is
renamed to PopupSet.symbols and changed from String? to
Collection<String>?. createPopupKeysArray addAll's them; getHintLabel
keeps a single-character hint via firstOrNull().

Fixes LeanBitLab#113

Assisted-by: GitHub Copilot CLI
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…us commit

The settings backup/restore previously only included the main SharedPreferences
(device-protected) and the "protected" SharedPreferences, missing the separate
loating_keyboard_prefs file used by FloatingKeyboardManager. After import on
a fresh install, that file would stay empty and the floating-keyboard window
position would silently reset.

Now also back up and restore loating_keyboard_prefs via a new
loating_keyboard_preferences.json zip entry. Encrypted gemini_prefs
(EncryptedSharedPreferences holding device-bound API keys) is intentionally
excluded, both because the master key is device-specific and because we don't
want plain backups of credentials.

Also switch the SharedPreferences writes during restore from �pply() to
commit(). The post-restore code path runs `AppUpgrade.checkVersionUpgrade`,
`Settings.startListener()`, etc. immediately afterwards, and can also be
terminated by the user closing the activity. `commit()` guarantees the new
preferences are persisted before any of that happens.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@LeanBitLab LeanBitLab merged commit d53f039 into LeanBitLab:main May 18, 2026
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