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.
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/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/BackupDataStorageRepository.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/BackupDataStorageRepository.kt
index 856a7eb..b7e5dc2 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.*
@@ -56,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)
}
@@ -64,6 +60,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 +80,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 +99,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 +113,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 +130,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 +165,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/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/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..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
@@ -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,63 @@ 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, 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()
@@ -61,10 +126,41 @@ 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)
+// 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
@@ -117,4 +213,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/dao/BackupMetaDataDao.kt b/app/src/main/java/org/secuso/privacyfriendlybackup/data/room/dao/BackupMetaDataDao.kt
index c1b7141..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
@@ -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,10 @@ 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
+
+ @Query("SELECT COUNT(*) FROM StoredBackupMetaData")
+ suspend fun getTotal(): Long
}
\ 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/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/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..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"
@@ -93,8 +99,25 @@ 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()
+ 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 9583372..b36e35e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -47,8 +47,15 @@
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
Wiederherstellen
Fehler: ID ist nicht korrekt
Exportieren
@@ -129,4 +136,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 6ed951b..51d72af 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -48,8 +48,15 @@
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
Google Drive
Inspect
Restore
@@ -133,5 +140,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
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
index 3cef938..55404cd 100644
--- a/app/src/main/res/xml/pref_general.xml
+++ b/app/src/main/res/xml/pref_general.xml
@@ -5,20 +5,19 @@
app:title="@string/pref_category_storage">
+ app:summary="@string/pref_storage_type_summary" />
-
+ app:title="Test" /> -->
-
+
\ No newline at end of file
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