Skip to content

Repository files navigation

HeyBaji

HeyBaji is a personal AI voice assistant for Android. The intended experience is:

  1. You say “Hey Baji”.
  2. The app answers “Yes, I'm listening.”
  3. You speak a command (for example, “What is the weather today?”).
  4. Speech is converted to text, an AI provider produces a reply, and the reply is spoken with text-to-speech.

The UI Activity can be closed while the assistant infrastructure keeps running only when Android allows it: a user-enabled foreground service, granted microphone and notification permissions, and OEM battery policy that does not kill the process. HeyBaji does not fake unsupported platform behavior (unrestricted background microphone, guaranteed boot-time restart, or bypassing battery optimization).

Current status

Phases 1 and 2 are implemented:

Area Status
Multi-module Gradle project, Hilt, Compose navigation Done
Home, Settings, and Conversation screens (scaffold UI) Done
Domain models (Message, AssistantError) Done
AssistantState + AssistantStateMachine Done
Wake word, speech-to-text, TTS, AI provider Not implemented yet
Foreground service, Always Listening, boot recovery Not implemented yet

You can install the debug app and navigate the three screens. Voice capture and AI replies are not wired yet.

Requirements

  • macOS, Linux, or Windows with Android Studio Otter / Narwhal or newer (AGP 9.3)
  • JDK 17+ (Android Studio’s bundled JDK is enough)
  • Android SDK with compile/target API 37
  • A device or emulator running Android 7.0 (API 24) or higher
  • Network the first time Gradle downloads dependencies

This project uses:

  • Gradle 9.5.0 (wrapper included)
  • Kotlin 2.2.10
  • Android Gradle Plugin 9.3.2

Open and run

Android Studio

  1. Start Android Studio.
  2. File → Open and select this folder (HeyBaji).
  3. Wait for Gradle sync to finish. If sync asks to trust the project, accept.
  4. Connect a phone with USB debugging, or start an emulator (Device Manager).
  5. Select the app run configuration.
  6. Click Run (green play button), or press Shift+F10 (Windows/Linux) / Control+R (macOS).

The first launch shows HeyBaji with buttons for Conversation and Settings.

Command line

From the project root:

# Debug APK
./gradlew :app:assembleDebug

# Install on a connected device/emulator
./gradlew :app:installDebug

# Unit tests (no device required)
./gradlew test

# Instrumented test (device or emulator required)
./gradlew :app:connectedDebugAndroidTest

On Windows use gradlew.bat instead of ./gradlew.

The debug APK is written to:

app/build/outputs/apk/debug/app-debug.apk

How the product is designed to work

When the remaining phases land, a session will follow this state machine:

Idle
  → wake word “Hey Baji”
WakeWordDetected
  → speak “Yes, I'm listening.”
Speaking (acknowledgement)
  → listening for a command
Listening
  → final speech transcript
Processing (query)
  → AI reply
Speaking (response)
  → Idle

Voice acknowledgement can be skipped (settings: voice response off): WakeWordDetected goes straight to Listening.

Races the state machine already handles:

  • Duplicate “Hey Baji” while a session is active is ignored.
  • Cancel from any active state returns to Idle.
  • Listening timeout (silence) returns to Idle; it is not treated as a failure.
  • Processing or speaking timeout becomes a domain AssistantError.
  • Late speech/AI events after cancel are ignored.
  • UI never receives a raw Throwable; it gets AssistantError.userMessage.

Timers are not started inside the state machine. The future engine will read timeoutFor(state) and send AssistantEvent.TimedOut.

Architecture

Clean Architecture + MVVM + unidirectional data flow.

  • UI (feature:*) talks to ViewModels only.
  • Domain (:domain:model) holds shared models such as Message and AssistantError.
  • Assistant state (:assistant:state) is the session state machine. Orchestration, voice, data, and the foreground service will be added as real modules when those phases are implemented.
:app
  ├── :feature:home
  ├── :feature:settings
  ├── :feature:conversation
  ├── :core:designsystem
  └── :core:common

:assistant:state  →  :domain:model
                  →  :core:common

Modules

