docs(android): plan M8 — Google Play

Records where the Play Console stands (7 of 11 setup tasks done), the
org lead's three decisions of 2026-10-23 (reuse the release keystore as
Play's app-signing key; CI publishes each v* tag to the internal track;
demo.runicgateway.com on the Oxide rig is the review and beta site,
website#222), what Play needs that the app lacks (target API 36, an AAB,
the upload step), the order of work, and three open questions (content
rating vs. no report/block, the data-safety premise, the dataSync push
service).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-23 03:38:08 -05:00
parent d32e443b16
commit 243d719d1c

View File

@@ -923,6 +923,71 @@ push, and Play (M6–M8) follow the designed app.
its `NTFY_*` deploy config (§13).
9. **M8 — Google Play**: Play Console listing, signing/upload key, and (optionally) an FCM build flavor
— after the direct-APK release is stable.
**Planned 2026-10-23**, after v0.6.0. The direct APK stays the default channel (§10); Play is
added beside it, not instead of it.
**Where the console stands (read 2026-10-23).** The app `com.runicgateway.app` exists as a
draft on a **personal** developer account. 7 of 11 setup tasks are done: privacy policy
(runicgateway.com `/privacy/`), ads (none), content rating, government, financial, health, and
category + contact. Still open: sign-in details, target audience, data safety (started), the
advertising ID declaration, and the store listing. No build has been uploaded yet.
**Decisions (org lead, 2026-10-23):**
- **Signing: reuse the existing release keystore as Play's app-signing key.** It is uploaded at
the first release with Play's "use my own key" option, through Google's encrypted export
tool. So a Play install and a sideloaded APK from Gitea have the same signature, and a user
can move between them without uninstalling. This cannot be changed after the first upload.
- **CI publishes every `v*` tag to the internal track.** Promotion to closed testing, and later
to production, is a click in the console, so a tag never reaches testers or review on its
own.
- **The review and beta site is `demo.runicgateway.com`**, a dedicated Runic Gateway deployment
on the Oxide Rust rig. Google's reviewers and the closed-test testers sign in there. It is
runicgateway.com's D27 demo, given a host. The website changes it needs (demo accounts, a
lock on breaking settings, a periodic wipe) are their own feature: `RunicGateway/website#222`.
**What Play requires that the app does not do today:**
- **Target API 36.** Since 2026-08-31, new apps and updates must target Android 16 (API 36).
The app targets 35. That means `compileSdk`/`targetSdk` 36, an AGP bump (8.6.1 does not
support 36), the SDK packages in both workflows, and a check of Android 16's behaviour
changes on a device.
- **An app bundle.** Play takes an AAB, not an APK. `release.yml` keeps building the signed
APK for the Gitea release and adds `bundleRelease`, signed with the same keystore.
- **The upload.** After the Gitea release, a step in `release.yml` uploads the AAB to the
internal track through the Google Play Developer API (`edits` → bundle upload → track →
commit). It authenticates with a service-account key in a new secret,
`PLAY_SERVICE_ACCOUNT_JSON`, the account having release rights on this app only. The step
uses `curl` + `jq` + `openssl` (for the JWT), in the style of the existing Gitea release step,
and adds no Gradle plugin. Gradle Play Publisher is the alternative if the script proves
brittle. **Google requires the first bundle to be uploaded by hand in the console**; CI owns
every upload after it. A Play failure fails the run *after* the Gitea release exists, so
the APK channel never waits on Google.
**Order of work:**
1. **App + CI** (one PR into `edge`): API 36 + AGP bump, AAB, Play upload step. Walked on the
emulator and the S22 before the PR.
2. **First bundle**, uploaded by hand from the next tag, with the signing key enrolled.
3. **Console declarations**: target audience 18+ (runicgateway.com D31), advertising ID none,
data safety from runicgateway.com's generated `PLAY_DATA_SAFETY.md`, and sign-in details
pointing at `demo.runicgateway.com` with a reviewer account. Then the store listing.
4. **Closed test**, once the demo site is up: a personal account needs **12 testers opted in
for 14 days in a row** before it can apply for production. runicgateway.com `/beta`
collects them.
**Open, each waiting for a decision:**
- **Content rating.** The questionnaire answered "Social or Communication: moderated chat;
users or user-generated content can be reported". The app shows other players' chat (the
Rust feed) but has no report or block action, and Play's user-generated-content policy
expects both where that is declared. Either retake the questionnaire, or add report/block
to the app.
- **Data safety's premise.** The generated notes answer "not collected" almost everywhere,
because the app talks only to a deployment the user chose. The notes call it the answer
most likely to be questioned in review. Declaring user IDs and messages as collected,
encrypted in transit and needed for the app to work, is the conservative alternative.
- **The push foreground service.** Play asks for a declaration (and a video) for the
`dataSync` service that holds the ntfy connection. Google reserves `dataSync` for transfers,
and Android 15 limits it to six hours a day. This brings forward §13's "FCM flavor for
Play?" question.
10. **M9 — Native SSO login** (post-v1, additive; independent of M8): in-app "Sign in with Google /
Discord" via the **Mobile SSO Authorization Bridge** (§4.2). **Backend-first**, mirroring M7's
split: