Dev Investigation Notes

Keeping the microphone on in an Android app: a pre-implementation checklist for boot startup and offline SpeechRecognizer

This is a translated article. Read the original in Korean ↗

The plan was to move a personal voice assistant (voice-assistant), which listened for a wake word on a PC and read responses aloud, to an always-listening app on an Android tablet docked on a charger. Before implementing it, I asked for a list of "what an always-on microphone needs." But checking it against the official documentation showed that two items were wrong as written: automatic startup on boot and built-in speech recognition = local.

This article is a design-stage checklist. There are no results from testing on a physical device; all factual judgments rely on the official Android documentation (checked on 2026-09-23). The target is Android 14 and later (targetSdk 34 or higher), and local recognition assumes devices running API 31 or higher. Devices below API 31 have no on-device recognizer, so they are excluded from support for apps that require local recognition. The app architecture—separating the ears and mouth from the brain, and using MockBrain—is covered separately in Connecting Claude Code to an Android tablet: where do you start with a voice assistant app?.

Conclusion

Things to check before building an always-listening app:

#ItemVerdict
1A service with android:foregroundServiceType="microphone" + declarations for RECORD_AUDIO, FOREGROUND_SERVICE, and FOREGROUND_SERVICE_MICROPHONERequired (targetSdk 34 and higher require declarations for the type and its type-specific permission)
2Start the service from a visible Activity after obtaining microphone permissionRequired. RECORD_AUDIO is a "while-in-use" permission, so starting a new service from the background can be blocked
3Use RECEIVE_BOOT_COMPLETED to start the recording service automatically right after bootNot allowed. A BOOT_COMPLETED receiver cannot launch a microphone-type FGS (starting with Android 14). Design around opening the app once after boot
4POST_NOTIFICATIONSNot a prerequisite for starting an FGS. Denying it only changes where the notification appears. The FGS still needs a notification
5Battery optimization exemption, charging dock, and WAKE_LOCKHelpful, but no guarantee of uninterrupted operation. Measure prolonged operation with the screen locked yourself
6Use SpeechRecognizer.createSpeechRecognizer() for STTDoes not guarantee local recognition. It may send audio to a remote server. If offline operation is required, explicitly use the on-device recognizer available in API 31+
7Do wake-word detection (continuous AudioRecord) and STT use the microphone at the same time?No guarantee. For the first implementation, switch sequentially: "detect wake word → release AudioRecord → STT → TTS → resume wake-word detection"

The basic on-device recognition setup looks like this (API 31 and higher).

val recognizer = if (SpeechRecognizer.isOnDeviceRecognitionAvailable(context)) {
    SpeechRecognizer.createOnDeviceSpeechRecognizer(context)
} else {
    // If local recognition is required, show an error here instead of silently falling back to the standard recognizer
    null
}

Even if isOnDeviceRecognitionAvailable() returns true, that does not mean the Korean model is ready. On API 33, follow these steps to check the Korean model.

  1. Pass a recognition intent containing EXTRA_LANGUAGE="ko-KR" to recognizer.checkRecognitionSupport(intent, executor, callback). The callback's RecognitionSupport provides the list of installed on-device languages (installedOnDeviceLanguages)
  2. If Korean is missing from the list, request a model download with recognizer.triggerModelDownload(intent), then repeat step 1 after it finishes. If the download has not finished or has failed, show a "한국어 모델 없음" (Korean model unavailable) error
  3. The final test is to try recognition with the network actually turned off

RecognizerIntent.EXTRA_PREFER_OFFLINE=true may be ignored depending on the implementation, so it must not be used as a guarantee of offline operation. (SpeechRecognizer, RecognizerIntent)

Situation

The planned app architecture was this: a Foreground Service (a service that keeps running while showing the user a notification) holds the microphone open. When an openWakeWord wake-word model (the same hey_jarvis used in the PC version) detects "Hey Jarvis," Android's SpeechRecognizer converts speech to text, and TextToSpeech reads the response aloud. The part that generates responses is initially a fake implementation called MockBrain.

The device would be a dedicated tablet left plugged into a charger, so the assumption was that most battery issues would disappear.

Symptom

The preparation list received before implementation looked like this.

The list makes it sound like a few declarations are all it takes. Build the app exactly this way, though, and recording may fail to start after boot, or recognition you believed was offline may actually send audio to a server.

Cause

The items on the list failed to distinguish "just declare it" from "it can be blocked depending on when you start it."

1. Automatic startup on boot. A microphone FGS that was started legitimately and continues recording after the app moves into the background is different from starting a new one in the background. RECORD_AUDIO is subject to while-in-use restrictions, and exemptions for starting an FGS in the background are separate from exemptions for microphone permission. An app targeting Android 14 or later can get a SecurityException if it launches a microphone FGS in violation of these conditions. At the moment the app receives the boot broadcast, it has no visible Activity. In fact, the Android 15 behavior changes documentation lists microphone among the FGS types that a BOOT_COMPLETED receiver cannot launch, and states that this restriction has been in place since Android 14. (FGS types, Background start restrictions, Android 15 behavior changes)

