148 lines
9.3 KiB
Markdown
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.
|