EarLLM One — Build & Maintain
Overview
Build, maintain, and extend the EarLLM One Android project — a Kotlin/Compose app that connects Bluetooth earbuds to an LLM via voice pipeline.
When to Use This Skill
- When the user mentions "earllm" or related topics
- When the user mentions "earbudllm" or related topics
- When the user mentions "earbud app" or related topics
- When the user mentions "voice pipeline kotlin" or related topics
- When the user mentions "bluetooth audio android" or related topics
- When the user mentions "sco microphone" or related topics
Do Not Use This Skill When
- The task is unrelated to earllm build
- A simpler, more specific tool can handle the request
- The user needs general-purpose assistance without domain expertise
How It Works
EarLLM One is a multi-module Android app (Kotlin + Jetpack Compose) that captures voice from Bluetooth earbuds, transcribes it, sends it to an LLM, and speaks the response back.
Project Location
C:\Users\renat\earbudllm
Module Dependency Graph
app ──→ voice ──→ audio ──→ core-logging
│ │
├──→ bluetooth ──→ core-logging
└──→ llm ──→ core-logging
Modules And Key Files
| Module |
Purpose |
Key Files |
| core-logging |
Structured logging, performance tracking |
EarLogger.kt, PerformanceTracker.kt |
| bluetooth |
BT discovery, pairing, A2DP/HFP profiles |
BluetoothController.kt, BluetoothState.kt, BluetoothPermissions.kt |
| audio |
Audio routing (SCO/BLE), capture, headset buttons |
AudioRouteController.kt, VoiceCaptureController.kt, HeadsetButtonController.kt |
| voice |
STT (SpeechRecognizer + Vosk stub), TTS, pipeline |
SpeechToTextController.kt, TextToSpeechController.kt, VoicePipeline.kt |
| llm |
LLM interface, stub, OpenAI-compatible client |
LlmClient.kt, StubLlmClient.kt, RealLlmClient.kt, SecureTokenStore.kt |
| app |
UI, ViewModel, Service, Settings, all screens |
MainViewModel.kt, EarLlmForegroundService.kt, 6 Compose screens |
Build Configuration
- SDK: minSdk 26, targetSdk 34, compileSdk 34
- Build tools: AGP 8.2.2, Kotlin 1.9.22, Gradle 8.5
- Compose BOM: 2024.02.00
- Key deps: OkHttp, AndroidX Security (EncryptedSharedPreferences), DataStore, Media
Target Hardware
| Device |
Model |
Key Details |
| Phone |
Samsung Galaxy S24 Ultra |
Android 14, One UI 6.1, Snapdragon 8 Gen 3 |
| Earbuds |
Xiaomi Redmi Buds 6 Pro |
BT 5.3, A2DP/HFP/AVRCP, ANC, LDAC |
Critical Technical Facts
These are verified facts from official documentation and device testing. Treat them as ground truth when making decisions:
Bluetooth SCO is limited to 8kHz mono input on most devices. Some support 16kHz mSBC. BLE Audio (Android 12+, TYPE_BLE_HEADSET = 26) supports up to 32kHz stereo. Always prefer BLE Audio when available.
startBluetoothSco() is deprecated since Android 12 (API 31). Use AudioManager.setCommunicationDevice(AudioDeviceInfo) and clearCommunicationDevice() instead. The project already implements both paths in AudioRouteController.kt.
Samsung One UI 7/8 has a known HFP corruption bug where A2DP playback corrupts the SCO link. The app handles this with silence detection and automatic fallback to the phone's built-in mic.
Redmi Buds 6 Pro tap controls must be set to "Default" (Play/Pause) in the Xiaomi Earbuds companion app. If set to ANC or custom functions, events are handled internally by the earbuds and never reach Android.
Android 14+ requires FOREGROUND_SERVICE_MICROPHONE permission and foregroundServiceType="microphone" in the service declaration. RECORD_AUDIO must be granted before startForeground().
VOICE_COMMUNICATION audio source enables AEC (Acoustic Echo Cancellation), which is critical to prevent TTS audio output from feeding back into the STT microphone input. Never change this source without understanding the echo implications.
Never play TTS (A2DP) while simultaneously recording via SCO. The correct sequence is: stop playback → switch to HFP → record → switch to A2DP → play response.
Data Flow
Headset button tap
→ MediaSession (HeadsetButtonController)
→ TapAction.RECORD_TOGGLE
→ VoicePipeline.toggleRecording()
→ VoiceCaptureController captures PCM (16kHz mono)
→ stopRecording() returns ByteArray
→ SpeechToTextController.transcribe(pcmData)
→ LlmClient.chat(messages)
→ TextToSpeechController.speak(response)
→ Audio output via A2DP to earbuds
Adding A New Feature
- Identify which module(s) are affected
- Read existing code in those modules first
- Follow the StateFlow pattern — expose state via
MutableStateFlow / StateFlow
- Update
MainViewModel.kt if the feature needs UI integration
- Add unit tests in the module's
src/test/ directory
- Update docs if the feature changes behavior
Modifying Audio Capture
-
1---2name: earllm-build3description: Build, maintain, and extend the EarLLM One Android project — a Kotlin/Compose app that connects Bluetooth earbuds to an LLM via voice pipeline.4---567# EarLLM One — Build & Maintain89## Overview1011Build, maintain, and extend the EarLLM One Android project — a Kotlin/Compose app that connects Bluetooth earbuds to an LLM via voice pipeline.1213## When to Use This Skill1415- When the user mentions "earllm" or related topics16- When the user mentions "earbudllm" or related topics17- When the user mentions "earbud app" or related topics18- When the user mentions "voice pipeline kotlin" or related topics19- When the user mentions "bluetooth audio android" or related topics20- When the user mentions "sco microphone" or related topics2122## Do Not Use This Skill When2324- The task is unrelated to earllm build25- A simpler, more specific tool can handle the request26- The user needs general-purpose assistance without domain expertise2728## How It Works2930EarLLM One is a multi-module Android app (Kotlin + Jetpack Compose) that captures voice from Bluetooth earbuds, transcribes it, sends it to an LLM, and speaks the response back.3132## Project Location3334`C:\Users\renat\earbudllm`3536## Module Dependency Graph3738```39app ──→ voice ──→ audio ──→ core-logging40 │ │41 ├──→ bluetooth ──→ core-logging42 └──→ llm ──→ core-logging43```4445## Modules And Key Files4647| Module | Purpose | Key Files |48|--------|---------|-----------|49| **core-logging** | Structured logging, performance tracking | `EarLogger.kt`, `PerformanceTracker.kt` |50| **bluetooth** | BT discovery, pairing, A2DP/HFP profiles | `BluetoothController.kt`, `BluetoothState.kt`, `BluetoothPermissions.kt` |51| **audio** | Audio routing (SCO/BLE), capture, headset buttons | `AudioRouteController.kt`, `VoiceCaptureController.kt`, `HeadsetButtonController.kt` |52| **voice** | STT (SpeechRecognizer + Vosk stub), TTS, pipeline | `SpeechToTextController.kt`, `TextToSpeechController.kt`, `VoicePipeline.kt` |53| **llm** | LLM interface, stub, OpenAI-compatible client | `LlmClient.kt`, `StubLlmClient.kt`, `RealLlmClient.kt`, `SecureTokenStore.kt` |54| **app** | UI, ViewModel, Service, Settings, all screens | `MainViewModel.kt`, `EarLlmForegroundService.kt`, 6 Compose screens |5556## Build Configuration5758- **SDK**: minSdk 26, targetSdk 34, compileSdk 3459- **Build tools**: AGP 8.2.2, Kotlin 1.9.22, Gradle 8.560- **Compose BOM**: 2024.02.0061- **Key deps**: OkHttp, AndroidX Security (EncryptedSharedPreferences), DataStore, Media6263## Target Hardware6465| Device | Model | Key Details |66|--------|-------|-------------|67| Phone | Samsung Galaxy S24 Ultra | Android 14, One UI 6.1, Snapdragon 8 Gen 3 |68| Earbuds | Xiaomi Redmi Buds 6 Pro | BT 5.3, A2DP/HFP/AVRCP, ANC, LDAC |6970## Critical Technical Facts7172These are verified facts from official documentation and device testing. Treat them as ground truth when making decisions:73741. **Bluetooth SCO is limited to 8kHz mono input** on most devices. Some support 16kHz mSBC. BLE Audio (Android 12+, `TYPE_BLE_HEADSET = 26`) supports up to 32kHz stereo. Always prefer BLE Audio when available.75762. **`startBluetoothSco()` is deprecated since Android 12 (API 31).** Use `AudioManager.setCommunicationDevice(AudioDeviceInfo)` and `clearCommunicationDevice()` instead. The project already implements both paths in `AudioRouteController.kt`.77783. **Samsung One UI 7/8 has a known HFP corruption bug** where A2DP playback corrupts the SCO link. The app handles this with silence detection and automatic fallback to the phone's built-in mic.79804. **Redmi Buds 6 Pro tap controls must be set to "Default" (Play/Pause)** in the Xiaomi Earbuds companion app. If set to ANC or custom functions, events are handled internally by the earbuds and never reach Android.81825. **Android 14+ requires `FOREGROUND_SERVICE_MICROPHONE` permission** and `foregroundServiceType="microphone"` in the service declaration. `RECORD_AUDIO` must be granted before `startForeground()`.83846. **`VOICE_COMMUNICATION` audio source enables AEC** (Acoustic Echo Cancellation), which is critical to prevent TTS audio output from feeding back into the STT microphone input. Never change this source without understanding the echo implications.85867. **Never play TTS (A2DP) while simultaneously recording via SCO.** The correct sequence is: stop playback → switch to HFP → record → switch to A2DP → play response.8788## Data Flow8990```91Headset button tap92 → MediaSession (HeadsetButtonController)93 → TapAction.RECORD_TOGGLE94 → VoicePipeline.toggleRecording()95 → VoiceCaptureController captures PCM (16kHz mono)96 → stopRecording() returns ByteArray97 → SpeechToTextController.transcribe(pcmData)98 → LlmClient.chat(messages)99 → TextToSpeechController.speak(response)100 → Audio output via A2DP to earbuds101```102103## Adding A New Feature1041051. Identify which module(s) are affected1062. Read existing code in those modules first1073. Follow the StateFlow pattern — expose state via `MutableStateFlow` / `StateFlow`1084. Update `MainViewModel.kt` if the feature needs UI integration1095. Add unit tests in the module's `src/test/` directory1106. Update docs if the feature changes behavior111112## Modifying Audio Capture113114-