Project Overview
This was one of those projects where the requirements pushed right up against Android’s limits. The client needed a reliable way to auto-answer incoming calls and play a specific audio file into the call — something the standard telephony APIs simply don’t allow without root. I ended up diving deep into ALSA and root-level audio routing to make it work consistently across devices.
Beyond the core automation, I added practical controls like answer delay, volume, looping, and a full call log with optional recording. The UI is straightforward but polished with Jetpack Compose, so users can configure everything quickly and see clear status feedback.
Technical Overview
A blend of modern Android practices with low-level root integration where absolutely necessary.
- UI: Jetpack Compose with custom components and subtle animations
- Architecture: Clean + MVVM, Koin DI, Kotlin Flows
- Audio Core: Root shell commands + ALSA mixer control for voice uplink injection
- Call Handling: TelephonyManager + foreground service
- Persistence: Room for settings and logs
- Testing: Heavy manual testing across devices due to root requirements
Visual Showcase
First of all, some permissions and dependencies must be installed to make the app work and for that, i'll prepared a screne that visualize the required things super clean and efficient.
In the home screen, i prepared preferences settings and the logbook. Users are able to toggle the enablity of the service, mute rington, make delay before answering the call, set the audio file, and etc.
When a call revices, an activity will popup that represents the automation hapenning.
The Challenge
Android deliberately restricts apps from injecting audio into active voice calls for privacy and security reasons. The only reliable way to achieve this is through root access and low-level audio routing — which brings its own set of device-specific quirks and stability concerns.
- Audio injection: No public API exists to play audio into an ongoing call — required root and ALSA manipulation.
- Device variability: Different manufacturers handle audio routing and root privileges differently.
- Reliability under load: Needed consistent behavior even with poor signal or background restrictions.
- User trust: Clear status and controls so users know exactly when the app is active.
- Battery & performance: Avoid draining resources while waiting for calls.
Approach & Key Decisions
I went all-in on root-level integration while keeping the rest of the app clean and modern. The core audio handling lives in a native module, while the UI and logic stay pure Kotlin/Compose.
Audio Routing
Used root to redirect audio output to the voice call uplink via ALSA mixer paths — the only consistent way across devices.
- Shell commands to set correct mixer controls before playback
- Pre-processed audio files to mono PCM to match telephony requirements
- Fallback detection and user guidance when root or paths weren’t available
Architecture
Kept the app modular with Clean Architecture so the risky root/audio parts are isolated.
- Foreground service for call detection and playback to survive background limits
- Koin for DI, Flow/StateFlow for reactive state
- Room for persisting settings and call logs
UI & Feedback
Made the interface calm and transparent — users need to trust what’s happening during calls.
- Persistent notification with current status
- Subtle in-call overlay with playback progress
- Clear enable/disable toggle with confirmation
- Detailed logs for troubleshooting
Stability
Added safeguards for real-world edge cases.
- Graceful cleanup on errors or call end
- Watchdog for stuck audio states
- Configurable timeouts and retries
Results & Impact
Successfully played selected audio into calls on all tested rooted devices.
Delay, volume, looping, and recording all configurable with immediate feedback.
Handled interruptions (signal loss, other calls) without crashing or leaving audio stuck.
Reflection
This app was a deep dive into Android’s underbelly — working around intentional platform restrictions to solve a very specific need. It reminded me how powerful root access can be when used carefully, but also how important clear communication and safeguards are when building something that operates at that level. In the end, the client got exactly what they needed: reliable, configurable call automation that just works.
Key Takeaways
- Root is a last resort: But when it’s the only way, isolate it completely.
- User trust matters most: Especially when the app does something invisible like injecting audio.
- Feedback is everything: Clear status and logs turn a 'magic' feature into something users feel in control of.
- Polish the basics: Even a niche tool feels professional with a clean, thoughtful UI.


