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:
Torsten Grote 2019-09-19 11:17:16 -03:00
parent b0386c8b66
commit 007dd7759d
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
19 changed files with 196 additions and 146 deletions

View file

@ -3,8 +3,6 @@ package com.stevesoltys.backup
import androidx.documentfile.provider.DocumentFile
import androidx.test.platform.app.InstrumentationRegistry
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.createOrGetFile
import org.junit.After
@ -21,9 +19,8 @@ private const val filename = "test-file"
class DocumentsStorageTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val token = getBackupToken(context)
private val folderUri = getStorage(context)
private val storage = DocumentsStorage(context, folderUri, token)
private val settingsManager = (context.applicationContext as Backup).settingsManager
private val storage = DocumentsStorage(context, settingsManager)
private lateinit var file: DocumentFile

View file

@ -3,12 +3,11 @@ package com.stevesoltys.backup
import android.app.Application
import android.app.backup.IBackupManager
import android.content.Context.BACKUP_SERVICE
import android.net.Uri
import android.os.Build
import android.os.ServiceManager.getService
import com.stevesoltys.backup.crypto.KeyManager
import com.stevesoltys.backup.crypto.KeyManagerImpl
import com.stevesoltys.backup.ui.storage.AUTHORITY_STORAGE
import com.stevesoltys.backup.settings.SettingsManager
/**
* @author Steve Soltys
@ -25,6 +24,9 @@ class Backup : Application() {
}
}
val settingsManager by lazy {
SettingsManager(this)
}
val notificationManager by lazy {
BackupNotificationManager(this)
}

View file

@ -13,9 +13,10 @@ import android.os.Handler
import android.provider.DocumentsContract
import android.util.Log
import com.stevesoltys.backup.settings.FlashDrive
import com.stevesoltys.backup.settings.getFlashDrive
import com.stevesoltys.backup.transport.requestBackup
import com.stevesoltys.backup.ui.storage.AUTHORITY_STORAGE
import java.util.*
import java.util.concurrent.TimeUnit.HOURS
private val TAG = UsbIntentReceiver::class.java.simpleName
@ -28,12 +29,17 @@ class UsbIntentReceiver : BroadcastReceiver() {
Log.d(TAG, "New USB mass-storage device attached.")
device.log()
val savedFlashDrive = getFlashDrive(context) ?: return
val settingsManager = (context.applicationContext as Backup).settingsManager
val savedFlashDrive = settingsManager.getFlashDrive() ?: return
val attachedFlashDrive = FlashDrive.from(device)
if (savedFlashDrive == attachedFlashDrive) {
Log.d(TAG, "Matches stored device, requesting backup...")
// TODO only if last backup older than 24h
Log.d(TAG, "Matches stored device, checking backup time...")
if (Date().time - settingsManager.getBackupTime() >= HOURS.toMillis(24)) {
Log.d(TAG, "Last backup older than 24 hours, requesting a backup...")
startBackupOnceMounted(context)
} else {
Log.d(TAG, "We have a recent backup, not requesting a new one.")
}
}
}

View file

@ -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 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.settings.SettingsManagerKt.getBackupPassword;
import static com.stevesoltys.backup.settings.SettingsManagerKt.getStorage;
/**
* @author Steve Soltys
@ -41,7 +39,7 @@ public class MainActivityController {
}
boolean isChangeBackupLocationButtonVisible(Activity parent) {
return getStorage(parent) != null;
return false;
}
private void showChooseFolderActivity(Activity parent, boolean continueToBackup) {
@ -74,17 +72,10 @@ public class MainActivityController {
}
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();
return false;
}
// show Toast informing the user
Toast.makeText(parent, "REMOVED", Toast.LENGTH_SHORT).show();
return true;
}
void onChangeBackupLocationButtonClicked(Activity parent) {
showChooseFolderActivity(parent, false);
}

View file

@ -21,8 +21,6 @@ import com.stevesoltys.backup.service.backup.BackupService;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupPassword;
/**
* @author Steve Soltys
*/
@ -70,13 +68,8 @@ class CreateBackupActivityController {
}
void onCreateBackupButtonClicked(Set<String> selectedPackages, Activity parent) {
String password = getBackupPassword(parent);
if (password == null) {
showEnterPasswordAlert(selectedPackages, parent);
} else {
backupService.backupPackageData(selectedPackages, parent);
}
}
private void showEnterPasswordAlert(Set<String> selectedPackages, Activity parent) {
final EditText passwordTextView = new EditText(parent);

View file

@ -10,10 +10,10 @@ import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.backup.Backup
import com.stevesoltys.backup.R
import com.stevesoltys.backup.getAppName
import com.stevesoltys.backup.isDebugBuild
import com.stevesoltys.backup.settings.getStorage
import kotlinx.android.synthetic.main.fragment_restore_progress.*
class RestoreProgressFragment : Fragment() {
@ -49,7 +49,8 @@ class RestoreProgressFragment : Fragment() {
if (finished == 0) {
// 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))
} else {
getString(R.string.restore_finished_warning_only_installed, null)

View file

@ -6,6 +6,8 @@ import android.os.Bundle
import android.os.RemoteException
import android.provider.Settings
import android.provider.Settings.Secure.BACKUP_AUTO_RESTORE
import android.text.format.DateUtils
import android.text.format.DateUtils.*
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
@ -18,6 +20,7 @@ import androidx.preference.TwoStatePreference
import com.stevesoltys.backup.Backup
import com.stevesoltys.backup.R
import com.stevesoltys.backup.restore.RestoreActivity
import java.util.*
private val TAG = SettingsFragment::class.java.name
@ -26,6 +29,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private val backupManager = Backup.backupManager
private lateinit var viewModel: SettingsViewModel
private lateinit var settingsManager: SettingsManager
private lateinit var backup: TwoStatePreference
private lateinit var autoRestore: TwoStatePreference
@ -36,6 +40,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
setHasOptionsMenu(true)
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
settingsManager = (requireContext().applicationContext as Backup).settingsManager
backup = findPreference<TwoStatePreference>("backup")!!
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
@ -74,7 +79,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
super.onStart()
// 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 {
backup.isChecked = backupManager.isBackupEnabled
@ -84,12 +90,21 @@ class SettingsFragment : PreferenceFragmentCompat() {
backup.isEnabled = false
}
val resolver = requireContext().contentResolver
val resolver = activity.contentResolver
autoRestore.isChecked = Settings.Secure.getInt(resolver, BACKUP_AUTO_RESTORE, 1) == 1
// TODO add time of last backup here
val storageName = getStorage(requireContext())?.name
backupLocation.summary = storageName ?: getString(R.string.settings_backup_location_none )
// get name of storage location
val storageName = settingsManager.getStorage()?.name
?: 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) {

View file

@ -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_BACKUP_TOKEN = "backupToken"
private const val PREF_KEY_BACKUP_TIME = "backupTime"
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(
val uri: Uri,
val name: String,
@ -27,24 +123,6 @@ data class Storage(
?: 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(
val name: 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)
}

View file

@ -8,8 +8,6 @@ import com.stevesoltys.backup.header.HeaderReaderImpl
import com.stevesoltys.backup.header.HeaderWriterImpl
import com.stevesoltys.backup.metadata.MetadataReaderImpl
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.FullBackup
import com.stevesoltys.backup.transport.backup.InputFactory
@ -24,9 +22,10 @@ import com.stevesoltys.backup.transport.restore.plugins.DocumentsProviderRestore
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 headerReader = HeaderReaderImpl()
@ -42,7 +41,7 @@ class PluginManager(context: Context) {
private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto)
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)
@ -50,6 +49,6 @@ class PluginManager(context: Context) {
private val kvRestore = KVRestore(restorePlugin.kvRestorePlugin, 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)
}

View file

@ -8,8 +8,7 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.backup.BackupNotificationManager
import com.stevesoltys.backup.metadata.MetadataWriter
import com.stevesoltys.backup.settings.getBackupToken
import com.stevesoltys.backup.settings.getStorage
import com.stevesoltys.backup.settings.SettingsManager
import java.io.IOException
import java.util.concurrent.TimeUnit.MINUTES
@ -25,6 +24,7 @@ class BackupCoordinator(
private val kv: KVBackup,
private val full: FullBackup,
private val metadataWriter: MetadataWriter,
private val settingsManager: SettingsManager,
private val nm: BackupNotificationManager) {
private var calledInitialize = false
@ -56,7 +56,7 @@ class BackupCoordinator(
Log.i(TAG, "Initialize Device!")
return try {
plugin.initializeDevice()
writeBackupMetadata(getBackupToken(context))
writeBackupMetadata(settingsManager.getBackupToken())
// [finishBackup] will only be called when we return [TRANSPORT_OK] here
// so we remember that we initialized successfully
calledInitialize = true
@ -100,8 +100,11 @@ class BackupCoordinator(
Log.i(TAG, "Request incremental backup time. Returned $this")
}
fun performIncrementalBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int) =
kv.performBackup(packageInfo, data, flags)
fun performIncrementalBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int): Int {
val result = kv.performBackup(packageInfo, data, flags)
if (result == TRANSPORT_OK) settingsManager.saveNewBackupTime()
return result
}
// ------------------------------------------------------------------------------------
// Full backup
@ -126,8 +129,11 @@ class BackupCoordinator(
fun checkFullBackupSize(size: Long) = full.checkFullBackupSize(size)
fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor, flags: Int) =
full.performFullBackup(targetPackage, fileDescriptor, flags)
fun performFullBackup(targetPackage: PackageInfo, fileDescriptor: ParcelFileDescriptor, flags: Int): Int {
val result = full.performFullBackup(targetPackage, fileDescriptor, flags)
if (result == TRANSPORT_OK) settingsManager.saveNewBackupTime()
return result
}
fun sendBackupData(numBytes: Int) = full.sendBackupData(numBytes)
@ -191,7 +197,7 @@ class BackupCoordinator(
val defaultBackoff = MINUTES.toMillis(10)
// 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
return if (!storage.ejectable || storage.getDocumentFile(context).isDirectory) noBackoff
// otherwise back off

View file

@ -4,8 +4,8 @@ import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.backup.settings.SettingsManager
import com.stevesoltys.backup.settings.Storage
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@ -19,7 +19,10 @@ private const val MIME_TYPE = "application/octet-stream"
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 {
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 {
if (token != 0L) token
else getAndSaveNewBackupToken(context).apply {
else settingsManager.getAndSaveNewBackupToken().apply {
Log.d(TAG, "Using a fresh backup token: $this")
}
}

View file

@ -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.RestoreSet
import android.content.Context
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.backup.header.UnsupportedVersionException
import com.stevesoltys.backup.metadata.DecryptionFailedException
import com.stevesoltys.backup.metadata.MetadataReader
import com.stevesoltys.backup.settings.getBackupToken
import com.stevesoltys.backup.settings.SettingsManager
import libcore.io.IoUtils.closeQuietly
import java.io.IOException
@ -23,7 +22,7 @@ private class RestoreCoordinatorState(
private val TAG = RestoreCoordinator::class.java.simpleName
internal class RestoreCoordinator(
private val context: Context,
private val settingsManager: SettingsManager,
private val plugin: RestorePlugin,
private val kv: KVRestore,
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.
*/
fun getCurrentRestoreSet(): Long {
return getBackupToken(context)
return settingsManager.getBackupToken()
.apply { Log.i(TAG, "Got current restore set token: $this") }
}

View file

@ -1,17 +1,14 @@
package com.stevesoltys.backup.ui.storage
import android.app.ActivityManager
import android.app.Application
import android.app.backup.BackupProgress
import android.app.backup.IBackupObserver
import android.net.Uri
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.backup.Backup
import com.stevesoltys.backup.R
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
import com.stevesoltys.backup.transport.TRANSPORT_ID
private val TAG = BackupStorageViewModel::class.java.simpleName
@ -24,7 +21,7 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
saveStorage(uri)
// use a new backup token
getAndSaveNewBackupToken(app)
settingsManager.getAndSaveNewBackupToken()
// initialize the new location
val observer = InitializationObserver()

View file

@ -12,9 +12,11 @@ import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.stevesoltys.backup.Backup
import com.stevesoltys.backup.R
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.ui.LiveEvent
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 {
protected val settingsManager = (app as Backup).settingsManager
private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
internal val storageRoots: LiveData<List<StorageRoot>> get() = mStorageRoots
@ -39,7 +43,8 @@ internal abstract class StorageViewModel(private val app: Application) : Android
companion object {
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
return storage.getDocumentFile(context).isDirectory
}
@ -84,12 +89,15 @@ internal abstract class StorageViewModel(private val app: Application) : Android
root.title
}
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) {
val wasSaved = saveUsbDevice()
// 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
@ -102,7 +110,7 @@ internal abstract class StorageViewModel(private val app: Application) : Android
val manager = app.getSystemService(USB_SERVICE) as UsbManager
manager.deviceList.values.forEach { device ->
if (device.isMassStorage()) {
setFlashDrive(app, FlashDrive.from(device))
settingsManager.setFlashDrive(FlashDrive.from(device))
return true
}
}

View file

@ -31,6 +31,8 @@
<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_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_auto_restore_title">Automatic restore</string>
<string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string>

View file

@ -43,14 +43,14 @@ internal class CoordinatorIntegrationTest : TransportTest() {
private val fullBackupPlugin = mockk<FullBackupPlugin>()
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
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 kvRestorePlugin = mockk<KVRestorePlugin>()
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
private val fullRestorePlugin = mockk<FullRestorePlugin>()
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 fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
@ -91,6 +91,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
appData2.size
}
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key264) } returns bOutputStream2
every { settingsManager.saveNewBackupTime() } just Runs
// start and finish K/V backup
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
@ -130,6 +131,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
every { fullBackupPlugin.getOutputStream(packageInfo) } returns bOutputStream
every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream
every { fullBackupPlugin.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
every { settingsManager.saveNewBackupTime() } just Runs
// perform backup to output stream
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import com.stevesoltys.backup.crypto.Crypto
import com.stevesoltys.backup.settings.SettingsManager
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@ -14,6 +15,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD
abstract class TransportTest {
protected val crypto = mockk<Crypto>()
protected val settingsManager = mockk<SettingsManager>()
protected val context = mockk<Context>(relaxed = true)
protected val packageInfo = PackageInfo().apply { packageName = "org.example" }

View file

@ -23,14 +23,15 @@ internal class BackupCoordinatorTest: BackupTest() {
private val metadataWriter = mockk<MetadataWriter>()
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>()
@Test
fun `device initialization succeeds and delegates to plugin`() {
every { plugin.initializeDevice() } just Runs
expectWritingMetadata(0L)
every { settingsManager.getBackupToken() } returns token
expectWritingMetadata(token)
every { kv.hasState() } returns false
every { full.hasState() } returns false

View file

@ -27,7 +27,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
private val full = mockk<FullRestore>()
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 inputStream = mockk<InputStream>()
@ -56,8 +56,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `getCurrentRestoreSet() delegates to plugin`() {
// We don't mock the SettingsManager, so the default value is returned here
assertEquals(0L, restore.getCurrentRestoreSet())
every { settingsManager.getBackupToken() } returns token
assertEquals(token, restore.getCurrentRestoreSet())
}
@Test