Files
zemion db873063f8
Verify source and build Android / android (push) Canceled after 0s
initial commit v 0.2.0
2026-09-24 22:49:27 +02:00

148 lines
9.3 KiB
Markdown

# Lightweight file browser — 0.2.0
This is implemented source, not a device-verified APK. See `VALIDATION.md` for the executed checks.
## Everyday workflow
Open **Browse → Add folder**, select an allowed folder in Android's system UI, and grant read access.
The folder becomes a saved location when the provider offers a persistable grant. Tap it to browse
its children. Folder rows navigate; file rows open the existing read-only preview. **Files** / Back
returns to the browser with its location, filter, and saved Compose scroll state. **Up** and the
breadcrumb buttons navigate visited folders. Breadcrumbs represent the visited route, not a guessed
filesystem path. An incoming nested directory link can have a direct root-to-target breadcrumb.
Use **Open file** for a single file anywhere the system picker can access, including individual
Downloads items. That system UI does not require opening Google Docs or Microsoft Office. A provider
may need its own network/account; Fileworkbench itself has neither an account nor network permission.
The **Recent** tab contains opened previews. Removing a recent item or clearing history does not
revoke saved folder grants. Removing a saved folder releases its tree read grant and does not delete
files. Removing a folder can make recent documents relying on that grant inaccessible.
## Browser controls
- Saved locations, nested folder navigation, breadcrumbs, Up, refresh, and explicit access errors.
- Literal, case-insensitive filename search **inside the loaded current folder only**.
- Sort by name, modified date, size, or MIME type. Folders stay first; unknown sizes/dates stay last.
- Dotfile visibility toggle. No recursive content indexing or background storage crawl.
- File type badges, provider-supplied size/date, and file-details dialog.
- **More → Preview** uses the existing PDF/image/text/Office/CSV/ZIP preview path.
- **More → Open in another app** hands off a read-only copy using a chooser that excludes this viewer.
This also works for unsupported preview formats when their bytes fit the copy limit.
- Opt-in provider thumbnails. Off by default; enable them in a folder's **Options** menu. Only visible
rows whose provider advertises thumbnail support are requested. Other files retain type badges.
No automatic full-file PDF/Office rendering is performed while listing a folder.
Office support remains **text/table reading previews**, not full document-layout rendering. Formats
unsupported by the existing viewer have not gained rendering engines merely by appearing in a list.
## Registration as a files application
The main manifest declares:
| Entry point | Behaviour |
|---|---|
| `MAIN` + `LAUNCHER` | Normal application entry. |
| `MAIN` + `android.intent.category.APP_FILES` | Android's files-app launch category, introduced in API 29. Opens browser locations. |
| `VIEW` + content-directory MIME types | Opens a usable granted document tree; otherwise explains how to select a folder. |
| Existing file `VIEW` / single-file `SEND` | Existing file-preview entry points remain present. |
| `GET_CONTENT` + `OPENABLE` + `*/*` | Separate `FilePickerActivity`, used by apps asking a third-party picker for file contents. |
`APP_FILES` is **not** the web-browser role. Registration makes the app an eligible handler; it does
not guarantee that an OEM or caller exposes a default-files-app switch or chooser.
`ACTION_OPEN_DOCUMENT` and `ACTION_OPEN_DOCUMENT_TREE` remain system-owned. No activity claims those
actions. Fileworkbench is not a general `DocumentsProvider` re-exporting the user's saved folders.
The debug variant alone has a protected synthetic-fixture DocumentsProvider for instrumentation;
it contains no user's files and is excluded from release builds.
## Acting as another app's file picker
The incoming request's base MIME filter and optional `EXTRA_MIME_TYPES` are checked; extra filters
narrow the base filter. Invalid requests fail closed. Folders remain navigable but cannot be selected.
Virtual documents cannot be returned as ordinary byte streams; export them in their provider first.
Single selection replaces the previous file; multi-selection is allowed only when requested.
Selections can span folders and be reviewed/removed. **Nothing is returned automatically:** the user
must press **Use file(s)**. Previewing is available through a row's More menu; the preview footer can
select/deselect that file. Cancelling returns `RESULT_CANCELED` and no file grant.
For `EXTRA_LOCAL_ONLY`, saved provider folders are deliberately not browsed: this app cannot prove
that an arbitrary provider's documents are already offline. **Open file** delegates to the system
picker with LOCAL_ONLY and the caller's MIME/multiple constraints. Provider/platform conformance to
that hint still matters; this is not a hard network sandbox around other installed providers.
At confirmation the app re-queries the selected documents and validates their types/capabilities.
Actual bytes are copied with per-file and aggregate limits. Results contain fresh narrow
FileProvider URIs, with only `FLAG_GRANT_READ_URI_PERMISSION`. Multi-results use ClipData, and a
single result also has a data URI/type. No original tree URI, prefix, write, or persistable grant is
returned. MIME metadata is preserved for copied extensionless files.
These are **temporary imported copies**, not in-place edit handles or permanent cloud links. Copies
may expire after a day, be evicted by Android, or be removed by explicit cache clearing. Receivers
should import/read them promptly. Browser external-open copies have the same limits and semantics.
Exported activities sanitize intent extras before ViewModel construction. External callers cannot
seed internal SavedStateHandle keys such as `sourceUri` or `browser.selection` to silently preselect
files from previously granted storage. Only the expected standard request parameters survive.
## Bounds and trade-offs
| Resource | Current bound |
|---|---|
| Saved folders | 24 |
| Navigation depth | 64 visited levels; repeated-folder loops rejected |
| Listing | 5,000 visited provider rows; truncation is explicitly shown |
| Search | Current loaded directory; query capped at 256 characters |
| Selection | 1 file, or up to 16 when multiple selection is requested |
| Preview / picker / external-open copy | 64 MiB per file |
| Picker request aggregate | 128 MiB |
| Picker-copy disk cache | 256 MiB and 32 sessions; new copies refused when full |
| Thumbnails | 96-pixel requested size, 4 MiB memory cache, two concurrent requests |
| Provider metadata/thumbnail queries | Bounded worker pool and 20-second awaiting timeout with CancellationSignal |
A truncated directory is not a complete device search: sort/filter only affect the loaded subset.
Use the system picker to reach other entries. Refresh is manual, including when a cloud provider
reports that more entries are still loading. There is no claim of live watching or recursive search.
Blocking provider streams and thumbnail/native decoders still need device-level cancellation,
resource-pressure, and hostile-provider testing. A timeout can stop the UI waiting; it cannot force
an arbitrary external provider to stop executing. See the remaining work in `SECURITY.md`.
## Storage that is deliberately unavailable
There is no all-files-access permission. Android 11+ restricts granting the internal-storage root,
reliable SD-card roots, and the Downloads root via a directory grant; Android/data and Android/obb
also have restrictions. Other apps' private storage is not accessible. A permitted Downloads
subfolder can be bookmarked where offered; individual Downloads files use **Open file**.
There are no rename/delete/move/create-folder controls, broad MediaStore gallery index, raw-path
browser, SMB/WebDAV/SFTP client, or archive extraction operations in this release.
## Reuse on iOS
`core/commonMain/browser` contains real shared entry/location/listing models, `DirectoryReader`,
filename filters/sorting, MIME validation, selection policy, safe labels, and streamed-copy budgets.
IDs remain opaque strings. Android ContentResolver, Uri grants, thumbnails, FileProvider, and Compose
remain in `androidApp`. The future iOS host supplies security-scoped URL/bookmark access and a native
browser/preview UI; Android picker registration is not an iOS feature. No Apple binary was built here.
## Folder-listing correction in 0.2.2
Versions 0.2.0 and 0.2.1 can reject a valid folder with "The storage provider did
not return a folder listing." A nullable `use` result, caused by the absence of an
optional provider error message, was incorrectly treated as a missing cursor.
0.2.2 moves the null check to the query itself. This is not a request for wider
permissions. Rebuild/install the fix and reopen or refresh the saved folder.
See `HOTFIX-0.2.2.md` and `VALIDATION.md` for the change and verification boundary.
## SAF-D1 diagnostics integrated in 0.2.3
The continuing user-reported listing failure is unresolved. This release keeps
the 0.2.2 null-check correction and adds a distinct `[SAF-D1/NULL_CURSOR]` error
only when the query itself returns no cursor. It reports the provider authority,
Android API and retained-read-grant status, not file paths or contents. The
Locations footer includes the app version and `Browser diagnostics SAF-D1` so
the installed source can be identified. Query behaviour and permissions remain
unchanged. See `RELEASE-0.2.3.md` and `BUILD.md` before reproducing the failure.