| 1 | # Android document-provider capability lab |
| 2 | |
| 3 | Two **test-only** APKs: a disposable `DocumentsProvider` and a separate-UID |
| 4 | instrumentation client. No product app, notebook backend, JNI, Gradle, or product UI. |
| 5 | Install only on an owned emulator; the exported controller intentionally grants, |
| 6 | revokes, and changes synthetic fixtures without authentication. |
| 7 | |
| 8 | Build with an installed SDK and JDK (no downloads or global changes): |
| 9 | |
| 10 | ```sh |
| 11 | nice -n 10 python3 tools/android/provider-lab/build.py \ |
| 12 | --sdk /path/to/sdk --java-home /path/to/jdk \ |
| 13 | --output /absolute/private/build |
| 14 | python3 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 | |
| 19 | Defaults are SDK/build-tools 36, with API 26 as the lab APK minimum. This does |
| 20 | not decide Snowbound's minimum Android version. Output directories must be new. |
| 21 | The runner refuses physical devices, installs the two packages, runs bounded |
| 22 | instrumentation, force-stops the client between phases, then uninstalls both. |
| 23 | The caller owns emulator creation, launch, and cleanup. Evidence stays private. |
| 24 | |
| 25 | The 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 |
| 27 | Snowbound 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`. |
| 29 | The controller's `offline` fails its queries and opens too, and `revoke-notebook` revokes |
| 30 | grants to it. |
| 31 | |
| 32 | A test-only provider Activity returns an Intent with prefix and persistable URI |
| 33 | grant flags; the client Activity persists only its actual returned read/write |
| 34 | flags. 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 | |
| 48 | The fixture assigns duplicate display names; the probe does not compare their |
| 49 | display-name columns. |
| 50 | |
| 51 | Provider events are explicit barriers, not timing guesses. Pipe workers have a |
| 52 | bounded completion wait; every external build/runtime command has a timeout. |
| 53 | Reports contain actual operations, exceptions, fixture byte hashes, emulator |
| 54 | fingerprint, process IDs, and APK/source hashes. A successful local fsync or |
| 55 | close is **not a remote upload receipt**. This synthetic provider does not prove |
| 56 | commercial-provider behavior, remote durability, file locking, atomicity, CAS, |
| 57 | cloud-conflict recovery, or `.snowbound/` protocol correctness. |
| 58 | |
| 59 | Android 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()). |