Module Role
:app Application, Hilt root, MainActivity, navigation graph
:core:common Shared utilities (AppDispatchers)
:core:designsystem Material 3 theme and Compose primitives
:domain:model Message, MessageRole, AssistantError
:feature:home Home screen
:feature:settings Settings screen
:feature:conversation Conversation screen
:assistant:state AssistantStateMachine

Tech stack

Concern Library
Language Kotlin 2.2.10
UI Jetpack Compose, Material 3
DI Hilt
Async Coroutines, Flow, StateFlow
Navigation Navigation Compose
Tests JUnit, Turbine, AndroidX Test

Versions live in gradle/libs.versions.toml.

Project layout

HeyBaji/
├── app/
├── core/
│   ├── common/
│   └── designsystem/
├── domain/
│   └── model/
├── feature/
│   ├── home/
│   ├── settings/
│   └── conversation/
├── assistant/
│   └── state/
├── gradle/libs.versions.toml
└── README.md

Assistant states

Defined in :assistant:state:

  • Idle — waiting for “Hey Baji” or a manual mic tap
  • WakeWordDetected — phrase heard; about to acknowledge or listen
  • Listening — capturing the command
  • Processing(query) — AI is running
  • Speaking(response) — TTS; acknowledgement returns to Listening, a reply returns to Idle
  • Error(error) — recoverable failure with a user-facing message

Domain errors include microphone permission, speech recognition unavailable, network, AI failure, wake-word failure, TTS failure, foreground-service restriction, and battery optimization. Those are mapped to copy the user can hear or read, not stack traces.

Platform limits (do not assume otherwise)

Topic What Android actually allows
Background microphone Only while a microphone foreground service is running and RECORD_AUDIO is granted. Closing the Activity is not enough by itself.
Always Listening User toggle + persistent notification. The OS can still stop the service.
START_STICKY A hint to restart the service, not a guarantee.
Boot recovery BOOT_COMPLETED may fire before microphone FGS is legal. Recovery must wait for a valid system state.
Battery optimization Must not be bypassed automatically. Settings should explain how the user can exempt the app.
OEM skins Xiaomi, Huawei, Samsung, and others may kill background work anyway.

Permissions (later phases)

Permission Why
RECORD_AUDIO Wake word and commands
POST_NOTIFICATIONS Persistent Always Listening notification (API 33+)
FOREGROUND_SERVICE Keep the assistant host alive
FOREGROUND_SERVICE_MICROPHONE Microphone FGS type (API 34+)
RECEIVE_BOOT_COMPLETED Restore Always Listening after reboot if the user enabled it

The UI must explain each permission before requesting it.

Tests

From the project root:

# Everything that does not need a device
./gradlew test

# State machine + domain errors only
./gradlew :assistant:state:test :domain:model:test

State machine coverage includes the happy path, duplicate wake word, timeout, cancellation, empty query/response, invalid transitions, and ignored late events.

Roadmap

Phase Work
1 Gradle, Hilt, modules, Compose navigation — done
2 Domain models, AssistantStateMachinedone
3 Fake wake word, fake STT, fake AI, Android TTS wrapper (new :voice:* modules)
4 AssistantEngine (new :assistant:engine module)
5 Home / settings / conversation production UI
6 VoiceAssistantService foreground service (new :assistant:service module)
7 Always Listening preference (DataStore in a new :core:data module)
8 Boot recovery architecture
9 Real wake-word provider (for example Porcupine), still behind WakeWordDetector

Troubleshooting

Gradle sync fails on Kotlin source sets
The project sets android.disallowKotlinSourceSets=false because KSP 2.2.x still registers generated Hilt sources through kotlin.sourceSets, which AGP 9 built-in Kotlin rejects. That flag is required until Kotlin/KSP move to a 2.3.4+ pair.

SDK / compileSdk 37 missing
In Android Studio: Settings → Languages & Frameworks → Android SDK and install API 37.

Device not listed
Enable USB debugging, accept the RSA prompt, or start an emulator from Device Manager.

Tests fail with a Main dispatcher error
Unit tests use AppDispatchers with Dispatchers.Default instead of Dispatchers.Main. Do not inject Dispatchers.Main on the JVM test classpath.

About

Always-On Voice Assistant :- App that listens for "Hello Assistant" even when the app UI is closed. It should recover appropriately after a device reboot.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages