Skip to content

[feature] android: screenshot demo mode with fictional fixtures - #236

Merged
YJack0000 merged 1 commit into
mainfrom
feat/android-screenshot-demo
Aug 14, 2026
Merged

YJack0000 merged 1 commit into
mainfrom
feat/android-screenshot-demo

Conversation

@YJack0000

Copy link
Copy Markdown
Contributor

Why

The Play listing needs the app populated, and every screen it needs is behind the sign-in wall. Capturing them today means signing a device into a real account — slow, impossible to reproduce exactly next release, and it puts live customer data one mis-tap away from a public store listing. iOS solved this in ios/App/Parley/ScreenshotDemo.swift; Android had no equivalent, which currently blocks the listing.

What

android/app/src/main/kotlin/com/pathors/parley/screenshot/DemoMode.kt — a debug-only, in-memory flag that stands the sign-in wall down without a token and answers every cloud call from fixed fictional fixtures. Driven entirely by deep links, because input automation on an emulator is unreliable and am start is not:

adb shell am start -a android.intent.action.VIEW -d "'parley://demo/library'"
#                                                    ^ the inner quotes matter
URL Screen
parley://demo/library The recordings list, populated
parley://demo/transcript The featured recording: transcript, findings, action items
parley://demo/record The live meeting, mid-transcript (alias meeting)
parley://demo/account The library with the account sheet open (alias settings)
parley://demo/off Leave demo mode

MainActivity still handles parley://auth-callback exactly as before — the demo handler claims only parley://demo/…, and only in a debug build, then hands everything else straight on.

The three invariants

  • No network. Every cloud call site is guarded (HomeViewModel.refresh / loadAccount, RecordingDetailViewModel.load, AppContainer.drainPendingUploads), so an offline reviewer's or CI machine captures the same frames and production is never touched.
  • No writes. Nothing reaches the auth DataStore or the pending-upload queue. The live screen runs a scripted DemoMeetingSession rather than the microphone, the relay, the encoder and the microphone foreground service — which is also what makes it capturable on an emulator with no audio input.
  • No residue. The flag lives in memory, so parley://demo/off (or a process restart) is the whole clean-up. BuildConfig.DEBUG gates activation, so a release build cannot be talked into serving fixtures.

Verified on device

AVD parley-test (Pixel 7 / API 35), debug APK, all four screens captured in both locales:

  • In airplane mode (Active default network: none) every screen was fully populated — a real cloud call would have surfaced the error banner instead.
  • With the network restored, the whole flow was run again and the app's uid never appears in dumpsys netstats detail's per-uid byte map: zero rx, zero tx. No OkHttp or DNS lines in logcat either.
  • A real pending upload from an earlier session was sitting in files/PendingUploads/ throughout. Demo mode neither displayed it, uploaded it, nor touched its mtime; parley_auth.preferences_pb stayed 0 bytes.
  • parley://demo/off returns the app to the sign-in screen, and parley://auth-callback?error=… still reaches the auth handler and renders its error — the demo route does not swallow it.

./gradlew assembleDebug :parleykit:test :app:testDebugUnitTest passes: 55 tests, 0 failures, including 7 new ones asserting both languages are complete and differ, that the summary counts are the truth about the meta, and that the fixtures name no real person, company or address.

Content

Entirely invented — a renewal negotiation, a discovery call and a quarterly review, written the way B2B calls actually sound (a seat count against a quote, a price hold, an onboarding estimate with an SSO condition). No real company, person, customer or meeting; the only address is alex@example.com, in the RFC-reserved domain. The copy is written separately in English and Traditional Chinese rather than translated one way, and follows the device locale, because the screenshot sets are captured per-locale.

Scope notes

  • android/AppStore/** untouched (owned by another PR).
  • ui/AccountSheet.kt and cloud/CloudClient.kt untouched — the account sheet is served through HomeViewModel's existing state, so the sibling feat/android-account-deletion PR has no conflict here.
  • No new dependencies. One build flag (buildConfig = true) so BuildConfig.DEBUG exists.

The Play listing needs populated screens, and every one of them is behind
the sign-in wall. Capturing them today means signing a device into a real
account: slow, impossible to reproduce exactly next release, and it puts
live customer data one mis-tap away from a public store listing.

This is the Android half of ios/App/Parley/ScreenshotDemo.swift. A
debug-only, in-memory flag stands the wall down without a token and
answers every cloud call from fixed fictional fixtures, driven entirely
by deep links because input automation on an emulator is unreliable and
`am start` is not:

    parley://demo/library     the recordings list, populated
    parley://demo/transcript  transcript + findings + action items
    parley://demo/record      the live meeting, mid-transcript
    parley://demo/account     the account sheet
    parley://demo/off         leave demo mode

Three invariants: no network (every call site guarded, so an offline
machine captures the same frames and production is never touched), no
writes (nothing reaches the auth DataStore or the pending-upload queue,
and the live screen runs a scripted DemoMeetingSession instead of the
microphone and the foreground service), and no residue (the flag is in
memory, so `off` or a restart is the whole clean-up). BuildConfig.DEBUG
gates activation so a release build cannot be talked into serving
fixtures.

The live screen swaps a MeetingSession for a scripted one through a new
`LiveMeeting` interface — the five flows the screen actually reads —
which is what lets it be captured on an emulator with no audio input.

Fixture content is entirely invented: no real company, person, customer
or meeting, and the only address is in the RFC-reserved example.com. The
copy is written separately in English and Traditional Chinese rather than
translated one way, so each store's set reads natively; the fixtures
follow the device locale because the sets are captured per-locale.
@sonarqubecloud

Copy link
Copy Markdown

@YJack0000
YJack0000 merged commit 7868dab into main Aug 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant