You open the voice ticket in your first hour, and the sample already starts a foreground recorder so Android will not kill the session. The notification title is fine, but the content line is the rolling transcript, which stays visible after the screen locks. A reviewer screenshot of that shade will keep the note even if your remote call never succeeds. Treat the notification text as production data, and fix it in the first pull request before you add any model call.
See the leak before you debate models
A laptop chat demo never shows you the lock screen, the shade, or a bug-report screenshot of that shade. Your phone does, and the operating system is allowed to display the text you put in a foreground-service notification. You should write down the device, the OS build, the framework version, and whether the microphone permission was granted or later revoked. Separate that record from any claim about model quality, because this test does not measure a model.
The same mistake appears on iOS when a Live Activity or Now Playing line carries the partial note. Widgets, the lock screen, and a screen recording can all retain that line after your view controller is gone. You are not testing recents thumbnails, crash payloads, or phone-backup archives in this first pull request. You are testing one surface only, which is text the system shows because your process asked it to.
Decide what the system surface may say
Your first pull request may show a stable status such as Listening or Saving on this phone. It may not show the transcript, the prompt, a user name, or a summary that came back from a server. It also may not update the notification every second with a longer string, because each update is another copy the shade can capture. If the session ends, you cancel the notification immediately and you leave no inbox entry behind.
You should hold the draft in process memory until a later foreground gate allows a deliberate user action. If the user locks the phone, backgrounds the app, or revokes the microphone, you stop the recorder and you drop the draft. A rollback of this branch should restore a notification that still says only Listening, even if the previous channel identifier remains installed. Write that expected rollback result in the pull request so a reviewer can check it without guessing.
Build the notification from a fixed label
The Android snippet below is a proposed pattern, not a log from a device run you can cite as evidence. You can mirror the same rule in Swift by keeping Live Activity content state free of note text. The helper returns a label, and the caller never passes the transcript into the builder.
fun recordingLabel(state: String): String = when (state) {
"listening" -> "Listening"
"saving" -> "Saving on this phone"
else -> "Voice session"
}
fun buildRecordingNotification(context: Context, state: String): Notification {
val label = recordingLabel(state)
return NotificationCompat.Builder(context, "voice-session")
.setSmallIcon(R.drawable.ic_mic)
.setContentTitle("Voice note")
.setContentText(label)
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
.setOnlyAlertOnce(true)
.setOngoing(true)
.build()
}
Create the channel once with low importance and secret lock-screen visibility, and leave the channel id unchanged. Android keeps existing channel settings after you roll back, so a renamed channel can leave the old public channel installed. Document an uninstall on the test phone if that was the only way you cleared a channel you had already published. On a shared client, reject any attempt to interpolate the draft into the label before it reaches native code.
val channel = NotificationChannel(
"voice-session",
"Voice session status",
NotificationManager.IMPORTANCE_LOW
).apply {
lockscreenVisibility = Notification.VISIBILITY_SECRET
setShowBadge(false)
description = "Status only. No note text."
}
export function notificationLabel(state: "listening" | "saving" | "idle"): string {
if (state === "listening") return "Listening";
if (state === "saving") return "Saving on this phone";
return "Voice session";
}
export function labelExcludesDraft(label: string, draft: string): boolean {
const allowed = new Set(["Listening", "Saving on this phone", "Voice session"]);
if (!allowed.has(label)) return false;
if (draft && label.includes(draft.trim())) return false;
return true;
}
The string check is a proposed unit test for the contract, and it does not prove what the shade rendered. You still need one physical phone, because a green test can hide a native call that appends the draft later. Run the check in the pull request so a junior change cannot smuggle note text through the shared layer.
Confirm the shade with a fixture, not a real note
Use one physical phone and a fixture string you can safely lose, such as FIXTURE_NOTE_ALPHA. Do not dictate a real message, and do not paste raw dumps into the pull request. The dumpsys command can print notification text, so treat its output as sensitive even when you expect a label.
adb shell dumpsys notification --noredact | head -n 40
adb shell dumpsys deviceidle | head
adb shell dumpsys battery | head
If the fixture appears in that output, the test failed, even if the remote call never started. If only the fixed label appears, record that observation with the device name and the OS version. An inconclusive dump is not a pass, and a simulator run is not a substitute for the lock screen. You should run the following steps in one sitting so the permission state stays easy to explain.
- Install the branch on one phone, grant the microphone, and start a session that uses the fixture string only.
- Open the notification shade and the lock screen, and confirm both show the fixed label rather than the fixture.
- Background the app and then return, and confirm the shade did not gain a second entry that contains the fixture.
- Revoke the microphone permission, and confirm the notification is cancelled and the fixture is not replayed from memory.
- Restore the permission, lock the phone while listening, and confirm no new note text appears on the lock screen.
- Roll back the branch and reinstall the previous build, then confirm the channel still cannot display note text.
- Attach a screenshot only after you have checked that the fixture string is absent from the shade.
Use the table when the ticket grows a remote call
| What you want to add | Allowed in this pull request | Required behavior |
|---|---|---|
| Status notification while recording | Yes | Fixed label only, secret lock-screen visibility, cancel on stop |
| Partial transcript in the shade or a Live Activity | No | Keep the draft in memory, and never pass it to the notification API |
| Remote summary while the screen is locked | No | Abort the call, and do not retry from a background worker |
| Remote summary after an explicit foreground tap | Only as a follow-up | Send a short draft the user can see, with no device identifier |
| Log or bug-report line that contains the fixture | No | Log a reason code only, and review dumpsys output locally |
| Channel id change bundled with the copy fix | No | Leave the id stable so rollback does not orphan a public channel |
A follow-up may call a remote model only after the user is in the foreground and the notification still shows a fixed label. That follow-up is a different pull request, because mixing both changes makes a rollback harder to trust. You still do not put the model response into the notification, or the response becomes a second copy on the lock screen.
Where a prototype endpoint can sit
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode's free model access and free server option can serve as that temporary prototype endpoint for the allowed draft. This draft does not verify a quota, a model list, a hardware size, or how long that access remains available. The endpoint does not relax the notification rule, and a free server is not a reason to put note text in the shade. Keep the URL in a debug configuration, send text only, and remove or flag it before anyone cuts a release build.
If your team already has that free access, try the foreground row only, and write the device result beside the pull request.
Limitations and who should skip this
This method does not tell you latency, battery cost, or whether any summary the server returned was accurate. It does not certify backup exclusion, crash redaction, or on-device speech, which need their own evidence. Channel behavior also differs by OEM skin, so one phone result must not be generalized to every Android device. iOS Live Activities have separate lifecycle rules, and a SwiftUI preview will not reveal the lock screen.
You should not use this approach for health, payment, or other notes your policy forbids leaving process memory. You should also skip it if you cannot install a build on a physical phone, because an emulator will not show the shade. Teams that need a fully offline voice product should not add a prototype server in the same first pull request.
Ask for a comparable phone, not a vibe check
Invite reviewers to repeat the fixture test and to reply with environment facts rather than a general impression. Ask for the device, the OS, the transition they tried, and whether the fixture stayed absent after rollback. A matching failure on their phone is useful evidence, and a simulator-only approval is not evidence at all.
- Name the device, the OS, the framework, and the notification channel id you actually exercised.
- State the microphone permission and whether the screen was locked, awake, or in battery saver.
- Report only the label you saw in the shade, not a transcript and not a dumpsys paste.
- Say whether rollback recovered the old label, restarted the channel, or left the fixture visible.
Your first pull request is done when a stranger can lock the phone and still see no note text in the shade. Model choice can wait until that surface stays empty on the same device you named in the test notes. Keep the fixture result, the channel id, and the rollback outcome together so the next engineer can repeat the hour.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.