M1-12: Get memos off the phone #12

Open
opened 2026-08-08 20:03:46 -04:00 by agent · 0 comments
Member

Make the recordings reachable from a computer, by hand, with no sync.

Why this is in M1

Sync arrives in M3. Without this, M1 produces an app that records beautifully and strands
everything on the phone — which is precisely the failure being fixed.

This is the biggest structural difference from iOS

iOS needed two Info.plist keys and nothing else. Its Documents directory was already the right
place; the keys just told the system it belonged to the user, and it showed up in Finder over a
cable and in the Files app.

Android has scoped storage, and the equivalent question is genuinely a design decision:

App-specific external files. Nothing to ask for, no permissions. But since Android 11 the Files
app cannot browse Android/data, so in practice the recordings are reachable over adb and little
else. That is a developer's answer, not Wes's.

MediaStore, under the shared Recordings collection (Environment.DIRECTORY_RECORDINGS, API
31+ — the Pixel 6 qualifies). The memos land somewhere the Files app, any file manager and MTP over
a cable can all see, alongside every other recorder on the phone. This is the honest translation of
"the Documents directory is the user's", and it is the recommendation.

The cost is that MediaStore is a queried collection rather than a plain directory, which rubs
against M1-11's "the list is a reading of the directory". That tension is the thing to resolve at
the planning checkpoint, not in code. It may mean capture writes to app storage and publishes to
MediaStore at finalisation, which would make the published copy the shared artifact and the
app-private one the working file.

Wes's call.

What comes with handing the directory over

Both are documented rather than prevented, exactly as on iOS:

  • A rename breaks the pair. <base>_audio.* and <base>_transcription.vtt are one memo because
    they share the <base>. Rename one and they become two files. Preventing that would mean a hidden
    index, which is the thing this format exists to avoid.
  • A recording in flight is visible, and deleting it mid-capture breaks that recording.

And one piece of hardening that is not optional

Once anything outside the app can write into the parts folder, finalisation must stop removing it
recursively.
Delete the recognised parts, then remove the folder only if it is empty; anything
else left behind keeps the folder, which is already the "somebody should look at this" state. On
iOS this was caught in review before the keys landed, and it is the one place where opening the
storage creates real user-data risk rather than a documented consequence.

Done when

  • Wes copies a memo off the phone over a cable, both files.
  • Wes finds a memo in the Files app on the phone itself.
  • A file dropped into a parts folder is not deleted by finalisation.

Milestone 1 of Stash for Android, mirroring stash-ios M1. M1 is a functioning app on the test
device with no sync at all — record voice memos reliably and get them off the device by hand.
Sync arrives in M3.

Test device is a Pixel 6 (Tensor G1) borrowed from a friend, until something better turns up.
Stock Android, so no OEM battery-killer behaviour to fight — worth remembering that a phone from
another manufacturer may not be as forgiving about background work.

Translation of Stash/stash-ios#12. Where the platform forces a different answer this issue says so; where it
does not, the iOS issue is the fuller statement of intent and should be read alongside this one.

Working agreement for every issue in this repo: feature branch, tested before the PR opens
(on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs
update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and
split it.

Make the recordings reachable from a computer, by hand, with no sync. ## Why this is in M1 Sync arrives in M3. Without this, M1 produces an app that records beautifully and strands everything on the phone — which is precisely the failure being fixed. ## This is the biggest structural difference from iOS iOS needed two Info.plist keys and nothing else. Its Documents directory was already the right place; the keys just told the system it belonged to the user, and it showed up in Finder over a cable and in the Files app. Android has **scoped storage**, and the equivalent question is genuinely a design decision: **App-specific external files.** Nothing to ask for, no permissions. But since Android 11 the Files app cannot browse `Android/data`, so in practice the recordings are reachable over `adb` and little else. That is a developer's answer, not Wes's. **MediaStore, under the shared `Recordings` collection** (`Environment.DIRECTORY_RECORDINGS`, API 31+ — the Pixel 6 qualifies). The memos land somewhere the Files app, any file manager and MTP over a cable can all see, alongside every other recorder on the phone. This is the honest translation of "the Documents directory is the user's", and it is the recommendation. The cost is that `MediaStore` is a queried collection rather than a plain directory, which rubs against M1-11's "the list is a reading of the directory". That tension is the thing to resolve at the planning checkpoint, not in code. It may mean capture writes to app storage and publishes to MediaStore at finalisation, which would make the published copy the shared artifact and the app-private one the working file. **Wes's call.** ## What comes with handing the directory over Both are documented rather than prevented, exactly as on iOS: - **A rename breaks the pair.** `<base>_audio.*` and `<base>_transcription.vtt` are one memo because they share the `<base>`. Rename one and they become two files. Preventing that would mean a hidden index, which is the thing this format exists to avoid. - **A recording in flight is visible**, and deleting it mid-capture breaks that recording. ## And one piece of hardening that is not optional Once anything outside the app can write into the parts folder, **finalisation must stop removing it recursively.** Delete the recognised parts, then remove the folder only if it is empty; anything else left behind keeps the folder, which is already the "somebody should look at this" state. On iOS this was caught in review before the keys landed, and it is the one place where opening the storage creates real user-data risk rather than a documented consequence. ## Done when - [ ] Wes copies a memo off the phone over a cable, both files. - [ ] Wes finds a memo in the Files app on the phone itself. - [ ] A file dropped into a parts folder is not deleted by finalisation. --- *Milestone 1 of Stash for Android, mirroring `stash-ios` M1. M1 is a functioning app on the test device with **no sync at all** — record voice memos reliably and get them off the device by hand. Sync arrives in M3.* *Test device is a **Pixel 6** (Tensor G1) borrowed from a friend, until something better turns up. Stock Android, so no OEM battery-killer behaviour to fight — worth remembering that a phone from another manufacturer may not be as forgiving about background work.* *Translation of `Stash/stash-ios#12`. Where the platform forces a different answer this issue says so; where it does not, the iOS issue is the fuller statement of intent and should be read alongside this one.* *Working agreement for every issue in this repo: feature branch, tested before the PR opens (on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and split it.*
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Stash/stash-android#12
No description provided.