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 [Get it on F-Droid](https://f-droid.org/packages/org.secuso.privacyfriendlybackup/) -[Get it on Google Play](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