A minimalist Android keyboard (IME) that converts speech to text via the Groq Whisper API. Compact design with a mic button, punctuation keys, and utility buttons. For regular typing, switch to Gboard.
Working and tested. The app builds, installs, and functions correctly on Xiaomi (MIUI) and other Android devices. Signed release APKs have been shared and tested by multiple users.
- Open Android Studio → "Open" → select the project root folder
- Wait for Gradle sync to complete (first time takes a few minutes)
- If SDK 35 is missing: File → Settings → Android SDK → install Android 15 (API 35)
- Gradle wrapper jar is auto-generated by Android Studio on sync
- Ensure JDK 17 is configured: File → Settings → Build → Gradle → Gradle JDK
- Build: Ctrl+F9 (should say "BUILD SUCCESSFUL")
- Run on device: Shift+F10 (requires USB debugging enabled on phone)
- Build → Generate Signed App Bundle or APK → APK
- Keystore is at
keystore.jksin project root (do NOT commit this file) - Select "release" build variant
- Output:
app/release/app-release.apk
- Settings → System → Languages & input → On-screen keyboard → Enable "GroqAndroid Voice"
- Open the GroqAndroid app → enter your Groq API key (from console.groq.com)
- Tip: In Gboard, long-press the spacebar to quickly switch to GroqAndroid Voice
- Language: Kotlin, minSdk 26, targetSdk 35
- API: Groq Whisper
whisper-large-v3-turbo(supports auto language detection + 15 languages) - Audio: 16kHz, mono, PCM 16-bit → WAV format
- HTTP: OkHttp with Kotlin coroutines (async, non-blocking)
- API key storage: EncryptedSharedPreferences (AES256)
- Build: Gradle with Kotlin DSL, AGP 8.7.x
app/src/main/java/com/groqandroid/
├── GroqIME.kt ← InputMethodService (main keyboard service)
├── GroqApiClient.kt ← HTTP calls to Groq Whisper API
├── AudioRecorder.kt ← Microphone recording → WAV file
├── SettingsActivity.kt ← API key input + keyboard activation helper (launcher)
└── PermissionActivity.kt ← Runtime RECORD_AUDIO permission request
app/src/main/res/layout/
├── keyboard_view.xml ← Keyboard IME layout (mic, keys, buttons)
└── activity_settings.xml ← Settings screen layout
app/src/main/res/drawable/
├── mic_button_bg.xml ← Idle state (blue #2196F3)
├── mic_button_recording.xml ← Recording state (red #F44336)
├── mic_button_processing.xml ← Processing state (orange #FF9800)
├── key_button_bg.xml ← Rectangular key background
├── switch_button_bg.xml ← Round button background
├── ic_mic.xml ← Microphone icon
├── ic_keyboard.xml ← Keyboard switch icon
├── ic_backspace.xml ← Backspace icon
├── ic_settings.xml ← Settings gear icon
└── ic_app.xml ← App launcher icon
[status text / transcription result]
[LANG] [🎤 MIC button] [⚙ Settings]
[ , ] [ . ] [ ? ] [ ! ] [ ⌫ Backspace ]
[ ⌨ ] [ Spacebar ] [ ↵ Enter ]
| Button | Tap | Long press |
|---|---|---|
| Mic | Start/stop recording | - |
Comma , |
Insert , |
Insert ; |
Period . |
Insert . |
Insert : |
? |
Insert ? |
- |
! |
Insert ! |
- |
| Backspace | Delete 1 char | Continuous delete |
| Spacebar | Insert space | Select all text |
Enter ↵ |
New line | - |
| LANG | Open language picker dropdown | - |
" " (quotes) |
Wrap selected text in quotes, or insert empty quotes with cursor between them | - |
| ⌨ (keyboard) | Show input method picker to switch keyboard | - |
| ⚙ (settings) | Open settings activity | - |
- User taps mic → button turns red, recording starts (PCM 16-bit, 16kHz mono)
- User taps mic again → button turns orange, recording stops, WAV is finalized
- WAV file is sent to Groq Whisper API with selected language (or auto-detect)
- Transcribed text is inserted at cursor position + a trailing space
- Button returns to blue, status shows the transcription result
- Keyboard activation banner: Detects if the keyboard is enabled/selected, guides user through setup
- Tip banner: Shows spacebar long-press tip (dismissible, shown once)
- API key input: Stored in EncryptedSharedPreferences
- Language selection is on the keyboard itself (LANG button), not in settings
- Fullscreen mode disabled:
onEvaluateFullscreenMode()returns false. Without this, the keyboard takes over the entire screen on some devices. - No
setInputViewoverride: Previous attempts to force keyboard height viasetInputViewbrokecurrentInputConnection. The current approach useswrap_contentwith fixed-size child views. - Switch keyboard uses picker:
switchToNextInputMethod()crashes on some devices (especially Xiaomi/MIUI). We useshowInputMethodPicker()instead, which is stable everywhere. - All
currentInputConnectioncalls are wrapped in try/catch: The connection can be null or stale, especially during keyboard transitions. - EncryptedSharedPreferences access is wrapped in try/catch: Can fail on first use or after app updates.
- Trailing space after transcription: Automatically adds a space after inserted text so consecutive transcriptions don't merge together.
gradlewscript has no gradle-wrapper.jar — Android Studio generates it on synckeystore.jksshould NOT be committed to version control
androidx.core:core-ktxandroidx.appcompat:appcompatandroidx.security:security-crypto(for EncryptedSharedPreferences)com.squareup.okhttp3:okhttp(HTTP client)org.jetbrains.kotlinx:kotlinx-coroutines-android(async operations)