M6: release signing, privacy policy and Play release checklist
This commit is contained in:
@@ -12,6 +12,15 @@ upload needs a **higher `versionCode`** than every previous upload.
|
||||
|
||||
### Added
|
||||
|
||||
- **M6 — release config and Play readiness.** An upload keystore (4096-bit RSA,
|
||||
`CN=Hadrian Burkhardt, L=Potsdam, C=DE`) is generated and wired through the git-ignored
|
||||
`keystore.properties`, so `bundleRelease` produces a signed, R8-minified, resource-shrunk
|
||||
`.aab` (4 682 814 bytes) and `assembleRelease` a signed APK. `docs/PLAY_RELEASE_CHECKLIST.md`
|
||||
walks the Play Console work, with step 4 covering the **Permissions Declaration Form** for
|
||||
"All files access" — the declaration a file manager must make, and the one that can block
|
||||
a release. `PRIVACY.md` is written to match the shipped permissions: no collection, no
|
||||
sharing, no `INTERNET`. The shipped permission set is audited from the release APK.
|
||||
|
||||
- **M5 — analysis, archives and viewers.** `domain/UsageAnalysis` rolls up recursive folder
|
||||
sizes and finds the largest files below a folder, which the new storage-usage screen
|
||||
shows as a size and a share bar per row. `domain/ArchiveEngine` creates ZIP archives and
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# Privacy Policy — File Manager
|
||||
|
||||
_Last updated: 2026-10-14_
|
||||
|
||||
## Summary
|
||||
|
||||
File Manager lets you browse and manage the files **on your own device**, entirely
|
||||
offline. The app does **not** collect, transmit, sell or share any of your data. It has no
|
||||
network access at all: it declares no `INTERNET` permission and contains no analytics, no
|
||||
advertising and no crash-reporting SDK. It is sold once through Google Play; the purchase
|
||||
is handled entirely by Google Play, never by the app (see [Purchases](#purchases)).
|
||||
|
||||
## What the app accesses
|
||||
|
||||
| Data | Why | Where it goes |
|
||||
|---|---|---|
|
||||
| Your files and folders | To show them and to carry out the actions you ask for (open, copy, move, rename, delete, compress, extract) | Never leaves your device |
|
||||
| File metadata (name, size, folder, modified date) | To sort, search, measure storage usage and show details | Never leaves your device |
|
||||
| The folder you grant (only in the fallback mode) | To browse a folder you picked yourself, when "All files access" is declined | A permission reference is stored by Android on this device |
|
||||
|
||||
## What the app does NOT do
|
||||
|
||||
- It does **not** have the `INTERNET` permission, so it cannot send data anywhere.
|
||||
- It does **not** collect analytics, crash reports, advertising identifiers or location
|
||||
data.
|
||||
- It does **not** contain advertising SDKs or third-party trackers.
|
||||
- It does **not** create accounts and does **not** ask for your name, email or phone
|
||||
number.
|
||||
- It does **not** upload, back up or sync your files.
|
||||
|
||||
## Permissions and what they mean
|
||||
|
||||
| Permission | Purpose |
|
||||
|---|---|
|
||||
| `MANAGE_EXTERNAL_STORAGE` ("All files access") | Required for the app's core purpose: browsing and managing files anywhere on the device. Google Play only allows this permission for apps whose core purpose needs it — file managers are one of the accepted cases — and the app must be granted it by you in Android Settings. If you decline, the app still works, but only in a folder you pick. |
|
||||
| Read media (`READ_MEDIA_IMAGES` / `READ_MEDIA_VIDEO` / `READ_MEDIA_AUDIO`, `READ_EXTERNAL_STORAGE` on older Android) | To show thumbnails and to open images, audio and video. |
|
||||
| `READ_MEDIA_VISUAL_USER_SELECTED` | Supports Android 14's "selected photos only" partial access. |
|
||||
| `WRITE_EXTERNAL_STORAGE` (Android 10 only) | Used only to write files you ask for on that older Android version. |
|
||||
| Notifications (`POST_NOTIFICATIONS`) | Optional. Shows progress while a long copy, move, delete or extraction is running. Denying it changes nothing except that the progress bar is not shown. |
|
||||
| Foreground service (`FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_DATA_SYNC`) | Keeps a long file operation running while the app is in the background, so a move is not cut in half. |
|
||||
|
||||
## Deleting files
|
||||
|
||||
Files are deleted only when you ask. Deleting through the app asks for confirmation first.
|
||||
Where Android provides its own delete dialog for media files, the app hands the request to
|
||||
the system and Android performs the deletion; otherwise the app deletes the files you
|
||||
selected directly. The app never deletes anything you did not select.
|
||||
|
||||
## Data retention and deletion
|
||||
|
||||
The app stores a small amount of data in its own private storage: your view preferences
|
||||
(list or grid, sort order), any folder permission you granted through the system picker,
|
||||
and nothing else. This data never leaves your device. Uninstalling the app, or clearing its
|
||||
storage in Android Settings, removes all of it.
|
||||
|
||||
The app does not keep copies of your files.
|
||||
|
||||
## Purchases
|
||||
|
||||
The app is **sold once through Google Play**. There is no subscription and there is nothing
|
||||
to buy inside the app.
|
||||
|
||||
Google Play processes the payment and holds the payment details. The app contains no
|
||||
payment code and never receives them, because it has no network access of any kind (see
|
||||
above). Refunds, order problems and any question about a charge are handled by Google Play,
|
||||
and the developer can be reached at the address below.
|
||||
|
||||
## Children
|
||||
|
||||
The app is a general-purpose utility and is not directed at children. It collects no
|
||||
personal data from anyone.
|
||||
|
||||
## Changes
|
||||
|
||||
If this policy changes, the updated version will be published at this address with a new
|
||||
"last updated" date.
|
||||
|
||||
## Contact
|
||||
|
||||
Questions about this policy: **softwareapp.hb@gmail.com**.
|
||||
@@ -12,10 +12,9 @@ no advertising or tracking SDKs.
|
||||
|
||||
## Status
|
||||
|
||||
**M5 — analysis, archives & viewers: done.** Recursive storage usage, ZIP creation and
|
||||
ZIP/TAR/GZ/BZ2/XZ extraction (with zip-slip protection), Coil thumbnails, an image viewer
|
||||
and a text editor. Covered by 141 JVM unit tests (26 added here). See
|
||||
[docs/ROADMAP.md](docs/ROADMAP.md) for the full milestone plan.
|
||||
**M6 — release config & Play readiness: done.** Signed, R8-minified `.aab` builds; the Play
|
||||
release checklist (including the "All files access" declaration) and the privacy policy are
|
||||
written. See [docs/ROADMAP.md](docs/ROADMAP.md) for the full milestone plan.
|
||||
|
||||
## Tech stack
|
||||
|
||||
@@ -78,6 +77,27 @@ R8/minification but produces an unsigned artefact.
|
||||
|
||||
Not tracked: `local.properties`, `keystore.properties`, `*.jks`, build outputs.
|
||||
|
||||
## Releasing
|
||||
|
||||
Release signing is driven by the git-ignored `keystore.properties` (see
|
||||
`keystore.properties.example`). Without that file the release build still runs R8 and
|
||||
resource shrinking, but produces an unsigned artefact that cannot be uploaded.
|
||||
|
||||
```bash
|
||||
./gradlew :app:bundleRelease # Play upload bundle (.aab)
|
||||
./gradlew :app:assembleRelease # APK, for local checks
|
||||
|
||||
# Audit what is actually shipped — in particular that there is no INTERNET permission:
|
||||
$ANDROID_HOME/build-tools/36.0.0/aapt2 dump permissions \
|
||||
app/build/outputs/apk/release/app-release.apk
|
||||
```
|
||||
|
||||
Read [docs/PLAY_RELEASE_CHECKLIST.md](docs/PLAY_RELEASE_CHECKLIST.md) before uploading.
|
||||
Step 4 is the one that matters most for this app: a file manager must declare **"All files
|
||||
access"** through Play's Permissions Declaration Form, and have the core purpose documented
|
||||
in its store description. [PRIVACY.md](PRIVACY.md) is written to match the shipped
|
||||
permissions.
|
||||
|
||||
## Licence
|
||||
|
||||
Proprietary — all rights reserved.
|
||||
|
||||
@@ -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.
|
||||
+1
-1
@@ -15,7 +15,7 @@ Legend: ✅ done · 🚧 in progress · ⏳ not started · 💡 candidate (propo
|
||||
| M3 | Operations engine + service | ✅ | `domain/FileOperations` composes the backend primitives into copy/move/delete/rename with recursive walking, an honest progress total, cooperative cancellation between entries and per-item failure reporting; conflicts are decided through the M1 `TransferPlanner` (`ASK` is surfaced for the UI to resolve). `service/FileOpService` is a `dataSync` foreground service mirroring progress into a notification (`FileOpNotifications` + `text/TextRes`). **99 JVM tests** total (23 added here) |
|
||||
| M4 | Compose UI | ✅ | `BrowserViewModel` + `BrowserUiState` over `StorageLocation`; browser screen with list/grid, breadcrumbs, in-folder search, a sort/hidden-files/view sheet, multi-select action bar, details sheet, and new-folder/rename/delete/conflict dialogs; a roots view and the "All files access" + SAF-folder prompts; `data/MediaStoreDeleter` routes a delete through the platform's own dialog where it can and falls back to a direct delete; a `FileProvider` backs "open with" and share. **115 JVM tests** total (16 added here) |
|
||||
| M5 | Analysis, archives, viewers | ✅ | `domain/UsageAnalysis` measures recursive folder sizes and finds the largest files, shown by a bar-per-row storage-usage screen; `domain/ArchiveEngine` creates ZIP and extracts ZIP/TAR/TAR.GZ/GZ/BZ2/XZ using `java.util.zip` + Commons Compress (no native code), with **every entry name filtered through `core/ArchivePath` against zip-slip**; a Coil-backed image viewer, thumbnails in the list and grid, a text editor, and compress/extract in the selection bar. **141 JVM tests** total (26 added here) |
|
||||
| M6 | Release config & Play readiness | ⏳ | R8/minify, optional signing, `.aab`; [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) (incl. the **Permissions Declaration Form**), [PRIVACY.md](../PRIVACY.md), README |
|
||||
| M6 | Release config & Play readiness | ✅ | Upload keystore generated (4096-bit RSA, `CN=Hadrian Burkhardt`) and wired through the git-ignored `keystore.properties`; `bundleRelease` + `assembleRelease` verified — **4 682 814-byte `.aab`** (`sha256 9014627a…`), 2 081 955-byte APK signed by the upload key, R8 mapping kept. [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) (incl. the **Permissions Declaration Form** for "All files access", the Data safety answers and the shipped-permission audit) and [PRIVACY.md](../PRIVACY.md) written for a restricted-permission app. Shipped permission set audited: **no `INTERNET`** |
|
||||
| M7 | On-device validation & hardening | ⏳ | AVD + real-device checks, per-operation error boundaries, edge-case fixtures |
|
||||
| M8 | Localisation groundwork | ⏳ | every user-visible string in `res/values/strings.xml` with real `<plurals>`; `text/` mapping |
|
||||
| M9 | Monetisation | ⏳ | **one-time paid** — price set only in the Play Console before the first upload; no Billing code and no `BILLING` permission |
|
||||
|
||||
Reference in New Issue
Block a user