Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/mobile-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ One-hand reach and visual polish can proceed alongside foundation work.

- **Checkout:** `android-keyboard-hardening` on `main`, verified at `aa296c9`.
- **Landed:** alpha17 polish, PR #52 (`80bec1b`), with exact-source Android CI passing.
- **Published:** signed alpha17. **Next:** prepare and verify the [signed alpha18 candidate](plans/active/android-alpha18-snapshot.md); no new keyboard features in this slice. Version metadata, candidate CI and signing are not yet started.
- **Published:** signed alpha17. **Next:** finish the [settings experience pass](plans/active/android-settings-experience.md), then prepare and verify the [signed alpha18 candidate](plans/active/android-alpha18-snapshot.md). Android usability is the current priority; desktop remains parked. Candidate signing is not yet started.
- **Open:** Pixel 8 Pro/GrapheneOS and named-editor QA, TalkBack/Switch Access, landscape, three-button navigation and real Obtainium updates. Emulator success does not close these gates.

## Current status
Expand Down
9 changes: 6 additions & 3 deletions docs/plans/active/android-alpha18-snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Prepare the next signed Android preview from the merged alpha17 stabilization
patch (PR #52, `80bec1b`). Start in `android-keyboard-hardening` from current
`main` (verified September 18 at `aa296c9`), using a dedicated Android release
branch for implementation. Own Android version metadata, release notes and
release documentation. Alpha17 remains the published APK.
release documentation. Alpha17 remains the published APK. The authorized
[settings experience pass](android-settings-experience.md) now precedes candidate
preparation; include its reviewed usability and staging fixes with the polish.

## Constraints and non-goals

Expand Down Expand Up @@ -59,8 +61,9 @@ gh workflow run android-release.yml --ref main -f tag=android-v0.1.0-alpha18 -f

## Current evidence, device gates and stop

Planning only: no alpha18 version change, tag, candidate build, signing or
publication has been performed. The [completed polish record](../completed/android-alpha17-polish.md)
Version metadata and release notes are prepared at 18 / `0.1.0-alpha18`, alongside
the settings experience pass. Final candidate build/CI, tag, signing and
publication remain pending. The [completed polish record](../completed/android-alpha17-polish.md)
records CI 35290270474: 173 instrumentation tests, 14 tooling tests and JVM/lint/builds.

Keep Pixel 8 Pro/GrapheneOS (record exact OS build), named editors, phone latency,
Expand Down
73 changes: 73 additions & 0 deletions docs/plans/active/android-settings-experience.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Android settings experience before alpha18

## Goal and area

Make customization predictable and easy to use before the signed alpha18
candidate. Own `KeyboardSettingsActivity.kt`, the optional preview preference
callback in `TypingPanel.kt`, focused settings instrumentation and Android docs.
The September 18 user instruction prioritizes Android usability improvements;
this bounded pass precedes the existing snapshot contract.

## Constraints

Every settings change, including voice, Reset and preview quick controls, stays
staged until Apply. Cancel must leave persisted preferences untouched. Reset must
keep models and other user data. No typing persistence, ambient recording, new
permissions/dependencies, desktop work or new prediction engine. Dispose replaced
preview panels and invalidate stale input callbacks. Real keyboard quick controls
retain their immediate-save behavior.

## Acceptance

- Apply stays reachable while scrolling; category controls precede the practice
preview. System Back returns from a category before leaving Settings.
- Theme/alignment/layout choices can be changed and changed back; each updates
the preview immediately. Search text and filtered results agree after return.
- Voice, reset and preview controls obey Apply/Cancel. Confirmed reset remains
cancellable until Apply, retains models and restores documented defaults.
- Preview input stays transient. Old panels cannot change preferences or input
after replacement/close. No-op previews must not silently discard staged edits.
- Named instrumentation, tooling/JVM/lint/build checks and owned-view visual
review pass. Physical phone and assistive acceptance stay explicitly open.

## Verification

`python -m unittest discover -s mobile/android/tools -p 'test_*.py'`

From `mobile/android`: `gradlew.bat testDebugUnitTest lintDebug assembleDebug
assembleDebugAndroidTest --no-daemon --console=plain`.

Run `KeyboardTuningTest`, new settings experience tests, `PersistenceTest`,
`PrivateTypingPanelTest`, `OneHandLayoutTest` and `LetterLayoutTest` on API 35.
Capture app-owned Settings views with synthetic practice text and inspect default,
scrolled, large-text and narrow/landscape states. Canonical CI owns the full suite.

## Stop and non-goals

Stop when the bounded contracts pass and the final diff is reviewed; integrate
the Android change and continue the signed alpha18 plan. No broad redesign,
keyboard-engine replacement or claim that emulator success proves phone comfort.

## Evidence

- Implemented staged voice/reset/preview controls, reversible radio choices,
persistent Apply, controls before practice, consistent Back/search, in-memory
rotation retention and disposal of replaced previews. No practice text is retained.
- Initial checks: 14 tooling and 40 JVM tests passed; lint/debug/test APK builds
passed. The first focused API 35 run passed 17 settings/persistence tests;
the related private-panel/one-hand/layout bundle passed 10 tests.
- Owned-view captures at 1080x2400, 840x1800 with font scale 1.3, and 2400x1080
are retained under `artifacts/screenshots/android-settings-alpha18/`. The two
narrow/landscape capture checks passed. Visual review shortened the preview's
country label to avoid truncating "United States".
- The added system Back test initially nested a UI-thread helper inside a
UI-thread predicate. Corrected the fixture; final candidate verification and
canonical CI remain required. No production test failure was hidden or waived.
- Version metadata is now 18 / `0.1.0-alpha18`; signing and publication remain pending.
- Final local candidate passed 28 focused instrumentation tests, 40 JVM tests,
14 tooling tests and lint/debug/release/test APK builds. Full CI 35415294078
ran 180 tests: 179 passed and one existing privacy fixture failed because it
cast the new Settings root to ScrollView. The fixture now finds the practice
field's scroll ancestor; its protection, transient-text and stale-key assertions
remain intact. The corrected privacy fixture plus all seven new settings tests
passed locally (8/8). A new exact-revision CI run is required before signing.
5 changes: 5 additions & 0 deletions mobile/android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ prediction, accessibility and device coverage remain on the
change sizing, theme, number row, terminal controls, vibration or repeat filtering. Reopen the keyboard to apply
saved preferences. The private practice field lets you try the layout without
entering text into another app; it clears when you leave settings.
In the upcoming alpha18 candidate, **Apply** stays visible while you scroll;
controls come before the practice preview. All changes, including voice,
preview quick controls and Reset, wait for Apply. **Cancel** discards them.
Theme/layout choices update the preview immediately. Rotating Settings keeps
pending preferences in memory, while practice text is cleared.
4. For optional dictation, open **Optional · offline voice** in setup. Choose a model
below, open its download in your browser, then return and **Import** that file.
Allow microphone permission. The voice status shows what is still missing;
Expand Down
126 changes: 48 additions & 78 deletions mobile/android/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,89 +1,59 @@
# Utterleaf Android 0.1.0-alpha17
# Utterleaf Android 0.1.0-alpha18

This experimental preview rebuilds the everyday keyboard to the approved design:
an icon toolbar with a fold-out Extra keys panel, a hinted number row, a
redesigned Settings screen, a password-manager shortcut restricted to password
fields, and a suggestion strip that completes the word you are typing.
This preview focuses on dependable everyday typing and predictable customization.
It includes the alpha17 typing/suggestion stabilization and a settings usability
pass. Alpha17's layouts, local emoji, Compose, private drafts, extra keys and
password-field-only password-manager shortcut remain available.

## What changed

- **The approved daily redesign.** The letter keyboard gains an icon toolbar
(undo, redo, copy, cut, paste, private draft, dictate and an expand chevron)
with editing actions one tap away instead of stacked text buttons. The
chevron opens the **Extra keys panel** — Esc, Tab, Ctrl, Alt, Shift, Home,
End, Ins, Del, PgUp, PgDn and the arrow keys — above the letters, with
**F1–F12** behind Fn. The old permanent Terminal row and the Tools/Edit
button strips are superseded; all previous routes keep tap-accessible
equivalents (Caps lock is a long-press on Shift, keyboard tools and settings
are a hold on the emoji key).
- **Hinted number row on by default**, with the digit hints you hold to type.
Letter hints show symbols while the number row is visible and digits when it
is hidden, so hold-to-insert stays complete. Hints sit where the design puts
them and remain optional in Settings.
- **The mockup bottom row**: `?123`, an emoji key (hold it for keyboard tools
and settings), a space labeled with the active language such as
"English (US)", period, and a rounded Enter pill that follows the editor's
action. The comma key moves to the period key's slide menu and the symbols
page.
- **Redesigned Settings**: a searchable category list (Layout & size,
Navigation & terminal, Typing assistance, Holds & gestures, Appearance,
Voice input, Privacy & data) with staged edits — **Apply** saves and
**Cancel** discards — plus a practice message with a live keyboard preview.
New options include fold-out extra keys, auto-capitalization, arrow repeat,
key borders and a System/Light/Dark theme. Reset preferences restores the
new defaults and never deletes models.
- **Password-manager shortcut, password fields only.** A keyring button
appears in the toolbar on explicit password fields and opens your configured
autofill application. It is never shown on ordinary text, email or number
fields; the keyboard sends nothing to the target application, and the key
does not exist when no autofill application is configured.
- **Suggestion strip.** While you type, up to three completions of the current
word appear below the toolbar, drawn from a frequency-ordered English list
derived solely from public-domain Project Gutenberg texts. Tapping a
suggestion completes only the word being typed — nothing is auto-corrected,
learned or sent. The strip is off on password and terminal fields, has an
on/off switch under Typing assistance, and adds no permissions.

Alpha16's All Actions behavior, latched Ctrl/Alt, complete US punctuation,
Backspace selection gesture, private drafts, local emoji, Latin composition,
QWERTY/QWERTZ/AZERTY layouts, one-hand alignment and long-press hint insertion
remain included. The keyboard engine is Utterleaf's own implementation.
Existing preferences migrate once to the new defaults; your explicit choices
are kept from then on.
- **Typing stays independent of suggestions.** Dictionary loading and bounded
suggestion reads run off the typing path. Completions refresh after cursor
changes and verify the exact word and session before replacing it. Stale or
selected text is rejected. Ordinary touches avoid redundant modifier updates.
- **Stable suggestion geometry.** Empty and populated completion states keep the
same row height, so the letters do not jump as you type. Insets and explicit
bottom spacing have separate regression coverage.
- **Settings that honor Apply and Cancel.** Voice options, preview quick controls
and confirmed resets now stay staged with the other preferences. Apply saves;
Cancel discards. Reset never deletes models or other user data.
- **Controls before the preview.** Apply stays at the top while categories scroll.
Theme, alignment and letter-layout selections immediately refresh the practice
keyboard, including when returning to the original choice.
- **Consistent navigation.** Back returns from a category to the settings list;
search text and results remain in sync. Rotation retains pending preferences in
memory, while private practice input is cleared. Replaced preview panels are
disposed so their old controls cannot insert text or save preferences.

## Install and updates

Use **Utterleaf-Android-0.1.0-alpha17.apk**, version code **17**. The package remains
`org.utterleaf.voice` and retains the persistent alpha03-and-later signing
identity. Update an existing signed preview in place; do not uninstall first.
The display name does not create a second app or a new settings store.
Alpha01/alpha02 used disposable debug signers and require a one-time reinstall,
which removes their app data.
Use **Utterleaf-Android-0.1.0-alpha18.apk**, version code **18**. The package remains
`org.utterleaf.voice` with the persistent alpha03-and-later signing identity.
Update an existing signed preview in place; do not uninstall first. Alpha01/02
used different debug signers and require a one-time reinstall that removes their
app data. The experimental foundation package is a separate channel.

The release includes SHA-256 checksums, public signing-certificate information
and version metadata. The separate experimental foundation package is not this
update channel. Never substitute an unsigned or CI debug APK for a signed update.
The release includes SHA-256 checksums, signing-certificate information and version
metadata. Do not substitute an unsigned or CI debug APK for a signed update.

## Verification and limits

This is an Android 8+ ARM64/x86_64 development preview, with English typing layouts
and reviewed English offline speech models. Automated checks cover the new
toolbar, panel and strip behavior, the restricted-field gates, native editor,
gesture, privacy and layout contracts. The signing workflow requires successful
exact-revision Android CI, package/version/certificate checks and signed
install/upgrade/reinstall checks before publication.

Physical-phone testing is deferred to follow-up user feedback. TalkBack, Switch
Access, landscape, broad editor behavior and real Obtainium updates remain
unverified. The suggestion list is drawn from classical prose and
under-represents some modern vocabulary; expandable local word lists are
planned. Suggestions complete words but never replace them: auto-correction,
next-word prediction, swipe typing and broader language support are not
included in this snapshot.

Typing needs no model or microphone permission. Dictation starts only by an
explicit action and is disabled in password fields. The password-manager key
launches your configured autofill application and sends it nothing. No Internet
permission, ambient recording, typing history, clipboard monitoring or saved
audio history is added. Explicit Copy/Cut/Paste still use the receiving editor's
clipboard commands. Keyboard screenshot protection remains enabled.
Android 8+ ARM64/x86_64 development preview. The release workflow requires
successful exact-revision CI, package/version/certificate checks and signed
install/upgrade/reinstall checks. Focused emulator tests cover staged voice/reset,
preview controls, reversible choices, rotation, navigation and model preservation.
Settings views are reviewed at default, narrow/large-text and landscape emulator
sizes. These checks do not establish physical-phone comfort or latency.

Pixel 8 Pro/GrapheneOS, broad external editors, TalkBack/Switch Access, sustained
landscape use and real Obtainium updates remain open acceptance gates. An editor
that blocks a suggestion read can delay later suggestions until that read returns;
ordinary typing remains independent. Tapping a suggestion still re-verifies the
word through the editor and can wait for a slow editor response.

The public-domain English completion list under-represents modern vocabulary.
No automatic correction, next-word prediction, swipe or broader language support
is added. No Internet permission, typing history, passive learning, clipboard
monitoring or ambient recording is added. Typing needs no model or microphone
permission. Dictation requires an explicit action and is disabled in password
fields. Keyboard screenshot protection remains enabled.
4 changes: 2 additions & 2 deletions mobile/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId = "org.utterleaf.voice"
minSdk = 26
targetSdk = 36
versionCode = 17
versionName = "0.1.0-alpha17"
versionCode = 18
versionName = "0.1.0-alpha18"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
externalNativeBuild { cmake { arguments += "-DANDROID_STL=c++_shared" } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ class DeviceTest {
}
instrumentation.runOnMainSync {
val content = activity.findViewById<android.view.ViewGroup>(android.R.id.content)
(content.getChildAt(0) as android.widget.ScrollView).fullScroll(android.view.View.FOCUS_DOWN)
// The persistent Apply header sits outside the scrolling content.
// Find the practice field's scroll ancestor instead of assuming a root type.
var ancestor = practice!!.parent
while (ancestor !is android.widget.ScrollView) {
check(ancestor !== content) { "Practice field has no scrolling container" }
ancestor = checkNotNull(ancestor.parent)
}
ancestor.scrollTo(0, ancestor.getChildAt(0).height)
}
instrumentation.waitForIdleSync()
val screenshot = instrumentation.uiAutomation.takeScreenshot()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,10 +462,12 @@ class KeyboardGeometryTest {
.performClick()
}
instrumentation.waitForIdleSync()
// Alignment radios sit below the practice preview; scroll to them.
// Controls precede the preview; scroll the alignment group into view.
main {
(descendants(preferences.window.decorView)
.filterIsInstance<android.widget.ScrollView>().first()).fullScroll(View.FOCUS_DOWN)
val views = descendants(preferences.window.decorView)
val alignment = views.filterIsInstance<android.widget.RadioButton>()
.single { it.text == "Full width" }.parent as View
views.filterIsInstance<android.widget.ScrollView>().first().scrollTo(0, alignment.top)
}
instrumentation.waitForIdleSync()
val bitmap = main {
Expand Down
Loading
Loading