GEMINI LABJP
ASSISTANT — Google Assistant begins shutting down on September 4, with Gemini taking over on Android phones and tablets, Wear OS, and Android AutoMIGRATION — The rollout is staged and may take several weeks to reach everyone. Once it lands, Assistant stops working and there is no way to switch backSURFACES — Wear OS and Android Auto are easy to overlook here. Voice control when you cannot look at a screen asks something rather different from talking to a phone at your deskDROP — The September Android Drop adds a Remembered list in Find Hub for items without a tracker tag, along with Motion Assist and Keep inside Google MessagesCODE — Gemini Advanced now accepts multiple code files in one upload, so you can hand over part of a repository instead of pasting a single file at a timeROLE — The framing around Gemini keeps shifting from chat tool toward a supervised digital worker that handles files, screens, documents, and codeASSISTANT — Google Assistant begins shutting down on September 4, with Gemini taking over on Android phones and tablets, Wear OS, and Android AutoMIGRATION — The rollout is staged and may take several weeks to reach everyone. Once it lands, Assistant stops working and there is no way to switch backSURFACES — Wear OS and Android Auto are easy to overlook here. Voice control when you cannot look at a screen asks something rather different from talking to a phone at your deskDROP — The September Android Drop adds a Remembered list in Find Hub for items without a tracker tag, along with Motion Assist and Keep inside Google MessagesCODE — Gemini Advanced now accepts multiple code files in one upload, so you can hand over part of a repository instead of pasting a single file at a timeROLE — The framing around Gemini keeps shifting from chat tool toward a supervised digital worker that handles files, screens, documents, and code
Articles/Updates
Updates/2026-09-03Intermediate

On Wear OS and Android Auto, Failure Has to Speak Too

Google Assistant starts giving way to Gemini on September 4. On Wear OS and Android Auto you cannot report a failure with a toast. Here is how I moved my error paths into media session state, and the three things I decided before the switch.

Gemini84Android AutoWear OSMediaSessionIndie Development15

I asked my watch to play rain sounds, and nothing happened. For a long time I filed that away as a recognition problem — the microphone in a noisy room, my own mumbling, something out of my hands.

I only went back through the logs because September 4 was getting close. The utterance had arrived. My app had been called. What ended the story was a branch I had written months earlier: when no track matched the query, I logged one line and returned.

On a phone that is survivable. The person is holding the screen, and silence is at least visible. On a wrist or in a car, silence is the entire response.

What changes on September 4 is the listener

Starting September 4, Google Assistant begins its retirement on Android phones and tablets, on Wear OS watches, and in Android Auto. Gemini takes its place.

Two details matter more than the headline. The rollout is gradual, so it will take weeks to reach everyone. And once a device has moved, there is no supported way back.

For those of us shipping apps, the important part is narrower than it first sounds. What is being replaced is the layer that interprets speech, not the entry point your app receives. A voice request to play something still arrives through the same media session callback it always did.

What shifts is the shape of the string that lands there. "Rain sounds" phrased one way and phrased another way exercise your matching logic differently. Any app that never decided how to behave on a miss will simply go quiet more often in the weeks right after the switch.

Screenless surfaces leave you one exit

Looking back at my phone implementation, I reported failure with a toast or a snackbar. One short line when nothing matched. That was the whole design.

Neither surface allows it. You cannot throw arbitrary toasts onto a car display while someone is driving, and text on a watch face assumes a glance that the person is not going to give you — the whole point of asking out loud was that their hands and eyes were busy.

What you get instead is the playback state itself. Set STATE_ERROR on PlaybackStateCompat along with a message, and the Android Auto and Wear OS media surfaces pick it up and render it in whatever way suits the moment.

So the job is not to display anything. A response here is something you return as session state, not something you draw. Until that landed for me, every failure branch in my code contained a log statement and nothing else.

Rewriting "show the error" into "return the error"

The change happened inside the callback that receives voice-initiated searches. It used to return early on a miss. Now it reports.

override fun onPlayFromSearch(query: String?, extras: Bundle?) {
    if (query.isNullOrBlank()) {
        // This means "play something." More on it in the next section.
        playFallback()
        return
    }
 
    val track = library.findBest(query)
    if (track == null) {
        session.setPlaybackState(
            PlaybackStateCompat.Builder()
                .setState(PlaybackStateCompat.STATE_ERROR, 0L, 0f)
                .setErrorMessage(
                    PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED,
                    "I couldn't find anything close to \"$query\""
                )
                .build()
        )
        return
    }
 
    play(track)
}

