Save the time of the last backup and only do automatic flash drive backups once a day
This commit also turns SettingsManager into a class, so we can mock and later also inject it.
This commit is contained in:
parent
b0386c8b66
commit
007dd7759d
19 changed files with 196 additions and 146 deletions
|
@ -3,8 +3,6 @@ package com.stevesoltys.backup
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import com.stevesoltys.backup.settings.getBackupToken
|
|
||||||
import com.stevesoltys.backup.settings.getStorage
|
|
||||||
import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage
|
import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage
|
||||||
import com.stevesoltys.backup.transport.backup.plugins.createOrGetFile
|
import com.stevesoltys.backup.transport.backup.plugins.createOrGetFile
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
@ -21,9 +19,8 @@ private const val filename = "test-file"
|
||||||
class DocumentsStorageTest {
|
class DocumentsStorageTest {
|
||||||
|
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
private val token = getBackupToken(context)
|
private val settingsManager = (context.applicationContext as Backup).settingsManager
|
||||||
private val folderUri = getStorage(context)
|
private val storage = DocumentsStorage(context, settingsManager)
|
||||||
private val storage = DocumentsStorage(context, folderUri, token)
|
|
||||||
|
|
||||||
private lateinit var file: DocumentFile
|
private lateinit var file: DocumentFile
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,11 @@ package com.stevesoltys.backup
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.backup.IBackupManager
|
import android.app.backup.IBackupManager
|
||||||
import android.content.Context.BACKUP_SERVICE
|
import android.content.Context.BACKUP_SERVICE
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ServiceManager.getService
|
import android.os.ServiceManager.getService
|
||||||
import com.stevesoltys.backup.crypto.KeyManager
|
import com.stevesoltys.backup.crypto.KeyManager
|
||||||
import com.stevesoltys.backup.crypto.KeyManagerImpl
|
import com.stevesoltys.backup.crypto.KeyManagerImpl
|
||||||
import com.stevesoltys.backup.ui.storage.AUTHORITY_STORAGE
|
import com.stevesoltys.backup.settings.SettingsManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
|
@ -25,6 +24,9 @@ class Backup : Application() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val settingsManager by lazy {
|
||||||
|
SettingsManager(this)
|
||||||
|
}
|
||||||
val notificationManager by lazy {
|
val notificationManager by lazy {
|
||||||
BackupNotificationManager(this)
|
BackupNotificationManager(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,10 @@ import android.os.Handler
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.backup.settings.FlashDrive
|
import com.stevesoltys.backup.settings.FlashDrive
|
||||||
import com.stevesoltys.backup.settings.getFlashDrive
|
|
||||||
import com.stevesoltys.backup.transport.requestBackup
|
import com.stevesoltys.backup.transport.requestBackup
|
||||||
import com.stevesoltys.backup.ui.storage.AUTHORITY_STORAGE
|
import com.stevesoltys.backup.ui.storage.AUTHORITY_STORAGE
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit.HOURS
|
||||||
|
|
||||||
private val TAG = UsbIntentReceiver::class.java.simpleName
|
private val TAG = UsbIntentReceiver::class.java.simpleName
|
||||||
|
|
||||||
|
@ -28,12 +29,17 @@ class UsbIntentReceiver : BroadcastReceiver() {
|
||||||
Log.d(TAG, "New USB mass-storage device attached.")
|
Log.d(TAG, "New USB mass-storage device attached.")
|
||||||
device.log()
|
device.log()
|
||||||
|
|
||||||
val savedFlashDrive = getFlashDrive(context) ?: return
|
val settingsManager = (context.applicationContext as Backup).settingsManager
|
||||||
|
val savedFlashDrive = settingsManager.getFlashDrive() ?: return
|
||||||
val attachedFlashDrive = FlashDrive.from(device)
|
val attachedFlashDrive = FlashDrive.from(device)
|
||||||
if (savedFlashDrive == attachedFlashDrive) {
|
if (savedFlashDrive == attachedFlashDrive) {
|
||||||
Log.d(TAG, "Matches stored device, requesting backup...")
|
Log.d(TAG, "Matches stored device, checking backup time...")
|
||||||
// TODO only if last backup older than 24h
|
if (Date().time - settingsManager.getBackupTime() >= HOURS.toMillis(24)) {
|
||||||
|
Log.d(TAG, "Last backup older than 24 hours, requesting a backup...")
|
||||||
startBackupOnceMounted(context)
|
startBackupOnceMounted(context)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "We have a recent backup, not requesting a new one.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@ import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||||
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||||
import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_BACKUP_REQUEST_CODE;
|
import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_BACKUP_REQUEST_CODE;
|
||||||
import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_REQUEST_CODE;
|
import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_REQUEST_CODE;
|
||||||
import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupPassword;
|
|
||||||
import static com.stevesoltys.backup.settings.SettingsManagerKt.getStorage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
|
@ -41,7 +39,7 @@ public class MainActivityController {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isChangeBackupLocationButtonVisible(Activity parent) {
|
boolean isChangeBackupLocationButtonVisible(Activity parent) {
|
||||||
return getStorage(parent) != null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showChooseFolderActivity(Activity parent, boolean continueToBackup) {
|
private void showChooseFolderActivity(Activity parent, boolean continueToBackup) {
|
||||||
|
@ -74,17 +72,10 @@ public class MainActivityController {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean onAutomaticBackupsButtonClicked(Activity parent) {
|
boolean onAutomaticBackupsButtonClicked(Activity parent) {
|
||||||
if (getStorage(parent) == null || getBackupPassword(parent) == null) {
|
|
||||||
Toast.makeText(parent, "Please make at least one manual backup first.", Toast.LENGTH_SHORT).show();
|
Toast.makeText(parent, "Please make at least one manual backup first.", Toast.LENGTH_SHORT).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// show Toast informing the user
|
|
||||||
Toast.makeText(parent, "REMOVED", Toast.LENGTH_SHORT).show();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChangeBackupLocationButtonClicked(Activity parent) {
|
void onChangeBackupLocationButtonClicked(Activity parent) {
|
||||||
showChooseFolderActivity(parent, false);
|
showChooseFolderActivity(parent, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,6 @@ import com.stevesoltys.backup.service.backup.BackupService;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupPassword;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
*/
|
*/
|
||||||
|
@ -70,13 +68,8 @@ class CreateBackupActivityController {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onCreateBackupButtonClicked(Set<String> selectedPackages, Activity parent) {
|
void onCreateBackupButtonClicked(Set<String> selectedPackages, Activity parent) {
|
||||||
String password = getBackupPassword(parent);
|
|
||||||
if (password == null) {
|
|
||||||
showEnterPasswordAlert(selectedPackages, parent);
|
|
||||||
} else {
|
|
||||||
backupService.backupPackageData(selectedPackages, parent);
|
backupService.backupPackageData(selectedPackages, parent);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void showEnterPasswordAlert(Set<String> selectedPackages, Activity parent) {
|
private void showEnterPasswordAlert(Set<String> selectedPackages, Activity parent) {
|
||||||
final EditText passwordTextView = new EditText(parent);
|
final EditText passwordTextView = new EditText(parent);
|
||||||
|
|
|
@ -10,10 +10,10 @@ import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.getAppName
|
import com.stevesoltys.backup.getAppName
|
||||||
import com.stevesoltys.backup.isDebugBuild
|
import com.stevesoltys.backup.isDebugBuild
|
||||||
import com.stevesoltys.backup.settings.getStorage
|
|
||||||
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
||||||
|
|
||||||
class RestoreProgressFragment : Fragment() {
|
class RestoreProgressFragment : Fragment() {
|
||||||
|
@ -49,7 +49,8 @@ class RestoreProgressFragment : Fragment() {
|
||||||
if (finished == 0) {
|
if (finished == 0) {
|
||||||
// success
|
// success
|
||||||
currentPackageView.text = getString(R.string.restore_finished_success)
|
currentPackageView.text = getString(R.string.restore_finished_success)
|
||||||
warningView.text = if (getStorage(requireContext())?.ejectable == true) {
|
val settingsManager = (requireContext().applicationContext as Backup).settingsManager
|
||||||
|
warningView.text = if (settingsManager.getStorage()?.ejectable == true) {
|
||||||
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
|
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.restore_finished_warning_only_installed, null)
|
getString(R.string.restore_finished_warning_only_installed, null)
|
||||||
|
|
|
@ -6,6 +6,8 @@ import android.os.Bundle
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.provider.Settings.Secure.BACKUP_AUTO_RESTORE
|
import android.provider.Settings.Secure.BACKUP_AUTO_RESTORE
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.text.format.DateUtils.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
|
@ -18,6 +20,7 @@ import androidx.preference.TwoStatePreference
|
||||||
import com.stevesoltys.backup.Backup
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.restore.RestoreActivity
|
import com.stevesoltys.backup.restore.RestoreActivity
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private val TAG = SettingsFragment::class.java.name
|
private val TAG = SettingsFragment::class.java.name
|
||||||
|
|
||||||
|
@ -26,6 +29,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
private val backupManager = Backup.backupManager
|
private val backupManager = Backup.backupManager
|
||||||
|
|
||||||
private lateinit var viewModel: SettingsViewModel
|
private lateinit var viewModel: SettingsViewModel
|
||||||
|
private lateinit var settingsManager: SettingsManager
|
||||||
|
|
||||||
private lateinit var backup: TwoStatePreference
|
private lateinit var backup: TwoStatePreference
|
||||||
private lateinit var autoRestore: TwoStatePreference
|
private lateinit var autoRestore: TwoStatePreference
|
||||||
|
@ -36,6 +40,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
|
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
|
||||||
|
settingsManager = (requireContext().applicationContext as Backup).settingsManager
|
||||||
|
|
||||||
backup = findPreference<TwoStatePreference>("backup")!!
|
backup = findPreference<TwoStatePreference>("backup")!!
|
||||||
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||||
|
@ -74,7 +79,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
// we need to re-set the title when returning to this fragment
|
// we need to re-set the title when returning to this fragment
|
||||||
requireActivity().setTitle(R.string.app_name)
|
val activity = requireActivity()
|
||||||
|
activity.setTitle(R.string.app_name)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
backup.isChecked = backupManager.isBackupEnabled
|
backup.isChecked = backupManager.isBackupEnabled
|
||||||
|
@ -84,12 +90,21 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
backup.isEnabled = false
|
backup.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val resolver = requireContext().contentResolver
|
val resolver = activity.contentResolver
|
||||||
autoRestore.isChecked = Settings.Secure.getInt(resolver, BACKUP_AUTO_RESTORE, 1) == 1
|
autoRestore.isChecked = Settings.Secure.getInt(resolver, BACKUP_AUTO_RESTORE, 1) == 1
|
||||||
|
|
||||||
// TODO add time of last backup here
|
// get name of storage location
|
||||||
val storageName = getStorage(requireContext())?.name
|
val storageName = settingsManager.getStorage()?.name
|
||||||
backupLocation.summary = storageName ?: getString(R.string.settings_backup_location_none )
|
?: getString(R.string.settings_backup_location_none)
|
||||||
|
|
||||||
|
// get time of last backup
|
||||||
|
val lastBackupInMillis = settingsManager.getBackupTime()
|
||||||
|
val lastBackup = if (lastBackupInMillis == 0L) {
|
||||||
|
getString(R.string.settings_backup_last_backup_never)
|
||||||
|
} else {
|
||||||
|
getRelativeTimeSpanString(lastBackupInMillis, Date().time, MINUTE_IN_MILLIS, 0)
|
||||||
|
}
|
||||||
|
backupLocation.summary = getString(R.string.settings_backup_location_summary, storageName, lastBackup)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
|
|
@ -17,8 +17,104 @@ private const val PREF_KEY_FLASH_DRIVE_VENDOR_ID = "flashDriveVendorId"
|
||||||
private const val PREF_KEY_FLASH_DRIVE_PRODUCT_ID = "flashDriveProductId"
|
private const val PREF_KEY_FLASH_DRIVE_PRODUCT_ID = "flashDriveProductId"
|
||||||
|
|
||||||
private const val PREF_KEY_BACKUP_TOKEN = "backupToken"
|
private const val PREF_KEY_BACKUP_TOKEN = "backupToken"
|
||||||
|
private const val PREF_KEY_BACKUP_TIME = "backupTime"
|
||||||
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
|
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
|
||||||
|
|
||||||
|
class SettingsManager(context: Context) {
|
||||||
|
|
||||||
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
fun setStorage(storage: Storage) {
|
||||||
|
prefs.edit()
|
||||||
|
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
||||||
|
.putString(PREF_KEY_STORAGE_NAME, storage.name)
|
||||||
|
.putBoolean(PREF_KEY_STORAGE_EJECTABLE, storage.ejectable)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStorage(): Storage? {
|
||||||
|
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
||||||
|
val uri = Uri.parse(uriStr)
|
||||||
|
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) ?: throw IllegalStateException()
|
||||||
|
val ejectable = prefs.getBoolean(PREF_KEY_STORAGE_EJECTABLE, false)
|
||||||
|
return Storage(uri, name, ejectable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFlashDrive(usb: FlashDrive?) {
|
||||||
|
if (usb == null) {
|
||||||
|
prefs.edit()
|
||||||
|
.remove(PREF_KEY_FLASH_DRIVE_NAME)
|
||||||
|
.remove(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER)
|
||||||
|
.remove(PREF_KEY_FLASH_DRIVE_VENDOR_ID)
|
||||||
|
.remove(PREF_KEY_FLASH_DRIVE_PRODUCT_ID)
|
||||||
|
.apply()
|
||||||
|
} else {
|
||||||
|
prefs.edit()
|
||||||
|
.putString(PREF_KEY_FLASH_DRIVE_NAME, usb.name)
|
||||||
|
.putString(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER, usb.serialNumber)
|
||||||
|
.putInt(PREF_KEY_FLASH_DRIVE_VENDOR_ID, usb.vendorId)
|
||||||
|
.putInt(PREF_KEY_FLASH_DRIVE_PRODUCT_ID, usb.productId)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFlashDrive(): FlashDrive? {
|
||||||
|
val name = prefs.getString(PREF_KEY_FLASH_DRIVE_NAME, null) ?: return null
|
||||||
|
val serialNumber = prefs.getString(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER, null)
|
||||||
|
val vendorId = prefs.getInt(PREF_KEY_FLASH_DRIVE_VENDOR_ID, -1)
|
||||||
|
val productId = prefs.getInt(PREF_KEY_FLASH_DRIVE_PRODUCT_ID, -1)
|
||||||
|
return FlashDrive(name, serialNumber, vendorId, productId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and returns a new backup token while saving it as well.
|
||||||
|
* Subsequent calls to [getBackupToken] will return this new token once saved.
|
||||||
|
*/
|
||||||
|
fun getAndSaveNewBackupToken(): Long = Date().time.apply {
|
||||||
|
prefs.edit()
|
||||||
|
.putLong(PREF_KEY_BACKUP_TOKEN, this)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current backup token or 0 if none exists.
|
||||||
|
*/
|
||||||
|
fun getBackupToken(): Long {
|
||||||
|
return prefs.getLong(PREF_KEY_BACKUP_TOKEN, 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last backup time to "now".
|
||||||
|
*/
|
||||||
|
fun saveNewBackupTime() {
|
||||||
|
prefs.edit()
|
||||||
|
.putLong(PREF_KEY_BACKUP_TIME, Date().time)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last backup time to "never".
|
||||||
|
*/
|
||||||
|
fun resetBackupTime() {
|
||||||
|
prefs.edit()
|
||||||
|
.putLong(PREF_KEY_BACKUP_TIME, 0L)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last backup time in unix epoch milli seconds.
|
||||||
|
*/
|
||||||
|
fun getBackupTime(): Long {
|
||||||
|
return prefs.getLong(PREF_KEY_BACKUP_TIME, 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Replaced by KeyManager#getBackupKey()")
|
||||||
|
fun getBackupPassword(): String? {
|
||||||
|
return prefs.getString(PREF_KEY_BACKUP_PASSWORD, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
data class Storage(
|
data class Storage(
|
||||||
val uri: Uri,
|
val uri: Uri,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@ -27,24 +123,6 @@ data class Storage(
|
||||||
?: throw AssertionError("Should only happen on API < 21.")
|
?: throw AssertionError("Should only happen on API < 21.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStorage(context: Context, storage: Storage) {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
.edit()
|
|
||||||
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
|
||||||
.putString(PREF_KEY_STORAGE_NAME, storage.name)
|
|
||||||
.putBoolean(PREF_KEY_STORAGE_EJECTABLE, storage.ejectable)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getStorage(context: Context): Storage? {
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
|
||||||
val uri = Uri.parse(uriStr)
|
|
||||||
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) ?: throw IllegalStateException()
|
|
||||||
val ejectable = prefs.getBoolean(PREF_KEY_STORAGE_EJECTABLE, false)
|
|
||||||
return Storage(uri, name, ejectable)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FlashDrive(
|
data class FlashDrive(
|
||||||
val name: String,
|
val name: String,
|
||||||
val serialNumber: String?,
|
val serialNumber: String?,
|
||||||
|
@ -59,55 +137,3 @@ data class FlashDrive(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFlashDrive(context: Context, usb: FlashDrive?) {
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
if (usb == null) {
|
|
||||||
prefs.edit()
|
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_NAME)
|
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER)
|
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_VENDOR_ID)
|
|
||||||
.remove(PREF_KEY_FLASH_DRIVE_PRODUCT_ID)
|
|
||||||
.apply()
|
|
||||||
} else {
|
|
||||||
prefs.edit()
|
|
||||||
.putString(PREF_KEY_FLASH_DRIVE_NAME, usb.name)
|
|
||||||
.putString(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER, usb.serialNumber)
|
|
||||||
.putInt(PREF_KEY_FLASH_DRIVE_VENDOR_ID, usb.vendorId)
|
|
||||||
.putInt(PREF_KEY_FLASH_DRIVE_PRODUCT_ID, usb.productId)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFlashDrive(context: Context): FlashDrive? {
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
val name = prefs.getString(PREF_KEY_FLASH_DRIVE_NAME, null) ?: return null
|
|
||||||
val serialNumber = prefs.getString(PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER, null)
|
|
||||||
val vendorId = prefs.getInt(PREF_KEY_FLASH_DRIVE_VENDOR_ID, -1)
|
|
||||||
val productId = prefs.getInt(PREF_KEY_FLASH_DRIVE_PRODUCT_ID, -1)
|
|
||||||
return FlashDrive(name, serialNumber, vendorId, productId)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates and returns a new backup token while saving it as well.
|
|
||||||
* Subsequent calls to [getBackupToken] will return this new token once saved.
|
|
||||||
*/
|
|
||||||
fun getAndSaveNewBackupToken(context: Context): Long = Date().time.apply {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
.edit()
|
|
||||||
.putLong(PREF_KEY_BACKUP_TOKEN, this)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current backup token or 0 if none exists.
|
|
||||||
*/
|
|
||||||
fun getBackupToken(context: Context): Long {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getLong(PREF_KEY_BACKUP_TOKEN, 0L)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Replaced by KeyManager#getBackupKey()")
|
|
||||||
fun getBackupPassword(context: Context): String? {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getString(PREF_KEY_BACKUP_PASSWORD, null)
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ import com.stevesoltys.backup.header.HeaderReaderImpl
|
||||||
import com.stevesoltys.backup.header.HeaderWriterImpl
|
import com.stevesoltys.backup.header.HeaderWriterImpl
|
||||||
import com.stevesoltys.backup.metadata.MetadataReaderImpl
|
import com.stevesoltys.backup.metadata.MetadataReaderImpl
|
||||||
import com.stevesoltys.backup.metadata.MetadataWriterImpl
|
import com.stevesoltys.backup.metadata.MetadataWriterImpl
|
||||||
import com.stevesoltys.backup.settings.getBackupToken
|
|
||||||
import com.stevesoltys.backup.settings.getStorage
|
|
||||||
import com.stevesoltys.backup.transport.backup.BackupCoordinator
|
import com.stevesoltys.backup.transport.backup.BackupCoordinator
|
||||||
import com.stevesoltys.backup.transport.backup.FullBackup
|
import com.stevesoltys.backup.transport.backup.FullBackup
|
||||||
import com.stevesoltys.backup.transport.backup.InputFactory
|
import com.stevesoltys.backup.transport.backup.InputFactory
|
||||||
|
@ -24,9 +22,10 @@ import com.stevesoltys.backup.transport.restore.plugins.DocumentsProviderRestore
|
||||||
|
|
||||||
class PluginManager(context: Context) {
|
class PluginManager(context: Context) {
|
||||||
|
|
||||||
// We can think about using an injection framework such as Dagger to simplify this.
|
// We can think about using an injection framework such as Dagger, Koin or Kodein to simplify this.
|
||||||
|
|
||||||
private val storage = DocumentsStorage(context, getStorage(context), getBackupToken(context))
|
private val settingsManager = (context.applicationContext as Backup).settingsManager
|
||||||
|
private val storage = DocumentsStorage(context, settingsManager)
|
||||||
|
|
||||||
private val headerWriter = HeaderWriterImpl()
|
private val headerWriter = HeaderWriterImpl()
|
||||||
private val headerReader = HeaderReaderImpl()
|
private val headerReader = HeaderReaderImpl()
|
||||||
|
@ -42,7 +41,7 @@ class PluginManager(context: Context) {
|
||||||
private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto)
|
private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto)
|
||||||
private val notificationManager = (context.applicationContext as Backup).notificationManager
|
private val notificationManager = (context.applicationContext as Backup).notificationManager
|
||||||
|
|
||||||
internal val backupCoordinator = BackupCoordinator(context, backupPlugin, kvBackup, fullBackup, metadataWriter, notificationManager)
|
internal val backupCoordinator = BackupCoordinator(context, backupPlugin, kvBackup, fullBackup, metadataWriter, settingsManager, notificationManager)
|
||||||
|
|
||||||
|
|
||||||
private val restorePlugin = DocumentsProviderRestorePlugin(storage)
|
private val restorePlugin = DocumentsProviderRestorePlugin(storage)
|
||||||
|
@ -50,6 +49,6 @@ class PluginManager(context: Context) {
|
||||||
private val kvRestore = KVRestore(restorePlugin.kvRestorePlugin, outputFactory, headerReader, crypto)
|
private val kvRestore = KVRestore(restorePlugin.kvRestorePlugin, outputFactory, headerReader, crypto)
|
||||||
private val fullRestore = FullRestore(restorePlugin.fullRestorePlugin, outputFactory, headerReader, crypto)
|
private val fullRestore = FullRestore(restorePlugin.fullRestorePlugin, outputFactory, headerReader, crypto)
|
||||||
|
|
||||||
internal val restoreCoordinator = RestoreCoordinator(context, restorePlugin, kvRestore, fullRestore, metadataReader)
|
internal val restoreCoordinator = RestoreCoordinator(settingsManager, restorePlugin, kvRestore, fullRestore, metadataReader)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.backup.BackupNotificationManager
|
import com.stevesoltys.backup.BackupNotificationManager
|
||||||
import com.stevesoltys.backup.metadata.MetadataWriter
|
import com.stevesoltys.backup.metadata.MetadataWriter
|
||||||
import com.stevesoltys.backup.settings.getBackupToken
|
import com.stevesoltys.backup.settings.SettingsManager
|
||||||
import com.stevesoltys.backup.settings.getStorage
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit.MINUTES
|
import java.util.concurrent.TimeUnit.MINUTES
|
||||||
|
|
||||||
|
@ -25,6 +24,7 @@ class BackupCoordinator(
|
||||||
private val kv: KVBackup,
|
private val kv: KVBackup,
|
||||||
private val full: FullBackup,
|
private val full: FullBackup,
|
||||||
private val metadataWriter: MetadataWriter,
|
private val metadataWriter: MetadataWriter,
|
||||||
|
private val settingsManager: SettingsManager,
|
||||||
private val nm: BackupNotificationManager) {
|
private val nm: BackupNotificationManager) {
|
||||||
|
|
||||||
private var calledInitialize = false
|
private var calledInitialize = false
|
||||||
|
@ -56,7 +56,7 @@ class BackupCoordinator(
|
||||||
Log.i(TAG, "Initialize Device!")
|
Log.i(TAG, "Initialize Device!")
|
||||||
return try {
|
return try {
|
||||||
plugin.initializeDevice()
|
plugin.initializeDevice()
|
||||||
writeBackupMetadata(getBackupToken(context))
|
writeBackupMetadata(settingsManager.getBackupToken())
|
||||||
// [finishBackup] will only be called when we return [TRANSPORT_OK] here
|
// [finishBackup] will only be called when we return [TRANSPORT_OK] here
|
||||||
// so we remember that we initialized successfully
|
// so we remember that we initialized successfully
|
||||||
calledInitialize = true
|
calledInitialize = true
|
||||||
|
@ -100,8 +100,11 @@ class BackupCoordinator(
|
||||||
Log.i(TAG, "Request incremental backup time. Returned $this")
|
Log.i(TAG, "Request incremental backup time. Returned $this")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performIncrementalBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int) =
|
fun performIncrementalBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int): Int {
|
||||||
kv.performBackup(packageInfo, data, flags)
|
val result = kv.performBackup(packageInfo, data, flags)
|
||||||
|
if (result == TRANSPORT_OK) settingsManager.saveNewBackupTime()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------
|
||||||
// Full backup
|
// Full backup
|
||||||
|
@ -126,8 +129,11 @@ class BackupCoordinator(
|
||||||
|
|
||||||
fun checkFullBackupSize(size: Long) = full.checkFullBackupSize(size)
|
fun checkFullBackupSize(size: Long) = full.checkFullBackupSize(size)
|
||||||
|
|
||||||
fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor, flags: Int) =
|
fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor, flags: Int): Int {
|
||||||
full.performFullBackup(targetPackage, fileDescriptor, flags)
|
val result = full.performFullBackup(targetPackage, fileDescriptor, flags)
|
||||||
|
if (result == TRANSPORT_OK) settingsManager.saveNewBackupTime()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
fun sendBackupData(numBytes: Int) = full.sendBackupData(numBytes)
|
fun sendBackupData(numBytes: Int) = full.sendBackupData(numBytes)
|
||||||
|
|
||||||
|
@ -191,7 +197,7 @@ class BackupCoordinator(
|
||||||
val defaultBackoff = MINUTES.toMillis(10)
|
val defaultBackoff = MINUTES.toMillis(10)
|
||||||
|
|
||||||
// back off if there's no storage set
|
// back off if there's no storage set
|
||||||
val storage = getStorage(context) ?: return defaultBackoff
|
val storage = settingsManager.getStorage() ?: return defaultBackoff
|
||||||
// don't back off if storage is not ejectable or available right now
|
// don't back off if storage is not ejectable or available right now
|
||||||
return if (!storage.ejectable || storage.getDocumentFile(context).isDirectory) noBackoff
|
return if (!storage.ejectable || storage.getDocumentFile(context).isDirectory) noBackoff
|
||||||
// otherwise back off
|
// otherwise back off
|
||||||
|
|
|
@ -4,8 +4,8 @@ import android.content.Context
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.stevesoltys.backup.settings.SettingsManager
|
||||||
import com.stevesoltys.backup.settings.Storage
|
import com.stevesoltys.backup.settings.Storage
|
||||||
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
@ -19,7 +19,10 @@ private const val MIME_TYPE = "application/octet-stream"
|
||||||
|
|
||||||
private val TAG = DocumentsStorage::class.java.simpleName
|
private val TAG = DocumentsStorage::class.java.simpleName
|
||||||
|
|
||||||
class DocumentsStorage(private val context: Context, storage: Storage?, token: Long) {
|
class DocumentsStorage(private val context: Context, private val settingsManager: SettingsManager) {
|
||||||
|
|
||||||
|
private val storage: Storage? = settingsManager.getStorage()
|
||||||
|
private val token: Long = settingsManager.getBackupToken()
|
||||||
|
|
||||||
internal val rootBackupDir: DocumentFile? by lazy {
|
internal val rootBackupDir: DocumentFile? by lazy {
|
||||||
val parent = storage?.getDocumentFile(context) ?: return@lazy null
|
val parent = storage?.getDocumentFile(context) ?: return@lazy null
|
||||||
|
@ -36,7 +39,7 @@ class DocumentsStorage(private val context: Context, storage: Storage?, token: L
|
||||||
|
|
||||||
private val currentToken: Long by lazy {
|
private val currentToken: Long by lazy {
|
||||||
if (token != 0L) token
|
if (token != 0L) token
|
||||||
else getAndSaveNewBackupToken(context).apply {
|
else settingsManager.getAndSaveNewBackupToken().apply {
|
||||||
Log.d(TAG, "Using a fresh backup token: $this")
|
Log.d(TAG, "Using a fresh backup token: $this")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,13 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.app.backup.RestoreDescription
|
import android.app.backup.RestoreDescription
|
||||||
import android.app.backup.RestoreDescription.*
|
import android.app.backup.RestoreDescription.*
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.backup.header.UnsupportedVersionException
|
import com.stevesoltys.backup.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.backup.metadata.DecryptionFailedException
|
import com.stevesoltys.backup.metadata.DecryptionFailedException
|
||||||
import com.stevesoltys.backup.metadata.MetadataReader
|
import com.stevesoltys.backup.metadata.MetadataReader
|
||||||
import com.stevesoltys.backup.settings.getBackupToken
|
import com.stevesoltys.backup.settings.SettingsManager
|
||||||
import libcore.io.IoUtils.closeQuietly
|
import libcore.io.IoUtils.closeQuietly
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ private class RestoreCoordinatorState(
|
||||||
private val TAG = RestoreCoordinator::class.java.simpleName
|
private val TAG = RestoreCoordinator::class.java.simpleName
|
||||||
|
|
||||||
internal class RestoreCoordinator(
|
internal class RestoreCoordinator(
|
||||||
private val context: Context,
|
private val settingsManager: SettingsManager,
|
||||||
private val plugin: RestorePlugin,
|
private val plugin: RestorePlugin,
|
||||||
private val kv: KVRestore,
|
private val kv: KVRestore,
|
||||||
private val full: FullRestore,
|
private val full: FullRestore,
|
||||||
|
@ -75,7 +74,7 @@ internal class RestoreCoordinator(
|
||||||
* or 0 if there is no backup set available corresponding to the current device state.
|
* or 0 if there is no backup set available corresponding to the current device state.
|
||||||
*/
|
*/
|
||||||
fun getCurrentRestoreSet(): Long {
|
fun getCurrentRestoreSet(): Long {
|
||||||
return getBackupToken(context)
|
return settingsManager.getBackupToken()
|
||||||
.apply { Log.i(TAG, "Got current restore set token: $this") }
|
.apply { Log.i(TAG, "Got current restore set token: $this") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
package com.stevesoltys.backup.ui.storage
|
package com.stevesoltys.backup.ui.storage
|
||||||
|
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.backup.BackupProgress
|
import android.app.backup.BackupProgress
|
||||||
import android.app.backup.IBackupObserver
|
import android.app.backup.IBackupObserver
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.os.UserManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.backup.Backup
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
|
|
||||||
import com.stevesoltys.backup.transport.TRANSPORT_ID
|
import com.stevesoltys.backup.transport.TRANSPORT_ID
|
||||||
|
|
||||||
private val TAG = BackupStorageViewModel::class.java.simpleName
|
private val TAG = BackupStorageViewModel::class.java.simpleName
|
||||||
|
@ -24,7 +21,7 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
|
||||||
saveStorage(uri)
|
saveStorage(uri)
|
||||||
|
|
||||||
// use a new backup token
|
// use a new backup token
|
||||||
getAndSaveNewBackupToken(app)
|
settingsManager.getAndSaveNewBackupToken()
|
||||||
|
|
||||||
// initialize the new location
|
// initialize the new location
|
||||||
val observer = InitializationObserver()
|
val observer = InitializationObserver()
|
||||||
|
|
|
@ -12,9 +12,11 @@ import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.isMassStorage
|
import com.stevesoltys.backup.isMassStorage
|
||||||
import com.stevesoltys.backup.settings.*
|
import com.stevesoltys.backup.settings.FlashDrive
|
||||||
|
import com.stevesoltys.backup.settings.Storage
|
||||||
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
|
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
|
||||||
import com.stevesoltys.backup.ui.LiveEvent
|
import com.stevesoltys.backup.ui.LiveEvent
|
||||||
import com.stevesoltys.backup.ui.MutableLiveEvent
|
import com.stevesoltys.backup.ui.MutableLiveEvent
|
||||||
|
@ -23,6 +25,8 @@ private val TAG = StorageViewModel::class.java.simpleName
|
||||||
|
|
||||||
internal abstract class StorageViewModel(private val app: Application) : AndroidViewModel(app), RemovableStorageListener {
|
internal abstract class StorageViewModel(private val app: Application) : AndroidViewModel(app), RemovableStorageListener {
|
||||||
|
|
||||||
|
protected val settingsManager = (app as Backup).settingsManager
|
||||||
|
|
||||||
private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
|
private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
|
||||||
internal val storageRoots: LiveData<List<StorageRoot>> get() = mStorageRoots
|
internal val storageRoots: LiveData<List<StorageRoot>> get() = mStorageRoots
|
||||||
|
|
||||||
|
@ -39,7 +43,8 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun validLocationIsSet(context: Context): Boolean {
|
internal fun validLocationIsSet(context: Context): Boolean {
|
||||||
val storage = getStorage(context) ?: return false
|
val settingsManager = (context.applicationContext as Backup).settingsManager
|
||||||
|
val storage = settingsManager.getStorage() ?: return false
|
||||||
if (storage.ejectable) return true
|
if (storage.ejectable) return true
|
||||||
return storage.getDocumentFile(context).isDirectory
|
return storage.getDocumentFile(context).isDirectory
|
||||||
}
|
}
|
||||||
|
@ -84,12 +89,15 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
root.title
|
root.title
|
||||||
}
|
}
|
||||||
val storage = Storage(uri, name, root.supportsEject)
|
val storage = Storage(uri, name, root.supportsEject)
|
||||||
setStorage(app, storage)
|
settingsManager.setStorage(storage)
|
||||||
|
|
||||||
|
// reset time of last backup to "Never"
|
||||||
|
settingsManager.resetBackupTime()
|
||||||
|
|
||||||
if (storage.ejectable) {
|
if (storage.ejectable) {
|
||||||
val wasSaved = saveUsbDevice()
|
val wasSaved = saveUsbDevice()
|
||||||
// reset stored flash drive, if we did not update it
|
// reset stored flash drive, if we did not update it
|
||||||
if (!wasSaved) setFlashDrive(app, null)
|
if (!wasSaved) settingsManager.setFlashDrive(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop backup service to be sure the old location will get updated
|
// stop backup service to be sure the old location will get updated
|
||||||
|
@ -102,7 +110,7 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
val manager = app.getSystemService(USB_SERVICE) as UsbManager
|
val manager = app.getSystemService(USB_SERVICE) as UsbManager
|
||||||
manager.deviceList.values.forEach { device ->
|
manager.deviceList.values.forEach { device ->
|
||||||
if (device.isMassStorage()) {
|
if (device.isMassStorage()) {
|
||||||
setFlashDrive(app, FlashDrive.from(device))
|
settingsManager.setFlashDrive(FlashDrive.from(device))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
<string name="settings_backup_location_invalid">The chosen location can not be used.</string>
|
<string name="settings_backup_location_invalid">The chosen location can not be used.</string>
|
||||||
<string name="settings_backup_location_none">None</string>
|
<string name="settings_backup_location_none">None</string>
|
||||||
<string name="settings_backup_location_internal">Internal Storage</string>
|
<string name="settings_backup_location_internal">Internal Storage</string>
|
||||||
|
<string name="settings_backup_last_backup_never">Never</string>
|
||||||
|
<string name="settings_backup_location_summary">%s · Last Backup %s</string>
|
||||||
<string name="settings_info">All backups are encrypted on your phone. To restore from backup you will need your 12-word recovery code.</string>
|
<string name="settings_info">All backups are encrypted on your phone. To restore from backup you will need your 12-word recovery code.</string>
|
||||||
<string name="settings_auto_restore_title">Automatic restore</string>
|
<string name="settings_auto_restore_title">Automatic restore</string>
|
||||||
<string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string>
|
<string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string>
|
||||||
|
|
|
@ -43,14 +43,14 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
private val fullBackupPlugin = mockk<FullBackupPlugin>()
|
private val fullBackupPlugin = mockk<FullBackupPlugin>()
|
||||||
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
|
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
private val backup = BackupCoordinator(context, backupPlugin, kvBackup, fullBackup, metadataWriter, notificationManager)
|
private val backup = BackupCoordinator(context, backupPlugin, kvBackup, fullBackup, metadataWriter, settingsManager, notificationManager)
|
||||||
|
|
||||||
private val restorePlugin = mockk<RestorePlugin>()
|
private val restorePlugin = mockk<RestorePlugin>()
|
||||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
||||||
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
||||||
private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||||
private val restore = RestoreCoordinator(context, restorePlugin, kvRestore, fullRestore, metadataReader)
|
private val restore = RestoreCoordinator(settingsManager, restorePlugin, kvRestore, fullRestore, metadataReader)
|
||||||
|
|
||||||
private val backupDataInput = mockk<BackupDataInput>()
|
private val backupDataInput = mockk<BackupDataInput>()
|
||||||
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
||||||
|
@ -91,6 +91,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
appData2.size
|
appData2.size
|
||||||
}
|
}
|
||||||
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key264) } returns bOutputStream2
|
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key264) } returns bOutputStream2
|
||||||
|
every { settingsManager.saveNewBackupTime() } just Runs
|
||||||
|
|
||||||
// start and finish K/V backup
|
// start and finish K/V backup
|
||||||
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
||||||
|
@ -130,6 +131,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
every { fullBackupPlugin.getOutputStream(packageInfo) } returns bOutputStream
|
every { fullBackupPlugin.getOutputStream(packageInfo) } returns bOutputStream
|
||||||
every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream
|
every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream
|
||||||
every { fullBackupPlugin.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
every { fullBackupPlugin.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
||||||
|
every { settingsManager.saveNewBackupTime() } just Runs
|
||||||
|
|
||||||
// perform backup to output stream
|
// perform backup to output stream
|
||||||
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))
|
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.backup.crypto.Crypto
|
import com.stevesoltys.backup.crypto.Crypto
|
||||||
|
import com.stevesoltys.backup.settings.SettingsManager
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
@ -14,6 +15,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD
|
||||||
abstract class TransportTest {
|
abstract class TransportTest {
|
||||||
|
|
||||||
protected val crypto = mockk<Crypto>()
|
protected val crypto = mockk<Crypto>()
|
||||||
|
protected val settingsManager = mockk<SettingsManager>()
|
||||||
protected val context = mockk<Context>(relaxed = true)
|
protected val context = mockk<Context>(relaxed = true)
|
||||||
|
|
||||||
protected val packageInfo = PackageInfo().apply { packageName = "org.example" }
|
protected val packageInfo = PackageInfo().apply { packageName = "org.example" }
|
||||||
|
|
|
@ -23,14 +23,15 @@ internal class BackupCoordinatorTest: BackupTest() {
|
||||||
private val metadataWriter = mockk<MetadataWriter>()
|
private val metadataWriter = mockk<MetadataWriter>()
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
|
|
||||||
private val backup = BackupCoordinator(context, plugin, kv, full, metadataWriter, notificationManager)
|
private val backup = BackupCoordinator(context, plugin, kv, full, metadataWriter, settingsManager, notificationManager)
|
||||||
|
|
||||||
private val metadataOutputStream = mockk<OutputStream>()
|
private val metadataOutputStream = mockk<OutputStream>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `device initialization succeeds and delegates to plugin`() {
|
fun `device initialization succeeds and delegates to plugin`() {
|
||||||
every { plugin.initializeDevice() } just Runs
|
every { plugin.initializeDevice() } just Runs
|
||||||
expectWritingMetadata(0L)
|
every { settingsManager.getBackupToken() } returns token
|
||||||
|
expectWritingMetadata(token)
|
||||||
every { kv.hasState() } returns false
|
every { kv.hasState() } returns false
|
||||||
every { full.hasState() } returns false
|
every { full.hasState() } returns false
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
private val full = mockk<FullRestore>()
|
private val full = mockk<FullRestore>()
|
||||||
private val metadataReader = mockk<MetadataReader>()
|
private val metadataReader = mockk<MetadataReader>()
|
||||||
|
|
||||||
private val restore = RestoreCoordinator(context, plugin, kv, full, metadataReader)
|
private val restore = RestoreCoordinator(settingsManager, plugin, kv, full, metadataReader)
|
||||||
|
|
||||||
private val token = Random.nextLong()
|
private val token = Random.nextLong()
|
||||||
private val inputStream = mockk<InputStream>()
|
private val inputStream = mockk<InputStream>()
|
||||||
|
@ -56,8 +56,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getCurrentRestoreSet() delegates to plugin`() {
|
fun `getCurrentRestoreSet() delegates to plugin`() {
|
||||||
// We don't mock the SettingsManager, so the default value is returned here
|
every { settingsManager.getBackupToken() } returns token
|
||||||
assertEquals(0L, restore.getCurrentRestoreSet())
|
assertEquals(token, restore.getCurrentRestoreSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue