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

6.2 KiB

Architecture

Boundaries

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.