M6: release signing, privacy policy and Play release checklist

This commit is contained in:
2026-10-14 16:44:58 +02:00
parent 3e39fc9b86
commit e2cfb5d9ca
5 changed files with 243 additions and 5 deletions
+129
View File
@@ -0,0 +1,129 @@
# Google Play release checklist
Work top to bottom. Items marked **[code]** are already handled in this repository; the
rest are things you must do in the Play Console or with your own accounts.
## 1. Identity and signing
- [x] **Application id** chosen: `de.softwareapp_hb.filemanager`
(`applicationId` in `app/build.gradle.kts`). It can **never be changed after the first
release**, so treat it as locked. `namespace` and the Kotlin package of the same name
are aligned with it; debug builds append `.debug`.
- [x] **[code] Upload keystore generated** — `filemanager-upload.jks` in the project root
(git-ignored, `alias upload`, 4096-bit RSA, 10 000 days, `CN=Hadrian Burkhardt,
L=Potsdam, C=DE`). Certificate SHA-256 fingerprint, to confirm the right key is
registered when Play App Signing is enabled:
`49:A9:FC:13:F5:13:24:48:AE:74:5A:AE:7E:05:D9:2F:67:55:1F:EE:71:A5:64:A1:10:13:57:4D:0E:AA:0E:4E`
- [x] **[code] `keystore.properties` written** (git-ignored, mode `600`). The Gradle build
picks it up automatically and signs release builds; without it the release APK stays
unsigned.
- [ ] **Store the keystore and its password in a password manager and back it up.** Losing
the upload key means you cannot ship updates.
- [ ] Enable **Play App Signing** in the Play Console with the first upload. Google then
holds the app signing key and this one only signs uploads:
```bash
keytool -exportcert -alias upload -keystore filemanager-upload.jks -rfc > upload_cert.pem
```
## 2. Versioning
- [x] `versionName` **[code]** — `1.0.0` in `app/build.gradle.kts`.
- [x] `versionCode` **[code]** — `1`. Play orders uploads by it, so every later upload must
be higher.
## 3. Build the release artefacts
```bash
./gradlew :app:bundleRelease # -> app/build/outputs/bundle/release/app-release.aab
./gradlew :app:assembleRelease # -> APK, for local checks
./gradlew :app:testDebugUnitTest # JVM unit tests
./gradlew :app:lintDebug # Android Lint
```
- [x] **[code] verified 2026-10-14.** `bundleRelease` and `assembleRelease` both succeed
with R8 minification and resource shrinking on.
- `app-release.aab` is **4 682 814 bytes**
(`sha256 9014627a8596397623404cb9d986af5019c5e33cf331c5d363427c140f121788`).
- `app-release.apk` is **2 081 955 bytes**, signed by the upload key above.
- Both report `versionCode=1 versionName=1.0.0 targetSdk=36` and exactly the
permission set in step 4 — **no `INTERNET`**, so "nothing leaves the device" holds
in the shipped artefact and not just in the source manifest.
- [ ] Upload the **`.aab`** (not the APK) to the Play Console.
⚠️ **Expect a native-debug-symbols warning and ignore it.** Play finds
`base/lib/<abi>/libandroidx.graphics.path.so`, which arrives transitively through
`androidx.compose.ui:ui-graphics` → `androidx.graphics:graphics-path`. It is a
recommendation, not an error: it does not block upload, review or rollout. This
project has **no** `ndk`/`externalNativeBuild`/`jniLibs` configuration and no native
sources of its own.
- [ ] Keep `app/build/outputs/mapping/release/mapping.txt` for every release so Play can
de-obfuscate crash reports.
## 4. Sensitive permissions — the Permissions Declaration Form
This is the step that can block a file manager, and the one to get right.
- [ ] In the Play Console open **App content → Sensitive app permissions** (older UI:
*Permissions Declaration Form*) and declare **All files access**
(`MANAGE_EXTERNAL_STORAGE`).
- [ ] Select the **file manager** use case. Google's policy — *Use of All files access
(MANAGE_EXTERNAL_STORAGE) permission* — names file managers explicitly:
*"intended and permitted use includes file managers, backup and restore apps,
anti-virus apps, and document management apps."*
- [ ] Make sure the **store description states the core purpose** (browsing, organising and
managing files on the device). The policy requires the core functionality to be
"prominently documented and promoted in the app's description" — the listing copy in
`docs/STORE_LISTING.md` (M10) is written to satisfy that.
- [ ] **Do not** add `QUERY_ALL_PACKAGES`, `REQUEST_INSTALL_PACKAGES` or `INTERNET` without
declaring them. The app currently requests none of them.
- [ ] Provide the **privacy policy URL** ([PRIVACY.md](../PRIVACY.md) — it must be a public
URL, not a file in the repo).
- [ ] Keep the permission set honest: declaring a permission the app does not use is itself
a policy violation.
**Verify the shipped permission set** after every release build:
```bash
$ANDROID_HOME/build-tools/36.0.0/aapt2 dump permissions \
app/build/outputs/apk/release/app-release.apk
```
Expected, and nothing else:
```
MANAGE_EXTERNAL_STORAGE
READ_EXTERNAL_STORAGE (maxSdkVersion=32)
READ_MEDIA_IMAGES
READ_MEDIA_VIDEO
READ_MEDIA_AUDIO
READ_MEDIA_VISUAL_USER_SELECTED
WRITE_EXTERNAL_STORAGE (maxSdkVersion=29)
FOREGROUND_SERVICE
FOREGROUND_SERVICE_DATA_SYNC
POST_NOTIFICATIONS
<applicationId>.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION (added by AndroidX)
```
## 5. Data safety, content rating, audience
- [ ] **Data safety** form: the app collects **nothing** and shares **nothing**. Answer "No"
to collection, "No" to sharing, and state that data is not transmitted off the device.
- [ ] Confirm the answers match [PRIVACY.md](../PRIVACY.md).
- [ ] Content rating questionnaire: utility, no user-generated content, no ads.
- [ ] Target audience: not directed at children.
## 6. Store listing
Full copy and the generated graphics land in **M10** (`docs/STORE_LISTING.md`). Console
fields needed: app name, short description, full description, app icon 512×512, feature
graphic 1024×500, and at least 4 phone screenshots (Play asks for 4+ per large-screen form
factor too).
## 7. Release tracks and pricing
- [ ] Create the app, complete *App content*, then roll out
**internal → closed → production**, checking the pre-launch report on the way.
- [ ] ⚠️ **Set the price before the first upload** (M9). Once an app has been offered for
free it can never be made paid, and this app is a **one-time paid** app: no ads, no
subscription, no in-app purchases — and therefore no Billing code and no
`com.android.vending.BILLING` permission in the APK. The price lives on the listing,
not in the build.