Adds an opt-in, off-by-default toggle to include app data in system Android Backup (Auto Backup / cloud + device-to-device transfer). When enabled, only re-creatable user preferences are backed up; secrets and the re-syncable mail cache are never included.
How
allowBackup is a manifest flag, not a runtime toggle, so a BackupAgent enforces the runtime opt-in:
AndroidManifest.xml — allowBackup="true", fullBackupOnly="true", backupAgent=".backup.LibreMailBackupAgent", plus fullBackupContent for API 29–30 alongside the existing dataExtractionRules (API 31+).
LibreMailBackupAgent (BackupAgentHelper) — onFullBackup calls superonly when the user has opted in; otherwise it ships nothing. The flag is read straight from the shared settingsDataStore singleton (fail-safe to "off"), so it doesn't depend on Hilt or Application.onCreate in the framework's restricted backup mode. Sharing the one DataStore delegate avoids a "multiple DataStores active for the same file" crash.
data_extraction_rules.xml / backup_rules.xml — strict <include> allowlists containing only datastore/libremail_settings.preferences_pb. Everything else is excluded by omission (Android lint's FullBackupContent rule forbids <exclude> paths outside an <include>).
Settings — new includeInBackup preference + a "Backup" section with F-Droid-honest copy (off by default; states it uses Google infrastructure and that mail/accounts/passwords/keys are never backed up). The setter nudges BackupManager.dataChanged() so the change takes effect on the next backup pass.
BackupPolicy — single source of truth for the eligible/excluded paths.
What is NOT backed up (excluded by not being on the allowlist)
datastore/libremail_dbkey.preferences_pb — the Keystore-sealed SQLCipher passphrase (device-bound; ciphertext is useless off-device).
libremail.db (+ -wal/-shm/-journal) — encrypted IMAP/OAuth credentials and the mail cache (re-downloads on next sync).
Testing
Fast CI gate green: assembleDebug, testDebugUnitTest, lintDebug, ktlintCheck, detekt (JDK 21).
New JVM unit tests:
BackupPolicyTest — opt-in default is off; shouldBackUp only true when enabled; secret paths are on the excluded lists.
DataExtractionRulesTest — parses the shipped data_extraction_rules.xml (cloud-backup + device-transfer) and backup_rules.xml and asserts the include set is exactly the settings DataStore and that the DB key / credentials DB are never eligible — so the rules can't silently drift.
On-device backup/restore was verified by inspection only (no emulator in this environment): the manifest + both XML allowlists include only the settings DataStore and exclude the DB key and credentials DB. Left to CI/manual.
Closes #21.
## What
Adds an **opt-in, off-by-default** toggle to include app data in system Android Backup (Auto Backup / cloud + device-to-device transfer). When enabled, **only re-creatable user preferences** are backed up; secrets and the re-syncable mail cache are never included.
## How
`allowBackup` is a manifest flag, not a runtime toggle, so a `BackupAgent` enforces the runtime opt-in:
- **`AndroidManifest.xml`** — `allowBackup="true"`, `fullBackupOnly="true"`, `backupAgent=".backup.LibreMailBackupAgent"`, plus `fullBackupContent` for API 29–30 alongside the existing `dataExtractionRules` (API 31+).
- **`LibreMailBackupAgent`** (`BackupAgentHelper`) — `onFullBackup` calls `super` **only when the user has opted in**; otherwise it ships nothing. The flag is read straight from the shared `settingsDataStore` singleton (fail-safe to "off"), so it doesn't depend on Hilt or `Application.onCreate` in the framework's restricted backup mode. Sharing the one DataStore delegate avoids a "multiple DataStores active for the same file" crash.
- **`data_extraction_rules.xml` / `backup_rules.xml`** — strict `<include>` allowlists containing only `datastore/libremail_settings.preferences_pb`. Everything else is excluded by omission (Android lint's `FullBackupContent` rule forbids `<exclude>` paths outside an `<include>`).
- **Settings** — new `includeInBackup` preference + a "Backup" section with F-Droid-honest copy (off by default; states it uses Google infrastructure and that mail/accounts/passwords/keys are never backed up). The setter nudges `BackupManager.dataChanged()` so the change takes effect on the next backup pass.
- **`BackupPolicy`** — single source of truth for the eligible/excluded paths.
## What is NOT backed up (excluded by not being on the allowlist)
- `datastore/libremail_dbkey.preferences_pb` — the Keystore-sealed SQLCipher passphrase (device-bound; ciphertext is useless off-device).
- `libremail.db` (+ `-wal`/`-shm`/`-journal`) — encrypted IMAP/OAuth credentials and the mail cache (re-downloads on next sync).
## Testing
- Fast CI gate green: `assembleDebug`, `testDebugUnitTest`, `lintDebug`, `ktlintCheck`, `detekt` (JDK 21).
- New JVM unit tests:
- `BackupPolicyTest` — opt-in default is off; `shouldBackUp` only true when enabled; secret paths are on the excluded lists.
- `DataExtractionRulesTest` — parses the shipped `data_extraction_rules.xml` (cloud-backup + device-transfer) and `backup_rules.xml` and asserts the include set is **exactly** the settings DataStore and that the DB key / credentials DB are never eligible — so the rules can't silently drift.
- On-device backup/restore was verified by inspection only (no emulator in this environment): the manifest + both XML allowlists include only the settings DataStore and exclude the DB key and credentials DB. Left to CI/manual.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #21.
What
Adds an opt-in, off-by-default toggle to include app data in system Android Backup (Auto Backup / cloud + device-to-device transfer). When enabled, only re-creatable user preferences are backed up; secrets and the re-syncable mail cache are never included.
How
allowBackupis a manifest flag, not a runtime toggle, so aBackupAgentenforces the runtime opt-in:AndroidManifest.xml—allowBackup="true",fullBackupOnly="true",backupAgent=".backup.LibreMailBackupAgent", plusfullBackupContentfor API 29–30 alongside the existingdataExtractionRules(API 31+).LibreMailBackupAgent(BackupAgentHelper) —onFullBackupcallssuperonly when the user has opted in; otherwise it ships nothing. The flag is read straight from the sharedsettingsDataStoresingleton (fail-safe to "off"), so it doesn't depend on Hilt orApplication.onCreatein the framework's restricted backup mode. Sharing the one DataStore delegate avoids a "multiple DataStores active for the same file" crash.data_extraction_rules.xml/backup_rules.xml— strict<include>allowlists containing onlydatastore/libremail_settings.preferences_pb. Everything else is excluded by omission (Android lint'sFullBackupContentrule forbids<exclude>paths outside an<include>).includeInBackuppreference + a "Backup" section with F-Droid-honest copy (off by default; states it uses Google infrastructure and that mail/accounts/passwords/keys are never backed up). The setter nudgesBackupManager.dataChanged()so the change takes effect on the next backup pass.BackupPolicy— single source of truth for the eligible/excluded paths.What is NOT backed up (excluded by not being on the allowlist)
datastore/libremail_dbkey.preferences_pb— the Keystore-sealed SQLCipher passphrase (device-bound; ciphertext is useless off-device).libremail.db(+-wal/-shm/-journal) — encrypted IMAP/OAuth credentials and the mail cache (re-downloads on next sync).Testing
assembleDebug,testDebugUnitTest,lintDebug,ktlintCheck,detekt(JDK 21).BackupPolicyTest— opt-in default is off;shouldBackUponly true when enabled; secret paths are on the excluded lists.DataExtractionRulesTest— parses the shippeddata_extraction_rules.xml(cloud-backup + device-transfer) andbackup_rules.xmland asserts the include set is exactly the settings DataStore and that the DB key / credentials DB are never eligible — so the rules can't silently drift.🤖 Generated with Claude Code