M4: Files — share sheet and composer send; Download → Open/Share (ENG-266) #30

Merged demi.dev
targeting main from stoopkidddd/eng-266-m4-files-send-from-share-sheet-and-composer-receive-with
Merged opened by and assigned to demi.dev
targeting main from stoopkidddd/eng-266-m4-files-send-from-share-sheet-and-composer-receive-with

Linear: https://linear.app/engineereddev/issue/ENG-266

What's in this PR (104 files, all under apps/android/):

  • Send: ShareActivity handles ACTION_SEND with EXTRA_STREAM and ACTION_SEND_MULTIPLE (one push per file); the Home composer gets Attach (ACTION_OPEN_DOCUMENT, multiple). Name, MIME and size via OpenableColumns; ciphertextSize checked against maxFileBytes before reserving.
  • UploadWorker (expedited where allowed, foreground progress on channel transfers): POST /api/files → stream-encrypt with FileCrypto through an OkHttp RequestBody of known length to the presigned (direct) or proxy URL → POST /api/pushes; retries on network errors, 429 and 5xx (ENG-264 classification). Source content is snapshotted to private cache (also for picker documents, so retries never re-encrypt changed bytes under the same key) and deleted afterwards; job metadata is encrypted.
  • Receive: the notification's Download → DownloadWorker: GET /api/files/:id → stream → decrypt → MediaStore.Downloads/Pneumo/<name> (API 29+) or app-specific Downloads (26–28), with progress; the same notification becomes Open / Share; tag failure deletes the partial file and shows "This file was corrupted or tampered with"; collisions → "name (1).ext". Home file rows get Download / Open / Share. Notification identity and the group summary rules from ENG-263 are preserved (new group regressions).
  • Generic downloaded MIME types fall back to the file extension for Open/Share; explicit MIME types win.
  • Tests: worker tests with MockWebServer + WorkManagerTestInitHelper (round trips 0 B / 1 B / 65,536 B / 1 MB, direct and proxy endpoints, tamper cleanup, retry), share-intent parsing, managed-device file exchange; 24 new goldens and 30 re-recorded (named in the commit). New test dependency: androidx.work:work-testing:2.12.0.

Verify (lead, rebased on 0ceccf2):

  • mise run check:android → exit 0
  • ./gradlew testDebugUnitTest --rerun → exit 0 (335 tests, 0 failures)
  • ./gradlew pixel6Api34DebugAndroidTest --rerun → exit 0 (14 tests, 0 failures)
  • Acceptance (implementer, API 34 emulator, local random-port server): Photos → share → Pneumo → dev-client download byte-identical; dev-client send file → notification Download → Open in Photos; SHA-256 matched both ways. Lead checked the Download-complete notification with Open/Share.

Deviations: picker documents are snapshotted to private cache rather than kept by persistable grant; MIME fallback by extension for generic types.

🤖 Generated with Claude Code

Commits 2

M4: Fix file transfer retry keys and bulk writes (ENG-266)
M4: Add encrypted Android file transfers (ENG-266)