Receive shares and serve file requests, so other apps can reach this one

The manifest had nothing but a LAUNCHER filter, so no other app could hand Plain Files anything. It now registers for ACTION_SEND and SEND_MULTIPLE (a bar over the browser: navigate to the folder, then Save here) and for GET_CONTENT and OPEN_DOCUMENT, where tapping a file hands it back through the existing FileProvider.

Both reuse the browser instead of a second screen, because both are navigate-to-a-folder with a different verb at the end. data/ gains one port, IncomingSource, to keep a ContentResolver out of the ViewModel the way DeleteRequestFactory keeps one out of the delete path. Shared files are named from the sender OpenableColumns, allocated by the same NameAllocator a paste uses, and deleted again if they cannot be read rather than left as zero-byte stubs. While providing, a tap on a file chooses it but a tap on a folder still navigates, or the user could never reach the file.

ACTION_VIEW is deliberately not registered: the app already hands files out through the system chooser, and registering as a viewer would offer Plain Files back to itself. No new permissions - the merged release manifest still declares no INTERNET.
This commit is contained in:
2026-10-15 09:18:10 +02:00
parent ffed22c1da
commit cc085b43de
15 changed files with 583 additions and 29 deletions
+21
View File
@@ -38,6 +38,27 @@ upload needs a **higher `versionCode`** than every previous upload.
### Added
- **The app can receive a share, and act as a file picker.** The manifest had nothing but a
`LAUNCHER` filter, so no other app could hand Plain Files anything at all. It now registers
for `ACTION_SEND`/`SEND_MULTIPLE` — a bar appears over the browser, you navigate to the
folder you want, then "Save here" — and for `ACTION_GET_CONTENT`/`OPEN_DOCUMENT`, where
tapping a file hands it back through the existing `FileProvider`.
Both reuse the browser rather than adding a second screen, because both are the same task
underneath — navigate to a folder — with a different verb at the end. `data/` gains one
port, `IncomingSource`, so a `ContentResolver` stays out of the ViewModel exactly as
`DeleteRequestFactory` keeps one out of the delete path. A shared file is named from the
sender's `OpenableColumns` where it offers one, goes through the same `NameAllocator` a
paste does (so sharing the same file twice cannot overwrite the first copy), and is removed
again if it cannot be read rather than left as a zero-byte stub. While another app is
waiting for a file, tapping a *file* chooses it and tapping a *folder* still navigates —
without that the user could never reach the file they were trying to choose.
`ACTION_VIEW` is deliberately **not** registered: the app already hands files out through
the system chooser, and registering as a viewer as well would offer Plain Files back to
itself in that chooser. No new permissions — the merged release manifest still declares no
`INTERNET`.
- **Recursive search.** The query box used to filter the folder you were standing in, which
can only ever answer "is it here?" — and the usual question is "where did I put it?". It
now walks *down* from the current folder (`domain/FileSearch`) and streams matches as they