org.poca.pac · PAC Reader owns the provider
One read-only ContentProvider re-serves everything on a community-curated microSD card to the whole suite. This page is the complete provider reference.
Both authorities are exported but guarded by org.poca.pac.permission.READ_CARDS, a signature-level permission: only apps signed with the shared POCA key can query. Clients also declare provider visibility:
<!-- client AndroidManifest.xml --> <uses-permission android:name="org.poca.pac.permission.READ_CARDS" /> <queries> <provider android:authorities="org.poca.pac.cards" /> </queries>
The surface is read-only: insert() and delete() throw, and update() accepts exactly one operation, the culture-wheel active-design override. Clients degrade gracefully when the provider is absent or the card is unmounted (empty cursors, null).
Card content, PAC metadata, app configuration, and raw file access. /file/ and /files/ accept multi-segment paths and are routed by hand; everything else goes through the UriMatcher.
| Endpoint | Returns | Cursor columns |
|---|---|---|
| /cards | All visible cards. | CARD_COLUMNS (below) |
| /cards/{card_id} | A single card by id. | |
| /apps/{app_id} | Cards for one app. Returns nothing unless the app is queryable under its provisioning tier. | |
| /featured | Featured cards from feed-visible apps, for dashboard and widget hydration. | |
| /index | One row of PAC metadata. | schema_version (Int), pac_version, tribe_name, locale, generated_at (String), total_cards (Int) |
| /config/{app_id} | One row: the app’s provision plus its raw app_config.json. Disabled unlockable apps return nothing. | app_id, directory, provision (core|bundled|unlockable), enabled (Int 1|0), config_json (String) |
| /file/{path} | openFile() read-only stream of any file under the PAC root. Path-traversal guarded; mode must be r. | n/a (stream) |
| /files/{dir} | Directory listing cursor, sorted by name. | name, path (String), is_dir (Int 1|0), size (Long) |
Every card endpoint returns these 20 columns. Strings unless noted.
| Column | Notes |
|---|---|
| card_id | Unique id within the PAC. |
| app_id | Owning app’s lowercase slug (see directory map below). |
| type | Per-app content type. Plain string; each app defines and routes on its own vocabulary. |
| title / description | Primary and secondary display text. |
| image / image_thumbnail / image_hero | PAC-relative media paths (nullable). |
| icon | Material symbol name or SVG path (nullable). |
| label | Pill label text (nullable). |
| deep_link | poca://{app_id}/{type}/{identifier} target. |
| locale | Content locale, default en. |
| updated_at | ISO 8601 timestamp. |
| featured | Int 1|0. Dashboard hint. |
| schema_version | Int. Forward-compat versioning. |
| extensions_json | Every non-universal key of the card’s JSON, re-serialized as one JSON object. This is how app-specific fields travel. |
| card_json_path | PAC-relative path to the card’s own detail manifest (nullable). |
| content_dir_path | PAC-relative directory holding the card’s media (nullable). |
| content_collection | Derived from the directory tree (the path segment after the app’s directory), not from JSON. A grouping label, never a routing key. |
| image_uri | Ready-to-open content://org.poca.pac.cards/file/… URI for image. The only image field that works across app boundaries; null when image is blank. |
Serves the Gateway launcher’s culture wheel. Design listings return WHEEL_DESIGN_COLUMNS: id, name, wheel_pattern_uri, pattern_path, pattern_hash, active (Int 1|0).
| Endpoint | Returns |
|---|---|
| /wheel/designs | All wheel designs on the PAC. |
| /wheel/designs/{id}/pattern | The design’s pattern image, opened as a file stream. |
| /wheel/active | The active design row. Writable: update() with ContentValues {"id": designId} sets a local override; the pattern must decode as a bitmap to be accepted. |
| /wheel/active/pattern | The active design’s pattern image stream. |
Consumers should open the image via the wheel_pattern_uri column rather than constructing pattern URIs by hand.
A PAC is plain files. Four JSON documents drive everything; markdown code fences around JSON are tolerated and stripped.
{
"tribe_name": "Example Nation", // default "Unknown"
"locale": "en", // default "en"
"pac_version": "1.2.0", // default "1.0.0"
"apps": [
{ "app_id": "amoxtli", // unknown ids are skipped
"directory": "stories",
"provision": "core", // core | bundled | unlockable
"enabled": true }
],
"ota": { /* optional update channel, see OTA below */ }
}
Index keys: schema_version (default 1), pac_version, tribe_name, locale, generated_at, cards[]. Each card entry uses the universal keys from the cursor schema above; any other key is captured into extensions and surfaces through extensions_json. When the index file is missing, PAC Reader walks every card.json under each app directory, builds the index, and caches it back to the card.
Read as raw text and returned verbatim in the config_json column. The provider never parses it; each app defines its own payload (Metztli’s calendar block is the flagship example).
{
"activeDesign": "sun-spiral", // or active_design; default: first entry
"designs": [
{ "id": "sun-spiral", // required
"name": "Sun Spiral",
"pattern": "identity/sun.webp", // or pattern_path; required
"pattern_hash": "…" } // optional
]
}
Legacy filename identity/wheels.json is also accepted. Pattern files must live under identity/.
Each app id maps to a fixed directory under the PAC root. Content collections are the subdirectories beneath it.
| app_id | PAC directory | app_id | PAC directory |
|---|---|---|---|
| amoxtli | stories/ | tlalli | nature/ |
| metztli | calendar/ | cualli | health/ |
| tlato | language/ | cuilo | creativity/ |
| ollin | ceremonies/ | nahual | primal/ |
| teo | spiritual/ | cristo | bible/ |
The misspelling meztli is accepted and normalized to metztli.
/file/{path} resolves against the PAC root with a canonical-path guard (no traversal, regular readable files only) and serves these MIME types:
| Extension | MIME type |
|---|---|
| png / webp / jpg, jpeg / svg | image/png · image/webp · image/jpeg · image/svg+xml |
| mp3 / m4a, m4b / ogg / wav / flac | audio/mpeg · audio/mp4 · audio/ogg · audio/wav · audio/flac |
| json / epub / pdf | application/json · application/epub+zip · application/pdf |
| anything else | application/octet-stream |
Wheel patterns get a second guard: they must resolve under identity/. PAC Reader also drops a .nomedia marker so card media stays out of the system gallery; cross-app image loading should always go through image_uri.
The PAC decides which apps exist for its holder. Two independent flags derive from the tier: queryable (can be listed via /apps and /cards) and feed-visible (appears in /featured).
Always queryable. Feed-visible while enabled.
Always queryable. Each person can toggle it; the toggle is stored on the device, not the card, and survives card updates.
Community-managed. Until enabled it is invisible everywhere: /apps returns nothing, /config returns nothing, as if the app weren’t there.
Cards missing a config entry default to visible. Local bundled toggles fire a change notification so every consumer refreshes.
Register a ContentObserver on content://org.poca.pac.cards with descendants enabled and invalidate caches on any change. The provider notifies:
Each community can run its own signed update channel; no central store, no third-party gatekeeper. The channel is declared in poca_config.json under ota: base_url, community_id, channel (default production), signing_public_key, optional enrollment_code.
| HTTP endpoint | Purpose |
|---|---|
| POST /v1/device/enroll | One-time-code device enrollment; returns device credentials (stored encrypted on device). |
| GET /v1/device/channels/{channel}/latest | Latest release id for the channel. |
| GET /v1/device/releases/{id}/manifest | The signed release manifest. |
| GET /v1/device/blobs/{sha256} | Content blobs, resumable via HTTP Range. |
Manifest (protocol_version 1): release_id, community_id, channel, key_id, min_pac_reader_version, files[] {path, sha256, size, content_type}, optional tombstones[] (deletions), signature. Verification: canonical sorted-key JSON signed with Ed25519; per-file SHA-256; duplicate and unsafe paths rejected (no absolute paths, no dot segments, .ota/ reserved). Files stage under .ota/{release}/staging and apply as a journaled transaction with rollback; poca_config.json and poca_cards_index.json apply last. The worker runs every 6 hours on unmetered connections, then reloads the reader and notifies observers.