This commit is contained in:
+147
@@ -0,0 +1,147 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user