Loading .gitlab-ci.yml +3 −12 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:master" variables: APK_PATH: "app/k9mail/build/outputs/apk/release" UNSIGNED_APK: "Mail-unsigned.apk" UNSIGNED_APK: "k9mail-release-unsigned.apk" COMMUNITY_APK: "Mail-community.apk" OFFICIAL_APK: "Mail-official.apk" TEST_APK: "Mail-test.apk" Loading @@ -29,19 +29,10 @@ test: build: stage: build script: - ./gradlew :app:k9mail:build - cd app/k9mail/build/outputs/apk/ - | if [[ ! -d "release" ]]; then echo "$APK_PATH does not exist." exit 1 fi cd "release" unsigned_build=$(ls *.apk | grep "unsigned") cp $unsigned_build $UNSIGNED_APK - ./gradlew :app:k9mail:assembleRelease artifacts: paths: - app/k9mail/build/outputs/apk/ - $APK_PATH init_submodules: stage: gitlab_release Loading app/k9mail/src/main/AndroidManifest.xml +5 −1 Original line number Diff line number Diff line Loading @@ -36,7 +36,11 @@ android:label="@string/app_name" android:theme="@style/Theme.K9.Startup" android:resizeableActivity="true" android:allowBackup="false" android:allowBackup="true" android:backupAgent="com.fsck.k9.MailBackupAgent" android:fullBackupContent="@xml/backup_rules" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupOnly="true" android:supportsRtl="true" android:hasFragileUserData="false" tools:replace="android:theme" Loading app/k9mail/src/main/java/com/fsck/k9/MailBackupAgent.kt 0 → 100644 +90 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package com.fsck.k9 import android.app.backup.BackupAgent import android.app.backup.BackupDataInput import android.app.backup.BackupDataOutput import android.app.backup.FullBackupDataOutput import android.os.Build import android.os.ParcelFileDescriptor import androidx.annotation.RequiresApi import java.io.File import java.io.IOException import timber.log.Timber /* * Note: * - We rely only on full backup. * - Backup is allowed only for encrypted or device-to-device transports. * - Only selected app data is backed up (databases + shared preferences). */ class MailBackupAgent : BackupAgent() { override fun onBackup( oldState: ParcelFileDescriptor?, data: BackupDataOutput?, newState: ParcelFileDescriptor? ) = Unit override fun onRestore( data: BackupDataInput?, appVersionCode: Int, newState: ParcelFileDescriptor? ) = Unit @RequiresApi(Build.VERSION_CODES.P) override fun onFullBackup(data: FullBackupDataOutput) { val flags = data.transportFlags val allowed = flags and FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED != 0 || flags and FLAG_DEVICE_TO_DEVICE_TRANSFER != 0 if (!allowed) return BACKUP_DIRS.forEach { backupDir(File(dataDir, it), data) } } private fun backupDir(dir: File, data: FullBackupDataOutput) { if (!dir.isDirectory) return try { dir.listFiles()?.forEach { file -> if (file.isDirectory) { backupDir(file, data) } else if (shouldBackupFile(file)) { fullBackupFile(file, data) } } } catch (e: SecurityException) { Timber.e(e, "Skipping restricted") } catch (e: IOException) { Timber.e(e, "I/O error during backup") } } private fun shouldBackupFile(file: File) = EXCLUDED_SUFFIXES.none(file.name::endsWith) companion object { private val EXCLUDED_SUFFIXES = listOf("-journal", "-shm", "-wal") private val BACKUP_DIRS = listOf( "databases", "shared_prefs" ) } } app/k9mail/src/main/res/xml/backup_rules.xml 0 → 100644 +24 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2026 e Foundation ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by ~ the Free Software Foundation, either version 3 of the License, or ~ (at your option) any later version. ~ ~ This program is distributed in the hope that it will be useful, ~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ GNU General Public License for more details. ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see <https://www.gnu.org/licenses/>. --> <full-backup-content> <include domain="sharedpref" path="." /> <include domain="database" path="." /> <exclude domain="database" path="preferences_storage-journal" /> <exclude domain="database" path="preferences_storage-shm" /> <exclude domain="database" path="preferences_storage-wal" /> </full-backup-content> app/k9mail/src/main/res/xml/data_extraction_rules.xml 0 → 100644 +33 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2026 e Foundation ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by ~ the Free Software Foundation, either version 3 of the License, or ~ (at your option) any later version. ~ ~ This program is distributed in the hope that it will be useful, ~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ GNU General Public License for more details. ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see <https://www.gnu.org/licenses/>. --> <data-extraction-rules> <cloud-backup disableIfNoEncryptionCapabilities="true"> <include domain="sharedpref" path="." /> <include domain="database" path="." /> <exclude domain="database" path="preferences_storage-journal" /> <exclude domain="database" path="preferences_storage-shm" /> <exclude domain="database" path="preferences_storage-wal" /> </cloud-backup> <device-transfer> <include domain="sharedpref" path="." /> <include domain="database" path="." /> <exclude domain="database" path="preferences_storage-journal" /> <exclude domain="database" path="preferences_storage-shm" /> <exclude domain="database" path="preferences_storage-wal" /> </device-transfer> </data-extraction-rules> Loading
.gitlab-ci.yml +3 −12 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:master" variables: APK_PATH: "app/k9mail/build/outputs/apk/release" UNSIGNED_APK: "Mail-unsigned.apk" UNSIGNED_APK: "k9mail-release-unsigned.apk" COMMUNITY_APK: "Mail-community.apk" OFFICIAL_APK: "Mail-official.apk" TEST_APK: "Mail-test.apk" Loading @@ -29,19 +29,10 @@ test: build: stage: build script: - ./gradlew :app:k9mail:build - cd app/k9mail/build/outputs/apk/ - | if [[ ! -d "release" ]]; then echo "$APK_PATH does not exist." exit 1 fi cd "release" unsigned_build=$(ls *.apk | grep "unsigned") cp $unsigned_build $UNSIGNED_APK - ./gradlew :app:k9mail:assembleRelease artifacts: paths: - app/k9mail/build/outputs/apk/ - $APK_PATH init_submodules: stage: gitlab_release Loading
app/k9mail/src/main/AndroidManifest.xml +5 −1 Original line number Diff line number Diff line Loading @@ -36,7 +36,11 @@ android:label="@string/app_name" android:theme="@style/Theme.K9.Startup" android:resizeableActivity="true" android:allowBackup="false" android:allowBackup="true" android:backupAgent="com.fsck.k9.MailBackupAgent" android:fullBackupContent="@xml/backup_rules" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupOnly="true" android:supportsRtl="true" android:hasFragileUserData="false" tools:replace="android:theme" Loading
app/k9mail/src/main/java/com/fsck/k9/MailBackupAgent.kt 0 → 100644 +90 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package com.fsck.k9 import android.app.backup.BackupAgent import android.app.backup.BackupDataInput import android.app.backup.BackupDataOutput import android.app.backup.FullBackupDataOutput import android.os.Build import android.os.ParcelFileDescriptor import androidx.annotation.RequiresApi import java.io.File import java.io.IOException import timber.log.Timber /* * Note: * - We rely only on full backup. * - Backup is allowed only for encrypted or device-to-device transports. * - Only selected app data is backed up (databases + shared preferences). */ class MailBackupAgent : BackupAgent() { override fun onBackup( oldState: ParcelFileDescriptor?, data: BackupDataOutput?, newState: ParcelFileDescriptor? ) = Unit override fun onRestore( data: BackupDataInput?, appVersionCode: Int, newState: ParcelFileDescriptor? ) = Unit @RequiresApi(Build.VERSION_CODES.P) override fun onFullBackup(data: FullBackupDataOutput) { val flags = data.transportFlags val allowed = flags and FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED != 0 || flags and FLAG_DEVICE_TO_DEVICE_TRANSFER != 0 if (!allowed) return BACKUP_DIRS.forEach { backupDir(File(dataDir, it), data) } } private fun backupDir(dir: File, data: FullBackupDataOutput) { if (!dir.isDirectory) return try { dir.listFiles()?.forEach { file -> if (file.isDirectory) { backupDir(file, data) } else if (shouldBackupFile(file)) { fullBackupFile(file, data) } } } catch (e: SecurityException) { Timber.e(e, "Skipping restricted") } catch (e: IOException) { Timber.e(e, "I/O error during backup") } } private fun shouldBackupFile(file: File) = EXCLUDED_SUFFIXES.none(file.name::endsWith) companion object { private val EXCLUDED_SUFFIXES = listOf("-journal", "-shm", "-wal") private val BACKUP_DIRS = listOf( "databases", "shared_prefs" ) } }
app/k9mail/src/main/res/xml/backup_rules.xml 0 → 100644 +24 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2026 e Foundation ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by ~ the Free Software Foundation, either version 3 of the License, or ~ (at your option) any later version. ~ ~ This program is distributed in the hope that it will be useful, ~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ GNU General Public License for more details. ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see <https://www.gnu.org/licenses/>. --> <full-backup-content> <include domain="sharedpref" path="." /> <include domain="database" path="." /> <exclude domain="database" path="preferences_storage-journal" /> <exclude domain="database" path="preferences_storage-shm" /> <exclude domain="database" path="preferences_storage-wal" /> </full-backup-content>
app/k9mail/src/main/res/xml/data_extraction_rules.xml 0 → 100644 +33 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2026 e Foundation ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by ~ the Free Software Foundation, either version 3 of the License, or ~ (at your option) any later version. ~ ~ This program is distributed in the hope that it will be useful, ~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ GNU General Public License for more details. ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see <https://www.gnu.org/licenses/>. --> <data-extraction-rules> <cloud-backup disableIfNoEncryptionCapabilities="true"> <include domain="sharedpref" path="." /> <include domain="database" path="." /> <exclude domain="database" path="preferences_storage-journal" /> <exclude domain="database" path="preferences_storage-shm" /> <exclude domain="database" path="preferences_storage-wal" /> </cloud-backup> <device-transfer> <include domain="sharedpref" path="." /> <include domain="database" path="." /> <exclude domain="database" path="preferences_storage-journal" /> <exclude domain="database" path="preferences_storage-shm" /> <exclude domain="database" path="preferences_storage-wal" /> </device-transfer> </data-extraction-rules>