2. Built-in STT = local. The default createSpeechRecognizer() is not local-only. The official reference states that this recognizer may send audio to remote servers and is not designed for continuous recognition. Starting with API 31, on-device recognition must be checked with isOnDeviceRecognitionAvailable() and created separately with createOnDeviceSpeechRecognizer(). (SpeechRecognizer)

3. Two small corrections. POST_NOTIFICATIONS is permission to display notifications, not a prerequisite for starting an FGS. (Notification permission) Also, the microphone type itself already appears in Android 11's camera and microphone FGS requirements. What changed in Android 14 is that apps with targetSdk 34 or higher must declare the type and its type-specific permission. (Android 11 changes)

4. Missing from the list—simultaneous microphone use. Wake-word detection keeps the microphone open through AudioRecord, while SpeechRecognizer opens its own microphone input separately. Under the input-sharing policy introduced in Android 10, two ordinary apps cannot receive actual audio simultaneously, and one may receive silence. We cannot conclude that every combination of AudioRecord and a particular recognition service within the same app will fail, but there is no guarantee that they will work simultaneously, either. Audio focus arbitrates output, so it does not solve this problem. (Sharing audio input)

Fix

Revise the checklist as follows before implementation.

Startup flow

Continuous listening

STT

Microphone switching

Bonus — the limits of "it's a dedicated docked device, so battery isn't a concern"

Leaving it plugged into a charger eliminates battery drain as a problem. But issues 1–4 in this article (startup timing restrictions, the recognizer sending audio to a server, and microphone sharing) are unrelated to power. It is safest to read "dedicated docked device" as a condition that crosses off only the battery item.

Reporter's Notes — the list that looked like a few declarations would be enough

There has been no reproduction on a physical device yet. The four of them checked the preparation list against the official documentation, line by line.

Park Doeun
You know that voice assistant on the PC that listens when you say "Hey Jarvis" and reads the answer aloud? I want it to listen all the time on a tablet plugged into a charger, so I started by getting a preparation list.
Jung Bada
The list looked simple. One microphone-type Foreground Service—that's a service that keeps running with a notification—four permissions, and RECEIVE_BOOT_COMPLETED for automatic startup on boot. Declare those and it should listen as soon as the power comes on.
Jung Haneul
Declarations and startup timing need separate checks. Let's start with the official documentation's background start restrictions to see whether we're allowed to launch a new microphone service from the background.
Park Doeun
I looked it up. RECORD_AUDIO is only valid while the app is in use, so creating a new microphone service when the app isn't on screen can throw a SecurityException under the Android 14 conditions. And the list explicitly says a receiver handling the boot signal cannot launch a microphone-type service at all.
Jung Haneul
Our app isn't on screen right after boot. So the hypothesis that receiving the boot broadcast alone can start recording is wrong. Opening the app once to start it is the default flow.
Park Dohyun
Then we can show a notification at boot and open the app to start listening when it's tapped. That's okay because it starts with the app on screen, right?
Jung Haneul
Right. What's blocked is the receiver launching the microphone service directly. If the user taps and we start it after the Activity becomes visible, the app is in use and meets the condition. The mistake was lumping continued operation and a fresh start into one line.
Jung Bada
Then STT is fine, right? The built-in SpeechRecognizer is Android's standard component for turning speech into text. It's free and runs on the device, so it should work offline too.
Jung Haneul
"Runs on the device" is an unsupported assumption. The reference should say where the default recognizer sends audio. Let's settle it with that.
Park Doeun
It says the default createSpeechRecognizer() may send audio to a remote server. To keep it entirely on the device, we have to explicitly use createOnDeviceSpeechRecognizer(), available starting with API 31.
Park Dohyun
The most dangerous code is a silent fallback to the standard recognizer when on-device recognition isn't available. It sends audio to a server while letting you believe it worked offline.
Jung Haneul
So the verdict is "explicitly use on-device recognition for local processing," not "built-in means local." Check whether the Korean model is actually installed with checkRecognitionSupport(), and download it with triggerModelDownload() if it isn't. Then test it in airplane mode at the end.
Jung Bada
The list also had the notification permission, POST_NOTIFICATIONS. We need that before the service can start too, right?
Park Doeun
The documentation says it isn't a prerequisite for starting the service. Denying it only changes where the notification appears. Let's move it from required to recommended.
Jung Bada
There's another issue that wasn't on the list. Wake-word detection keeps the microphone open through AudioRecord, and the recognizer opens its own microphone too. If both grab it at once, won't one get silence?
Park Doeun
The audio input sharing documentation says two ordinary apps can't receive actual sound simultaneously, and one may receive silence. It doesn't go as far as saying a combination within the same app always fails.
Jung Haneul
That means there's no guarantee it works, either. For the first implementation, let's switch sequentially: release AudioRecord when the wake word is detected, then return to wake-word detection when recognition finishes or fails.
Park Dohyun
A charging dock takes battery off the list, but startup timing and microphone sharing have nothing to do with power. One condition doesn't solve the whole list.
Jung Haneul
So today's verdict goes only as far as a checklist corrected against the documentation. How many hours it listens with the screen locked, and whether the first syllable gets clipped—we need to run the app and measure those with logs.
Park Dohyun
A preparation list for an always-listening app should ask "when does it start, where does the sound go, and who holds the microphone?" before "what do we declare?"