●SUNSET — gemini-robotics-er-1.6-preview shuts down on August 31. Thirteen days out, which makes this the week to actually start the migration●MIGRATION — The replacements are gemini-robotics-er-2-preview and gemini-robotics-er-2-streaming-preview, both in public preview since July 30 and both accepting text, image, video, and audio input●PRICING — Gemini 3.7 Flash went GA on August 13 at an introductory price that runs through December 31, 2026, so anything spanning the new year needs a two-stage cost estimate●DEPRECATION — The temperature, top_p, and top_k sampling parameters are now deprecated. If your code sets them explicitly, measure the output difference without them while there is still time●VIDEO — Gemini Omni Flash, a model for generating and editing video, is available in Google AI Studio and through the Gemini API●LOGS — The Interactions API now supports developer logs, viewable for supported calls from the AI Studio dashboard●SUNSET — gemini-robotics-er-1.6-preview shuts down on August 31. Thirteen days out, which makes this the week to actually start the migration●MIGRATION — The replacements are gemini-robotics-er-2-preview and gemini-robotics-er-2-streaming-preview, both in public preview since July 30 and both accepting text, image, video, and audio input●PRICING — Gemini 3.7 Flash went GA on August 13 at an introductory price that runs through December 31, 2026, so anything spanning the new year needs a two-stage cost estimate●DEPRECATION — The temperature, top_p, and top_k sampling parameters are now deprecated. If your code sets them explicitly, measure the output difference without them while there is still time●VIDEO — Gemini Omni Flash, a model for generating and editing video, is available in Google AI Studio and through the Gemini API●LOGS — The Interactions API now supports developer logs, viewable for supported calls from the AI Studio dashboard
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.
On the morning I turn a staged rollout from 5% to 25%, I look at the same two numbers every time. Has crash-free users dropped below 99.7%, and has ANR crossed 0.20%. If both are flat, I go to the next step.
Staring at those two in the Google Play Console last week, I got a little uneasy. From September 4, Google Assistant is being replaced by Gemini on Android and Wear OS. The rollout is progressive, expected to take several weeks, and once a device has switched, it cannot be moved back.
If the path by which people open my app by voice changes across that line, the change will not appear in crash-free users or in ANR. Nothing crashes. No exception is thrown. The traffic simply stops arriving.
Traffic that stops arriving is invisible unless you were already counting it. I run six apps as an indie developer, and I had never once measured what share of launches came from voice.
A one-way switch leaves you no control group afterwards
For most platform changes, "fix it after it breaks" is good enough. You can observe the break, and you can go back. Neither holds here.
The switch is irreversible per device. There is no control to restore the previous assistant. Reporting suggests routines and third-party integrations either carry over into Gemini's framework or are lost during migration, but I have no way to check, device by device, which way any given one will fall.
What I do have is the ability to move a device over early, from the Google app or from device settings. Which means the window for observing the "before" state has a fixed end date and no extension.
Surface
From September 4
Android phones and tablets
Progressively replaced by Gemini
Wear OS smartwatches
Progressively replaced by Gemini
Assistant-enabled headphones
Progressively replaced by Gemini
Phone-projected Android Auto
Progressively replaced by Gemini
Cars with Google built-in
Continues
I wrote about designing across a cutoff date, for the settings frozen inside binaries already shipped, in rollback design across a retirement date. This piece is about the layer below that — the one where neither your server nor your client is the thing that changed.
How to recognise a voice-originated launch from inside the app
One caveat first. I do not have a reliable way to determine that a launch came from voice. What I have are the hints that arrive at launch time.
What is available when the Activity starts
Roughly three things are readable at the moment your Activity comes up.
The Intent action and data — a deep link via ACTION_VIEW, something search-shaped, or an ordinary launcher start
Whatever Activity.getReferrer() returns for the calling package or scheme
Strings riding in the Intent extras, such as a search query or deep link parameters
Which of these are populated depends entirely on the caller. That is exactly why hard-coding a check against one of them today leaves you with nothing if the caller's implementation changes after the switch.
Record first, classify later
What I settled on is a thin capture that puts the raw values into a single event. Classification can happen later, against data I already hold.
// MainActivity.kt — record where the launch came from, verbatim// Policy: do not decide "was this voice?" here. That decision belongs downstream.class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) recordLaunchOrigin(intent) // normal initialisation follows } private fun recordLaunchOrigin(launchIntent: Intent) { // referrer is API 22+, usually android-app://<package> val referrerHost = referrer?.host ?: "none" val action = launchIntent.action ?: "none" // Sometimes present on search-shaped launches. More often absent. val rawQuery = launchIntent.getStringExtra(SearchManager.QUERY) // Keep deep link parameter names, not their values val extraKeys = launchIntent.extras?.keySet() ?.filterNot { it.startsWith("android.") } // drop platform-internal keys ?.sorted() ?.joinToString(",") ?: "" Firebase.analytics.logEvent("launch_origin") { param("action", action.takeLast(40)) param("referrer_host", referrerHost.takeLast(40)) param("has_query", if (rawQuery.isNullOrBlank()) 0L else 1L) param("extra_keys", extraKeys.takeLast(90)) // stay under the 100-char limit param("app_version", BuildConfig.VERSION_NAME) } // The utterance itself stays out of Analytics and goes to my own sink, // and only from devices that have granted consent. if (rawQuery != null && consentManager.analyticsGranted) { utteranceCollector.enqueue(rawQuery.trim()) } }}
Two things bit me here. Firebase Analytics caps the length of event parameter values, so a naive join of extras keys gets silently truncated. And putting the raw utterance into Analytics leaves room for personal data to end up somewhere I did not intend. I split the utterance body out of Analytics entirely and routed it separately, consent-gated.
What I refuse to assume
I am not assuming that a voice launch means a particular package name lands in referrer_host. The calling package may change after the switch, and on some paths the field is never populated at all. Instead of a verdict, I keep the distribution of action and referrer_host pairs. A distribution survives a wrong assumption, because you can re-read it later.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You will be able to tell voice-originated launches apart inside your own app, so the change after the switch becomes a measured delta instead of a hunch
✦You will be able to add, before September 4, the one class of regression that never surfaces in your crash-free rate or your ANR numbers
✦You will be able to cluster the captured utterances with Gemini and separate a shift in phrasing from an actual drop in usage
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The more fields you record, the less appetite you have for reading them later. Since this has to be live before September 4, I cut it to five.
Field
Source
What I want to see afterwards
Daily count of launch_origin
Firebase Analytics
Whether non-launcher starts are thinning out
action and referrer_host pairs
Same
Whether unfamiliar pairs are appearing
Share of has_query
Same
Whether the path that carries utterances is narrowing
Session duration for those launches
Firebase Analytics
Whether the same entry point now behaves differently
Cluster distribution of utterances
Own sink plus Gemini
Whether phrasing has shifted
Big numbers like DAU and retention are not the protagonists here. If voice-originated launches are a few percent of the total, halving them disappears into the noise on a DAU chart. This work is about holding a number at a granularity where it cannot hide.
I look at the AdMob side separately for the same reason. When one entry point thins out, total impressions get backfilled by the others, so the revenue chart is the last thing to move.
Clustering the utterances with Gemini to watch the distribution
Reading utterances one at a time tells you nothing. Whether "change my wallpaper", "open the wallpaper app" and "show me an ukiyo-e wallpaper" are one intent or three stops being a judgement you can make by hand once the volume grows.
So I classify weekly with Gemini and keep only the per-cluster counts as a time series. If the granularity of the classification drifts week to week, nothing is comparable, so the categories are fixed and passed in.
# weekly_utterance_profile.py — weekly cluster profile, counts onlyimport osimport jsonfrom collections import Counterfrom google import genaifrom pydantic import BaseModelclient = genai.Client(api_key=os.environ["GEMINI_API_KEY"])# Fixed granularity. Changing this weekly breaks comparison with last week.CATEGORIES = [ "open_app", # just open the app "change_wallpaper", # set or change a wallpaper "search_theme", # ask for a specific subject or theme "control_playback", # play, pause and similar "other",]class Labeled(BaseModel): index: int category: strclass Batch(BaseModel): items: list[Labeled]def classify(utterances: list[str]) -> Counter: numbered = "\n".join(f"{i}: {u}" for i, u in enumerate(utterances)) prompt = ( "Assign each utterance below to exactly one of the given categories.\n" f"Categories: {', '.join(CATEGORIES)}\n" "Use other when you cannot decide.\n\n" f"{numbered}" ) # temperature / top_p / top_k are deprecated, so nothing is set here res = client.models.generate_content( model="gemini-3.7-flash", contents=prompt, config={ "response_mime_type": "application/json", "response_schema": Batch, }, ) parsed = Batch.model_validate_json(res.text) counts = Counter() for item in parsed.items: # guard against a category name outside the fixed set counts[item.category if item.category in CATEGORIES else "other"] += 1 return countsdef profile(week_id: str, utterances: list[str], batch_size: int = 80) -> dict: total = Counter() for i in range(0, len(utterances), batch_size): total += classify(utterances[i : i + batch_size]) n = sum(total.values()) or 1 return { "week": week_id, "n": n, "share": {c: round(total[c] / n, 4) for c in CATEGORIES}, }if __name__ == "__main__": with open("utterances_2026w34.json", encoding="utf-8") as f: rows = json.load(f) print(json.dumps(profile("2026-W34", rows), ensure_ascii=False, indent=2))
Leaving temperature unset is deliberate rather than forgetful. The sampling parameters are deprecated, and I would rather not add new explicit uses of them to code written today. I wrote separately about what actually broke first when they went deprecated in the side of temperature deprecation that hurt first.
The output is stored as shares rather than counts. The denominator moves week to week, and raw counts blend a change in usage with a change in distribution.
Switching exactly one device early
There were three ways to handle my own devices.
Option
What it gives
What it costs
Wait for automatic migration everywhere
Same conditions as ordinary users
No hands-on look at post-switch behaviour
Manually switch one device early
Before and after side by side, in hand
That device's "before" is gone
Switch everything early
Migration work done in one pass
No comparison left anywhere
I chose the middle one. The shorter your hands-on comparison window, the harder it becomes to tell whether a later report of "I can't open it by voice anymore" belongs to the platform switch or to something I changed in my own app.
In this situation I would recommend making that early device a secondary one. Switch your daily driver first and your own changing habits get mixed into the signal. I moved over a device I only ever use for testing.
The part I got wrong was interpretation, not recording
When I started, I assumed that recording was the hard part and that I could simply draw a line at September 4 and compare either side. That assumption was wrong.
The switch does not happen all at once on September 4. It is progressive and expected to take several weeks. Which means the same day's event log contains both switched and unswitched devices. Split before and after by date and the "after" bucket is full of "before", so any real difference looks diluted. Whether the difference is small or merely mixed is not something a date can tell you.
There is also no way for the app to ask a device which assistant it is running. Since the device will not tell you, splitting by a date imposed from outside cannot be precise, in principle.
So I moved the estimate of the switch from an outside date to an inside signal. Whether the distribution of phrasing has moved should track reality more closely than the calendar does. If only the other share rises during the migration window, that points at a change in phrasing rather than a drop in usage.
The real reason the Gemini clustering step exists is not that I wanted the analysis to look sophisticated. It is that the date could not be used as the split. Had I discovered that in the other order, I would probably have settled for staring at raw counts.
The question of what to start counting before a shutdown came up the same way during the image model retirement. That inventory procedure is written up in the pre-shutdown usage audit.
What to finish before September 4
With the days left, the ordering is fairly clear.
Ship the build carrying launch_origin early enough to absorb store review. Review plus a staged rollout eats several days
Verify the events actually arrive, by launching your own device both by voice and from the launcher
Settle the consent handling and the privacy policy wording for utterance collection first
Move exactly one test device to Gemini manually and look at the post-switch behaviour yourself
Capture the weekly cluster distribution at least once before the switch. A baseline of one point still beats a baseline of zero
Step 3 is the one worth pulling forward. Ship before the collection design is settled and you will need a second release just to change how consent is handled.
The next move
There is honestly not much you can build ahead of a switch like this. Counting something in a form you can re-read later pays off more than trying to predict the behaviour and code against it.
Start with one line on one chart — the daily count of launches that did not come from your launcher. Whether that line exists changes how you will read every report that arrives in September.
For me, the useful part of this exercise was noticing that I had never measured it at all. If you have the same gap, I hope this helps.
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.