This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Architecture
|
||||
|
||||
## Boundaries
|
||||
|
||||
```text
|
||||
Android picker / content URI / single-file open-with
|
||||
|
|
||||
DocumentStore (Android)
|
||||
metadata -> size-limited private snapshot -> detector
|
||||
|
|
||||
+----------------+------------------+
|
||||
| | |
|
||||
PDF images text / packages
|
||||
| | |
|
||||
read-only PFD ImageLoader LocalPreviewLoader
|
||||
| (Android bitmap) | |
|
||||
isolated Service bytes JvmZipArchive
|
||||
| | |
|
||||
bounded raw ARGB +------+-----+
|
||||
| |
|
||||
PdfRenderClient common Kotlin core
|
||||
| XML / Office / CSV / Markdown
|
||||
| models / policy / search / export
|
||||
+------------------- Compose UI -------------+
|
||||
```
|
||||
|
||||
Native PDF data travels through granted file descriptors, not provider URIs or filesystem paths.
|
||||
The isolated worker cannot browse the app's private files. The host validates dimensions, byte
|
||||
length and page metadata before copying raw pixels to a bitmap. There is no PNG decoder in that
|
||||
return path. General image decoding still runs in the host process, and common parsers run on an
|
||||
IO dispatcher in the host; do not describe all parsing as process-isolated.
|
||||
|
||||
## Why Kotlin Multiplatform first, not a new C++ format stack
|
||||
|
||||
The current reusable work is format identification, reading-preview extraction, policy, models,
|
||||
search and export. It needs neither Android APIs nor a handwritten native Office rendering engine.
|
||||
Those pieces live in `core/src/commonMain`, which also enables direct JVM tests. Kotlin common code
|
||||
can be compiled for Apple targets without passing Android URI or Bitmap types through the API.
|
||||
|
||||
A later LibreOfficeKit or other native renderer can be a separate native adapter. This does not
|
||||
require the pure common core or native user interfaces to be rewritten in C++. Conversely, the JVM
|
||||
ZIP implementation is explicitly **not** portable to iOS; an equivalent Apple adapter is required.
|
||||
|
||||
## Common model
|
||||
|
||||
- `FileClassifier` combines bounded header evidence, name/MIME hints and package entry inspection.
|
||||
- `SafetyPolicy` centralizes ceilings and is shared by readers and archive adapters.
|
||||
- `ArchiveContainer` exposes metadata and bounded part reads; it must never resolve external URLs.
|
||||
- `FormatHandler` is the internal extension seam for package-reading preview handlers.
|
||||
- `DocumentPreview` declares its fidelity, section/table/block data, warnings and archive metadata.
|
||||
- `PreviewSafety` limits aggregate output amplification, including repeated shared strings.
|
||||
- `PreviewText` performs literal searches and a text export; no formula, script or macro execution.
|
||||
- `CoreApi` provides a small public boundary useful to a native host.
|
||||
|
||||
`SafeXml` is a bounded preview-specific XML subset, not a validating standards-complete XML library.
|
||||
It rejects DTD/custom entities and verifies basic syntax/nesting. It does not resolve namespace
|
||||
URIs, validate schemas, establish signature trust or guarantee complete OOXML/ODF semantics.
|
||||
Fuzzing and comparative testing against well-maintained parsers remain release gates.
|
||||
|
||||
## Android lifecycle
|
||||
|
||||
`ViewerViewModel` owns the current document, page bitmap, reading preview, recents and loading/error
|
||||
state. Rotation retains the ViewModel; saved provider URI/page state allows process-restoration
|
||||
attempts. External attachment grants may expire, so reselection is an explicit recovery path.
|
||||
|
||||
A processing mutex serializes active document operations. New opens cancel earlier requests; input
|
||||
copy loops observe coroutine cancellation, and stale staged files are cleaned. Common parsing is
|
||||
bounded but not a hard-preemptible sandbox. PDF rendering requests have a timeout; cancellation and
|
||||
worker death need further real-device stress testing.
|
||||
|
||||
Snapshots use random internal names. Display names are sanitized separately. The original provider
|
||||
file is never opened for writing. Share exports go into a separate narrow FileProvider directory;
|
||||
that provider is not exported. Save/preview-export callbacks check the expected document identity
|
||||
so a new incoming document does not silently replace the intended export.
|
||||
|
||||
## Storage contract
|
||||
|
||||
The native browser lists user-chosen document trees through `SafBrowserStore`. The system picker
|
||||
still supplies user-mediated directory grants and standalone files. There is no whole-device crawl,
|
||||
proprietary cloud or direct WebDAV/SFTP client. The app has no network permission; a provider may.
|
||||
|
||||
`BrowserViewModel` owns locations, visited breadcrumbs, options, selection and provider jobs, separate
|
||||
from `ViewerViewModel`. Shared `BrowserEntry` / `BrowserLocation` / `DirectoryReader` types, sorting,
|
||||
MIME/selection rules and `SelectionCopyBudget` live in commonMain. Android URI values are opaque IDs,
|
||||
not parsed filesystem paths in the core. A saveable Compose state holder retains browser scroll
|
||||
when the viewer replaces that screen. Provider metadata work has bounded workers and cancellation.
|
||||
|
||||
`FilePickerActivity` is a separate standard-launch activity. It accepts GET_CONTENT and returns only
|
||||
user-confirmed, bounded snapshots via `WorkbenchFileProvider`, a narrow FileProvider subclass that
|
||||
preserves MIME metadata. A single read grant applies to each selected copy, never to a source tree.
|
||||
Only the requested standard extras survive activity-intent sanitization before ViewModel creation.
|
||||
System OPEN_DOCUMENT is used for standalone selection, preventing GET_CONTENT picker recursion.
|
||||
|
||||
Folder bookmarks own tree grants. Document-history eviction only releases separate document grants.
|
||||
The per-process active-snapshot set protects a live MainActivity preview when a picker viewer opens;
|
||||
explicit clear-cache remains an intentional global invalidation of exported copies. Registry/cache
|
||||
cleanup and blocked-stream cancellation still require device stress tests.
|
||||
|
||||
Persistent recents store URI, display name, persistence flag and timestamp, up to 20 entries. No
|
||||
content indexing is performed across unopened files. Cache files are plaintext inside the Android
|
||||
sandbox; cache deletion is logical deletion, not guaranteed secure erasure.
|
||||
+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.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Browser acceptance plan — device execution pending
|
||||
|
||||
Run the normal build before claiming any row below passes:
|
||||
|
||||
```sh
|
||||
./scripts/verify-android.sh
|
||||
./gradlew :androidApp:connectedDebugAndroidTest
|
||||
```
|
||||
|
||||
The added `BrowserIntegrationTest` sources cover synthetic-provider listings, a 5,000-row cap,
|
||||
nested IDs, MIME-preserving read-only copies, multi-result ClipData, manifest resolution, local-only
|
||||
request forwarding, virtual-file rejection, and malicious restore-key stripping. They have **not**
|
||||
been compiled or run in the authoring environment. `ViewerSmokeTest` includes Browse/Recent/Add
|
||||
folder checks and retains the isolated PDF-service test.
|
||||
|
||||
The debug variant exposes **Fileworkbench test files** through a protected fixture DocumentsProvider.
|
||||
This is intentional testing support and absent from the release variant. It does not expose the
|
||||
app's real documents or browser grants. Same-UID instrumentation checks result shape/bytes, not
|
||||
cross-UID enforcement; perform the external-caller checks below on a real phone.
|
||||
|
||||
| Case | Expected result | Status |
|
||||
|---|---|---|
|
||||
| First launch | Browse/Recent, Open file/Add folder, no account or broad permission prompt | Not run |
|
||||
| Add Documents/test folder | One system consent, saved location, files shown within app | Not run |
|
||||
| Restart/reboot | Granted folder reopens; otherwise explicit renewal path | Not run |
|
||||
| Download root on Android 11+ | Directory restriction visible; individual file open works | Not run |
|
||||
| SD/USB removed or provider logged out | Recoverable error, no false empty-folder claim | Not run |
|
||||
| Large/nested folder | Responsive bounded listing, folders first, explicit truncation | Not run |
|
||||
| Search/sort/hidden files | Correct current-folder filtering, no claim of global results | Not run |
|
||||
| Preview and Back | Same folder/filter/scroll; supported PDF/image/text/Office views | Not run |
|
||||
| Provider thumbnails on/off | Only opted-in visible-row requests; type badge fallback | Not run |
|
||||
| Clear recents/cache | Files remain, saved folder grants remain, temporary copies removed | Not run |
|
||||
| Remove folder | Bookmark/tree grant removed, source files remain | Not run |
|
||||
| Files-app category | Correct manifest handler where platform/caller supports it | Not run |
|
||||
| Directory VIEW with tree/read grant | Target folder or explicit reselection recovery | Not run |
|
||||
| Directory VIEW without usable grant | No raw path crawling; asks user to add folder | Not run |
|
||||
| Separate caller GET_CONTENT single MIME | Only matching files selectable; cancel grants nothing | Not run |
|
||||
| GET_CONTENT multiple MIME / multiple files | Reviewed files only; ClipData readable from caller | Not run |
|
||||
| Caller attempts write / sibling / tree access | Denied; only each selected copy is read-granted | Not run |
|
||||
| Extensionless copied content | Resolver MIME matches preserved metadata | Not run |
|
||||
| Caller injects sourceUri/browser.selection extras | No unsolicited preview or preselection | Not run |
|
||||
| Caller LOCAL_ONLY | System delegation preserves hint; saved roots not queried | Not run |
|
||||
| Caller rotates / process dies / changes MIME | Request constraints survive; stale grants revalidated | Not run |
|
||||
| Provider changes file MIME/size/virtual status | Revalidated; mismatches refused before return | Not run |
|
||||
| Provider dishonest size / overlong stream | Actual 64/128 MiB budgets stop copying | Not run |
|
||||
| Cancel while copying / provider hang | No result/grant; eventual partial-copy cleanup | Not run |
|
||||
| Main viewer and picker coexist | New picker does not delete live viewer snapshot | Not run |
|
||||
| Open unsupported file in another app | Explicit read-only copy; viewer excluded from chooser | Not run |
|
||||
| TalkBack / large fonts / portrait/landscape | Usable labels, checks, focus and scrolling | Not run |
|
||||
|
||||
For a launch-only smoke check after installation:
|
||||
|
||||
```sh
|
||||
adb shell am start -a android.intent.action.MAIN \
|
||||
-c android.intent.category.APP_FILES -p dev.fileworkbench.android
|
||||
adb shell am start -a android.intent.action.GET_CONTENT \
|
||||
-c android.intent.category.OPENABLE -t 'image/*' -p dev.fileworkbench.android
|
||||
```
|
||||
|
||||
`am start` is not a real result-receiving application. It cannot replace verification that a distinct
|
||||
UID receives exactly the granted result URIs and cannot read siblings/write originals. Record actual
|
||||
SDK, device, command, build commit, and outcome for every completed test. Do not mark the whole plan
|
||||
passed merely because an APK builds or the offline core tests succeed.
|
||||
|
||||
## 0.2.2 listing regression
|
||||
|
||||
After installing this build over the previous app, reopen a saved folder and
|
||||
refresh. Confirm normal and empty folders do not show the no-listing exception,
|
||||
then navigate into a subfolder and preview a file. Reselect through Add folder
|
||||
only when the provider's original permission was temporary or has been revoked.
|
||||
|
||||
The debug provider includes additional synthetic folders for empty, info-only,
|
||||
loading (with and without cached rows), and provider-error listings. The updated
|
||||
`BrowserIntegrationTest` covers these cases; run `:androidApp:connectedDebugAndroidTest`
|
||||
with a device/emulator. Host cursor tests in `scripts/test-folder-listing.py` do not
|
||||
replace those Android tests or verify real storage-provider permissions.
|
||||
|
||||
## 0.2.3 diagnostic reproduction
|
||||
|
||||
Install the newly built APK over the existing app using the same signing identity.
|
||||
Do not uninstall/clear app data merely to reproduce the failure. Confirm the
|
||||
Locations footer reads `Fileworkbench 0.2.3 · Browser diagnostics SAF-D1` before
|
||||
reopening the previously failing folder. Record the whole new on-screen error,
|
||||
including marker/provider/API/grant status. A missing retained grant is not proof
|
||||
that temporary access is absent. This is a diagnostic test, not a claimed fix.
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
# Building and running — Fileworkbench 0.2.3
|
||||
|
||||
## Pinned baseline
|
||||
|
||||
| Component | Selected version |
|
||||
|---|---|
|
||||
| Gradle build JVM / daemon | Installed JDK **17**, any vendor |
|
||||
| Java/Kotlin compilation toolchains and JVM bytecode target | **17** |
|
||||
| Gradle wrapper distribution | **8.14.5** |
|
||||
| Android Gradle Plugin | 8.11.1 (unchanged) |
|
||||
| Kotlin / Compose compiler plugin | 2.2.21 (unchanged) |
|
||||
| Compose BOM | 2025.08.00 (unchanged) |
|
||||
| Android compile / target SDK | 36 / 36 |
|
||||
| Android minimum SDK | 26 |
|
||||
| Android Build Tools | 35.0.0 (AGP default) |
|
||||
|
||||
These are deliberate project pins, not a recommendation to use the newest releases.
|
||||
This source package is **not a verified Android APK**. The current execution results
|
||||
and limitations are recorded in `VALIDATION.md`.
|
||||
|
||||
## Start with a fresh extraction
|
||||
|
||||
This is the whole project, not a patch. Extract it to a new directory and open the
|
||||
contained `fileworkbench` directory (the one containing `settings.gradle.kts`).
|
||||
Keep your existing checkout and any local changes separately. Do not copy the old
|
||||
`build.gradle.kts`, `settings.gradle.kts`, version catalog, wrapper files,
|
||||
`gradle/gradle-daemon-jvm.properties`, `.idea`, `.gradle` or `.kotlin` over this release.
|
||||
Otherwise old Gradle 9.x / Java 25 / duplicate-plugin settings can be reintroduced.
|
||||
|
||||
There are no machine-specific IDE files or absolute SDK/JDK paths in this archive.
|
||||
For your Gitea repository, review the new files against your checkout after the
|
||||
fresh project imports successfully; do not overwrite uncommitted work blindly.
|
||||
|
||||
## Install/select JDK 17 and the SDK
|
||||
|
||||
Install a **JDK 17**, not only a JRE. In Android Studio/IntelliJ, open
|
||||
**Settings → Build, Execution, Deployment → Build Tools → Gradle**, use the wrapper
|
||||
from this project, and select that JDK 17 as the **Gradle JDK/JVM**. On IDE versions
|
||||
that expose daemon JVM criteria, leave this project's version-17 criteria intact.
|
||||
You do not need to change the IDE's own boot runtime or uninstall other JDKs.
|
||||
|
||||
In SDK Manager install Android SDK Platform 36, Build Tools 35.0.0 and Platform
|
||||
Tools. Accept any SDK licences yourself. Let the IDE generate a new
|
||||
`local.properties` with `sdk.dir`, or use your existing `ANDROID_HOME` environment
|
||||
setting. Do not commit local.properties, signing keys or private documents.
|
||||
|
||||
Set terminal `JAVA_HOME` to your installed JDK 17 directory (not its `bin` folder).
|
||||
The project also recognises an optional `JDK17_HOME` for toolchain discovery.
|
||||
The checked-in `gradle/gradle-daemon-jvm.properties` contains `toolchainVersion=17`;
|
||||
module toolchains also select 17. No vendor or absolute installation path is pinned.
|
||||
Automatic JDK downloads are disabled: install a JDK 17 before building.
|
||||
|
||||
### Linux/macOS terminal
|
||||
|
||||
```bash
|
||||
cd /path/to/new/fileworkbench
|
||||
# Replace this example with the path of your actual JDK 17 installation.
|
||||
export JAVA_HOME="/path/to/jdk-17"
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
chmod +x gradlew scripts/*.sh
|
||||
java -version
|
||||
./gradlew --version
|
||||
./gradlew help
|
||||
./gradlew :core:jvmTest :platform-jvm:test \
|
||||
:androidApp:assembleDebug :androidApp:assembleDebugAndroidTest :androidApp:lintDebug
|
||||
```
|
||||
|
||||
### Windows PowerShell
|
||||
|
||||
```powershell
|
||||
cd C:\path\to\new\fileworkbench
|
||||
# Replace this example with the path of your actual JDK 17 installation.
|
||||
$env:JAVA_HOME = "C:\path\to\jdk-17"
|
||||
$env:Path = "$env:JAVA_HOME\bin;$env:Path"
|
||||
java -version
|
||||
.\gradlew.bat --version
|
||||
.\gradlew.bat help
|
||||
.\gradlew.bat :core:jvmTest :platform-jvm:test :androidApp:assembleDebug :androidApp:assembleDebugAndroidTest :androidApp:lintDebug
|
||||
```
|
||||
|
||||
The environment changes above apply only to the current terminal. Verify that
|
||||
`--version` reports **Gradle 8.14.5**, with the intended Java 17 launcher and daemon
|
||||
criteria. A version command alone does not prove that the application compiles.
|
||||
|
||||
## First-run wrapper bootstrap
|
||||
|
||||
The external `gradle-wrapper.jar` could not be downloaded in the authoring runtime,
|
||||
so it is **not bundled**. All project source, Gradle configuration, launchers and
|
||||
bootstrap scripts are present. The first `./gradlew --version` invocation (Windows:
|
||||
`.\gradlew.bat --version`) downloads the wrapper binary and verifies its SHA-256.
|
||||
Do this once **before importing into the IDE**, so its Gradle integration can see
|
||||
the wrapper JAR. An internet connection is required for this and dependency setup.
|
||||
|
||||
Both shell and PowerShell bootstrap scripts now request the 8.14.5 source tag and
|
||||
release distribution. A downloaded JAR with the wrong checksum is not executed;
|
||||
the script falls back to the separately verified distribution and checks its
|
||||
embedded wrapper. The fallback needs `unzip` on Unix-like systems or .NET ZIP
|
||||
support in PowerShell. A failed initial network request still aborts; resolve that
|
||||
network problem or bootstrap from an official local Gradle installation.
|
||||
|
||||
Official **8.14.5 wrapper JAR SHA-256**:
|
||||
|
||||
```text
|
||||
7d3a4ac4de1c32b59bc6a4eb8ecb8e612ccd0cf1ae1e99f66902da64df296172
|
||||
```
|
||||
|
||||
Official **8.14.5 binary distribution SHA-256**:
|
||||
|
||||
```text
|
||||
6f74b601422d6d6fc4e1f9a1ab6522f642c2fdcbc15ae33ebd30ba3d7198e854
|
||||
```
|
||||
|
||||
Hashes were verified against Gradle's release-checksum reference on 2026-09-24;
|
||||
this is not a claim that the binary downloads succeeded here.
|
||||
|
||||
Once the official wrapper runs, the standard upstream launchers can be generated:
|
||||
|
||||
```sh
|
||||
./gradlew wrapper --gradle-version 8.14.5 --distribution-type bin \
|
||||
--gradle-distribution-sha256-sum 6f74b601422d6d6fc4e1f9a1ab6522f642c2fdcbc15ae33ebd30ba3d7198e854
|
||||
./gradlew wrapper
|
||||
```
|
||||
|
||||
Commit the verified wrapper JAR and generated scripts in your own repository.
|
||||
A network-free build is possible only after the required tooling and dependencies
|
||||
are locally available. The installed application's main manifest has no network
|
||||
permission; an external storage provider may use its own network connection.
|
||||
|
||||
## Install and identify this diagnostic build
|
||||
|
||||
After successful APK assembly, with a connected and authorised device:
|
||||
|
||||
```sh
|
||||
adb install -r androidApp/build/outputs/apk/debug/androidApp-debug.apk
|
||||
```
|
||||
|
||||
Use your existing debug signing identity when installing over the current app.
|
||||
Changing source directories on the same machine normally keeps the same user
|
||||
keystore; this archive does not contain a signing key. Do not uninstall or clear
|
||||
app data just to test the folder issue. A signing mismatch needs to be resolved
|
||||
with the existing signing key rather than silently discarding application data.
|
||||
|
||||
The package remains `dev.fileworkbench.android`, version **0.2.3**, code **5**.
|
||||
In **Browse → Locations**, verify the footer:
|
||||
|
||||
```text
|
||||
Fileworkbench 0.2.3 · Browser diagnostics SAF-D1 · read-only
|
||||
```
|
||||
|
||||
Then open the previously failing folder. A genuinely null cursor now produces
|
||||
`[SAF-D1/NULL_CURSOR]`, the storage-provider authority, Android API level, and
|
||||
retained-grant status. Send the full new message for diagnosis. A false retained
|
||||
grant does not establish a permission failure: temporary access may still exist.
|
||||
The query, successful listing behaviour and storage permissions are unchanged.
|
||||
|
||||
## Verification and troubleshooting
|
||||
|
||||
With a device/emulator, execute the Android instrumentation sources:
|
||||
|
||||
```sh
|
||||
./gradlew :androidApp:connectedDebugAndroidTest
|
||||
```
|
||||
|
||||
`./scripts/verify-android.sh` runs source checks plus Gradle tests, APK assembly and
|
||||
lint. The existing optional GitHub workflow uses Java 17 and these same commands;
|
||||
it has not been run here. These commands can also run on your local machine or a
|
||||
suitably configured Gitea runner; no Gitea workflow is introduced in this release.
|
||||
|
||||
For offline **host-only** checks with Python 3, Kotlin compiler and an installed JVM:
|
||||
|
||||
```sh
|
||||
python3 scripts/check-project.py
|
||||
python3 scripts/test-build-config.py
|
||||
bash scripts/test-core.sh
|
||||
python3 scripts/test-folder-listing.py
|
||||
bash scripts/check-kotlin-syntax.sh
|
||||
```
|
||||
|
||||
Host test bytecode targets 17, but the scripts run on the local JVM; see the
|
||||
validation report for the actual host version. They do not compile Android APIs.
|
||||
|
||||
**A duplicate `org.jetbrains.kotlin.jvm` plugin request:** this archive's root
|
||||
script contains five distinct plugin aliases, with no extra `id("...")` or
|
||||
`kotlin("jvm")` declarations. Do not append a second request for the JVM plugin.
|
||||
Its use in `platform-jvm/build.gradle.kts` is intentional. The offline
|
||||
`scripts/check-build-config.py` verifies the root aliases and catalog IDs.
|
||||
|
||||
**Gradle still selects 9.x or Java 25:** confirm that you opened this new directory,
|
||||
are using its wrapper and selected JDK 17 in the IDE. Inspect user-level Gradle
|
||||
properties/init scripts and environment variables for local overrides. The new
|
||||
settings guards reject a different Gradle version or daemon JVM rather than
|
||||
silently building with an unintended toolchain.
|
||||
|
||||
**Could not find a matching JDK / no download URL:** install JDK 17 and make it
|
||||
discoverable using JAVA_HOME/JDK17_HOME. The package deliberately has no per-machine
|
||||
path or automatically provisioned JDK download.
|
||||
|
||||
## Primary references
|
||||
|
||||
- Gradle release hashes: https://gradle.org/release-checksums/
|
||||
- Gradle 8.14.5 release notes: https://docs.gradle.org/8.14.5/release-notes.html
|
||||
- Daemon JVM criteria: https://docs.gradle.org/current/userguide/gradle_daemon.html
|
||||
- Android build JVM selection: https://developer.android.com/build/jdks
|
||||
- Android command-line builds: https://developer.android.com/build/building-cmdline
|
||||
@@ -0,0 +1,19 @@
|
||||
Fileworkbench 0.2.0 actual Android build attempt — 2026-09-23
|
||||
Command: ./scripts/verify-android.sh
|
||||
Environment:
|
||||
openjdk version "21.0.11" 2026-04-21
|
||||
OpenJDK Runtime Environment (build 21.0.11+10-1-deb13u2-Debian)
|
||||
OpenJDK 64-Bit Server VM (build 21.0.11+10-1-deb13u2-Debian, mixed mode, sharing)
|
||||
info: kotlinc-jvm 1.9.0 (JRE 21.0.11+10-1-deb13u2-Debian)
|
||||
ANDROID_HOME: not configured
|
||||
Android SDK/emulator: not installed in this authoring environment
|
||||
|
||||
PASS project invariants: XML, permission-free manifest, isolated PDF service, narrow FileProvider, portable core, no WebView/log calls.
|
||||
PASS browser source invariants: APP_FILES/GET_CONTENT, standard picker activity, no system-picker impersonation, narrow copied paths, read-only results, sanitized activity defaults, debug fixture excluded from main manifest.
|
||||
Fetching the official Gradle 8.13 wrapper; a pinned SHA-256 is required.
|
||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||||
Dload Upload Total Spent Left Speed
|
||||
␍ 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (6) Could not resolve host: raw.githubusercontent.com
|
||||
|
||||
Build command exit status: 6
|
||||
No Android compiler, D8, lint or device execution was reached when bootstrap failed.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Continuation brief
|
||||
|
||||
## User objective
|
||||
|
||||
Replace the routine act of opening mobile file attachments in Google/Microsoft apps with an
|
||||
account-free local viewer. Android first; preserve an iOS path through reusable core logic.
|
||||
Do not turn this into a storage cloud or a new full Office suite as the first deliverable.
|
||||
|
||||
## Current source
|
||||
|
||||
Read `README.md`, `FEATURES.md`, `ARCHITECTURE.md`, `SECURITY.md`, `BUILD.md` and `VALIDATION.md`.
|
||||
Android Kotlin/Compose implementation, isolated PDF service, JVM ZIP adapter and genuine common
|
||||
Kotlin reading-preview parsers are present. 112 offline core/JVM checks passed. Android and Apple
|
||||
builds have not completed; Android bootstrap attempts fail before compilation. The archive contains no APK and no LibreOffice binary.
|
||||
|
||||
## Browser addition in 0.2.0
|
||||
|
||||
Read `BROWSER.md` and `BROWSER_ACCEPTANCE.md`. Native SAF folder browsing and a separate GET_CONTENT
|
||||
picker are implemented, with APP_FILES/directory VIEW registration. Shared browser models, sort/filter,
|
||||
MIME/selection policy and streamed-copy limits are in commonMain. Android adapters own grants, metadata,
|
||||
opt-in thumbnails and copied FileProvider results. Exported activity extras are sanitized before
|
||||
SavedStateHandle defaults are created. Do not remove that protection or forward directory grants.
|
||||
|
||||
No broad storage access, all-files permission, raw-path enumeration, file mutation, persistent picker
|
||||
result or replacement system DocumentsUI is implemented. Android 11+ root/Downloads restrictions are
|
||||
intentional and explained in the UI. The debug-only fixture DocumentsProvider is for tests only.
|
||||
|
||||
## First actions
|
||||
|
||||
Bootstrap the official checksum-verified Gradle wrapper on a connected build machine; install the
|
||||
pinned Android SDK/JDK tooling; resolve/build/test/lint. Fix all actual compile/runtime issues before
|
||||
extending formats. Run `ViewerSmokeTest`, `BrowserIntegrationTest`, and the manual distinct-UID real-provider flows. Preserve known limitations
|
||||
in the UI and documentation. Update the validation report only with commands actually run.
|
||||
|
||||
## Non-negotiable boundaries
|
||||
|
||||
Keep originals read-only; explicit copy/export/share only. Do not add INTERNET, all-files access,
|
||||
telemetry or account requirements to solve a document-opening problem. A selected provider may
|
||||
have independent networking. Do not execute scripts/macros/formulas or load external resources.
|
||||
Keep input/output/ZIP/XML/repetition limits enforced during parsing. Never extract archive paths to
|
||||
the filesystem. Preserve capability/fidelity labels and explicit error recovery.
|
||||
|
||||
Keep Android types out of commonMain. `platform-jvm` is not the iOS implementation. Use the configured
|
||||
Apple framework and build a native ZIP/file/PDF adapter later. Do not call an untested interface or
|
||||
placeholder native bridge an implemented engine.
|
||||
|
||||
## Known areas for review
|
||||
|
||||
Custom preview XML parser is bounded but not schema/namespace validating. Office previews omit
|
||||
substantial content. PDF IPC, worker cancellation/lifecycle and actual device memory need testing.
|
||||
Image EXIF/animation handling is absent. Export/share jobs and cache cleanup need race tests. UI
|
||||
strings are currently English and mostly inline; move them to a localization layer before adding
|
||||
languages. Dependency locking/verification/SBOM requires an actual resolved build.
|
||||
|
||||
Follow the implementation slices in `ROADMAP.md`, with measurable acceptance results. Keep source
|
||||
and documentation complete in each release ZIP. Do not commit user documents, local SDK paths,
|
||||
private signing keys or generated build caches.
|
||||
|
||||
## Continuation from 0.2.3
|
||||
|
||||
Current requested baseline: Gradle 8.14.5, installed JDK 17, Kotlin 2.2.21, AGP 8.11.1.
|
||||
All sources and SAF-D1 diagnostics are integrated; no patch step remains. Check
|
||||
`docs/VALIDATION.md` before claiming build/device success. The persistent folder
|
||||
failure is unresolved; obtain the exact new message after confirming the 0.2.3 /
|
||||
SAF-D1 footer. Do not infer permission failure from retained-grant=false alone.
|
||||
Do not upgrade the build toolchain or add a duplicate root Kotlin JVM plugin to
|
||||
investigate the storage-provider issue. Current pins are checked offline by
|
||||
`scripts/check-build-config.py` as well as by Gradle settings at configuration time.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Office engine decision record
|
||||
|
||||
## Decision in 0.1.0
|
||||
|
||||
Ship an Android source implementation with local reading previews and a reusable Kotlin core.
|
||||
Do not claim the LibreOfficeKit engine is bundled. Do not claim a reading preview preserves document
|
||||
layout, all text, formula results or embedded objects.
|
||||
|
||||
The earlier high-level discussion made embedding LibreOfficeKit sound closer to adding a small
|
||||
library dependency than it really is. LibreOffice's own Android notes describe bundling a native
|
||||
LibreOffice core, bootstrapping it, a dedicated LOK thread, native callbacks and tiled rendering.
|
||||
This is a substantive integration/build/security/licensing track; `SOURCES.md` points to the primary
|
||||
upstream documentation. Its existing work remains valuable and is the preferred direction to
|
||||
investigate rather than writing a new compatible Office layout engine.
|
||||
|
||||
## Required prototype deliverables
|
||||
|
||||
1. A pinned upstream revision, licence inventory and documented build recipe with real Android
|
||||
binaries for the required ABIs. Do not assume a maintained public AAR exists.
|
||||
2. A local open/page/tile/render prototype on actual hardware, including startup resources/fonts.
|
||||
Do not redistribute fonts copied from the authoring environment.
|
||||
3. Single-thread/queue ownership rules for engine calls and callbacks, a crash-isolated service,
|
||||
and a bounded transport for page metadata, pixels, text and search results.
|
||||
4. Explicit macro/external-link/font/network policies enforced inside the chosen engine and verified
|
||||
against hostile documents, not just disabled in the host UI.
|
||||
5. Fidelity tests across DOCX/XLSX/PPTX/ODF and only then legacy formats. Account for missing fonts,
|
||||
unsupported layout behaviours, cached vs calculated spreadsheet values, and revision semantics.
|
||||
6. Independent assessment of Apple build/distribution integration. The common Kotlin preview model
|
||||
remains reusable; the native engine may need different platform bootstrapping/rendering adapters.
|
||||
|
||||
## Not done in this repository
|
||||
|
||||
There is no `liblo-native-code.so`, LOK JNI bridge, Collabora build, converted PDF cache, native engine
|
||||
licence bundle or Office layout validation. The `FormatHandler` interface is an extension seam for
|
||||
reading previews; it is not evidence of a full engine implementation.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Feature and format matrix — 0.2.0
|
||||
|
||||
“Implemented” below means implementation source is present. Android runtime features remain
|
||||
unverified until a real SDK build and device run. Core/JVM behaviour has the executed tests recorded
|
||||
in `VALIDATION.md`.
|
||||
|
||||
| Area / format | Implemented source behaviour | Not implemented / important limits |
|
||||
|---|---|---|
|
||||
| Android file opening | System picker, VIEW intent, single-file SEND, read grants, bounded snapshots | No multi-file SEND, raw file paths, virtual-file conversion or cloud API |
|
||||
| Native file browser | Saved folders, nested navigation, breadcrumbs/Up, metadata, search/sort/dotfiles, opt-in provider thumbnails | Scoped folders only; no root/all-files access, global index, live watch, file mutation or grid mode |
|
||||
| Files-app registration | APP_FILES, content-directory VIEW, separate GET_CONTENT picker | Does not replace system OPEN_DOCUMENT / OPEN_DOCUMENT_TREE or guarantee a device default-files setting |
|
||||
| File picker | MIME constraints, single/multiple selection, review/confirmation, read-only copied URI results, LOCAL_ONLY system delegation | No directory results, in-place editing, permanent URI promise, cloud-native conversion or original tree grants |
|
||||
| External opening | Read-only copy and chooser excluding this viewer | 64 MiB limit; edits by recipients cannot update the original |
|
||||
| Recent files | Up to 20 URI/name entries; permission retention when granted; remove/clear | Not copies retained forever; temporary attachment access can expire |
|
||||
| PDF | Isolated Android renderer, one page at a time, previous/next/jump, pinch zoom | No text search/selection, continuous page scrolling, OCR, forms, annotations, passwords or printing |
|
||||
| JPEG/PNG/WebP/GIF/BMP | Platform bitmap decoding, bounds checks/downsampling, zoom | First frame only; no EXIF orientation, animated playback, image editing or HEIC guarantee |
|
||||
| Plain text and source | UTF-8 / BOM-tagged UTF-16, selectable text, literal search | No syntax highlighting, arbitrary legacy encodings, hex editor or giant-file streaming |
|
||||
| Markdown | Heading/list/code-block emphasis; original inline markup retained | Not CommonMark-complete; no rich inline rendering, HTML execution or remote resources |
|
||||
| CSV/TSV | Quoted fields, escaped quotes, multiline fields, comma/semicolon/tab detection, table view | Literal values only; no formulas, editing or schema inference |
|
||||
| DOCX | Body paragraphs, basic heading/list recognition, flattened table rows | Not page layout; images, fields/revisions, comments, drawings, headers/footers and advanced structures incomplete |
|
||||
| XLSX | Named sheets, relationships, sparse cell positions, shared/inline strings, stored values, formula strings | No calculation; cached values may be stale; date serials may show raw; no number styles/charts/merged cells/filter fidelity |
|
||||
| PPTX | Presentation relationship order, slide sections, text paragraphs | Shape/XML text order not visual reading order; no graphical slides, charts, animations, speaker notes |
|
||||
| ODT | Basic paragraphs/headings and mixed text | No layout fidelity, embedded graphics, revision semantics or full list/table formatting |
|
||||
| ODS | Named sheets, stored values, bounded row/cell repeats, sparse blank-padding skip | No calculations/styles/charts; hidden content semantics incomplete |
|
||||
| ODP | Named slide sections, text paragraphs | No full slide rendering or object-placement fidelity |
|
||||
| ZIP | Entry/path/size listing and filename search; no extraction | No TAR/7z/RAR, encrypted ZIP, nested opening, selective extraction or compression |
|
||||
| HTML/SVG/XML/JSON/YAML/etc. | Inert source text when recognized | No WebView, embedded scripting, SVG rasterization or structural tree editor |
|
||||
| ICS/VCF | Raw text by extension | No calendar/contact import or structured card UI |
|
||||
| Legacy DOC/XLS/PPT/RTF | Explicit unsupported message | No legacy rendering; no LibreOffice native engine |
|
||||
| Macro-enabled Office | Known macro extensions and VBA packages rejected | Never execute macros; no automatic conversion |
|
||||
| Encrypted Office | Unsupported package/binary error | No password flow or decryption |
|
||||
| Share/save | Share an original snapshot, save a copy, export labelled preview text | Never edit original; no general conversion or Office-to-PDF export |
|
||||
| Search | Supported extracted blocks/rows, literal case-insensitive matching, bounded hits | No PDF/OCR search; counts refer to matching blocks/rows, not all word occurrences |
|
||||
| iOS | Common Kotlin core and opt-in framework targets | No app, ZIP adapter, bookmark implementation, native renderer or tested Apple build |
|
||||
|
||||
Browser limits: 24 saved folders, 64 visited levels, 5,000 listing rows, 16 selected files when allowed,
|
||||
64 MiB per copied file, 128 MiB per selection, 256 MiB picker-copy cache, and 4 MiB thumbnail cache.
|
||||
These are additional to the preview/parser limits below. See `BROWSER.md`.
|
||||
|
||||
## Initial policy ceilings
|
||||
|
||||
64 MiB imported file; 4 MiB plain-text bytes / expanded preview characters; 8 MiB per inflated ZIP
|
||||
part; 32 MiB cumulative inflation; compression ratio up to 300:1; 4,096 archive entries; XML depth
|
||||
96; 100,000 XML nodes; 64 attributes per element; 20,000 blocks; 32,768 characters per block/cell;
|
||||
2,000 rows per displayed table; 128 columns; 50,000 total table cells; 200 sections; 1,000 search
|
||||
result blocks/rows. Native PDF output is limited to 3 million pixels, width 2,048 and height 4,096.
|
||||
Images over 100 million declared input pixels are rejected and decoded previews are downsampled.
|
||||
|
||||
These are conservative ceilings, not performance guarantees. A file below a byte ceiling can still
|
||||
be rejected for structure, content, missing parts, encoding or a secondary limit. ZIP indexes do
|
||||
not inflate file entries; expansion ceilings apply when a part is actually read. Very compressible
|
||||
legitimate XML can hit the same ratio limit as malicious content and be refused.
|
||||
|
||||
## Fidelity contract
|
||||
|
||||
The app does not silently present a reading preview as the complete original. A visible banner
|
||||
identifies Office previews, and a tap shows detailed caveats. Text exports retain notices.
|
||||
Original-file sharing sends the untouched snapshot, not converted/extracted text.
|
||||
|
||||
Documents requiring exact placement, complete revision history, financial recalculation, or legal/
|
||||
scientific completeness must be checked in an appropriate full-fidelity application. This release
|
||||
is intended for opening and inspecting routine files, not certifying their complete contents.
|
||||
@@ -0,0 +1,53 @@
|
||||
# 0.2.1 — PDF instrumentation-test compilation fix
|
||||
|
||||
The reported failure was in `:androidApp:compileDebugAndroidTestKotlin`, at the
|
||||
`PdfDocument().use` expression in `ViewerSmokeTest.kt`.
|
||||
|
||||
## Cause
|
||||
|
||||
Android's `android.graphics.pdf.PdfDocument` has a `close()` method, but does not
|
||||
implement `java.io.Closeable` or `java.lang.AutoCloseable`. The Kotlin standard
|
||||
library's `use` extensions require one of these interfaces. A method named `close`
|
||||
is not sufficient. The type-parameter R diagnostic is a consequence of the invalid
|
||||
receiver; explicit type arguments or dependency upgrades are not the fix.
|
||||
|
||||
## Correction
|
||||
|
||||
The PDF fixture now uses explicit `try/finally`: finish the started page, write the
|
||||
PDF through an output stream (which can still use `use`), and close the document.
|
||||
A unique temporary fixture is deleted even when setup or rendering throws; the
|
||||
render client also retains explicit cleanup. Existing test assertions are retained.
|
||||
|
||||
No runtime feature or dependency changes were made. The complete source release
|
||||
increments versionCode to 3 / versionName to 0.2.1 and updates validation documents.
|
||||
The one-file patch changes only ViewerSmokeTest.kt, so it can be applied without
|
||||
updating version metadata or replacing other locally modified files.
|
||||
|
||||
## Apply to a local 0.2.0 checkout
|
||||
|
||||
Replace `androidApp/src/androidTest/kotlin/dev/fileworkbench/android/ViewerSmokeTest.kt`
|
||||
with the supplied fixed file, or run `git apply` with the supplied one-file patch.
|
||||
The patch was checked against the exact test in the supplied 0.2.0 source archive.
|
||||
|
||||
Then run from PowerShell at the repository root:
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :androidApp:compileDebugAndroidTestKotlin
|
||||
.\gradlew.bat :core:jvmTest :platform-jvm:test :androidApp:assembleDebug :androidApp:assembleDebugAndroidTest :androidApp:lintDebug
|
||||
```
|
||||
|
||||
The first command retries the failed compilation task. The second continues full
|
||||
build validation. These do not run the instrumentation tests on a device; that
|
||||
requires `:androidApp:connectedDebugAndroidTest` with a connected device/emulator.
|
||||
|
||||
## Verification boundary
|
||||
|
||||
See `VALIDATION.md`. The portable checks and syntax checks passed here. The Android
|
||||
compilation retry stopped at Gradle-wrapper download with a DNS error before the
|
||||
compiler ran; no APK or device-test success is claimed.
|
||||
|
||||
## Primary API references
|
||||
|
||||
- Android PdfDocument API: https://developer.android.com/reference/android/graphics/pdf/PdfDocument
|
||||
- Kotlin Closeable.use: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.io/use.html
|
||||
- Kotlin AutoCloseable.use: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/use.html
|
||||
@@ -0,0 +1,61 @@
|
||||
# 0.2.2 — Valid folder listings incorrectly treated as missing
|
||||
|
||||
## Runtime correction
|
||||
|
||||
File: `androidApp/src/main/kotlin/dev/fileworkbench/android/browser/SafBrowserStore.kt`.
|
||||
|
||||
Before, the no-listing check followed the complete resource-handling expression:
|
||||
|
||||
```kotlin
|
||||
resolver.query(children, PROJECTION, null, null, null, signal)?.use { cursor ->
|
||||
// Read rows and optional status messages.
|
||||
cursor.extras.getString(DocumentsContract.EXTRA_ERROR)?.let { information = safeFileLabel(it) }
|
||||
} ?: throw PreviewException("The storage provider did not return a folder listing.")
|
||||
```
|
||||
|
||||
The last expression returns null when no error is reported, and `use` returns the
|
||||
result of its block. Thus success triggered the no-listing error. This was a
|
||||
Fileworkbench control-flow bug, not a requirement for broader storage access.
|
||||
|
||||
After, check only the cursor returned by the provider:
|
||||
|
||||
```kotlin
|
||||
val queryResult = resolver.query(children, PROJECTION, null, null, null, signal)
|
||||
?: throw PreviewException("The storage provider did not return a folder listing.")
|
||||
queryResult.use { cursor ->
|
||||
// The existing row/extras handling stays unchanged.
|
||||
}
|
||||
```
|
||||
|
||||
A genuinely null query result still fails; an empty cursor is a valid empty listing.
|
||||
The change does not swallow exceptions or treat missing access as an empty folder.
|
||||
|
||||
## Apply without replacing local build configuration
|
||||
|
||||
Use either the supplied replacement `SafBrowserStore.kt` at the path above or
|
||||
`git apply` with the supplied one-file patch. Do not apply both. The patch was
|
||||
verified against the shipped 0.2.0 and 0.2.1 source, whose browser file is identical.
|
||||
It changes no Gradle/JDK settings, dependencies, manifest permissions, stored data,
|
||||
or document contents. Applying only the patch does not change the displayed
|
||||
application version. The complete source release sets version 0.2.2 / code 4.
|
||||
|
||||
Rebuild and install over the old debug app, then open/refresh the saved folder.
|
||||
There is no migration and no reason to clear app data for this fix. Temporary grants
|
||||
may require selecting a folder again; no new broad permission is requested.
|
||||
|
||||
## Regression coverage
|
||||
|
||||
`scripts/test-folder-listing.py` extracts the actual cursor-reading block and runs
|
||||
14 JVM checks on test doubles. The old source fails 9 of these with the reported
|
||||
message; the corrected source passes all 14. This is not an Android API/device test.
|
||||
The debug fixture and `BrowserIntegrationTest` add five real Android test cases;
|
||||
those still need a connected device/emulator to execute. The existing normal,
|
||||
nested and large-folder tests also cover the corrected path.
|
||||
|
||||
See `VALIDATION.md` for the executed checks and their exact limitations.
|
||||
|
||||
## Primary references
|
||||
|
||||
- Kotlin use returns the block result: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.io/use.html
|
||||
- Android EXTRA_ERROR is optional: https://developer.android.com/reference/android/provider/DocumentsContract#EXTRA_ERROR
|
||||
- ContentResolver query result: https://developer.android.com/reference/android/content/ContentResolver
|
||||
@@ -0,0 +1,21 @@
|
||||
# Fileworkbench 0.2.0
|
||||
|
||||
Complete source update of the 0.1.0 project, not an APK or a patch-only archive.
|
||||
|
||||
Added a native read-only folder browser and Android file-picker registration. The app can browse
|
||||
chosen document trees, preview supported files with the existing viewer, return to the browser,
|
||||
and hand read-only copies to other apps. See BROWSER.md for the UI and exact integration contract.
|
||||
|
||||
Android integration: APP_FILES category, granted content-directory VIEW, and GET_CONTENT with
|
||||
single/multiple MIME-filtered user-confirmed selections. System OPEN_DOCUMENT/OPEN_DOCUMENT_TREE
|
||||
are not replaced. No unrestricted storage access, editing or file deletion was added.
|
||||
|
||||
Shared core: browser data models, directory-reader boundary, filename filtering/sorting, MIME
|
||||
matching, selection validation and streaming copy budgets. Android UI/provider code stays separate.
|
||||
|
||||
Validation: 112 offline core/JVM checks passed; 44 Kotlin/Gradle-Kotlin sources parsed without syntax
|
||||
errors; source/XML invariants passed. Real Android build failed before compilation at wrapper DNS
|
||||
resolution. Twelve Android instrumentation tests are included but unexecuted. No APK is included.
|
||||
|
||||
Build on the same network-capable JDK 17 / SDK 36 machine or runner described in BUILD.md, then execute
|
||||
BROWSER_ACCEPTANCE.md. No GitHub or Gitea connection, repository upload or remote CI run was performed.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Fileworkbench 0.2.3 — complete diagnostic source release
|
||||
|
||||
This is a full source project with the previously separate SAF-D1 changes already
|
||||
integrated. It is not a patch and does not require an existing checkout.
|
||||
|
||||
## Included changes
|
||||
|
||||
- Preserve all 0.2.2 modules, browser/viewer features, native iOS-facing core,
|
||||
fixtures, earlier fixes and regression tests.
|
||||
- Integrate the SAF-D1 null-cursor diagnostic and its updated 16-case host harness.
|
||||
- Display `Fileworkbench 0.2.3 · Browser diagnostics SAF-D1` in the Locations footer.
|
||||
- Set Android versionName to 0.2.3 and versionCode to 5, retaining the package ID.
|
||||
- Pin Gradle 8.14.5 in the distribution URL, shell/PowerShell bootstraps and hashes.
|
||||
- Add checked-in JDK-17 daemon criteria. All module toolchains and bytecode targets
|
||||
use 17; the settings script rejects conflicting Gradle/JVM versions.
|
||||
- Keep the original five distinct root plugin aliases and Kotlin/AGP versions;
|
||||
add an offline source check to catch alias duplication and mismatched pins.
|
||||
- Update setup instructions for a fresh extraction and preserve historical evidence.
|
||||
|
||||
No extra permissions, cloud services, Office rendering engines, JDK, SDK, signing
|
||||
keys or compiled APK are added. The external wrapper JAR still needs first-run
|
||||
bootstrap on a network-capable machine.
|
||||
|
||||
## Folder issue status
|
||||
|
||||
The earlier nullable-`use` result bug was corrected, but the user still reports a
|
||||
listing failure. There is no device trace establishing why it persists. SAF-D1
|
||||
identifies an actual null query result separately, with provider authority, API
|
||||
level and retained-read-grant status. It does not change how files are queried or
|
||||
claim to repair provider behaviour. No paths, document IDs or contents are added
|
||||
to the diagnostic. Temporary grants remain distinct from persistent grants.
|
||||
|
||||
After building and installing, verify the 0.2.3/SAF-D1 footer, then reproduce the
|
||||
folder problem and capture the full new error. Do not clear app data for this test.
|
||||
|
||||
## Start here
|
||||
|
||||
Extract into a fresh directory, install/select JDK 17, and run the supplied wrapper
|
||||
once before IDE import. Use `docs/BUILD.md` for Windows and Linux/macOS commands.
|
||||
Do not copy old IDE/Gradle state or build configuration over the new source.
|
||||
The updated `docs/VALIDATION.md` distinguishes host tests from Android verification.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Next implementation slices
|
||||
|
||||
The source release is an MVP implementation baseline, not a declaration that every runtime gate
|
||||
has passed. Complete A before treating it as an installable/public release. Version 0.2.0 adds the requested
|
||||
lightweight browser and picker in source; this does not waive the uncompleted Android runtime gate.
|
||||
|
||||
## A — Build and Android runtime gate
|
||||
|
||||
Resolve the pinned dependencies, run unit tests/lint, build debug and instrumented APKs, and execute
|
||||
`ViewerSmokeTest` and `BrowserIntegrationTest` on an emulator/device. Test real picker and incoming VIEW/SEND flows from at least
|
||||
a file manager, a browser and a mail app. Cover current Android and API 26. Fix compilation/API
|
||||
mismatches before adding features. Record exact SDK/device/build results in `VALIDATION.md`.
|
||||
|
||||
Also execute `BROWSER_ACCEPTANCE.md`: folder grants/revocation, picker MIME/multi-selection, distinct-UID
|
||||
copy/read-only grants, local-only requests, injected restore keys, and return-to-browser lifecycle.
|
||||
|
||||
Acceptance: a real APK opens PDF, image, text, CSV and each supported Office/ODF preview; search,
|
||||
page changes, rotation, two successive incoming documents, back, save/share and cache clearing work.
|
||||
|
||||
## B — Lifecycle, storage and security hardening
|
||||
|
||||
Extend the new debug provider fixtures with doubles for non-seekable streams, unknown or dishonest sizes, revoked grants,
|
||||
slow/blocked reads and inaccessible virtual documents. Test process death, cancelled requests,
|
||||
renderer crash/hang, rapid opens, exports interrupted by a new open, memory pressure and cleanup.
|
||||
Use dedicated jobs/budgets for export/share operations and robust cancellation/worker recycling.
|
||||
Fuzz the bounded XML/ZIP/Office core and compare accepted structures with mature implementations.
|
||||
|
||||
Acceptance: no original is modified, no wrong document is exported, leaked grant/cache growth is
|
||||
bounded, malformed files fail clearly, and no failed render leaves the UI permanently busy.
|
||||
|
||||
## C — Better everyday reading
|
||||
|
||||
Apply EXIF orientation, add richer Markdown parsing without web execution, improve CSV dialect
|
||||
selection and syntax/source presentation, table navigation, reader font/spacing controls, accessibility,
|
||||
localization, and preserved reader positions. Consider safe ZIP-entry preview without extraction.
|
||||
Add PDF text extraction/search with version-aware capability detection and careful engine evaluation.
|
||||
|
||||
Acceptance: capabilities are visible per format; unsupported actions are not presented as available;
|
||||
real users can read/search without confusing raw source, cached values and graphical layout.
|
||||
|
||||
## D — Full-fidelity Office engine integration
|
||||
|
||||
Do not enlarge the hand-written preview parser into a full Office renderer. Evaluate/build a real
|
||||
LibreOfficeKit-based Android engine (or a suitable maintained alternative) against the current
|
||||
Android SDK/NDK, ABI, packaging, security and licence requirements. Pin source revisions, establish
|
||||
reproducible native builds and collect a representative Office/ODF fidelity corpus. Use native engine
|
||||
thread ownership and bounded tile/page delivery; preserve the phone UI and the shared core models.
|
||||
|
||||
Acceptance: genuine native rendering from real DOCX/XLSX/PPTX/ODF files on arm64 hardware; measured
|
||||
startup, memory, installation size and known fidelity differences; no host networking/macros/external
|
||||
resource access. Legacy DOC/XLS/PPT are enabled only when actually rendered and tested. Maintain the
|
||||
reading-preview mode as a separate labelled choice. Never ship a placeholder JNI/empty engine as support.
|
||||
|
||||
See `ENGINE_INTEGRATION.md` for the decision record.
|
||||
|
||||
## E — iOS shell
|
||||
|
||||
Build the actual common framework on compatible Mac/Xcode tooling. Add SwiftUI file UI, scoped URL
|
||||
access/bookmarks, an equivalent bounded ZIP adapter, native PDF/image handling, search, shares and
|
||||
cache cleanup. Reuse common readers instead of rewriting them in Swift.
|
||||
|
||||
Acceptance: equivalent first-file-open and expired-grant recovery; common fixture parity; framework
|
||||
and native host tests. An Android isolated service is not an available iOS feature and needs a separate
|
||||
platform security design.
|
||||
|
||||
## Later, optional
|
||||
|
||||
Annotations with a clear original/sidecar/export distinction; PDF forms; EPUB; EML; structured ICS/VCF;
|
||||
certificates/fonts; media and more archive formats; printing and conversion; simple text editing;
|
||||
then focused Office editing only when a tested engine and deliberate mobile UX exist. Storage
|
||||
connectors or broader file management must remain separate from document rendering. Collaboration/accounts
|
||||
are not necessary to achieve the original goal and are not MVP prerequisites.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Security and privacy model
|
||||
|
||||
This is an unaudited MVP source release. “No network permission” does not mean “no possible
|
||||
vulnerability,” and passing the included tests does not prove safety for arbitrary hostile files.
|
||||
|
||||
## Trust boundaries
|
||||
|
||||
An external app/provider controls document bytes, MIME type, metadata and timing. Name/MIME hints
|
||||
are not treated as proof of format. The app accepts external `content://` URIs, not arbitrary
|
||||
filesystem paths or HTTP URLs. Its own share-provider URIs are rejected as incoming documents to
|
||||
reduce confused-deputy access to private cached data.
|
||||
|
||||
Providers and the Android platform are still dependencies. A chosen cloud provider can fetch bytes
|
||||
using its own network permissions. Provider reads/queries may block; a coroutine cancellation is
|
||||
not a universal hard timeout for a malfunctioning provider.
|
||||
|
||||
## Implemented controls
|
||||
|
||||
- Main manifest requests no INTERNET, broad storage, contacts or account permissions.
|
||||
- Originals are read-only; bounded private copies provide stable/seekable inputs.
|
||||
- Known macro-enabled files and VBA packages are rejected; no formula/macro/script engine exists.
|
||||
- HTML, XML and SVG can be shown as source, never executed in a WebView.
|
||||
- The document core does not fetch embedded external resources, relationships, fonts, images or URLs.
|
||||
User-chosen provider files and opt-in provider thumbnails can involve the provider's own networking.
|
||||
- ZIP entries are never extracted to filesystem paths. Traversal/absolute/ambiguous paths and
|
||||
duplicate names are rejected; inflated bytes, entry count, ratio, cumulative expansion and CRC
|
||||
are checked when applicable. Listing does not inflate large entries.
|
||||
- XML DTD, custom/general entities and other declarations are disabled. No network/entity resolver
|
||||
exists. Nesting/node/attribute/output limits reduce denial-of-service opportunities.
|
||||
- Repeated ODF rows/cells and OOXML shared strings have output budgets.
|
||||
- PDF input/output are confined to an isolated Android service using file descriptors. The host
|
||||
accepts only dimension-checked raw ARGB output, not attacker-provided executable objects/HTML.
|
||||
- The nonexported share FileProvider exposes only `cache/shared/` and `cache/picked/`, not the app root,
|
||||
sessions, recents or saved folder grants. Outgoing intents grant individual copies read-only access.
|
||||
- Picker MIME filters are checked before selection and rechecked against fresh metadata at confirmation.
|
||||
Actual streamed byte counts enforce per-file and aggregate budgets regardless of declared sizes.
|
||||
- LOCAL_ONLY picker requests delegate to Android with the same constraints rather than query arbitrary
|
||||
saved provider folders. No URI supplied in incoming picker extras is automatically selected/read.
|
||||
- Exported activity intents are sanitized before ViewModel creation to prevent caller-supplied saved-state
|
||||
keys such as sourceUri/browser.selection from seeding previews or selections using existing grants.
|
||||
- Document-history grant cleanup is separate from bookmark tree-grant cleanup. Originals are never deleted.
|
||||
- Provider query jobs have a bounded worker pool, CancellationSignal, and an awaiting timeout. Listings,
|
||||
labels, thumbnails, selections and copied bytes have explicit bounds. No recursive scan occurs.
|
||||
- Active preview snapshots are tracked across concurrent viewer/picker activities; one viewer's startup
|
||||
no longer unconditionally deletes another viewer's live input file.
|
||||
- Backup and device-transfer exclusions are configured. No content/URI logging or analytics calls
|
||||
are present in application source.
|
||||
|
||||
## Explicit remaining work
|
||||
|
||||
Native image decoding is not separately sandboxed. The common XML/Office code is a limited custom
|
||||
parser, not independently audited. Complete namespace/schema semantics are not checked. Unicode
|
||||
and malformed-package fuzzing needs broader coverage. Rich Office rendering will substantially
|
||||
increase the native attack surface and requires a fresh review.
|
||||
|
||||
Android compilation, IPC, lifecycle, provider behaviour, device-memory pressure, process death,
|
||||
export races, accessibility and actual permission/backup behaviour have not been runtime-tested
|
||||
here. The PDF timeout handles the awaiting client; it is not a proof of immediate worker termination
|
||||
on every device or cancellation path. Run the acceptance checks before ordinary distribution.
|
||||
|
||||
There is no application-level encryption, master password or secure-window mode. File names/URIs
|
||||
in recents can be sensitive. Displayed content can appear in screenshots/app-switcher previews.
|
||||
Copying selected text and explicit shares pass content to platform/recipient surfaces. Cache cleanup
|
||||
is not cryptographic erasure. Uninstall behaviour, device backups and rooted-device access are not
|
||||
security guarantees provided by this project.
|
||||
|
||||
Dependencies are directly version-pinned but no resolved dependency verification manifest, SBOM or
|
||||
transitive lockfile is included. Generate and review these after a real build. Release signing keys
|
||||
must be created and controlled by the project owner, not bundled into this repository.
|
||||
|
||||
## Before sensitive production use
|
||||
|
||||
Require successful Android tests on at least the oldest supported API and a current device, a
|
||||
reviewed hostile-document corpus, memory/timeout tests, IPC failure tests, dependency scanning,
|
||||
licence review and an external security review appropriate to the intended users.
|
||||
|
||||
## Browser-specific residual risks
|
||||
|
||||
FileProvider/intent grants, actual Android SavedStateHandle behaviour, external caller access, and
|
||||
provider query cancellation have not been device-verified. Same-UID fixture tests are not proof of
|
||||
cross-UID grant isolation. Copied content can change between metadata retrieval and streaming; the
|
||||
copy is bounded, not a transactional snapshot of an arbitrary external provider. MIME is metadata,
|
||||
not proof of safe bytes. Native thumbnail/image decoding needs independent hostile-input review.
|
||||
|
||||
Temporary GET_CONTENT copies are for prompt import, not permanent links. Explicit cache clearing,
|
||||
Android cache eviction or expiry can invalidate them. A receiver cannot write the source document.
|
||||
No immutable/versioned cloud snapshot semantics are claimed. Shared MIME metadata is private,
|
||||
bounded and cleared alongside snapshots. Persistent browser bookmarks contain sensitive names/URIs.
|
||||
|
||||
A debug-only DocumentsProvider supplies synthetic fixture files for tests. It is protected by the
|
||||
system document-provider permission and is absent from release builds; it never serves user storage.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Primary technical references
|
||||
|
||||
Consulted for design/tooling verification on 2026-09-23. These references explain platform APIs and
|
||||
upstream architecture; they do not verify this project's Android/iOS runtime behaviour.
|
||||
|
||||
- Android Storage Access Framework: user-selected provider files, grants, metadata, byte streams,
|
||||
and special handling required for virtual documents.
|
||||
https://developer.android.com/training/data-storage/shared/documents-files
|
||||
- Android PdfRenderer: seekable descriptors, lifecycle and recommendation to use an isolated process
|
||||
for untrusted input.
|
||||
https://developer.android.com/reference/android/graphics/pdf/PdfRenderer
|
||||
- Kotlin Multiplatform overview and platform-independent source sets.
|
||||
https://kotlinlang.org/docs/multiplatform/get-started.html
|
||||
https://kotlinlang.org/docs/multiplatform/multiplatform-discover-project.html
|
||||
- Kotlin Multiplatform compatibility table: Kotlin 2.2.21, Gradle, AGP and Xcode ranges.
|
||||
https://kotlinlang.org/docs/multiplatform/multiplatform-compatibility-guide.html
|
||||
- Android Gradle Plugin 8.11 release notes: Gradle 8.13, JDK 17, API 36 and Build Tools 35 baseline.
|
||||
https://developer.android.com/build/releases/agp-8-11-0-release-notes
|
||||
- Compose BOM / compiler configuration.
|
||||
https://developer.android.com/develop/ui/compose/bom
|
||||
https://developer.android.com/jetpack/androidx/releases/compose
|
||||
https://developer.android.com/jetpack/androidx/releases/compose-kotlin
|
||||
- Gradle official distribution and wrapper checksums (8.13 entries).
|
||||
https://gradle.org/release-checksums/
|
||||
- LibreOffice Android architecture and bootstrapping notes: real native engine, LOK thread and tiles.
|
||||
https://raw.githubusercontent.com/LibreOffice/core/master/android/README.md
|
||||
|
||||
Versions in this project are pinned development choices, not “latest” recommendations. Check current
|
||||
compatibility/security information before upgrading or distributing. The new preview parsers were
|
||||
written for this project; they are neither upstream LibreOffice components nor standards-certified
|
||||
implementations.
|
||||
|
||||
## Browser and picker implementation references (reviewed 2026-09-23)
|
||||
|
||||
- Android SAF folder access and persistable grants: https://developer.android.com/training/data-storage/shared/documents-files
|
||||
- Android 11 directory restrictions: https://developer.android.com/about/versions/11/privacy/storage
|
||||
- Intent categories / GET_CONTENT / LOCAL_ONLY / multi-result contracts: https://developer.android.com/reference/android/content/Intent
|
||||
- System-owned OPEN_DOCUMENT: https://developer.android.com/guide/components/intents-common#Storage
|
||||
- FileProvider sharing and temporary URI grants: https://developer.android.com/training/secure-file-sharing/share-file
|
||||
- Provider metadata / thumbnails: https://developer.android.com/reference/android/provider/DocumentsContract
|
||||
- Root capabilities / LOCAL_ONLY: https://developer.android.com/reference/android/provider/DocumentsContract.Root
|
||||
- AndroidX ComponentActivity lifecycle / ViewModel default arguments (inspect the pinned dependency version): https://developer.android.com/reference/androidx/activity/ComponentActivity
|
||||
|
||||
These references establish API contracts, not proof that this source has passed a real Android build.
|
||||
|
||||
## 0.2.3 tooling update (verified 2026-09-24)
|
||||
|
||||
The current project changes the previous Gradle 8.13 baseline to the user's requested
|
||||
8.14.5 while retaining JDK 17, Kotlin 2.2.21 and AGP 8.11.1.
|
||||
|
||||
- Official 8.14.5 binary distribution and wrapper JAR SHA-256 hashes:
|
||||
https://gradle.org/release-checksums/
|
||||
- Gradle 8.14.5 release:
|
||||
https://docs.gradle.org/8.14.5/release-notes.html
|
||||
- Daemon JVM criteria (`toolchainVersion=17`), local detection and precedence:
|
||||
https://docs.gradle.org/current/userguide/gradle_daemon.html
|
||||
|
||||
Official documentation and published hashes were accessible through web retrieval;
|
||||
the local build runtime could not resolve the download host. These references do
|
||||
not establish that the project has compiled or that the folder issue is fixed.
|
||||
@@ -0,0 +1,3 @@
|
||||
PASS project invariants: XML, permission-free manifest, isolated PDF service, narrow FileProvider, portable core, no WebView/log calls.
|
||||
PASS browser source invariants: APP_FILES/GET_CONTENT, standard picker activity, no system-picker impersonation, narrow copied paths, read-only results, sanitized activity defaults, debug fixture excluded from main manifest.
|
||||
Kotlin PSI syntax: 44 files, 0 parse errors. Android/Compose type resolution is NOT tested.
|
||||
@@ -0,0 +1,114 @@
|
||||
Prepared 112 production-core/JVM checks.
|
||||
PASS CoreTests.pdfMagicWinsOverName
|
||||
PASS CoreTests.imageMagicWinsOverName
|
||||
PASS CoreTests.caseInsensitiveExtensions
|
||||
PASS CoreTests.mimeFallbackHandlesMissingExtension
|
||||
PASS CoreTests.htmlAndSvgAreOnlyText
|
||||
PASS CoreTests.unknownBinaryIsNotAssumedText
|
||||
PASS CoreTests.legacyBinaryDetected
|
||||
PASS CoreTests.zipMagicDisallowsTextPretence
|
||||
PASS CoreTests.extensionlessOfficeInspected
|
||||
PASS CoreTests.utf8BomAndUnicode
|
||||
PASS CoreTests.utf16LittleEndian
|
||||
PASS CoreTests.utf16BigEndian
|
||||
PASS CoreTests.utf16RejectsOddBytes
|
||||
PASS CoreTests.utf16RejectsLoneSurrogate
|
||||
PASS CoreTests.invalidUtf8Rejected
|
||||
PASS CoreTests.nulBinaryRejected
|
||||
PASS CoreTests.xmlPreservesMixedContent
|
||||
PASS CoreTests.xmlDecodesSafeEntities
|
||||
PASS CoreTests.xmlCdataIsLiteral
|
||||
PASS CoreTests.xmlRejectsDtd
|
||||
PASS CoreTests.xmlRejectsCustomEntity
|
||||
PASS CoreTests.xmlRejectsMismatchedTags
|
||||
PASS CoreTests.xmlRejectsMultipleRoots
|
||||
PASS CoreTests.xmlRejectsDeepNesting
|
||||
PASS CoreTests.xmlRejectsTooManyNodes
|
||||
PASS CoreTests.xmlRejectsTooManyAttributes
|
||||
PASS CoreTests.xmlRejectsDuplicateAttribute
|
||||
PASS CoreTests.xmlRejectsUnquotedAttribute
|
||||
PASS CoreTests.xmlRejectsInvalidCharacterReference
|
||||
PASS CoreTests.packagePathsStayInsideRoot
|
||||
PASS CoreTests.packageRootEscapeRejected
|
||||
PASS CoreTests.externalRelationshipsRejected
|
||||
PASS CoreTests.csvQuotedDelimitersAndNewlines
|
||||
PASS CoreTests.csvEscapedQuotes
|
||||
PASS CoreTests.csvEmptyTrailingCells
|
||||
PASS CoreTests.csvWindowsNewlines
|
||||
PASS CoreTests.csvSemicolonDetection
|
||||
PASS CoreTests.csvFormulaRemainsLiteral
|
||||
PASS CoreTests.csvBadQuotesFailClearly
|
||||
PASS CoreTests.csvRowLimit
|
||||
PASS CoreTests.csvColumnLimit
|
||||
PASS CoreTests.basicMarkdownHasHeadingsAndCode
|
||||
PASS CoreTests.textByteLimit
|
||||
PASS CoreTests.columnLabelsBeyondZ
|
||||
PASS CoreTests.docxReadingTextAndTables
|
||||
PASS CoreTests.xlsxSparseCellsAndSharedStrings
|
||||
PASS CoreTests.xlsxTooWideRejected
|
||||
PASS CoreTests.pptxFollowsRelationshipOrderNotFilenames
|
||||
PASS CoreTests.odtMixedTextSpacesAndHeadings
|
||||
PASS CoreTests.odsRepeatedPaddingDoesNotExplode
|
||||
PASS CoreTests.odsRepeatedNonemptyRowsRejected
|
||||
PASS CoreTests.odpSlideNamesPreserved
|
||||
PASS CoreTests.malformedOfficePartRejected
|
||||
PASS CoreTests.zipIndexDoesNotReadEntries
|
||||
PASS CoreTests.literalSearchAndHitCap
|
||||
PASS CoreTests.tableSearchUsesActualRows
|
||||
PASS CoreTests.textExportContainsSectionNamesAndValues
|
||||
PASS CoreTests.repeatedContentAmplificationBounded
|
||||
PASS CoreTests.veryLongSingleBlockRejected
|
||||
PASS CoreTests.macroExtensionsNotReclassifiedAsZip
|
||||
PASS CoreTests.renamedMacroPackageRejected
|
||||
PASS CoreTests.publicCoreFacadeReadsText
|
||||
PASS CoreTests.publicCoreFacadeRejectsBinary
|
||||
PASS BrowserTests.directoriesRemainFirstWhenDescending
|
||||
PASS BrowserTests.filtersNamesCaseInsensitively
|
||||
PASS BrowserTests.searchIsLiteralNotRegex
|
||||
PASS BrowserTests.hidesDotFilesUnlessEnabled
|
||||
PASS BrowserTests.unknownSizesRemainLastInBothDirections
|
||||
PASS BrowserTests.modifiedSortingUsesTimestamps
|
||||
PASS BrowserTests.typeSortingIsDeterministic
|
||||
PASS BrowserTests.duplicateProviderRowsDoNotDuplicateComposeKeys
|
||||
PASS BrowserTests.listingProcessingHasAnUpperBound
|
||||
PASS BrowserTests.labelsStripSpoofingButLeaveIdsUntouched
|
||||
PASS BrowserTests.labelsAreBoundedAndNonempty
|
||||
PASS BrowserTests.mimeMatchingHandlesWildcardsAndParameters
|
||||
PASS BrowserTests.malformedMimeRequestsFailClosed
|
||||
PASS BrowserTests.wildcardIsNotAnActualFileMimeType
|
||||
PASS BrowserTests.extraMimeTypesNarrowTheBaseType
|
||||
PASS BrowserTests.excessiveMimeTypesAreRejected
|
||||
PASS BrowserTests.foldersStayNavigableInFilteredPicker
|
||||
PASS BrowserTests.directoriesAndVirtualDocumentsCannotBeReturned
|
||||
PASS BrowserTests.singleSelectionReplacesPreviousSelection
|
||||
PASS BrowserTests.togglingSelectedFileRemovesIt
|
||||
PASS BrowserTests.multipleSelectionIsBounded
|
||||
PASS BrowserTests.unsupportedSelectionIsRejected
|
||||
PASS BrowserTests.emptySelectionIsRejected
|
||||
PASS BrowserTests.duplicateSelectionIsRejected
|
||||
PASS BrowserTests.singleRequestCannotReturnTwoFiles
|
||||
PASS BrowserTests.individualSizeBudgetIsChecked
|
||||
PASS BrowserTests.aggregateSizeBudgetIsChecked
|
||||
PASS BrowserTests.negativeSizeIsNotTrusted
|
||||
PASS BrowserTests.unknownSizesAreAllowedPendingStreamingLimit
|
||||
PASS BrowserTests.comparatorRemainsAntisymmetric
|
||||
PASS BrowserTests.actualStreamBytesAreLimitedEvenWithUnknownMetadata
|
||||
PASS BrowserTests.totalByteBudgetSpansFiles
|
||||
PASS BrowserTests.newFileResetsOnlyTheFileCounter
|
||||
PASS BrowserTests.zeroByteFilesAreAllowed
|
||||
PASS BrowserTests.negativeCopyCountsAreRejected
|
||||
PASS BrowserTests.byteCountersRejectHugeChunksWithoutOverflow
|
||||
PASS BrowserTests.invalidCopyBudgetsAreRejected
|
||||
PASS ZipTests.archiveRoundtrip
|
||||
PASS ZipTests.traversalRejected
|
||||
PASS ZipTests.absolutePathsRejected
|
||||
PASS ZipTests.backslashPathsRejected
|
||||
PASS ZipTests.tooManyEntriesRejected
|
||||
PASS ZipTests.oversizedEntryRejectedOnlyWhenRead
|
||||
PASS ZipTests.compressionBombRejected
|
||||
PASS ZipTests.cumulativeExpansionBudgetEnforced
|
||||
PASS ZipTests.repeatedReadsConsumeBudget
|
||||
PASS ZipTests.extensionlessOfficeLoadedThroughFullAdapter
|
||||
PASS ZipTests.textByteBudgetEnforcedBeforeRead
|
||||
PASS ZipTests.htmlRemainsInertSource
|
||||
RESULT: 112/112 checks passed
|
||||
@@ -0,0 +1,75 @@
|
||||
# Validation report — Fileworkbench source release 0.2.0
|
||||
|
||||
Date: 2026-09-23. This is an implemented-source release, **not a verified Android APK**.
|
||||
|
||||
## Executed
|
||||
|
||||
| Check | Result | Scope |
|
||||
|---|---|---|
|
||||
| Offline production core/JVM compilation and checks | **112 / 112 passed** | Kotlin/JVM 1.9.0, OpenJDK 21.0.11; not the pinned Android toolchain |
|
||||
| Android resource/manifest XML and source invariants | Passed | Source-level checks only |
|
||||
| Kotlin PSI parse | **44 files; 0 parse errors** | Syntax only; does not resolve Android/Compose/Gradle symbols |
|
||||
| Android build entry point `./scripts/verify-android.sh` | **Failed at wrapper download, exit 6** | DNS lookup for raw.githubusercontent.com failed before Android compilation |
|
||||
| APK generation | **No APK produced** | No Android SDK/Gradle dependencies/emulator available |
|
||||
|
||||
The 112 checks comprise the retained 75 document-core/JVM checks plus 37 new browser/MIME/selection/
|
||||
byte-budget checks. The offline runner invokes the same unit-test method bodies used by the Gradle
|
||||
suite after removing only test-runner annotations/imports. It does not substitute for a resolved
|
||||
Gradle build, Android instrumentation, or Apple framework compilation.
|
||||
|
||||
Executed commands:
|
||||
|
||||
```sh
|
||||
./scripts/test-core.sh
|
||||
python3 scripts/check-project.py
|
||||
./scripts/check-kotlin-syntax.sh
|
||||
./scripts/verify-android.sh
|
||||
```
|
||||
|
||||
Raw evidence is in `TEST_RESULTS.txt`, `SOURCE_CHECKS.txt`, and `BUILD_ATTEMPT.txt`. The actual build
|
||||
attempt reached project source checks and the Gradle wrapper bootstrap, then failed with:
|
||||
|
||||
```text
|
||||
curl: (6) Could not resolve host: raw.githubusercontent.com
|
||||
```
|
||||
|
||||
No Android compiler, D8, lint, packaging, installation, or emulator ran after that failure.
|
||||
|
||||
## What the structural checks establish
|
||||
|
||||
The main manifest is parseable and declares no uses-permission entries; the PDF service is marked
|
||||
isolated/nonexported; the production FileProvider is nonexported and its allowlist contains only
|
||||
shared/ and picked/ caches. APP_FILES and a separate standard-launch GET_CONTENT activity are
|
||||
registered, and no activity declares OPEN_DOCUMENT/OPEN_DOCUMENT_TREE. The debug fixture provider
|
||||
is absent from the main manifest. Source assertions check read-only copied-result flags, use of
|
||||
streaming byte budgets, and intent sanitization before activity ViewModel creation.
|
||||
|
||||
Common Kotlin files have no Android/Java imports. There is no WebView instantiation or Log call in
|
||||
main Android source. These are useful invariants, **not** runtime grant-isolation or security proofs.
|
||||
|
||||
## Written but not executed
|
||||
|
||||
There are **12 Android instrumentation test methods**: the two retained/extended viewer smoke tests
|
||||
and ten browser/picker integration tests. They cover UI entry points, PDF IPC, synthetic SAF metadata,
|
||||
large/nested folders, copied MIME metadata, single/multiple result structure, intent registration,
|
||||
LOCAL_ONLY/MIME forwarding, virtual documents, and rejection of caller-supplied restore-state keys.
|
||||
|
||||
These Android tests have not been compiled, installed or run. The debug-only synthetic document
|
||||
provider is test support, not a user-storage provider. Same-UID result checks cannot establish
|
||||
cross-UID grants; a separate caller app is explicitly required by `BROWSER_ACCEPTANCE.md`.
|
||||
|
||||
## Remaining verification gates
|
||||
|
||||
- Resolve pinned Gradle/Kotlin/Android dependencies under JDK 17; compile, lint, D8 and assemble both
|
||||
debug APKs. Fix actual compiler/API/packaging errors before claiming an installable release.
|
||||
- Run instrumented tests and `BROWSER_ACCEPTANCE.md` on real devices/emulators, including API 26,
|
||||
Android 11+ storage restrictions, a current phone, and an independent result-receiving app.
|
||||
- Verify preview-to-browser navigation/scroll, rotation/process restoration, separate picker/viewer
|
||||
lifetimes, folder grant renewal/removal, cache/URI invalidation, and existing preview regressions.
|
||||
- Verify cancellation with slow/unresponsive providers, truthful and dishonest sizes, truncated
|
||||
listings, malformed documents, thumbnail decoding, memory pressure and copied-file limits.
|
||||
- Verify accessibility, large fonts, portrait/landscape layouts, and actual visual rendering.
|
||||
- Build the Apple framework and implement/test its native browser/file-access adapters separately.
|
||||
|
||||
No device screenshots, performance measurements, whole-phone storage access, perfect Office
|
||||
rendering, security audit, iOS app, or successfully executed CI build are claimed by this release.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Validation report — Fileworkbench source release 0.2.1
|
||||
|
||||
Date: 2026-09-24. **Source hotfix, not a verified Android APK.**
|
||||
|
||||
## Change scope
|
||||
|
||||
Corrected the invalid `PdfDocument().use` call in the Android instrumentation smoke
|
||||
test. Added explicit page/document/client cleanup and unique temporary-file cleanup.
|
||||
No production feature or dependency changes. Updated version metadata and documentation,
|
||||
and added a narrow source-pattern regression guard. Details: `HOTFIX-0.2.1.md`.
|
||||
|
||||
## Executed in this session
|
||||
|
||||
| Check | Result | Boundary |
|
||||
|---|---|---|
|
||||
| Portable core/JVM checks | **112/112 passed** | Kotlin/JVM 1.9.0, JDK 21.0.11; not pinned Android/Gradle toolchain |
|
||||
| Kotlin PSI syntax | **44 files, zero parse errors** | Does not resolve Android or Compose types |
|
||||
| XML and source invariants | Passed | Includes guard against the exact invalid constructor/use expression |
|
||||
| Regression guard positive/negative check | Passed | Rejects original source; accepts fixed source; not type checking |
|
||||
| One-file patch application and reversal | Passed | Exact original 0.2.0 ViewerSmokeTest; output matches supplied fixed file |
|
||||
| `:androidApp:compileDebugAndroidTestKotlin` retry | **Blocked before compilation** | Gradle-wrapper download failed at DNS resolution; exit 6 |
|
||||
| APK / installation / device tests | **Not produced / not run** | No Android SDK or emulator installed here |
|
||||
|
||||
The new source guard catches the reported expression only; it does not establish
|
||||
that arbitrary `use` calls have valid receivers. No Android build is claimed.
|
||||
|
||||
Raw evidence is under `validation/0.2.1/`. Historical 0.2.0 evidence remains in its
|
||||
original files; the previous report is preserved as `VALIDATION-0.2.0.md`.
|
||||
|
||||
The attempted Android command was:
|
||||
|
||||
```sh
|
||||
./gradlew --no-daemon --console=plain :androidApp:compileDebugAndroidTestKotlin
|
||||
```
|
||||
|
||||
The failure was:
|
||||
|
||||
```text
|
||||
curl: (6) Could not resolve host: raw.githubusercontent.com
|
||||
```
|
||||
|
||||
## Still required on a configured build machine
|
||||
|
||||
Retry instrumentation compilation, then assemble the debug app and test APKs, run
|
||||
unit tests and lint, and run instrumented tests on a phone or emulator. Follow the
|
||||
existing `BROWSER_ACCEPTANCE.md` checklist. iOS framework/application verification
|
||||
is unchanged and remains outstanding.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Validation report — Fileworkbench source release 0.2.2
|
||||
|
||||
Date: 2026-09-24. **Folder-listing source hotfix; not a verified new Android APK.**
|
||||
|
||||
The user reported a successful local build of the preceding source, then a runtime
|
||||
failure when adding a folder: "The storage provider did not return a folder listing."
|
||||
The local device, provider and Android version have not been inspected here.
|
||||
|
||||
## Reproduced cause
|
||||
|
||||
`SafBrowserStore.list` used `query(...)?.use { ... } ?: throw ...`. The final
|
||||
expression inside `use` was a nullable read of `DocumentsContract.EXTRA_ERROR`.
|
||||
When the provider returned a valid cursor without that optional message, the block
|
||||
returned null and the trailing Elvis operator raised the no-listing exception,
|
||||
even after successfully reading all rows. Empty directories had the same issue.
|
||||
|
||||
The fix checks the query result for null **before** entering `use`. A genuinely
|
||||
missing cursor still raises the original exception. Cursor closure, row limits,
|
||||
loading/info/error messages, URI checks and permission handling are unchanged.
|
||||
Only `SafBrowserStore.kt` needs to change to apply the runtime correction.
|
||||
|
||||
## Executed checks
|
||||
|
||||
| Check | Result | Boundary |
|
||||
|---|---|---|
|
||||
| Extracted original production listing block | **9 failures / 14 checks** | Reproduces the exact reported exception on valid cursors without EXTRA_ERROR |
|
||||
| Extracted fixed production listing block | **14/14 passed** | JVM control-flow/resource checks using real common models and Android test doubles |
|
||||
| Shared-core/JVM adapter suite | **112/112 passed** | Local Kotlin/JVM compiler, not the Android/Gradle toolchain |
|
||||
| Kotlin PSI syntax | **44 files, zero parse errors** | Syntax only; no Android/Compose type resolution |
|
||||
| XML and existing source invariants | Passed | No new storage permissions or dependencies |
|
||||
| One-file patch | Applies to both shipped 0.2.0 and 0.2.1 source | Exact checked archive baselines, not an unseen locally modified checkout |
|
||||
| New Android instrumentation tests | **5 added; not run here** | Empty, info-only, loading with/without cached rows, and provider-error listings |
|
||||
| Android APK/test-APK build attempt | **Blocked before compilation** | Gradle-wrapper bootstrap cannot resolve raw.githubusercontent.com; exit 6 |
|
||||
| Phone/emulator/iOS checks | Not performed here | No APK or device-test result supplied |
|
||||
|
||||
The host harness copies the cursor-reading block directly out of the selected
|
||||
production source and compiles it with the real common models. The resolver,
|
||||
cursor, extras, cancellation signal and URI builder are lightweight test doubles.
|
||||
It does **not** test Android API compatibility, Binder, real permissions, provider
|
||||
behaviour, asynchronous scheduling, or the screen. It is not a substitute for the
|
||||
instrumentation tests or the local Android build.
|
||||
|
||||
Raw logs are in `validation/0.2.2/`. The preceding report is preserved as
|
||||
`VALIDATION-0.2.1.md`; older logs retain their original provenance.
|
||||
|
||||
## Reproduce the host checks
|
||||
|
||||
With a local Kotlin compiler, Java and Python 3:
|
||||
|
||||
```sh
|
||||
python3 scripts/test-folder-listing.py
|
||||
bash scripts/test-core.sh
|
||||
bash scripts/check-kotlin-syntax.sh
|
||||
python3 scripts/check-project.py
|
||||
```
|
||||
|
||||
To demonstrate the regression against the preceding file, pass its actual local
|
||||
path to `python3 scripts/test-folder-listing.py --source /path/to/old/SafBrowserStore.kt`.
|
||||
That command should fail on the old implementation; the failure is expected.
|
||||
|
||||
## Local Android acceptance
|
||||
|
||||
Keep the local Gradle/JDK configuration that already builds. Apply the one-file fix,
|
||||
then rebuild and install over the existing debug app using the same signing key:
|
||||
|
||||
```powershell
|
||||
.\gradlew.bat :androidApp:assembleDebug :androidApp:assembleDebugAndroidTest
|
||||
adb install -r .\androidApp\build\outputs\apk\debug\androidApp-debug.apk
|
||||
```
|
||||
|
||||
Open the saved folder and refresh, or select it again if its grant was temporary.
|
||||
Check a normal folder, an empty folder, a nested folder and a document preview.
|
||||
With the complete updated source and a device/emulator, run
|
||||
`:androidApp:connectedDebugAndroidTest` to exercise the Android regression cases.
|
||||
The separate broader browser checklist remains in `BROWSER_ACCEPTANCE.md`.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Validation — Fileworkbench 0.2.3
|
||||
|
||||
Date: 2026-09-24. **Complete diagnostic source release; no verified APK supplied.**
|
||||
|
||||
## Scope and provenance
|
||||
|
||||
The shipped 0.2.2 source ZIP was extracted into a fresh working directory. The
|
||||
previously supplied SAF-D1 diagnostic changes were applied there and incorporated
|
||||
into this complete project. The current source retains the earlier PDF lifecycle
|
||||
and nullable-cursor-result corrections. Gradle is pinned to 8.14.5; the daemon,
|
||||
module toolchains and bytecode targets select 17. Root plugin aliases are distinct.
|
||||
|
||||
The user still reports the folder-listing failure. No subsequent device trace has
|
||||
established its cause. **This release instruments that failure; it does not claim
|
||||
to fix it.** A versioned footer identifies the diagnostic source, and a genuinely
|
||||
null query cursor has the distinct `[SAF-D1/NULL_CURSOR]` message.
|
||||
|
||||
## Executed checks for this source
|
||||
|
||||
| Check | Result | Boundary |
|
||||
|---|---|---|
|
||||
| Common core + JVM adapter suite | **112/112 passed** | Local Kotlin compiler; not Gradle/Android compilation |
|
||||
| Actual production listing block + diagnostic harness | **16/16 passed** | Real shared models and Android test doubles; no Binder/real provider/permission validation |
|
||||
| Build-configuration invariants | **18 passed** | Static pins/aliases/hashes/markers, not Gradle plugin resolution |
|
||||
| Build-guard regression cases | **8/8 passed** | Accepts the baseline with local SDK/IDE files; rejects wrong Gradle/JVM, duplicate plugin aliases/IDs and stale shell/Windows bootstrap tags |
|
||||
| Kotlin PSI syntax | **44 files, zero parse errors** | Includes Kotlin source and Gradle Kotlin scripts; no Android/Compose/Gradle type resolution |
|
||||
| Existing project/XML/security invariants | Passed | Permission-free main manifest, isolated PDF service, read-only picker boundaries, portable core, prior PDF regression guard |
|
||||
| Shell syntax | Passed | Bash/sh syntax only; PowerShell not executed |
|
||||
| Android build attempt | **Blocked before Gradle/Android compilation** | Wrapper fetch: `curl: (6) Could not resolve host: raw.githubusercontent.com` |
|
||||
| Device/emulator tests | Not executed here | The test sources are retained; no device is attached |
|
||||
| iOS build/tests | Not executed here | Core architecture retained; no iOS shell added |
|
||||
|
||||
The local host has **OpenJDK 21.0.11 and kotlinc 1.9.0**, not JDK 17 or the
|
||||
project's Kotlin 2.2.21 toolchain. The host harnesses now emit JVM-17 bytecode but
|
||||
run on that local JVM 21. **Do not interpret their results as a JDK-17 Android
|
||||
build.** The actual Gradle configuration requests an installed JDK 17; no JDK-21
|
||||
fallback has been added to the project. No Android SDK or downloaded wrapper is
|
||||
available in this authoring runtime.
|
||||
|
||||
The full Android command was attempted using the newly pinned launcher. Its
|
||||
bootstrap failed before the settings guards or compiler could run. No APK or
|
||||
successful Gradle sync is claimed. The network-dependent bootstrap fallback and
|
||||
Windows PowerShell bootstrap remain unexecuted here.
|
||||
|
||||
Raw evidence for this release is under `validation/0.2.3/`. Previous results retain
|
||||
their versioned paths; the old report is preserved as `VALIDATION-0.2.2.md`. Old
|
||||
`BUILD_ATTEMPT.txt`/test outputs elsewhere in docs are historical, not new results.
|
||||
|
||||
## Packaging checks
|
||||
|
||||
The full archive is assembled from this source tree, excluding `out`, build caches,
|
||||
local IDE/SDK state, keystores and generated test JARs. Prior source modules,
|
||||
resources, fixtures, licence, documentation and examples remain present. It ships
|
||||
no APK, wrapper JAR, JDK or Android SDK. The first wrapper invocation bootstraps the
|
||||
verified external JAR. See `SOURCE-MANIFEST.sha256` for per-file integrity hashes
|
||||
and `validation/0.2.3/archive-checks.txt` for extraction/integrity checks.
|
||||
|
||||
## Reproduce host checks
|
||||
|
||||
```sh
|
||||
python3 scripts/check-project.py
|
||||
python3 scripts/test-build-config.py
|
||||
bash scripts/test-core.sh
|
||||
python3 scripts/test-folder-listing.py
|
||||
bash scripts/check-kotlin-syntax.sh
|
||||
```
|
||||
|
||||
## Next local acceptance step
|
||||
|
||||
Follow `BUILD.md`: fresh extraction, installed JDK 17, wrapper Gradle 8.14.5,
|
||||
SDK 36, then tests/assembly/lint. Install the resulting APK with the existing
|
||||
signing identity, confirm **Fileworkbench 0.2.3 · Browser diagnostics SAF-D1** in
|
||||
Browse → Locations, and reproduce the folder problem. Preserve the existing app
|
||||
data. Capture the full new error instead of inferring permission or provider
|
||||
failure from the earlier ambiguous wording.
|
||||
Reference in New Issue
Block a user