# 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.