# 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] re-verified 2026-10-15.** `bundleRelease` and `assembleRelease` both succeed with R8 minification and resource shrinking on. - `app-release.aab` is **4 836 490 bytes** (`sha256 0c0702d3917e9b94f42dabfce8002cae6b9377ee4b25d054bf97f73b4c3a8652`). - `app-release.apk` is **2 155 563 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. - The figures moved from the first measurement (4 682 814 / 2 081 955) because of the LZMA library behind `.xz` and 7z. The permission set did **not** change: the share target, the file picker and the trash index all arrived without a single new permission, which is the point of having designed them that way. - ⚠️ **Re-measure after any further change.** These numbers are a record of a verified artefact, not a constant — a stale hash here is worse than no hash, because it looks checked. - [ ] Upload the **`.aab`** (not the APK) to the Play Console. ⚠️ **Expect a native-debug-symbols warning and ignore it.** Play finds `base/lib//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 .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). - [ ] The app now keeps a small local SQLite index of staged deletes (the trash), so that a deletion can be undone after a restart. It never leaves the device, so **"collects nothing" is still the honest answer** — but if a form asks whether the app *stores* anything, the truthful answer is "on the device only, and it is the user's own file paths". [PRIVACY.md](../PRIVACY.md) should say so explicitly. - [ ] 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 the screenshots. - [ ] Upload the screenshots from `store-assets/screenshots/`: **phone** 1080×2160 (5), **7-inch tablet** 1200×1920 (4), **10-inch tablet** 2560×1600 (4). Play wants 4+ per large-screen form factor. All three sets clear its rules — 24-bit PNG with no alpha, shorter side ≥ 320, longer side ≤ 3840, and longer side ≤ 2× the shorter side. `tools/make-store-screenshots.sh` enforces the aspect ratio and rejects a capture whose app area is blank. - [ ] Expect the 7-inch set to be single-pane and the 10-inch set two-pane. That is the adaptive layout working, not an inconsistency — see the CHANGELOG. - [ ] The committed sets show browsing, a folder list, an image preview and storage usage. **None of them show search, undo, the trash or receiving a share** — four features the app now has. The existing shots stay accurate (no layout changed), so this is an opportunity rather than a defect: two or three more captures would let the listing advertise them. See `tools/make-store-screenshots.sh`. ## 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.** Play's own wording: *"You can change your app from paid to free. Once your app has been offered for free, the app can't be changed to paid."* So: 1. Create a **payments profile** (identity, tax, bank details). 2. *Products › App pricing*: base price **€4.49**. 3. Launch price **€2.29** for the first **14 days**, then diarise changing it back. 4. Do every step above **before uploading**. - [x] **[code] no Billing client and no `BILLING` permission** — the price lives on the listing, not in the build, so the APK contains no purchase code at all. Verified 2026-10-14 against the release APK. Full rationale and the rejected alternatives: [MONETISATION](MONETISATION.md).