You sit down with a Pixel 8 on Android 15, a debug Flutter build in the foreground, and a first-hour onboarding ticket. The ticket says you should send one chat turn, then read the README while the app sits in recents. You type a dummy question about a visa interview, press Home, and the recents card still shows the last bubble. That Home press is the lifecycle transition you should treat as a data store, not as a pause.
Juniors usually keep the activity resumed while they click through the happy path on a loaner phone. Seniors often review the same diff on a laptop and never background the process at all. App switcher thumbnails, iOS snapshot images, and Android recents tasks are operating-system mechanisms you did not opt into. An agent transcript is worse than a settings screen because tool results can include names, amounts, and health details.
This article is a proposed first-hour workflow, not a claimed lab run with invented timings. You should record the device, OS, framework, network, and permission state before you open a pull request. You should also plan the first rollback if a secure-flag change breaks QA screenshots.
What the OS actually keeps after you press Home
When you leave the chat, the system may freeze a bitmap for the recents tray. On Android that bitmap is the task thumbnail. On iOS it is the snapshot taken around willResignActive. Neither surface is your product database, but both can display the last assistant bubble to anyone who borrows the phone.
You should distinguish three stores that juniors often mix together during onboarding:
- In-memory chat state that should survive a short backgrounding and restore cleanly.
- Durable logs or caches that must not contain raw tool payloads after a rollback.
- OS snapshots that you do not control unless you blank them on purpose.
Source-based platform docs describe FLAG_SECURE and snapshot overlays. Hands-on testing is the only way to see whether your Flutter, React Native, or native shell actually applied them. Do not treat a simulator screenshot as proof that the recents card is blank.
Proposed first-hour experiment
Label this as a proposed test on hardware you control. Do not copy these steps into a launch checklist until you have watched the switcher yourself.
Environment to record
Write these fields in the PR description before you ask for review:
- Device and OS, for example Pixel 8 / Android 15 or iPhone 14 / iOS 18.
- Framework and versions, for example Flutter 3.24 plus the exact plugin that sets window flags.
- Network state: Wi-Fi to a stub endpoint, or airplane mode if the client already caches the last turn.
- Permission state: microphone and photos denied unless this flow truly needs them.
- Power condition: unplugged, battery saver off, so the OS is not already hiding surfaces.
- Application state: one completed agent turn in the foreground, then Home, then recents.
Exact steps
- Install a debug build that still talks to a stub model, not to production keys on a shared device.
- Send one fixture prompt that contains a fake passport number and a fake medical leave date.
- Wait until the last bubble is fully painted, then press Home within two seconds.
- Open the app switcher and look at the card with your eyes, not through
adb screencapalone. - Return to the app and confirm the conversation is still there, because blanking the snapshot must not wipe state.
- Force-stop the app, relaunch, and check whether the same fixture text reappears in recents before first paint.
- Capture whether the session recovered, restarted empty, or silently disappeared from disk.
Expected observations
With a secure flag or overlay in place, the recents card should be blank, letterboxed, or covered by a branded shield. The in-app transcript should still restore after you foreground the activity. Without the flag, the last bubble remains readable on the card, which is a ship blocker for any agent that handles HR, health, or finance text.
If the overlay stays up after resume, you have a different bug: you hid the product from the user, not from the operating system. That failure belongs in the first rollback plan, not in a follow-up sprint.
Artifact: a decision table you can paste into the PR
Use this table as the original artifact for the first pull request. Fill the right-hand columns on the device, and do not invent milliseconds.
| Lifecycle condition | Secure surface on? | Recents / snapshot | In-app recovery after return |
|---|---|---|---|
| Foreground chat after one turn | n/a | live UI, not a card | n/a |
| Home, then app switcher | no | last bubbles readable | chat still in memory |
| Home, then app switcher | yes | blank or shield overlay | chat still in memory |
| Recents after force-stop | yes | no leftover fixture text | session restarts or restores from encrypted store |
| Debug flavor for QA | optional | screenshots allowed | same chat code path |
| Release flavor | required for agent chat | screenshots blocked | same chat code path |
Ask the reviewer to reject the PR if the “yes” row still shows readable tool output. Readable output means the OS mechanism still holds sensitive data.
Code you can apply in the first hour
Keep the flag next to the chat route, not on the whole application, so login and help screens remain capturable for support. The snippets below are starting points you still have to verify on device.
Android (Kotlin), proposed for the agent Activity only:
class AgentChatActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
setContentView(R.layout.activity_agent_chat)
}
}
Confirm the window flag from a shell, proposed check:
adb shell dumpsys window | grep -E "mSecure|FLAG_SECURE"
That grep does not prove the recents bitmap is blank. You still owe the visual switcher check from the table.
iOS (Swift), proposed overlay around resign-active:
final class SnapshotShield {
private var cover: UIView?
func install(on window: UIWindow?) {
NotificationCenter.default.addObserver(
forName: UIApplication.willResignActiveNotification,
object: nil,
queue: .main
) { [weak self] _ in
guard let window else { return }
let view = UIView(frame: window.bounds)
view.backgroundColor = .systemBackground
window.addSubview(view)
self?.cover = view
}
NotificationCenter.default.addObserver(
forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: .main
) { [weak self] _ in
self?.cover?.removeFromSuperview()
self?.cover = nil
}
}
}
Flutter, proposed only on the chat route:
import 'package:flutter_windowmanager/flutter_windowmanager.dart';
Future<void> protectAgentRoute() async {
await FlutterWindowManager.addFlags(
FlutterWindowManager.FLAG_SECURE,
);
}
Future<void> unprotectOtherRoutes() async {
await FlutterWindowManager.clearFlags(
FlutterWindowManager.FLAG_SECURE,
);
}
Clear the flag when you pop the chat route. Otherwise QA cannot capture a bug on a non-sensitive screen, and they will ask you to revert the whole change.
React Native, proposed native call from the chat screen:
import { NativeModules, Platform } from 'react-native';
export function setAgentChatSecure(enabled: boolean) {
if (Platform.OS === 'android') {
NativeModules.SecureWindow?.setSecure(enabled);
}
}
The native module should toggle FLAG_SECURE and nothing else. Do not hide the webview debugger behind the same switch on day one.
First PR, then the first rollback
Your first PR should contain the route-scoped flag, the decision table with device fields filled, and a debug flavor that leaves screenshots on. It should not contain a global secure flag, a production API key, or a claim that “recents is fine on my laptop.”
Your first rollback should be equally boring. If QA cannot reproduce a layout bug because the flavor still sets FLAG_SECURE, revert only the debug flavor, not the release shield. If the iOS overlay fails to remove on didBecomeActive, revert the overlay and keep the Android flag, then file a follow-up with the exact OS version.
A useful rollback command for the junior who already pushed is a revert commit, not a force-push on a shared onboarding branch:
git revert HEAD --no-edit
git push origin HEAD
After the revert, rerun steps 3 through 6 on the same device. Write down whether the recents card returned to showing the last bubble. That sentence is the recovery outcome reviewers need.
Where a free stub belongs in this hour
You still need fixture transcripts that look like PII without putting production secrets on a shared phone. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option you can point the debug flavor at, so the loaner device never holds a customer key while you watch the app switcher.
Keep that stub on the debug product flavor only. The release flavor should keep talking to whatever endpoint the repo already documents. The stub exists so you can generate a visa-shaped sentence and a medical-leave-shaped sentence, then prove the snapshot does not keep them.
Limitations, and who should skip this
This workflow does not encrypt the chat database, does not stop a determined screen recording while the app is foregrounded, and does not replace a threat model. FLAG_SECURE can also block legitimate assistive screenshots and some OEM screen-sharing tools. If your product is a public demo with no personal data, a shield overlay is noise.
Skip this approach when you do not own the native shell, when the chat is only on desktop, or when legal already forbids storing the transcript on device at all. In those cases you need a different first-hour ticket: do not render the raw tool payload in the first place.
Do not treat this article as a benchmark. There are no milliwatt numbers here, and there is no claim that every Android skin honors FLAG_SECURE the same way. Some launchers recapture the task after you return, which is why the force-stop row in the table matters.
What to send back if you run it
If you run the same Home-press on your hardware, reply with the device, OS, framework versions, and the three-step transition you used. Say whether the recents card recovered blank, restarted into an empty chat, or still showed the last tool result. Comparable environment evidence is more useful than another screenshot from a simulator that never builds a recents bitmap.
When the first-hour ticket is closed, the next junior should be able to rerun the table without asking where the stub lives. If you want that stub off the shared phone, the free model access and free server option mentioned above is enough to host dummy completions for the debug flavor.
Top comments (0)