From 5768d0235927d7111894e49fe6b70e923ead9635 Mon Sep 17 00:00:00 2001
From: udenr <98773052+udenr@users.noreply.github.com>
Date: Fri, 6 Jun 2025 16:29:45 +0200
Subject: [PATCH 1/5] Remove link to Google Play from `README.md`
---
README.md | 3 ---
1 file changed, 3 deletions(-)
diff --git a/README.md b/README.md
index 1b142d6..cbc7026 100644
--- a/README.md
+++ b/README.md
@@ -19,9 +19,6 @@ This is the privacy friendly backup application that enables users to create and
[
](https://f-droid.org/packages/org.secuso.privacyfriendlybackup/)
-[
](https://play.google.com/store/apps/details?id=org.secuso.privacyfriendlybackup)
## Integrated PFAs
These apps support backups with the Privacy Friendly Backup app.
From 88a446e94bf1d516459747afa07b866d3c426022 Mon Sep 17 00:00:00 2001
From: Patrick Schneider
Date: Thu, 16 Oct 2025 21:40:33 +0200
Subject: [PATCH 2/5] [chore] updates AGP and kotlin.
---
app/build.gradle | 3 ++-
.../privacyfriendlybackup/data/cloud/WebserviceProvider.kt | 5 +----
build.gradle | 6 +++---
gradle/wrapper/gradle-wrapper.properties | 4 ++--
4 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 0ee5bf3..90435e4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -107,6 +107,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
+ implementation 'androidx.annotation:annotation:1.9.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
@@ -123,7 +124,7 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version"
// Room Database
- def roomVersion = "2.6.1"
+ def roomVersion = "2.7.0"
implementation "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
ksp "androidx.room:room-compiler:$roomVersion"
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/cloud/WebserviceProvider.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/cloud/WebserviceProvider.kt
index 8f8d26e..9f84b37 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/cloud/WebserviceProvider.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/cloud/WebserviceProvider.kt
@@ -3,7 +3,6 @@ package org.secuso.privacyfriendlybackup.data.cloud
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import org.secuso.privacyfriendlybackup.api.util.copyInputStreamToFile
import org.secuso.privacyfriendlybackup.api.util.hash
import org.secuso.privacyfriendlybackup.api.util.toHex
import org.secuso.privacyfriendlybackup.data.BackupDataStorageRepository
@@ -13,8 +12,6 @@ import org.secuso.privacyfriendlybackup.data.internal.InternalBackupDataStoreHel
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.room.model.StoredBackupMetaData
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
-import org.secuso.privacyfriendlybackup.util.BackupDataUtil
-import java.io.ByteArrayOutputStream
import java.io.File
import java.util.*
@@ -74,7 +71,7 @@ class WebserviceProvider {
return withContext(Dispatchers.IO) {
val files = File(context.getExternalFilesDir(null),
ExternalBackupDataStoreHelper.BACKUP_DIR
- ).listFiles { _, name -> name.toLowerCase(Locale.ENGLISH).endsWith(".backup") }
+ ).listFiles { _, name -> name.lowercase(Locale.ENGLISH).endsWith(".backup") }
files?.map { it.name } ?: emptyList()
}
}
diff --git a/build.gradle b/build.gradle
index bd077c6..3e5f302 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = "1.9.22"
+ ext.kotlin_version = "2.2.20"
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.7.3'
+ classpath 'com.android.tools.build:gradle:8.10.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@@ -16,7 +16,7 @@ buildscript {
}
plugins {
- id 'com.google.devtools.ksp' version "$kotlin_version-1.0.17" apply false
+ id 'com.google.devtools.ksp' version "$kotlin_version-2.0.4" apply false
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index da6416a..120d9fd 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Jan 23 03:28:08 CET 2021
+#Sun Oct 12 18:03:10 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
From 451bd8a5eb7b57606d2774cd7356568e63b38ead Mon Sep 17 00:00:00 2001
From: Patrick Schneider
Date: Thu, 16 Oct 2025 21:41:07 +0200
Subject: [PATCH 3/5] [feat] Adds storage type Internal. Adds option to select
storage location.
---
.../data/BackupDataStorageRepository.kt | 30 +++++--
.../external/ExternalBackupDataStoreHelper.kt | 2 +-
.../internal/InternalBackupDataStoreHelper.kt | 81 ++++++++++++++++++-
.../data/room/model/enums/StorageType.kt | 3 +-
.../ApplicationOverviewViewModel.kt | 2 +-
.../ui/backup/FilterableBackupAdapter.kt | 2 +-
.../ui/main/MainActivity.kt | 5 +-
.../ui/settings/SettingsFragment.kt | 3 +
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
app/src/main/res/xml/pref_general.xml | 8 +-
11 files changed, 121 insertions(+), 17 deletions(-)
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
index 856a7eb..5a4c3d8 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
@@ -4,18 +4,13 @@ import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
-import androidx.room.ColumnInfo
-import androidx.room.PrimaryKey
import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.cloud.WebserviceProvider
import org.secuso.privacyfriendlybackup.data.external.ExternalBackupDataStoreHelper
-import org.secuso.privacyfriendlybackup.data.room.model.BackupJob
-import org.secuso.privacyfriendlybackup.data.room.model.enums.BackupJobAction
+import org.secuso.privacyfriendlybackup.data.internal.InternalBackupDataStoreHelper
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import java.util.*
@@ -64,6 +59,7 @@ class BackupDataStorageRepository private constructor(
suspend fun storeFile(context: Context, packageName: String, dataId : Long, storageType: StorageType = StorageType.EXTERNAL) {
when(storageType) {
StorageType.EXTERNAL -> ExternalBackupDataStoreHelper.storeData(context, packageName, dataId)
+ StorageType.INTERNAL -> InternalBackupDataStoreHelper.storeData(context, packageName, dataId)
StorageType.CLOUD -> webserviceProvider.storeData(context, packageName, dataId)
}
}
@@ -83,6 +79,9 @@ class BackupDataStorageRepository private constructor(
StorageType.EXTERNAL -> {
data.postValue(ExternalBackupDataStoreHelper.getData(context,metadata))
}
+ StorageType.INTERNAL -> {
+ data.postValue(InternalBackupDataStoreHelper.getData(context,metadata))
+ }
StorageType.CLOUD -> {
data.postValue( TODO() )
}
@@ -99,6 +98,7 @@ class BackupDataStorageRepository private constructor(
return when (metadata.storageService) {
StorageType.EXTERNAL -> ExternalBackupDataStoreHelper.getData(context, metadata)
+ StorageType.INTERNAL -> InternalBackupDataStoreHelper.getData(context, metadata)
StorageType.CLOUD -> webserviceProvider.getData(context, metadata)
}
}
@@ -112,6 +112,7 @@ class BackupDataStorageRepository private constructor(
runBlocking {
val externalFilenames = ExternalBackupDataStoreHelper.listAvailableData(context)
+ val internalFilenames = InternalBackupDataStoreHelper.listAvailableData(context)
for(meta in metalist) {
when(meta.storageService) {
StorageType.EXTERNAL -> {
@@ -128,6 +129,20 @@ class BackupDataStorageRepository private constructor(
)
result.add(backupDataInfo)
}
+ StorageType.INTERNAL -> {
+ val available = internalFilenames.contains(meta.filename)
+ val backupDataInfo = BackupData(
+ id = meta._id,
+ filename = meta.filename,
+ packageName = meta.packageName,
+ timestamp = meta.timestamp,
+ data = null,
+ encrypted = meta.encrypted,
+ storageType = meta.storageService,
+ available = available
+ )
+ result.add(backupDataInfo)
+ }
StorageType.CLOUD -> {
TODO()
}
@@ -149,6 +164,9 @@ class BackupDataStorageRepository private constructor(
StorageType.EXTERNAL -> {
ExternalBackupDataStoreHelper.deleteData(context, metaData)
}
+ StorageType.INTERNAL -> {
+ InternalBackupDataStoreHelper.deleteData(context, metaData)
+ }
StorageType.CLOUD -> {
webserviceProvider.deleteData(context, metaData)
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/external/ExternalBackupDataStoreHelper.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/external/ExternalBackupDataStoreHelper.kt
index f667809..ef8a401 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/external/ExternalBackupDataStoreHelper.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/external/ExternalBackupDataStoreHelper.kt
@@ -106,7 +106,7 @@ object ExternalBackupDataStoreHelper {
suspend fun listAvailableData(context: Context) : List {
return withContext(Dispatchers.IO) {
- val files = File(context.getExternalFilesDir(null), BACKUP_DIR).listFiles { _, name -> name.toLowerCase(Locale.ENGLISH).endsWith(".backup") }
+ val files = File(context.getExternalFilesDir(null), BACKUP_DIR).listFiles { _, name -> name.lowercase(Locale.ENGLISH).endsWith(".backup") }
files?.map { it.name } ?: emptyList()
}
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt
index df3fce4..5a0dbad 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt
@@ -1,13 +1,20 @@
package org.secuso.privacyfriendlybackup.data.internal
import android.content.Context
-import android.text.TextUtils
import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.secuso.privacyfriendlybackup.api.util.copyInputStreamToFile
+import org.secuso.privacyfriendlybackup.api.util.hash
+import org.secuso.privacyfriendlybackup.api.util.toHex
+import org.secuso.privacyfriendlybackup.data.BackupDataStorageRepository
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.room.model.enums.BackupJobAction
import org.secuso.privacyfriendlybackup.data.room.model.InternalBackupData
+import org.secuso.privacyfriendlybackup.data.room.model.StoredBackupMetaData
+import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import org.secuso.privacyfriendlybackup.util.BackupDataUtil.getFileName
+import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
@@ -17,6 +24,7 @@ object InternalBackupDataStoreHelper {
const val TAG = "PFA Internal"
const val BACKUP_DIR = "tempData"
+ const val INTERNAL_BACKUP_DIR = "backupData"
suspend fun storeBackupData(context: Context, packageName: String, inputStream: InputStream, date: Date, encrypted: Boolean = false) : Long {
val dataId = storeData(context, packageName, inputStream, date, encrypted)
@@ -42,6 +50,42 @@ object InternalBackupDataStoreHelper {
return dataId
}
+ suspend fun storeData(context: Context, packageName: String, dataId: Long) : Long {
+ return withContext(Dispatchers.IO) {
+ val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
+ path.mkdirs()
+
+ val date = Date()
+
+ val (inputStream, data) = getInternalData(context, dataId)
+ val fileName = getFileName(date, packageName, data?.encrypted == true)
+ val file = File(path, fileName)
+
+ Log.d(TAG, file.toString())
+
+ val dataBytes = inputStream?.use {
+ return@use inputStream.readBytes()
+ }
+ file.copyInputStreamToFile(ByteArrayInputStream(dataBytes))
+
+ if (data != null) {
+ val hash = dataBytes!!.hash("SHA-1").toHex()
+ BackupDatabase.getInstance(context).backupMetaDataDao().insert(
+ StoredBackupMetaData(
+ packageName = data.packageName,
+ timestamp = date,
+ storageService = StorageType.INTERNAL,
+ filename = fileName,
+ encrypted = data.encrypted,
+ hash = hash
+ )
+ )
+ } else {
+ -1L
+ }
+ }
+ }
+
suspend fun storeData(context: Context, packageName: String, inputStream: InputStream, date: Date, encrypted : Boolean = false) : Long {
val path = File(context.filesDir, BACKUP_DIR)
path.mkdirs()
@@ -61,6 +105,24 @@ object InternalBackupDataStoreHelper {
return BackupDatabase.getInstance(context).internalBackupDataDao().insert(data)
}
+ suspend fun getData(context: Context, metadata : StoredBackupMetaData) : BackupDataStorageRepository.BackupData? {
+ return withContext(Dispatchers.IO) {
+ val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
+ val file = File(path, metadata.filename)
+
+ return@withContext BackupDataStorageRepository.BackupData(
+ metadata._id,
+ metadata.filename,
+ metadata.packageName,
+ metadata.timestamp,
+ file.inputStream().readBytes(),
+ metadata.encrypted,
+ StorageType.INTERNAL,
+ true
+ )
+ }
+ }
+
suspend fun getInternalData(context: Context, dataId: Long): Pair {
val data = BackupDatabase.getInstance(context).internalBackupDataDao().getById(dataId)
?: return Pair(null, null)
@@ -117,4 +179,21 @@ object InternalBackupDataStoreHelper {
}
}
+ suspend fun deleteData(context: Context, metadata : StoredBackupMetaData) {
+ withContext(Dispatchers.IO) {
+ val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
+ val file = File(path, metadata.filename)
+ file.delete()
+ }
+ }
+
+ suspend fun listAvailableData(context: Context) : List {
+ return withContext(Dispatchers.IO) {
+ val files = File(context.filesDir, INTERNAL_BACKUP_DIR).listFiles { _, name ->
+ name.lowercase(Locale.ENGLISH).endsWith(".backup")
+ }
+ files?.map { it.name } ?: emptyList()
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/model/enums/StorageType.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/model/enums/StorageType.kt
index 0813734..5d17d42 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/model/enums/StorageType.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/model/enums/StorageType.kt
@@ -7,10 +7,11 @@ enum class StorageType(
@StringRes val nameResId : Int
) {
EXTERNAL(R.string.storage_type_external),
+ INTERNAL(R.string.storage_type_internal),
CLOUD(R.string.storage_type_drive);
companion object {
@JvmStatic
- fun getStorageOptions() = listOf(EXTERNAL)
+ fun getStorageOptions() = listOf(EXTERNAL, INTERNAL)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/application/ApplicationOverviewViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/application/ApplicationOverviewViewModel.kt
index 7a6c73d..e8c5bae 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/application/ApplicationOverviewViewModel.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/application/ApplicationOverviewViewModel.kt
@@ -79,7 +79,7 @@ class ApplicationOverviewViewModel(app : Application) : AndroidViewModel(app) {
fun createBackupForPackage(packageName: String) {
viewModelScope.launch {
val jobManager = BackupJobManager.getInstance(getApplication())
- val storageName = PreferenceManager.getDefaultSharedPreferences(getApplication()).getString(PreferenceKeys.PREF_STORAGE_TYPE, StorageType.EXTERNAL.name)
+ val storageName = PreferenceManager.getDefaultSharedPreferences(getApplication()).getString(PreferenceKeys.PREF_STORAGE_TYPE, StorageType.INTERNAL.name)
val storageType = StorageType.valueOf(storageName!!)
jobManager.createBackupJobChain(packageName, storageType)
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/FilterableBackupAdapter.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/FilterableBackupAdapter.kt
index 3defa88..cffc052 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/FilterableBackupAdapter.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/FilterableBackupAdapter.kt
@@ -124,7 +124,7 @@ class FilterableBackupAdapter(val context : Context, adapterCallback : ManageLis
}
val icon = when(data.storageType) {
- StorageType.EXTERNAL -> if(data.encrypted) R.drawable.ic_baseline_phonelink_lock_24 else R.drawable.ic_baseline_smartphone_24
+ StorageType.EXTERNAL, StorageType.INTERNAL -> if(data.encrypted) R.drawable.ic_baseline_phonelink_lock_24 else R.drawable.ic_baseline_smartphone_24
StorageType.CLOUD -> if(data.encrypted) R.drawable.ic_cloud_24 else R.drawable.ic_cloud_24
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/main/MainActivity.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/main/MainActivity.kt
index acf9753..fb87906 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/main/MainActivity.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/main/MainActivity.kt
@@ -26,6 +26,7 @@ import org.secuso.privacyfriendlybackup.ui.backup.BackupOverviewFragment
import org.secuso.privacyfriendlybackup.ui.common.DisplayMenuItemActivity
import org.secuso.privacyfriendlybackup.ui.encryption.EncryptionSettingsFragment
import org.secuso.privacyfriendlybackup.ui.help.HelpFragment
+import org.secuso.privacyfriendlybackup.ui.settings.SettingsFragment
/**
* An activity representing a list of Pings. This activity
@@ -46,7 +47,7 @@ class MainActivity : AppCompatActivity() {
MENU_MAIN_BACKUP_OVERVIEW(R.drawable.ic_backup_24, R.string.menu_main_backup, BackupOverviewFragment::class.java, DisplayMenuItemActivity::class.java),
MENU_MAIN_APPS(R.drawable.ic_apps_24, R.string.menu_main_apps, ApplicationOverviewFragment::class.java, DisplayMenuItemActivity::class.java),
MENU_MAIN_ENCRYPTION(R.drawable.ic_encryption_24, R.string.menu_main_encryption, EncryptionSettingsFragment::class.java, DisplayMenuItemActivity::class.java),
- //MENU_MAIN_SETTINGS(R.drawable.ic_settings_24, R.string.menu_main_settings, SettingsFragment::class.java, DisplayMenuItemActivity::class.java),
+ MENU_MAIN_SETTINGS(R.drawable.ic_settings_24, R.string.menu_main_settings, SettingsFragment::class.java, DisplayMenuItemActivity::class.java),
MENU_MAIN_HELP(R.drawable.ic_help_outline_24, R.string.menu_main_help, HelpFragment::class.java, DisplayMenuItemActivity::class.java),
MENU_MAIN_ABOUT(R.drawable.ic_about_24, R.string.menu_main_about, AboutFragment::class.java, DisplayMenuItemActivity::class.java);
@@ -72,7 +73,7 @@ class MainActivity : AppCompatActivity() {
MenuItem.MENU_MAIN_APPS,
MenuItem.MENU_MAIN_BACKUP_OVERVIEW,
MenuItem.MENU_MAIN_ENCRYPTION,
- //MenuItem.MENU_MAIN_SETTINGS,
+ MenuItem.MENU_MAIN_SETTINGS,
MenuItem.MENU_MAIN_HELP,
MenuItem.MENU_MAIN_ABOUT
)
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt
index 2605158..55dfd75 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt
@@ -93,6 +93,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
storageTypePref = findPreference(PREF_STORAGE_TYPE)
storageTypePref?.apply {
+ if (value == null) {
+ value = StorageType.EXTERNAL.toString()
+ }
entryValues = StorageType.getStorageOptions().map { it.name }.toTypedArray()
entries = StorageType.getStorageOptions().map { requireContext().getString(it.nameResId) }.toTypedArray()
}
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 9583372..4b498ed 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -49,6 +49,7 @@
Speichertyp
Automatische Backups
Externer Speicher
+ Interner Speicher
Wiederherstellen
Fehler: ID ist nicht korrekt
Exportieren
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6ed951b..bd90862 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -50,6 +50,7 @@
Storage Type
Automatic Backups
External Storage
+ Internal Storage
Google Drive
Inspect
Restore
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
index 3cef938..ecb1482 100644
--- a/app/src/main/res/xml/pref_general.xml
+++ b/app/src/main/res/xml/pref_general.xml
@@ -5,20 +5,20 @@
app:title="@string/pref_category_storage">
-
+ app:title="Test" /> -->
-
+
\ No newline at end of file
From 2b2c4971566481961619d5a6391c39d733fe9102 Mon Sep 17 00:00:00 2001
From: Patrick Schneider
Date: Wed, 4 Feb 2026 21:48:05 +0100
Subject: [PATCH 4/5] Adds option to move all backups from/to external/internal
storage. Backups and restoral still works as expected.
---
.../data/BackupDataStorageRepository.kt | 1 +
.../internal/InternalBackupDataStoreHelper.kt | 34 +++++++++++++++++
.../data/room/dao/BackupMetaDataDao.kt | 4 ++
.../ui/backup/BackupOverviewViewModel.kt | 1 -
.../ui/settings/SettingsFragment.kt | 20 ++++++++++
.../ui/settings/SettingsViewmodel.kt | 38 +++++++++++++++++++
.../worker/StoreWorker.kt | 1 -
app/src/main/res/values-de/strings.xml | 3 ++
app/src/main/res/values/strings.xml | 3 ++
9 files changed, 103 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsViewmodel.kt
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
index 5a4c3d8..b7e5dc2 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
@@ -51,6 +51,7 @@ class BackupDataStorageRepository private constructor(
suspend fun storeFile(context: Context, backupData: BackupData) : Pair {
return when(backupData.storageType) {
StorageType.EXTERNAL -> ExternalBackupDataStoreHelper.storeData(context, backupData)
+ StorageType.INTERNAL -> InternalBackupDataStoreHelper.storeData(context, backupData)
else -> { false to 0 }
//StorageType.CLOUD -> webserviceProvider.storeData(context, packageName, dataId)
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt
index 5a0dbad..c08bfb2 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/internal/InternalBackupDataStoreHelper.kt
@@ -86,6 +86,27 @@ object InternalBackupDataStoreHelper {
}
}
+ suspend fun storeData(context: Context, data: BackupDataStorageRepository.BackupData) : Pair {
+ return withContext(Dispatchers.IO) {
+ val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
+ path.mkdirs()
+ val file = File(path, data.filename)
+
+ file.copyInputStreamToFile(ByteArrayInputStream(data.data))
+ val hash = data.data!!.hash("SHA-1").toHex()
+
+ val id = BackupDatabase.getInstance(context).backupMetaDataDao().insert(StoredBackupMetaData(
+ packageName = data.packageName,
+ timestamp = data.timestamp,
+ storageService = StorageType.INTERNAL,
+ filename = data.filename,
+ encrypted = data.encrypted,
+ hash = hash
+ ))
+ return@withContext true to id
+ }
+ }
+
suspend fun storeData(context: Context, packageName: String, inputStream: InputStream, date: Date, encrypted : Boolean = false) : Long {
val path = File(context.filesDir, BACKUP_DIR)
path.mkdirs()
@@ -127,6 +148,19 @@ object InternalBackupDataStoreHelper {
val data = BackupDatabase.getInstance(context).internalBackupDataDao().getById(dataId)
?: return Pair(null, null)
+// if(data.packageName != callingPackageName && data.uid == callingUid) {
+// Log.d(TAG, "[No Restore Data found.]")
+// return null
+// }
+
+ val path = File(context.filesDir, BACKUP_DIR)
+ return File(path, data.file).inputStream() to data
+ }
+
+ suspend fun getInternalStoredData(context: Context, dataId: Long): Pair {
+ val data = BackupDatabase.getInstance(context).internalBackupDataDao().getById(dataId)
+ ?: return Pair(null, null)
+
// if(data.packageName != callingPackageName && data.uid == callingUid) {
// Log.d(TAG, "[No Restore Data found.]")
// return null
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
index c1b7141..b119060 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
@@ -3,6 +3,7 @@ package org.secuso.privacyfriendlybackup.data.room.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import org.secuso.privacyfriendlybackup.data.room.model.StoredBackupMetaData
+import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
@Dao
interface BackupMetaDataDao {
@@ -32,4 +33,7 @@ interface BackupMetaDataDao {
@Query("DELETE FROM StoredBackupMetaData WHERE _id IN (:ids)")
suspend fun deleteForIds(ids: List)
+
+ @Query("SELECT * FROM StoredBackupMetaData WHERE storageService != :storageType")
+ suspend fun getAllOfOtherStorageType(storageType: StorageType): List
}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/BackupOverviewViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/BackupOverviewViewModel.kt
index ae007f4..130a5f5 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/BackupOverviewViewModel.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/backup/BackupOverviewViewModel.kt
@@ -2,7 +2,6 @@ package org.secuso.privacyfriendlybackup.ui.backup
import android.app.Application
import android.net.Uri
-import android.widget.Toast
import androidx.collection.ArraySet
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt
index 55dfd75..60dd808 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsFragment.kt
@@ -9,11 +9,15 @@ import android.os.Bundle
import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
import androidx.preference.*
import com.google.android.material.appbar.AppBarLayout
+import kotlinx.coroutines.launch
import org.secuso.privacyfriendlybackup.R
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import org.secuso.privacyfriendlybackup.preference.PreferenceKeys
@@ -25,6 +29,8 @@ import org.secuso.privacyfriendlybackup.ui.main.MainActivity
class SettingsFragment : PreferenceFragmentCompat() {
+ private val viewModel by lazy { ViewModelProvider(this)[SettingsViewmodel::class.java] }
+
companion object {
const val DIALOG_FRAGMENT_TAG = "SettingsFragment.DIALOG_FRAGMENT_TAG"
const val TAG = "PFA Settings"
@@ -98,6 +104,20 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
entryValues = StorageType.getStorageOptions().map { it.name }.toTypedArray()
entries = StorageType.getStorageOptions().map { requireContext().getString(it.nameResId) }.toTypedArray()
+ onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
+ AlertDialog.Builder(requireContext()).apply {
+ setTitle(R.string.storage_location_move_all_to_new_title)
+ setMessage(R.string.storage_location_move_all_to_new_desc)
+ setNegativeButton(R.string.cancel, null)
+ setPositiveButton(R.string.move) { _,_ ->
+ val storageName = PreferenceManager.getDefaultSharedPreferences(requireContext()).getString(PreferenceKeys.PREF_STORAGE_TYPE, StorageType.INTERNAL.name)
+ val storageType = StorageType.valueOf(storageName!!)
+ viewModel.moveBackups(storageType)
+ }
+ show()
+ }
+ return@OnPreferenceChangeListener true
+ }
}
val test : SwitchPreference? = findPreference("pref_test")
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsViewmodel.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsViewmodel.kt
new file mode 100644
index 0000000..162d8dd
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/ui/settings/SettingsViewmodel.kt
@@ -0,0 +1,38 @@
+package org.secuso.privacyfriendlybackup.ui.settings
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
+import org.secuso.privacyfriendlybackup.data.external.ExternalBackupDataStoreHelper
+import org.secuso.privacyfriendlybackup.data.internal.InternalBackupDataStoreHelper
+import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
+import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
+
+class SettingsViewmodel(app: Application) : AndroidViewModel(app) {
+
+ fun moveBackups(location: StorageType) {
+ viewModelScope.launch {
+ val data = BackupDatabase.getInstance(getApplication()).backupMetaDataDao().getAllOfOtherStorageType(location)
+
+ when (location) {
+ StorageType.INTERNAL -> {
+ data.forEach { metadata ->
+ val backupData = ExternalBackupDataStoreHelper.getData(getApplication(), metadata)
+ InternalBackupDataStoreHelper.storeData(getApplication(), backupData!!)
+ ExternalBackupDataStoreHelper.deleteData(getApplication(), metadata)
+ }
+ }
+ StorageType.EXTERNAL -> {
+ data.forEach { metadata ->
+ val backupData = InternalBackupDataStoreHelper.getData(getApplication(), metadata)
+ ExternalBackupDataStoreHelper.storeData(getApplication(), backupData!!)
+ InternalBackupDataStoreHelper.deleteData(getApplication(), metadata)
+ }
+ }
+ else -> {}
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/worker/StoreWorker.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/worker/StoreWorker.kt
index 491b28c..03986c3 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/worker/StoreWorker.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/worker/StoreWorker.kt
@@ -5,7 +5,6 @@ import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import org.secuso.privacyfriendlybackup.data.BackupDataStorageRepository
-import org.secuso.privacyfriendlybackup.data.cloud.WebserviceProvider
import org.secuso.privacyfriendlybackup.data.internal.InternalBackupDataStoreHelper
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.room.model.BackupJob
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4b498ed..532a2dc 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -130,4 +130,7 @@
Um die Verschlüsselung nutzen zu können, müssen Sie eine Kryptoanbieter-Anwendung installieren. Wir empfehlen die OpenKeyChain-Anwendung, da sie quelloffen ist und sowohl im Google Play Store als auch im F-Droid-Store verfügbar ist.
Wie viele und welche Apps werden zur Zeit unterstützt?
Wir arbeiten stetig daran neue Apps zu unterstützen. Zurzeit wird nur die Notizen-App unterstützt.
+ Sollen alle Backups zu dem gerade ausgewählten Speicherort verschoben werden?
+ Verschieben
+ Alle Backups verschieben
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bd90862..afb39d4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -134,5 +134,8 @@
In order to use the encryption you will need to install a cryptographic provider application. We recommend the OpenKeyChain application because it is open source and available in both the Google Play Store as well as the f-Droid store.
How many and which apps are currently supported?
Currently only the Notes App is supported but we are working on adding more applications to this list.
+ Move all backups to new location
+ Do you want to move all backups made to the just selected storage location?
+ Relocate
\ No newline at end of file
From 8841967588af8e66ea0863abd324408963c4c0d0 Mon Sep 17 00:00:00 2001
From: Patrick Schneider
Date: Fri, 6 Mar 2026 09:59:45 +0100
Subject: [PATCH 5/5] [feat] Correct default storage type. Adds warning to only
use external iff encryption enabled.
If the app was upgraded, so there were backups present, then external is kept as default.
Otherwise internal is used.
---
.../BackupApplication.kt | 29 +++++++++++++++++++
.../data/room/dao/BackupMetaDataDao.kt | 3 ++
app/src/main/res/values-de/strings.xml | 6 ++++
app/src/main/res/values/strings.xml | 6 ++++
app/src/main/res/xml/pref_general.xml | 3 +-
5 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/BackupApplication.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/BackupApplication.kt
index b86df29..d3454bc 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/BackupApplication.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/BackupApplication.kt
@@ -5,12 +5,19 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import android.util.Log
+import androidx.core.content.edit
+import androidx.lifecycle.ProcessLifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceManager
import androidx.work.BackoffPolicy
import androidx.work.Configuration
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkRequest
+import kotlinx.coroutines.launch
+import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
+import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import org.secuso.privacyfriendlybackup.worker.BackupJobManagerWorker
import java.util.concurrent.TimeUnit
@@ -25,6 +32,28 @@ class BackupApplication : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()
+ if (applicationContext != null) {
+ with(ProcessLifecycleOwner.get()) {
+ lifecycleScope.launch {
+ PreferenceManager.getDefaultSharedPreferences(applicationContext).apply{
+ val current = getString("pref_storage_type", null)
+ if (current == null) {
+ val total = BackupDatabase.getInstance(applicationContext)
+ .backupMetaDataDao()
+ .getTotal()
+ edit(commit = true) {
+ putString("pref_storage_type", if (total > 0) {
+ StorageType.EXTERNAL.toString()
+ } else {
+ StorageType.INTERNAL.toString()
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+
createNotificationChannel()
schedulePeriodicWork()
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
index b119060..76aa676 100644
--- a/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
+++ b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
@@ -36,4 +36,7 @@ interface BackupMetaDataDao {
@Query("SELECT * FROM StoredBackupMetaData WHERE storageService != :storageType")
suspend fun getAllOfOtherStorageType(storageType: StorageType): List
+
+ @Query("SELECT COUNT(*) FROM StoredBackupMetaData")
+ suspend fun getTotal(): Long
}
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 532a2dc..b36e35e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -47,6 +47,12 @@
Google Drive
Speichereinstellungen
Speichertyp
+
+Wählen Sie explizit aus, an welcher Stelle die Backup-Daten und -Metadaten gespeichert werden soll.\n
+Achtung: Der externe Speicher ermöglicht anderen Apps, Diensten und Nutzern diese auszulesen oder zu verändern. Nutzen Sie diese Option nur, wenn die Verschlüsselung der Backups eingerichtet ist und verwendet wird. \n
+\n
+Aktueller Speicherort: %s
+
Automatische Backups
Externer Speicher
Interner Speicher
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index afb39d4..51d72af 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -48,6 +48,12 @@
Restore data
Storage Settings
Storage Type
+
+Specify the storage location used to store the backup data and metadata. \n
+Warning: Using the external storage makes the backup data available to other apps and users. Only use the external storage if encryption is properly setup and enabled. \n
+\n
+Current storage location: %s
+
Automatic Backups
External Storage
Internal Storage
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
index ecb1482..55404cd 100644
--- a/app/src/main/res/xml/pref_general.xml
+++ b/app/src/main/res/xml/pref_general.xml
@@ -5,10 +5,9 @@
app:title="@string/pref_category_storage">
+ app:summary="@string/pref_storage_type_summary" />