1# Android document-provider capability lab
2
3Two **test-only** APKs: a disposable `DocumentsProvider` and a separate-UID
4instrumentation client. No product app, notebook backend, JNI, Gradle, or product UI.
5Install only on an owned emulator; the exported controller intentionally grants,
6revokes, and changes synthetic fixtures without authentication.
7
8Build with an installed SDK and JDK (no downloads or global changes):
9
10```sh
11nice -n 10 python3 tools/android/provider-lab/build.py \
12 --sdk /path/to/sdk --java-home /path/to/jdk \
13 --output /absolute/private/build
14python3 tools/android/provider-lab/run.py \
15 --adb /path/to/sdk/platform-tools/adb --serial emulator-5556 \
16 --build /absolute/private/build --output /absolute/private/run
17```
18
19Defaults are SDK/build-tools 36, with API 26 as the lab APK minimum. This does
20not decide Snowbound's minimum Android version. Output directories must be new.
21The runner refuses physical devices, installs the two packages, runs bounded
22instrumentation, force-stops the client between phases, then uninstalls both.
23The caller owns emulator creation, launch, and cleanup. Evidence stays private.
24
25The provider also serves a second root, "Disposable lab notebook": the folder
26`files/notebook` in its own storage, documents named by their paths, for trying the
27Snowbound app against a provider by hand. Fill it with a disposable notebook pushed to
28`/data/local/tmp` and copied in with `adb shell run-as net.paperclover.snowbound.providerlab`.
29The controller's `offline` fails its queries and opens too, and `revoke-notebook` revokes
30grants to it.
31
32A test-only provider Activity returns an Intent with prefix and persistable URI
33grant flags; the client Activity persists only its actual returned read/write
34flags. This substitutes for a picker result. This does **not** test
35`ACTION_OPEN_DOCUMENT_TREE` UI. All data is synthetic bytes, not `.one` files.
36
37| Experiment | Assertion |
38| --- | --- |
39| Separate UID / tree grant | Ungranted read refused; read-only grant cannot widen to write; read/write grant persisted; available in a fresh client process; provider revocation refuses further reads and removes persisted grant. |
40| Opaque IDs / metadata | Slash and non-ASCII identities survive contract URI construction; null size and timestamp remain unknown. |
41| Local `rw` descriptor | Actual seek, partial overwrite, fsync, close and reopen retain the expected bytes. |
42| Stream `r` / `w` | Actual pipes reject seek/fsync; `rw` is rejected; bytes round-trip after a bounded provider completion event. |
43| Reliable peer failure | Provider `closeWithError` reaches client `checkError`. |
44| Rename | Returned document identity changes; old URI fails; fresh listing contains the returned identity and bytes remain intact. |
45| Delayed visibility | Writer's actual `rw` write/fsync/close/reopen succeed while the observer still reads the old bytes. Only an explicit provider publication event advances the observer. |
46| Availability | Injected offline open fails while the persisted grant remains; restoring availability restores the bytes. |
47
48The fixture assigns duplicate display names; the probe does not compare their
49display-name columns.
50
51Provider events are explicit barriers, not timing guesses. Pipe workers have a
52bounded completion wait; every external build/runtime command has a timeout.
53Reports contain actual operations, exceptions, fixture byte hashes, emulator
54fingerprint, process IDs, and APK/source hashes. A successful local fsync or
55close is **not a remote upload receipt**. This synthetic provider does not prove
56commercial-provider behavior, remote durability, file locking, atomicity, CAS,
57cloud-conflict recovery, or `.snowbound/` protocol correctness.
58
59Android contracts:
60[ContentResolver modes](https://developer.android.com/reference/android/content/ContentResolver#openFileDescriptor(android.net.Uri,%20java.lang.String)),
61[DocumentsProvider](https://developer.android.com/reference/android/provider/DocumentsProvider),
62[rename identity](https://developer.android.com/reference/android/provider/DocumentsContract#renameDocument(android.content.ContentResolver,%20android.net.Uri,%20java.lang.String)),
63[descriptor errors](https://developer.android.com/reference/android/os/ParcelFileDescriptor#checkError()).