Repo-actionable deliverables for the Google Play compliance work (issue #17), every claim verified against the code and the built release artifacts: - PRIVACY.md: user-facing privacy policy (device-local mail cache, optional SQLCipher encryption, traffic only to the user's own mail provider, on-device-only contacts autocomplete, strictly local opt-in debug reports, no ads/analytics/tracking SDKs). - docs/play-data-safety.md: Play Data safety questionnaire mapping -- answer 'no data collected/shared' with per-category code evidence, the policy exemptions relied on, a dependency audit, and a conservative fallback. - docs/play-permissions.md: merged-manifest permission audit (incl. the WorkManager-injected WAKE_LOCK / RECEIVE_BOOT_COMPLETED) with paste-ready Console justifications for READ_CONTACTS, POST_NOTIFICATIONS, and the FOREGROUND_SERVICE_DATA_SYNC declaration + demo-video script. - docs/play-compliance.md: verified targetSdk 37 (requirement: 35+), 16 KB page-size compliance (all packaged .so PT_LOAD p_align=0x4000, incl. sqlcipher-android 4.16.0), bundleRelease AAB check, the Gmail-app-password / no-CASA OAuth note, the console-steps checklist with drafted content-rating and listing answers, and repo findings (push-mail default vs docs, README minSdk/app-lock drift, debug-key release fallback). - README.md: link PRIVACY.md and note the no-Google-OAuth/no-CASA status (fuller README pass stays issue #20). Part of #17. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.4 KiB
Google Play Data safety form — mapping (issue #17)
Fill-in guide for Play Console → App content → Data safety. Every answer below is grounded
in this repository's code; re-verify against source if the data flows change. Companion docs:
PRIVACY.md (the policy to link in the form),
play-permissions.md, play-compliance.md.
How Play defines "collection", and why LibreMail declares none
Play's definition (Play Console Help — Provide information for Google Play's Data safety section): "Collect" means transmitting data from your app off a user's device, with exemptions that do not need to be disclosed:
- On-device access/processing — data "only processed locally on the user's device and not sent off device".
- End-to-end encryption — data unreadable by anyone other than sender and recipient.
- Ephemeral processing — data held in memory and "retained for no longer than necessary to service a specific request in real time".
LibreMail's data flows fall under exemptions 1 and 3:
- The developer operates no servers and receives no user data. There is no analytics,
crash-reporting, or ad SDK in the dependency tree (see audit below), and the only
developer-directed channel that exists in code — opt-in debug-report upload
(
app/src/main/kotlin/org/libremail/reporting/ReportUploadWorker.kt) — is dead in shipped builds becauseDEBUG_REPORT_ENDPOINTdefaults to""(app/build.gradle.kts), which makes the upload worker fail without transmitting. - All other traffic is the app doing its job as the user's mail agent against servers the user chose (their own IMAP/SMTP provider; Microsoft's OAuth/Graph endpoints for the Outlook account type). Each transfer services a specific user request in real time (sign-in, sync, send, server search); the app retains nothing off-device and the developer can never access any of it.
The same reasoning is the established practice of comparable open-source mail clients on Play that declare no collection. If a Play reviewer pushes back, use the conservative alternative at the bottom of this page — it is also truthful.
Form answers
| Form question | Answer |
|---|---|
| Does your app collect or share any of the required user data types? | No |
| Is all of the user data collected by your app encrypted in transit? | Not asked when "No" above; for the record: yes, all connections are TLS (ImapClient.kt, SmtpSender.kt set ssl.checkserveridentity=true; MailSecurity.NONE is not offered in the UI — ManualSetupScreen.kt:211) |
| Do you provide a way for users to request that their data is deleted? | Not asked when "No" above; see account-deletion notes in play-compliance.md |
| Privacy policy URL | https://github.com/JMR-dev/LibreMail/blob/main/PRIVACY.md |
Result shown on the store listing: "No data collected" / "No data shared with third parties".
Category-by-category evidence
Every Play data-safety category, the truthful answer, and where the code proves it:
| Play category | Collected? | Shared? | Evidence in code |
|---|---|---|---|
| Personal info → Name | No | No | Account display name stored in local Room DB only (data/local/entity/AccountEntity via AccountRepositoryImpl.kt); appears off-device only inside mail the user sends |
| Personal info → Email address | No | No | The user's own address is their mail login, sent only to their chosen provider to authenticate/send (ImapClient.kt, SmtpSender.kt, GraphSender.kt) — user-initiated, real-time, never to the developer |
| Personal info → User IDs | No | No | No developer-side accounts or IDs exist; OAuth tokens go only between the device and login.microsoftonline.com (auth/OutlookAuthManager.kt) |
| Financial info / Health / Location | No | No | No such APIs or permissions anywhere in the merged manifest (see play-permissions.md) |
| Messages → Emails | No | No | Mail syncs from the user's server to the device (MailSyncer), is cached locally (Room, optional SQLCipher — di/DatabaseModule.kt), and is transmitted only when the user sends a message to their own SMTP/Graph endpoint |
| Photos and videos / Audio files / Files and docs | No | No | Attachments are chosen via the system document picker (ComposeScreen.kt OpenMultipleDocuments, no storage permission), stored under cacheDir (MailRepositoryImpl.kt:361), and leave the device only inside mail the user sends |
| Calendar | No | No | No calendar API usage |
| Contacts | No | No | contacts/ContactsRepository.kt queries ContactsContract on-device for ≤8 autocomplete matches; results are held in memory for the compose screen. Nothing is uploaded — Play's on-device exemption applies |
| App activity (interactions, search history, installed apps) | No | No | No analytics SDK; server search sends the query string to the user's own IMAP server as an IMAP SEARCH command (user-initiated, ephemeral) |
| Web browsing | No | No | The reader WebView has JavaScript disabled and network loads blocked unless the user enables remote images (ui/reader/HtmlBody.kt:62,98) — and even then requests go to hosts referenced by the email, not to the developer |
| App info and performance (crash logs, diagnostics) | No | No | Crash/debug reports are written to local app storage only (reporting/ReportStore.kt → filesDir/debug_reports); upload requires an explicit user tap and a configured endpoint, and the endpoint is empty in this repo (ReportSubmitter.isEnabled → false) |
| Device or other IDs | No | No | No advertising ID (no AD_ID permission in the merged manifest), no device-ID reads; debug reports include only Build.MANUFACTURER/MODEL/OS version, and stay on device (reporting/DiagnosticsCollector.kt) |
Dependency audit (no ads / analytics / tracking SDKs)
The complete runtime dependency list (app/build.gradle.kts + gradle/libs.versions.toml) is:
AndroidX (core, lifecycle, activity, navigation, webkit, Compose BOM, Room, DataStore,
WorkManager, Hilt-androidx), Dagger Hilt, kotlinx-coroutines, Eclipse Angus Mail (IMAP/SMTP),
AppAuth-Android (OAuth), and Zetetic SQLCipher. There is no Google Play Services, Firebase,
ad, analytics, or crash-reporting dependency, and the merged release manifest contains no
com.google.android.gms.permission.AD_ID permission (verified in
app/build/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml).
Security-practices section of the form
- Encrypted in transit: yes — TLS everywhere, hostname verification pinned on
(
mail.<proto>.ssl.checkserveridentity=trueinImapClient.kt:480/SmtpSender.kt:50). - Encryption at rest (optional extra credit, not a form field): credentials are always
encrypted with an Android Keystore key (
data/security/KeystoreCrypto.kt,CredentialStore.kt); the mail cache can be SQLCipher-encrypted with a Keystore-sealed random key (data/security/DatabaseKeyStore.kt, opt-in, default off —SettingsRepository.kt:44). - Independent security review badge: not requested (optional program).
If anything changes, this form must change
| Future change | Data-safety impact |
|---|---|
Configuring a real DEBUG_REPORT_ENDPOINT (issue #34) |
Declare App info and performance → Crash logs / Diagnostics: collected, optional (user-initiated), not shared, encrypted in transit, user can delete (reports are deletable pre-submit) |
| Any opt-in telemetry from issues #10/#11 | Declare the specific types as collected + optional; backlog decision requires it stay strictly opt-in (F-Droid constraint) |
| Any new SDK with network access | Re-run this audit; SDKs count toward the form ("data transmitted by libraries/SDKs") |
Conservative alternative declaration (only if Google rejects "no collection")
Declare the following, all with Collected: yes · Optional: no · Shared: no · Processed ephemerally: yes · Purpose: App functionality · Encrypted in transit: yes · Deletion: user can delete data in-app (remove account):
- Personal info → Email address (account sign-in)
- Messages → Emails (sending/syncing the user's own mail with their provider)
Contacts, crash logs, and diagnostics remain not collected under any reading — they demonstrably never leave the device in this codebase.