androidengineers.Book a session

Session ownership and the AI boundary

PocketCook: how a live voice session works

article35–50 min

Start with the product boundary

PocketCook helps you follow a recipe while your hands are busy. The recipe library, ingredient checklist, current step and progress storage are deterministic Android features. Gemini interprets spoken questions and generates spoken guidance using the selected recipe as context. The app connects directly to Gemini Live through an OkHttp WebSocket; this sample does not use Firebase.

Your first milestone is deliberately offline: choose Tomato pasta, open its ingredients, start cooking, advance and finish. If this fails, cloud credentials are irrelevant to the failure. Only after that baseline works should you enable voice. The codelab supplies the clone and project setup commands.

This course starts from the implemented core at 60d86c9, not an empty starter. Code excerpts are reading exercises unless a section explicitly asks you to edit a file. You will make a tested change to capture chunk configuration later.

Three states, three questions

A live interaction lasts across many messages. A connected WebSocket does not prove that the microphone is recording, and a completed model turn does not prove that the speaker has finished playing buffered audio. VoiceState keeps connection, mute and speaking fields separate:

// LiveConnection.kt — excerpt from the baseline
 data class VoiceState(
     val connection: Connection = Connection.Idle,
     val muted: Boolean = false,
     val speaking: Boolean = false,
     val level: Float = 0f,
     val transcript: String = "",
     val message: String = "Ready when you are"
 )

Predict what the UI should display when connected but muted. The correct interpretation is that the conversation still exists, input is paused, and playback may still be active. Treating mute as disconnect would lose that distinction. A transcript arriving is evidence that a server event was processed; it is not evidence of audible output.

Follow ownership through the app

PocketCook ownership: Compose sends actions to the ViewModel, the session owns protocol and audio, and only the socket sends data to Gemini.

Open the diagram at full size · Mermaid source

PocketCookApp renders state collected with lifecycle awareness. CookViewModel owns the selected recipe, screen and progress. GeminiLiveConnection owns the socket and exposes session state. AndroidPcmAudio owns the platform audio resources. LiveProtocol translates JSON into typed events. ProgressStore persists recipe position; it does not store transcripts or credentials.

Constructor injection makes these boundaries replaceable in tests. A fake audio adapter can prove that setup failure never starts capture without asking a real microphone for permission. It cannot prove speaker routing or acoustic quality. One app module is enough for these responsibilities; extra modules or a DI framework would not make the evidence stronger.

Setup is a gate, not just a message

The sequence is: explicit user action → key/model validation → socket connection → setup message → setupComplete → audio start. The controller ignores duplicate starts while connecting or connected. It also has a 20-second setup timeout and a ten-minute local session limit. These are sample policies, not promises about provider quotas or maximum session length.

// LiveProtocol.kt — the setup message requests audio and transcripts
putJsonObject("generationConfig") {
    putJsonArray("responseModalities") { add("AUDIO") }
}
putJsonObject("inputAudioTranscription") {}
putJsonObject("outputAudioTranscription") {}

The system instruction includes ingredients, steps and current position. It explicitly says the assistant cannot control a timer or change app state. Nothing in this setup declares tools. If the assistant says “timer started,” the sentence does not create a timer.

Credentials and the deployment boundary

In the debug build, the learner enters a project-owned key through Connection settings. It is held in memory and cleared when the app backgrounds. A local properties file would hide a value from Git but would not protect it if compiled into an APK. This sample neither reads a key from that file nor embeds one in BuildConfig. Release voice is intentionally unavailable until a token service exists; recipes still work.

Google documents direct client connections and recommends ephemeral tokens for production client access. Read the Live API overview for the provider boundary. Do not interpret this educational debug route as a production credential architecture.

Verify your understanding

Run LiveSessionTest.duplicateStartCreatesOneSocketAndNoMicrophoneBeforeSetup. Explain the two assertions: one socket after two starts, and no microphone before acknowledgement. Then sketch the path where acknowledgement never arrives. Your explanation should name the owner that cancels work, the visible error state and the fact that local cooking progress remains usable.

Course study guide · Hands-on codelab · Pinned Android source

YOUR LEARNING JOURNEY

0 of 16 available lessons completed

Progress saved in this browser. No account needed.
PocketCook: how a live voice session works | Gemini Live for Android with PocketCook | Android Engineers