Keyboard app (IME) with a tabbed file manager and word prediction. 100% Kotlin, builds with Gradle.
Grab the latest signed APK from the GitHub Releases:
Every tagged release (v*) is built and published automatically by CI:
| File | Purpose |
|---|---|
KeyTab-<version>.apk |
Signed release APK — install this |
Install: open the APK in a file manager (allow "install unknown apps"), then enable KeyTab in Settings → System → Languages & input → On-screen keyboard and switch to it in any text field.
File manager in the keyboard -- browse folders, switch tabs, navigate with back-stack and parent-navigation. Tapping a file inserts its path; in the app it opens via VIEW-Intent. Each tab remembers its own directory. Listing runs asynchronously so large folders don't freeze the UI.
Word prediction -- offline n-gram model (FrequencyWords de_50k, CC-BY-SA-4.0) with bigrams for next-word prediction, a user dictionary that learns as you type, prefix autocomplete, Damerau-Levenshtein fuzzy correction, and case matching. The top suggestion is rendered 2x wider with a green accent bar for easier tapping. Toggleable in settings.
Dynamic key sizing -- likely-next keys scale up to 1.15x, unlikely ones down to 0.85x, driven by the current suggestion scores. Toggleable in settings.
Notes tab -- editor and clipboard merged into one tab. The "Load" button opens a folder browser (IME dialog with proper window token). The clipboard holds up to 50 persistent entries; tapping one inserts directly into the target field (the editor does not intercept it -- only Load fills the editor).
Terminal tab -- optional interactive shell in the keyboard, togglable in settings.
Keyboard -- full InputMethodService with TAB key (sends KEYCODE_TAB, useful for Termux/SSH), shift/caps-lock, long-press popups for umlauts/special characters, accelerating backspace on long-press (250ms down to 30ms).
Privacy -- no network permission, no data collection. All data stays on the device.
KeyTabImeService is the keyboard core. Sub-features live in their own classes:
| Class | Responsibility |
|---|---|
FileManagerPanel |
File manager (navigation, async listing) |
EditorPanel |
Notes editor with save/load and folder browser |
ClipboardPanel |
Clipboard history |
SuggestionEngine |
Word prediction (offline, testable) |
TerminalPanel |
Interactive shell |
TextEditLogic |
Pure, Android-free text logic |
All panels share a background executor for file I/O and a main handler for UI updates; stale results are discarded on navigation.
./gradlew :app:testDebugUnitTest20 unit tests for TextEditLogic, 11 Robolectric panel tests, 19 tests for SuggestionEngine. The logic class is intentionally Android-free so it runs without an emulator.
./gradlew :app:assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apkRequires JDK 17 and Android SDK (compileSdk 34). Set the SDK path in local.properties (sdk.dir=...) or via ANDROID_HOME.
The release keystore lives at keystore/keytab-release.jks (local only, git-ignored — back it up, it is required to sign updates with the same signature).
# Defaults: keystore/keytab-release.jks, store/key password "keytab-release", alias "keytab"
./gradlew :app:assembleRelease
# APK: app/build/outputs/apk/release/app-release.apkOverride via environment variables:
| Variable | Purpose |
|---|---|
KEYTAB_KEYSTORE |
Path to the .jks keystore |
KEYTAB_KEYSTORE_PASSWORD |
Keystore password |
KEYTAB_KEY_ALIAS |
Key alias (keytab) |
KEYTAB_KEY_PASSWORD |
Key password |
CI note: lintVital is disabled (
lint { checkReleaseBuilds = false }) becauselint-gradledownloads break behind restrictive TLS environments.
Google's aapt2 is x86_64-only. Use the ARM build tools from https://github.com/lzhiyong/android-sdk-tools and set in ~/.gradle/gradle.properties:
android.aapt2FromMavenOverride=/path/to/aapt2
Building directly on the SD card is unreliable (the Gradle daemon gets killed, FUSE file locks cause issues). Copy the project to internal storage and build there:
cp -r /path/to/keytab ~/build/
cd ~/build/keytab
# gradlew has no exec bit on FAT -- call it with sh
sh ./gradlew :app:assembleDebug --offline
cp app/build/outputs/apk/debug/app-debug.apk /path/to/keytab/app-debug-new.apkLong-running builds should be started with setsid … & to avoid the terminal timeout killing the Gradle process. --offline saves time when no new dependencies are needed.
Requires a running Shizuku server and the ~/bin/rsh wrapper.
# Copy APK to /data/local/tmp (the shell cannot see /mnt/... paths)
sh ~/bin/rsh 'cp /sdcard/path/to/keytab/app-debug-new.apk /data/local/tmp/keytab.apk'
# Install (use -r for update)
sh ~/bin/rsh 'pm install -r /data/local/tmp/keytab.apk'
# -> "Success"
# Verify
sh ~/bin/rsh 'rm -f /data/local/tmp/keytab.apk; dumpsys package com.piotv.keytab | grep lastUpdateTime'Or copy the APK, open it in a file manager, and confirm the package installer dialog. Then enable the keyboard in Settings -> System -> Languages & input -> On-screen keyboard and switch to it in any text field.
app/src/main/java/com/piotv/keytab/
├── MainActivity.kt # Settings: enable keyboard, theme, toggles
├── file/FileManagerFragment.kt # File manager in the app (with DiffUtil)
└── ime/
├── KeyTabImeService.kt # Keyboard core: keys, shift, popups, tabs
├── FileManagerPanel.kt # IME file manager
├── EditorPanel.kt # Notes editor with folder browser
├── ClipboardPanel.kt # Clipboard history
├── SuggestionEngine.kt # Word prediction
├── TerminalPanel.kt # Interactive shell
├── LetterPopup.kt # Long-press characters + drag selection
└── TextEditLogic.kt # Pure, testable text logic
app/src/test/java/com/piotv/keytab/ime/
├── PanelsTest.kt # 11 Robolectric tests
├── SuggestionEngineTest.kt # 19 unit tests
└── TextEditLogicTest.kt # 20 unit tests
app/src/main/assets/
└── de_freq_top6000.txt # Frequency corpus (CC-BY-SA-4.0)
- Housekeeping: removed stray debug APK from repo root, aligned version metadata (0.7.2, versionCode 14), fastlane changelog added
- Maintenance release: word-delete logic fixed (single separator space kept, multi-space gap deleted with the word)
- Case matching: suggestions capitalize on empty input
- CI: unit tests green, actions upgraded to v5
- Release automation: tag
v*builds a signed APK and publishes a GitHub release
- Fix: SuggestionEngine.topBaseOrder jetzt lazy-initialisiert (Crash bei Instanziierung behoben)
- Fix: FileManagerPanel.navigate() null-sicher (Crash bei Navigation behoben)
- Fix: TerminalPanel Shell-Fallback für verschiedene Android-Geräte
- Fix: SuggestionEngine thread-safe (ConcurrentHashMap)
- Fix: KeyTabImeService.onDestroy() für korrektes Cleanup
- Feature: Build-Skripte (build_keytab.sh, install_keytab.sh)
- Word prediction with unigram frequencies, bigrams, user dictionary, prefix autocomplete, fuzzy correction
- Dynamic key sizing driven by suggestion scores
- Optional toggles for suggestions and dynamic keys in settings
- Notes tab merges editor and clipboard; folder browser on load (IME dialog fix)
- Terminal tab label spelled out; accelerating backspace
- Theme fix: sun symbol now visible in light mode
- 19 unit tests for SuggestionEngine
- Optional terminal tab with interactive shell, toggleable in settings
- Enter key keeps constant size and position across all tabs
- File manager state (current dir + back-stack) persisted across restarts
- formatSize supports GB and TB
- Long-Press popup extracted into LetterPopup class
- Long-press popups with punctuation on letter keys
- Drag selection in popup (Gboard-style)
- Dark/light toggle, persisted
- Number row toggle in settings
- TAB key and dot button in bottom row
- Files tab: long-press context menu
- Long-press Backspace deletes whole word
- File I/O runs asynchronously
- Long-press popup uses theme colors
- Text logic extracted into testable class
- Character layer (?123) with toggle
- File manager shows files with sizes
- Storage permission handling
- Initial MVP: keyboard with TAB key and tabbed file manager
MIT -- see LICENSE