Files
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

102 lines
6.2 KiB
Markdown

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