The part I would not skip is echoing the query back. "Nothing found" tells the person nothing about why. Once they hear what was actually understood, they can tell the difference between rephrasing and giving up — between a recognition slip and a library that genuinely does not contain what they want.

Before any of this matters, confirm the callback is reachable at all. Unless PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH is included in the state's setActions, voice-initiated search never reaches your app in the first place. You can polish the error path all you like and still watch nothing happen. That oversight cost me the longest stretch of confused debugging in this whole exercise.

You also do not need the hardware to check your work. Android Auto runs on a development machine through the Desktop Head Unit, and a Wear OS emulator paired to a phone will take voice-initiated playback well enough to see whether a miss ends in silence. Neither is identical to the real thing, but both answer the question you actually have.

Pick the error code deliberately. I settled on ERROR_CODE_NOT_SUPPORTED for a missing track and ERROR_CODE_APP_ERROR for a network failure, mostly so that later log reading stays honest.

There is a second tier for failures you cannot fix from inside the callback. An expired sign-in is the obvious one, and for that you can attach a resolution the person can act on.

val resolution = Bundle().apply {
    putString(
        "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
        "Sign in"
    )
    putParcelable(
        "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
        signInPendingIntent
    )
}
 
session.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR, 0L, 0f)
        .setErrorMessage(
            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
            "Your session has expired"
        )
        .setExtras(resolution)
        .build()
)

Those keys also exist as constants in androidx.media.utils.MediaConstants, so reach for those if raw strings bother you. I left the literals in place here because they make it much easier to grep for which key is doing the work when something does not surface.

An empty query is not a failure

The case that held me up longest was the one where the query arrives empty.

Nothing went wrong there. An empty query is the request "play something." Returning an error turns a reasonable ask into a refusal, and the person has no idea what they did to deserve it.

So I made it always produce sound: the last thing that was playing, and failing that, the first item of a default list. When I do not have enough information to choose well, I choose anyway rather than offering options — asking someone to pick from a menu is exactly the wrong thing to do when they cannot look.

A non-empty query that matches nothing is the opposite situation, and it deserves the honest answer. Folding both into one branch left me with two mediocre behaviours instead of two clear ones.

Three things worth deciding before the switch

Here is what I would settle in the remaining weeks, narrowed to three.

DecisionThe concrete workWhat it costs you to defer
How failure is reportedWalk every voice-reachable branch and replace log-only exits with STATE_ERRORYou get "it stopped responding" reports right after the switch with nothing to narrow them down
The default for empty queriesChoose between last-played and a default list, and pick a track for when neither existsA reasonable request ends in silence, and people conclude the app is broken
A baseline to compare againstStart counting voice-initiated plays and unmatched queries now, before any device movesThe migration is one-way, so the earlier state cannot be measured after the fact

Only the third is really about preparation rather than code. If it feels worse afterwards and you have nothing from before, you are left arguing with an impression. This week is the last comfortable moment to start counting.

The mechanics of detecting and recording voice-initiated launches are in Google Assistant's replacement is one-way, so the baseline has to be taken now. If you would rather start from an inventory of what still assumes Assistant, How many places in your own app still assume Assistant is the better entry point.

One thing to do next

Find a single voice-reachable callback that currently ends in a bare return, and give it a STATE_ERROR and a sentence. One branch is enough to start.

That is where I began. Hearing a short reply come back from my watch was the moment I understood how many times it had answered with nothing at all.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Updates2026-08-18
The Assistant Switch Is One-Way, So the Baseline Has to Be Captured Now
From September 4, Google Assistant is replaced by Gemini, and a device that has switched cannot go back. Here is how I started recording voice-originated launches inside my own apps, and the assumption about splitting before and after by date that turned out to be wrong.
Updates2026-07-17
The Model Didn't Ship on Its Rumored Date — Read Your Context Limit From the API, Not the Headlines
July 17 came and went with no official word on Gemini 3.5 Pro. Instead of baking rumored numbers into constants, here's a context budget layer that reads the real limit from models.get and degrades quietly when input overflows.
Updates2026-06-11
Google I/O 2026 Preview — What I'm Watching for in Gemini This Year
Google I/O 2026 is approaching. Based on current Gemini development trends and past announcement patterns, here's what I'm personally expecting — no guarantees.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →