Use dependency injection with Koin

This commit is contained in:
Torsten Grote 2019-12-16 13:11:11 -03:00
parent bb9d498ea8
commit 94c7663daf
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
59 changed files with 381 additions and 327 deletions

View file

@ -109,6 +109,7 @@ dependencies {
implementation 'commons-io:commons-io:2.6' implementation 'commons-io:commons-io:2.6'
implementation 'io.github.novacrypto:BIP39:2019.01.27' implementation 'io.github.novacrypto:BIP39:2019.01.27'
implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
implementation 'androidx.core:core-ktx:1.1.0' implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.preference:preference-ktx:1.1.0' implementation 'androidx.preference:preference-ktx:1.1.0'

View file

@ -3,23 +3,26 @@ package com.stevesoltys.seedvault
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.seedvault.transport.backup.plugins.DocumentsStorage import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.plugins.createOrGetFile import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage
import com.stevesoltys.seedvault.plugins.saf.createOrGetFile
import org.junit.After import org.junit.After
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.koin.core.KoinComponent
import org.koin.core.inject
import kotlin.random.Random import kotlin.random.Random
private const val filename = "test-file" private const val filename = "test-file"
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class DocumentsStorageTest { class DocumentsStorageTest : KoinComponent {
private val context = InstrumentationRegistry.getInstrumentation().targetContext private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val settingsManager = (context.applicationContext as Backup).settingsManager private val settingsManager by inject<SettingsManager>()
private val storage = DocumentsStorage(context, settingsManager) private val storage = DocumentsStorage(context, settingsManager)
private lateinit var file: DocumentFile private lateinit var file: DocumentFile

View file

@ -25,7 +25,7 @@
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<application <application
android:name=".Backup" android:name=".App"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"

View file

@ -0,0 +1,66 @@
package com.stevesoltys.seedvault
import android.app.Application
import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL
import android.app.backup.IBackupManager
import android.content.Context.BACKUP_SERVICE
import android.os.Build
import android.os.ServiceManager.getService
import com.stevesoltys.seedvault.crypto.cryptoModule
import com.stevesoltys.seedvault.header.headerModule
import com.stevesoltys.seedvault.metadata.metadataModule
import com.stevesoltys.seedvault.restore.RestoreViewModel
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.SettingsViewModel
import com.stevesoltys.seedvault.transport.backup.backupModule
import com.stevesoltys.seedvault.plugins.saf.documentsProviderModule
import com.stevesoltys.seedvault.transport.restore.restoreModule
import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel
import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel
import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.dsl.module
/**
* @author Steve Soltys
* @author Torsten Grote
*/
class App : Application() {
private val appModule = module {
single { SettingsManager(this@App) }
single { BackupNotificationManager(this@App) }
factory<IBackupManager> { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) }
viewModel { SettingsViewModel(this@App, get(), get()) }
viewModel { RecoveryCodeViewModel(this@App, get()) }
viewModel { BackupStorageViewModel(this@App, get(), get()) }
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
viewModel { RestoreViewModel(this@App, get(), get(), get()) }
}
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@App)
modules(listOf(
cryptoModule,
headerModule,
metadataModule,
documentsProviderModule, // storage plugin
backupModule,
restoreModule,
appModule
))
}
}
}
const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
fun isDebugBuild() = Build.TYPE == "userdebug"

View file

@ -1,39 +0,0 @@
package com.stevesoltys.seedvault
import android.app.Application
import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL
import android.app.backup.IBackupManager
import android.content.Context.BACKUP_SERVICE
import android.os.Build
import android.os.ServiceManager.getService
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.crypto.KeyManagerImpl
import com.stevesoltys.seedvault.settings.SettingsManager
/**
* @author Steve Soltys
* @author Torsten Grote
*/
class Backup : Application() {
companion object {
val backupManager: IBackupManager by lazy {
IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE))
}
val keyManager: KeyManager by lazy {
KeyManagerImpl()
}
}
val settingsManager by lazy {
SettingsManager(this)
}
val notificationManager by lazy {
BackupNotificationManager(this)
}
}
const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
fun isDebugBuild() = Build.TYPE == "userdebug"

View file

@ -7,13 +7,15 @@ import android.content.pm.PackageManager
import android.util.Log import android.util.Log
import android.util.Log.INFO import android.util.Log.INFO
import android.util.Log.isLoggable import android.util.Log.isLoggable
import org.koin.core.KoinComponent
import org.koin.core.inject
private val TAG = NotificationBackupObserver::class.java.simpleName private val TAG = NotificationBackupObserver::class.java.simpleName
class NotificationBackupObserver(context: Context, private val userInitiated: Boolean) : IBackupObserver.Stub() { class NotificationBackupObserver(context: Context, private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent {
private val pm = context.packageManager private val pm = context.packageManager
private val nm = (context.applicationContext as Backup).notificationManager private val nm: BackupNotificationManager by inject()
/** /**
* This method could be called several times for packages with full data backup. * This method could be called several times for packages with full data backup.

View file

@ -12,19 +12,23 @@ import android.os.Handler
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.util.Log import android.util.Log
import com.stevesoltys.seedvault.settings.FlashDrive import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.requestBackup import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit.HOURS import java.util.concurrent.TimeUnit.HOURS
private val TAG = UsbIntentReceiver::class.java.simpleName private val TAG = UsbIntentReceiver::class.java.simpleName
class UsbIntentReceiver : UsbMonitor() { class UsbIntentReceiver : UsbMonitor(), KoinComponent {
private val settingsManager by inject<SettingsManager>()
override fun shouldMonitorStatus(context: Context, action: String, device: UsbDevice): Boolean { override fun shouldMonitorStatus(context: Context, action: String, device: UsbDevice): Boolean {
if (action != ACTION_USB_DEVICE_ATTACHED) return false if (action != ACTION_USB_DEVICE_ATTACHED) return false
Log.d(TAG, "Checking if this is the current backup drive.") Log.d(TAG, "Checking if this is the current backup drive.")
val settingsManager = (context.applicationContext as Backup).settingsManager
val savedFlashDrive = settingsManager.getFlashDrive() ?: return false val savedFlashDrive = settingsManager.getFlashDrive() ?: return false
val attachedFlashDrive = FlashDrive.from(device) val attachedFlashDrive = FlashDrive.from(device)
return if (savedFlashDrive == attachedFlashDrive) { return if (savedFlashDrive == attachedFlashDrive) {

View file

@ -13,7 +13,7 @@ interface CipherFactory {
fun createDecryptionCipher(iv: ByteArray): Cipher fun createDecryptionCipher(iv: ByteArray): Cipher
} }
class CipherFactoryImpl(private val keyManager: KeyManager) : CipherFactory { internal class CipherFactoryImpl(private val keyManager: KeyManager) : CipherFactory {
override fun createEncryptionCipher(): Cipher { override fun createEncryptionCipher(): Cipher {
return Cipher.getInstance(CIPHER_TRANSFORMATION).apply { return Cipher.getInstance(CIPHER_TRANSFORMATION).apply {

View file

@ -71,7 +71,7 @@ interface Crypto {
fun decryptSegment(inputStream: InputStream): ByteArray fun decryptSegment(inputStream: InputStream): ByteArray
} }
class CryptoImpl( internal class CryptoImpl(
private val cipherFactory: CipherFactory, private val cipherFactory: CipherFactory,
private val headerWriter: HeaderWriter, private val headerWriter: HeaderWriter,
private val headerReader: HeaderReader) : Crypto { private val headerReader: HeaderReader) : Crypto {

View file

@ -0,0 +1,9 @@
package com.stevesoltys.seedvault.crypto
import org.koin.dsl.module
val cryptoModule = module {
factory<CipherFactory> { CipherFactoryImpl(get()) }
single<KeyManager> { KeyManagerImpl() }
single<Crypto> { CryptoImpl(get(), get(), get()) }
}

View file

@ -35,7 +35,7 @@ interface KeyManager {
fun getBackupKey(): SecretKey fun getBackupKey(): SecretKey
} }
class KeyManagerImpl : KeyManager { internal class KeyManagerImpl : KeyManager {
private val keyStore by lazy { private val keyStore by lazy {
KeyStore.getInstance(ANDROID_KEY_STORE).apply { KeyStore.getInstance(ANDROID_KEY_STORE).apply {

View file

@ -0,0 +1,8 @@
package com.stevesoltys.seedvault.header
import org.koin.dsl.module
val headerModule = module {
single<HeaderWriter> { HeaderWriterImpl() }
single<HeaderReader> { HeaderReaderImpl() }
}

View file

@ -17,7 +17,7 @@ internal const val JSON_TOKEN = "token"
internal const val JSON_ANDROID_VERSION = "androidVersion" internal const val JSON_ANDROID_VERSION = "androidVersion"
internal const val JSON_DEVICE_NAME = "deviceName" internal const val JSON_DEVICE_NAME = "deviceName"
class DecryptionFailedException(cause: Throwable) : Exception(cause) internal class DecryptionFailedException(cause: Throwable) : Exception(cause)
class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) { class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) {
constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false) constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false)

View file

@ -0,0 +1,8 @@
package com.stevesoltys.seedvault.metadata
import org.koin.dsl.module
val metadataModule = module {
single<MetadataWriter> { MetadataWriterImpl(get()) }
single<MetadataReader> { MetadataReaderImpl(get()) }
}

View file

@ -18,7 +18,7 @@ interface MetadataReader {
} }
class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
@Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class) @Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class)
override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata { override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata {

View file

@ -14,7 +14,7 @@ interface MetadataWriter {
} }
class MetadataWriterImpl(private val crypto: Crypto): MetadataWriter { internal class MetadataWriterImpl(private val crypto: Crypto): MetadataWriter {
@Throws(IOException::class) @Throws(IOException::class)
override fun write(outputStream: OutputStream, token: Long) { override fun write(outputStream: OutputStream, token: Long) {

View file

@ -1,4 +1,4 @@
package com.stevesoltys.seedvault.transport.backup.plugins package com.stevesoltys.seedvault.plugins.saf
import android.content.pm.PackageManager import android.content.pm.PackageManager
import com.stevesoltys.seedvault.transport.backup.BackupPlugin import com.stevesoltys.seedvault.transport.backup.BackupPlugin
@ -7,7 +7,7 @@ import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
class DocumentsProviderBackupPlugin( internal class DocumentsProviderBackupPlugin(
private val storage: DocumentsStorage, private val storage: DocumentsStorage,
packageManager: PackageManager) : BackupPlugin { packageManager: PackageManager) : BackupPlugin {

View file

@ -1,4 +1,4 @@
package com.stevesoltys.seedvault.transport.backup.plugins package com.stevesoltys.seedvault.plugins.saf
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.util.Log import android.util.Log
@ -9,7 +9,7 @@ import java.io.OutputStream
private val TAG = DocumentsProviderFullBackup::class.java.simpleName private val TAG = DocumentsProviderFullBackup::class.java.simpleName
class DocumentsProviderFullBackup( internal class DocumentsProviderFullBackup(
private val storage: DocumentsStorage) : FullBackupPlugin { private val storage: DocumentsStorage) : FullBackupPlugin {
override fun getQuota() = DEFAULT_QUOTA_FULL_BACKUP override fun getQuota() = DEFAULT_QUOTA_FULL_BACKUP

View file

@ -1,12 +1,11 @@
package com.stevesoltys.seedvault.transport.restore.plugins package com.stevesoltys.seedvault.plugins.saf
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
class DocumentsProviderFullRestorePlugin( internal class DocumentsProviderFullRestorePlugin(
private val documentsStorage: DocumentsStorage) : FullRestorePlugin { private val documentsStorage: DocumentsStorage) : FullRestorePlugin {
@Throws(IOException::class) @Throws(IOException::class)

View file

@ -1,4 +1,4 @@
package com.stevesoltys.seedvault.transport.backup.plugins package com.stevesoltys.seedvault.plugins.saf
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
@ -7,7 +7,7 @@ import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin { internal class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin {
private var packageFile: DocumentFile? = null private var packageFile: DocumentFile? = null

View file

@ -1,14 +1,12 @@
package com.stevesoltys.seedvault.transport.restore.plugins package com.stevesoltys.seedvault.plugins.saf
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.seedvault.transport.backup.plugins.assertRightFile
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin { internal class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin {
private var packageDir: DocumentFile? = null private var packageDir: DocumentFile? = null

View file

@ -0,0 +1,12 @@
package com.stevesoltys.seedvault.plugins.saf
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val documentsProviderModule = module {
single { DocumentsStorage(androidContext(), get()) }
single<BackupPlugin> { DocumentsProviderBackupPlugin(get(), androidContext().packageManager) }
single<RestorePlugin> { DocumentsProviderRestorePlugin(androidContext(), get()) }
}

View file

@ -0,0 +1,89 @@
package com.stevesoltys.seedvault.plugins.saf
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
import java.io.IOException
private val TAG = DocumentsProviderRestorePlugin::class.java.simpleName
internal class DocumentsProviderRestorePlugin(
private val context: Context,
private val storage: DocumentsStorage) : RestorePlugin {
override val kvRestorePlugin: KVRestorePlugin by lazy {
DocumentsProviderKVRestorePlugin(storage)
}
override val fullRestorePlugin: FullRestorePlugin by lazy {
DocumentsProviderFullRestorePlugin(storage)
}
@WorkerThread
override fun hasBackup(uri: Uri): Boolean {
val parent = DocumentFile.fromTreeUri(context, uri) ?: throw AssertionError()
val rootDir = parent.findFileBlocking(context, DIRECTORY_ROOT) ?: return false
val backupSets = getBackups(context, rootDir)
return backupSets.isNotEmpty()
}
override fun getAvailableBackups(): Sequence<EncryptedBackupMetadata>? {
val rootDir = storage.rootBackupDir ?: return null
val backupSets = getBackups(context, rootDir)
val iterator = backupSets.iterator()
return generateSequence {
if (!iterator.hasNext()) return@generateSequence null // end sequence
val backupSet = iterator.next()
try {
val stream = storage.getInputStream(backupSet.metadataFile)
EncryptedBackupMetadata(backupSet.token, stream)
} catch (e: IOException) {
Log.e(TAG, "Error getting InputStream for backup metadata.", e)
EncryptedBackupMetadata(backupSet.token)
}
}
}
@WorkerThread
fun getBackups(context: Context, rootDir: DocumentFile): List<BackupSet> {
val backupSets = ArrayList<BackupSet>()
val files = try {
// block until the DocumentsProvider has results
rootDir.listFilesBlocking(context)
} catch (e: IOException) {
Log.e(TAG, "Error loading backups from storage", e)
return backupSets
}
for (set in files) {
if (!set.isDirectory || set.name == null) {
if (set.name != FILE_NO_MEDIA) {
Log.w(TAG, "Found invalid backup set folder: ${set.name}")
}
continue
}
val token = try {
set.name!!.toLong()
} catch (e: NumberFormatException) {
Log.w(TAG, "Found invalid backup set folder: ${set.name}")
continue
}
// block until children of set are available
val metadata = set.findFileBlocking(context, FILE_BACKUP_METADATA)
if (metadata == null) {
Log.w(TAG, "Missing metadata file in backup set folder: ${set.name}")
} else {
backupSets.add(BackupSet(token, metadata))
}
}
return backupSets
}
}
class BackupSet(val token: Long, val metadataFile: DocumentFile)

View file

@ -1,4 +1,4 @@
package com.stevesoltys.seedvault.transport.backup.plugins package com.stevesoltys.seedvault.plugins.saf
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@ -26,7 +26,9 @@ 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, private val settingsManager: SettingsManager) { internal class DocumentsStorage(
private val context: Context,
private val settingsManager: SettingsManager) {
private val storage: Storage? = settingsManager.getStorage() private val storage: Storage? = settingsManager.getStorage()
private val token: Long = settingsManager.getBackupToken() private val token: Long = settingsManager.getBackupToken()

View file

@ -3,19 +3,18 @@ package com.stevesoltys.seedvault.restore
import android.os.Bundle import android.os.Bundle
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
class RestoreActivity : RequireProvisioningActivity() { class RestoreActivity : RequireProvisioningActivity() {
private lateinit var viewModel: RestoreViewModel private val viewModel: RestoreViewModel by viewModel()
override fun getViewModel(): RequireProvisioningViewModel = viewModel override fun getViewModel(): RequireProvisioningViewModel = viewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
viewModel = ViewModelProviders.of(this).get(RestoreViewModel::class.java)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (isSetupWizard) hideSystemUI() if (isSetupWizard) hideSystemUI()

View file

@ -10,16 +10,18 @@ import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 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 com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.getAppName import com.stevesoltys.seedvault.getAppName
import com.stevesoltys.seedvault.isDebugBuild import com.stevesoltys.seedvault.isDebugBuild
import com.stevesoltys.seedvault.settings.SettingsManager
import kotlinx.android.synthetic.main.fragment_restore_progress.* import kotlinx.android.synthetic.main.fragment_restore_progress.*
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RestoreProgressFragment : Fragment() { class RestoreProgressFragment : Fragment() {
private lateinit var viewModel: RestoreViewModel private val viewModel: RestoreViewModel by sharedViewModel()
private val settingsManager: SettingsManager by inject()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@ -32,8 +34,6 @@ class RestoreProgressFragment : Fragment() {
// decryption will fail when the device is locked, so keep the screen on to prevent locking // decryption will fail when the device is locked, so keep the screen on to prevent locking
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON) requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
viewModel = ViewModelProviders.of(requireActivity()).get(RestoreViewModel::class.java)
viewModel.chosenRestoreSet.observe(this, Observer { set -> viewModel.chosenRestoreSet.observe(this, Observer { set ->
backupNameView.text = set.device backupNameView.text = set.device
}) })
@ -50,7 +50,6 @@ 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)
val settingsManager = (requireContext().applicationContext as Backup).settingsManager
warningView.text = if (settingsManager.getStorage()?.isUsb == true) { warningView.text = if (settingsManager.getStorage()?.isUsb == 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 {

View file

@ -9,13 +9,13 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import kotlinx.android.synthetic.main.fragment_restore_set.* import kotlinx.android.synthetic.main.fragment_restore_set.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RestoreSetFragment : Fragment() { class RestoreSetFragment : Fragment() {
private lateinit var viewModel: RestoreViewModel private val viewModel: RestoreViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@ -24,7 +24,6 @@ class RestoreSetFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(RestoreViewModel::class.java)
viewModel.restoreSets.observe(this, Observer { result -> onRestoreSetsLoaded(result) }) viewModel.restoreSets.observe(this, Observer { result -> onRestoreSetsLoaded(result) })

View file

@ -1,6 +1,7 @@
package com.stevesoltys.seedvault.restore package com.stevesoltys.seedvault.restore
import android.app.Application import android.app.Application
import android.app.backup.IBackupManager
import android.app.backup.IRestoreObserver import android.app.backup.IRestoreObserver
import android.app.backup.IRestoreSession import android.app.backup.IRestoreSession
import android.app.backup.RestoreSet import android.app.backup.RestoreSet
@ -9,20 +10,25 @@ import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.TRANSPORT_ID import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
private val TAG = RestoreViewModel::class.java.simpleName private val TAG = RestoreViewModel::class.java.simpleName
class RestoreViewModel(app: Application) : RequireProvisioningViewModel(app), RestoreSetClickListener { internal class RestoreViewModel(
app: Application,
settingsManager: SettingsManager,
keyManager: KeyManager,
private val backupManager: IBackupManager
) : RequireProvisioningViewModel(app, settingsManager, keyManager), RestoreSetClickListener {
override val isRestoreOperation = true override val isRestoreOperation = true
private val backupManager = Backup.backupManager
private var session: IRestoreSession? = null private var session: IRestoreSession? = null
private var observer: RestoreObserver? = null private var observer: RestoreObserver? = null
private val monitor = BackupMonitor() private val monitor = BackupMonitor()

View file

@ -2,7 +2,6 @@ package com.stevesoltys.seedvault.settings
import android.content.ContentResolver import android.content.ContentResolver
import android.provider.Settings import android.provider.Settings
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.DAYS import java.util.concurrent.TimeUnit.DAYS
private val SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS private val SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS

View file

@ -2,20 +2,21 @@ package com.stevesoltys.seedvault.settings
import android.os.Bundle import android.os.Bundle
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.lifecycle.ViewModelProviders import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
class SettingsActivity : RequireProvisioningActivity() { class SettingsActivity : RequireProvisioningActivity() {
private lateinit var viewModel: SettingsViewModel private val viewModel: SettingsViewModel by viewModel()
private val notificationManager: BackupNotificationManager by inject()
override fun getViewModel(): RequireProvisioningViewModel = viewModel override fun getViewModel(): RequireProvisioningViewModel = viewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
viewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fragment_container) setContentView(R.layout.activity_fragment_container)
@ -36,7 +37,7 @@ class SettingsActivity : RequireProvisioningActivity() {
} else if (!viewModel.validLocationIsSet()) { } else if (!viewModel.validLocationIsSet()) {
showStorageActivity() showStorageActivity()
// remove potential error notifications // remove potential error notifications
(application as Backup).notificationManager.onBackupErrorSeen() notificationManager.onBackupErrorSeen()
} }
} }

View file

@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.settings package com.stevesoltys.seedvault.settings
import android.app.backup.IBackupManager
import android.content.Context import android.content.Context
import android.content.Context.BACKUP_SERVICE import android.content.Context.BACKUP_SERVICE
import android.content.Intent import android.content.Intent
@ -17,26 +18,25 @@ import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.lifecycle.ViewModelProviders
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceChangeListener import androidx.preference.Preference.OnPreferenceChangeListener
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference import androidx.preference.TwoStatePreference
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.UsbMonitor import com.stevesoltys.seedvault.UsbMonitor
import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.restore.RestoreActivity import com.stevesoltys.seedvault.restore.RestoreActivity
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import java.util.* import java.util.*
private val TAG = SettingsFragment::class.java.name private val TAG = SettingsFragment::class.java.name
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private val backupManager = Backup.backupManager private val viewModel: SettingsViewModel by sharedViewModel()
private val settingsManager: SettingsManager by inject()
private lateinit var viewModel: SettingsViewModel private val backupManager: IBackupManager by inject()
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
@ -63,9 +63,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.settings, rootKey) setPreferencesFromResource(R.xml.settings, rootKey)
setHasOptionsMenu(true) setHasOptionsMenu(true)
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 ->
val enabled = newValue as Boolean val enabled = newValue as Boolean

View file

@ -18,12 +18,12 @@ 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_TIME = "backupTime"
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
class SettingsManager(context: Context) { class SettingsManager(context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// FIXME Storage is currently plugin specific and not generic
fun setStorage(storage: Storage) { fun setStorage(storage: Storage) {
prefs.edit() prefs.edit()
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString()) .putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
@ -108,11 +108,6 @@ class SettingsManager(context: Context) {
return prefs.getLong(PREF_KEY_BACKUP_TIME, 0L) 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(

View file

@ -1,10 +1,15 @@
package com.stevesoltys.seedvault.settings package com.stevesoltys.seedvault.settings
import android.app.Application import android.app.Application
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.transport.requestBackup import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
class SettingsViewModel(app: Application) : RequireProvisioningViewModel(app) { class SettingsViewModel(
app: Application,
settingsManager: SettingsManager,
keyManager: KeyManager
) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
override val isRestoreOperation = false override val isRestoreOperation = false

View file

@ -10,6 +10,10 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log import android.util.Log
import com.stevesoltys.seedvault.settings.SettingsActivity import com.stevesoltys.seedvault.settings.SettingsActivity
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
import org.koin.core.KoinComponent
import org.koin.core.inject
val TRANSPORT_ID: String = ConfigurableBackupTransport::class.java.name val TRANSPORT_ID: String = ConfigurableBackupTransport::class.java.name
@ -20,11 +24,10 @@ private val TAG = ConfigurableBackupTransport::class.java.simpleName
* @author Steve Soltys * @author Steve Soltys
* @author Torsten Grote * @author Torsten Grote
*/ */
class ConfigurableBackupTransport internal constructor(private val context: Context) : BackupTransport() { class ConfigurableBackupTransport internal constructor(private val context: Context) : BackupTransport(), KoinComponent {
private val pluginManager = PluginManager(context) private val backupCoordinator by inject<BackupCoordinator>()
private val backupCoordinator = pluginManager.backupCoordinator private val restoreCoordinator by inject<RestoreCoordinator>()
private val restoreCoordinator = pluginManager.restoreCoordinator
override fun transportDirName(): String { override fun transportDirName(): String {
return TRANSPORT_DIRECTORY_NAME return TRANSPORT_DIRECTORY_NAME

View file

@ -2,19 +2,21 @@ package com.stevesoltys.seedvault.transport
import android.app.Service import android.app.Service
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Context
import android.content.Context.BACKUP_SERVICE
import android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP import android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP
import android.app.backup.BackupTransport.FLAG_USER_INITIATED import android.app.backup.BackupTransport.FLAG_USER_INITIATED
import android.app.backup.IBackupManager
import android.content.Context
import android.content.Context.BACKUP_SERVICE
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import android.os.RemoteException import android.os.RemoteException
import android.util.Log import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.Backup import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.NotificationBackupObserver import com.stevesoltys.seedvault.NotificationBackupObserver
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.BackupMonitor import org.koin.core.context.GlobalContext.get
private val TAG = ConfigurableBackupTransportService::class.java.simpleName private val TAG = ConfigurableBackupTransportService::class.java.simpleName
@ -50,17 +52,18 @@ class ConfigurableBackupTransportService : Service() {
@WorkerThread @WorkerThread
fun requestBackup(context: Context) { fun requestBackup(context: Context) {
// show notification // show notification
val nm = (context.applicationContext as Backup).notificationManager val nm: BackupNotificationManager = get().koin.get()
nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true) nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true)
val observer = NotificationBackupObserver(context, true) val observer = NotificationBackupObserver(context, true)
val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED
val packages = PackageService().eligiblePackages val packages = PackageService.eligiblePackages
val result = try { val result = try {
Backup.backupManager.requestBackup(packages, observer, BackupMonitor(), flags) val backupManager: IBackupManager = get().koin.get()
backupManager.requestBackup(packages, observer, BackupMonitor(), flags)
} catch (e: RemoteException) { } catch (e: RemoteException) {
// TODO show notification on backup error
Log.e(TAG, "Error during backup: ", e) Log.e(TAG, "Error during backup: ", e)
nm.onBackupError()
} }
if (result == BackupManager.SUCCESS) { if (result == BackupManager.SUCCESS) {
Log.i(TAG, "Backup succeeded ") Log.i(TAG, "Backup succeeded ")

View file

@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.transport package com.stevesoltys.seedvault.transport
import android.app.backup.IBackupManager
import android.content.pm.IPackageManager import android.content.pm.IPackageManager
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.RemoteException import android.os.RemoteException
@ -7,8 +8,9 @@ import android.os.ServiceManager.getService
import android.os.UserHandle import android.os.UserHandle
import android.util.Log import android.util.Log
import com.google.android.collect.Sets.newArraySet import com.google.android.collect.Sets.newArraySet
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.* import java.util.*
private val TAG = PackageService::class.java.simpleName private val TAG = PackageService::class.java.simpleName
@ -27,9 +29,9 @@ private val IGNORED_PACKAGES = newArraySet(
* @author Steve Soltys * @author Steve Soltys
* @author Torsten Grote * @author Torsten Grote
*/ */
internal class PackageService { internal object PackageService : KoinComponent {
private val backupManager = Backup.backupManager private val backupManager: IBackupManager by inject()
private val packageManager: IPackageManager = IPackageManager.Stub.asInterface(getService("package")) private val packageManager: IPackageManager = IPackageManager.Stub.asInterface(getService("package"))
val eligiblePackages: Array<String> val eligiblePackages: Array<String>

View file

@ -1,54 +0,0 @@
package com.stevesoltys.seedvault.transport
import android.content.Context
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
import com.stevesoltys.seedvault.crypto.CryptoImpl
import com.stevesoltys.seedvault.header.HeaderReaderImpl
import com.stevesoltys.seedvault.header.HeaderWriterImpl
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
import com.stevesoltys.seedvault.metadata.MetadataWriterImpl
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import com.stevesoltys.seedvault.transport.backup.FullBackup
import com.stevesoltys.seedvault.transport.backup.InputFactory
import com.stevesoltys.seedvault.transport.backup.KVBackup
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsProviderBackupPlugin
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.seedvault.transport.restore.FullRestore
import com.stevesoltys.seedvault.transport.restore.KVRestore
import com.stevesoltys.seedvault.transport.restore.OutputFactory
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
import com.stevesoltys.seedvault.transport.restore.plugins.DocumentsProviderRestorePlugin
class PluginManager(context: Context) {
// We can think about using an injection framework such as Dagger, Koin or Kodein to simplify this.
private val settingsManager = (context.applicationContext as Backup).settingsManager
private val storage = DocumentsStorage(context, settingsManager)
private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl()
private val cipherFactory = CipherFactoryImpl(Backup.keyManager)
private val crypto = CryptoImpl(cipherFactory, headerWriter, headerReader)
private val metadataWriter = MetadataWriterImpl(crypto)
private val metadataReader = MetadataReaderImpl(crypto)
private val backupPlugin = DocumentsProviderBackupPlugin(storage, context.packageManager)
private val inputFactory = InputFactory()
private val kvBackup = KVBackup(backupPlugin.kvBackupPlugin, inputFactory, headerWriter, crypto)
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, settingsManager, notificationManager)
private val restorePlugin = DocumentsProviderRestorePlugin(storage)
private val outputFactory = OutputFactory()
private val kvRestore = KVRestore(restorePlugin.kvRestorePlugin, outputFactory, headerReader, crypto)
private val fullRestore = FullRestore(restorePlugin.fullRestorePlugin, outputFactory, headerReader, crypto)
internal val restoreCoordinator = RestoreCoordinator(context, settingsManager, restorePlugin, kvRestore, fullRestore, metadataReader)
}

View file

@ -18,7 +18,7 @@ private val TAG = BackupCoordinator::class.java.simpleName
* @author Steve Soltys * @author Steve Soltys
* @author Torsten Grote * @author Torsten Grote
*/ */
class BackupCoordinator( internal class BackupCoordinator(
private val context: Context, private val context: Context,
private val plugin: BackupPlugin, private val plugin: BackupPlugin,
private val kv: KVBackup, private val kv: KVBackup,

View file

@ -0,0 +1,11 @@
package com.stevesoltys.seedvault.transport.backup
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val backupModule = module {
single { InputFactory() }
single { KVBackup(get<BackupPlugin>().kvBackupPlugin, get(), get(), get()) }
single { FullBackup(get<BackupPlugin>().fullBackupPlugin, get(), get(), get()) }
single { BackupCoordinator(androidContext(), get(), get(), get(), get(), get(), get()) }
}

View file

@ -26,7 +26,7 @@ const val DEFAULT_QUOTA_FULL_BACKUP = (2 * (25 * 1024 * 1024)).toLong()
private val TAG = FullBackup::class.java.simpleName private val TAG = FullBackup::class.java.simpleName
class FullBackup( internal class FullBackup(
private val plugin: FullBackupPlugin, private val plugin: FullBackupPlugin,
private val inputFactory: InputFactory, private val inputFactory: InputFactory,
private val headerWriter: HeaderWriter, private val headerWriter: HeaderWriter,

View file

@ -8,7 +8,7 @@ import java.io.InputStream
/** /**
* This class exists for easier testing, so we can mock it and return custom data inputs. * This class exists for easier testing, so we can mock it and return custom data inputs.
*/ */
class InputFactory { internal class InputFactory {
fun getBackupDataInput(inputFileDescriptor: ParcelFileDescriptor): BackupDataInput { fun getBackupDataInput(inputFileDescriptor: ParcelFileDescriptor): BackupDataInput {
return BackupDataInput(inputFileDescriptor.fileDescriptor) return BackupDataInput(inputFileDescriptor.fileDescriptor)

View file

@ -17,7 +17,7 @@ const val DEFAULT_QUOTA_KEY_VALUE_BACKUP = (2 * (5 * 1024 * 1024)).toLong()
private val TAG = KVBackup::class.java.simpleName private val TAG = KVBackup::class.java.simpleName
class KVBackup( internal class KVBackup(
private val plugin: KVBackupPlugin, private val plugin: KVBackupPlugin,
private val inputFactory: InputFactory, private val inputFactory: InputFactory,
private val headerWriter: HeaderWriter, private val headerWriter: HeaderWriter,

View file

@ -8,7 +8,7 @@ import java.io.OutputStream
/** /**
* This class exists for easier testing, so we can mock it and return custom data outputs. * This class exists for easier testing, so we can mock it and return custom data outputs.
*/ */
class OutputFactory { internal class OutputFactory {
fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput { fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput {
return BackupDataOutput(outputFileDescriptor.fileDescriptor) return BackupDataOutput(outputFileDescriptor.fileDescriptor)

View file

@ -5,7 +5,6 @@ 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
@ -23,7 +22,6 @@ 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 settingsManager: SettingsManager,
private val plugin: RestorePlugin, private val plugin: RestorePlugin,
private val kv: KVRestore, private val kv: KVRestore,
@ -39,7 +37,7 @@ internal class RestoreCoordinator(
* or null if an error occurred (the attempt should be rescheduled). * or null if an error occurred (the attempt should be rescheduled).
**/ **/
fun getAvailableRestoreSets(): Array<RestoreSet>? { fun getAvailableRestoreSets(): Array<RestoreSet>? {
val availableBackups = plugin.getAvailableBackups(context) ?: return null val availableBackups = plugin.getAvailableBackups() ?: return null
val restoreSets = ArrayList<RestoreSet>() val restoreSets = ArrayList<RestoreSet>()
for (encryptedMetadata in availableBackups) { for (encryptedMetadata in availableBackups) {
if (encryptedMetadata.error) continue if (encryptedMetadata.error) continue

View file

@ -0,0 +1,10 @@
package com.stevesoltys.seedvault.transport.restore
import org.koin.dsl.module
val restoreModule = module {
single { OutputFactory() }
single { KVRestore(get<RestorePlugin>().kvRestorePlugin, get(), get(), get()) }
single { FullRestore(get<RestorePlugin>().fullRestorePlugin, get(), get(), get()) }
single { RestoreCoordinator(get(), get(), get(), get(), get()) }
}

View file

@ -1,6 +1,7 @@
package com.stevesoltys.seedvault.transport.restore package com.stevesoltys.seedvault.transport.restore
import android.content.Context import android.net.Uri
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
interface RestorePlugin { interface RestorePlugin {
@ -15,6 +16,15 @@ interface RestorePlugin {
* @return metadata for the set of restore images available, * @return metadata for the set of restore images available,
* or null if an error occurred (the attempt should be rescheduled). * or null if an error occurred (the attempt should be rescheduled).
**/ **/
fun getAvailableBackups(context: Context): Sequence<EncryptedBackupMetadata>? fun getAvailableBackups(): Sequence<EncryptedBackupMetadata>?
/**
* Searches if there's really a backup available in the given location.
* Returns true if at least one was found and false otherwise.
*
* FIXME: Passing a Uri is maybe too plugin-specific?
*/
@WorkerThread
fun hasBackup(uri: Uri): Boolean
} }

View file

@ -1,81 +0,0 @@
package com.stevesoltys.seedvault.transport.restore.plugins
import android.content.Context
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
import com.stevesoltys.seedvault.transport.backup.plugins.*
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
import java.io.IOException
private val TAG = DocumentsProviderRestorePlugin::class.java.simpleName
class DocumentsProviderRestorePlugin(private val storage: DocumentsStorage) : RestorePlugin {
override val kvRestorePlugin: KVRestorePlugin by lazy {
DocumentsProviderKVRestorePlugin(storage)
}
override val fullRestorePlugin: FullRestorePlugin by lazy {
DocumentsProviderFullRestorePlugin(storage)
}
override fun getAvailableBackups(context: Context): Sequence<EncryptedBackupMetadata>? {
val rootDir = storage.rootBackupDir ?: return null
val backupSets = getBackups(context, rootDir)
val iterator = backupSets.iterator()
return generateSequence {
if (!iterator.hasNext()) return@generateSequence null // end sequence
val backupSet = iterator.next()
try {
val stream = storage.getInputStream(backupSet.metadataFile)
EncryptedBackupMetadata(backupSet.token, stream)
} catch (e: IOException) {
Log.e(TAG, "Error getting InputStream for backup metadata.", e)
EncryptedBackupMetadata(backupSet.token)
}
}
}
companion object {
@WorkerThread
fun getBackups(context: Context, rootDir: DocumentFile): List<BackupSet> {
val backupSets = ArrayList<BackupSet>()
val files = try {
// block until the DocumentsProvider has results
rootDir.listFilesBlocking(context)
} catch (e: IOException) {
Log.e(TAG, "Error loading backups from storage", e)
return backupSets
}
for (set in files) {
if (!set.isDirectory || set.name == null) {
if (set.name != FILE_NO_MEDIA) {
Log.w(TAG, "Found invalid backup set folder: ${set.name}")
}
continue
}
val token = try {
set.name!!.toLong()
} catch (e: NumberFormatException) {
Log.w(TAG, "Found invalid backup set folder: ${set.name}")
continue
}
// block until children of set are available
val metadata = set.findFileBlocking(context, FILE_BACKUP_METADATA)
if (metadata == null) {
Log.w(TAG, "Missing metadata file in backup set folder: ${set.name}")
} else {
backupSets.add(BackupSet(token, metadata))
}
}
return backupSets
}
}
}
class BackupSet(val token: Long, val metadataFile: DocumentFile)

View file

@ -2,10 +2,15 @@ package com.stevesoltys.seedvault.ui
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import com.stevesoltys.seedvault.Backup import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.storage.StorageViewModel import com.stevesoltys.seedvault.ui.storage.StorageViewModel
abstract class RequireProvisioningViewModel(protected val app: Application) : AndroidViewModel(app) { abstract class RequireProvisioningViewModel(
protected val app: Application,
private val settingsManager: SettingsManager,
private val keyManager: KeyManager
) : AndroidViewModel(app) {
abstract val isRestoreOperation: Boolean abstract val isRestoreOperation: Boolean
@ -13,8 +18,8 @@ abstract class RequireProvisioningViewModel(protected val app: Application) : An
internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true) internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
internal fun validLocationIsSet() = StorageViewModel.validLocationIsSet(app) internal fun validLocationIsSet() = StorageViewModel.validLocationIsSet(app, settingsManager)
internal fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey() internal fun recoveryCodeIsSet() = keyManager.hasBackupKey()
} }

View file

@ -2,16 +2,16 @@ package com.stevesoltys.seedvault.ui.recoverycode
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.BackupActivity import com.stevesoltys.seedvault.ui.BackupActivity
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
import com.stevesoltys.seedvault.ui.LiveEventHandler import com.stevesoltys.seedvault.ui.LiveEventHandler
import org.koin.androidx.viewmodel.ext.android.viewModel
class RecoveryCodeActivity : BackupActivity() { class RecoveryCodeActivity : BackupActivity() {
private lateinit var viewModel: RecoveryCodeViewModel private val viewModel: RecoveryCodeViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -20,7 +20,6 @@ class RecoveryCodeActivity : BackupActivity() {
setContentView(R.layout.activity_recovery_code) setContentView(R.layout.activity_recovery_code)
viewModel = ViewModelProviders.of(this).get(RecoveryCodeViewModel::class.java)
viewModel.isRestore = isRestore() viewModel.isRestore = isRestore()
viewModel.confirmButtonClicked.observeEvent(this, LiveEventHandler { clicked -> viewModel.confirmButtonClicked.observeEvent(this, LiveEventHandler { clicked ->
if (clicked) showInput(true) if (clicked) showInput(true)

View file

@ -11,7 +11,6 @@ import android.widget.AutoCompleteTextView
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isDebugBuild import com.stevesoltys.seedvault.isDebugBuild
import io.github.novacrypto.bip39.Validation.InvalidChecksumException import io.github.novacrypto.bip39.Validation.InvalidChecksumException
@ -19,10 +18,11 @@ import io.github.novacrypto.bip39.Validation.WordNotFoundException
import io.github.novacrypto.bip39.wordlists.English import io.github.novacrypto.bip39.wordlists.English
import kotlinx.android.synthetic.main.fragment_recovery_code_input.* import kotlinx.android.synthetic.main.fragment_recovery_code_input.*
import kotlinx.android.synthetic.main.recovery_code_input.* import kotlinx.android.synthetic.main.recovery_code_input.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RecoveryCodeInputFragment : Fragment() { class RecoveryCodeInputFragment : Fragment() {
private lateinit var viewModel: RecoveryCodeViewModel private val viewModel: RecoveryCodeViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@ -31,7 +31,6 @@ class RecoveryCodeInputFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(RecoveryCodeViewModel::class.java)
if (viewModel.isRestore) { if (viewModel.isRestore) {
introText.setText(R.string.recovery_code_input_intro) introText.setText(R.string.recovery_code_input_intro)

View file

@ -6,15 +6,15 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import kotlinx.android.synthetic.main.fragment_recovery_code_output.* import kotlinx.android.synthetic.main.fragment_recovery_code_output.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RecoveryCodeOutputFragment : Fragment() { class RecoveryCodeOutputFragment : Fragment() {
private lateinit var viewModel: RecoveryCodeViewModel private val viewModel: RecoveryCodeViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@ -23,7 +23,6 @@ class RecoveryCodeOutputFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(RecoveryCodeViewModel::class.java)
setGridParameters(wordList) setGridParameters(wordList)
wordList.adapter = RecoveryCodeAdapter(viewModel.wordList) wordList.adapter = RecoveryCodeAdapter(viewModel.wordList)

View file

@ -1,8 +1,8 @@
package com.stevesoltys.seedvault.ui.recoverycode package com.stevesoltys.seedvault.ui.recoverycode
import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import com.stevesoltys.seedvault.Backup import com.stevesoltys.seedvault.App
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent
import io.github.novacrypto.bip39.* import io.github.novacrypto.bip39.*
@ -17,7 +17,7 @@ import java.util.*
internal const val WORD_NUM = 12 internal const val WORD_NUM = 12
internal const val WORD_LIST_SIZE = 2048 internal const val WORD_LIST_SIZE = 2048
class RecoveryCodeViewModel(application: Application) : AndroidViewModel(application) { class RecoveryCodeViewModel(app: App, private val keyManager: KeyManager) : AndroidViewModel(app) {
internal val wordList: List<CharSequence> by lazy { internal val wordList: List<CharSequence> by lazy {
val items: ArrayList<CharSequence> = ArrayList(WORD_NUM) val items: ArrayList<CharSequence> = ArrayList(WORD_NUM)
@ -49,7 +49,7 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica
} }
val mnemonic = input.joinToString(" ") val mnemonic = input.joinToString(" ")
val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "") val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "")
Backup.keyManager.storeBackupKey(seed) keyManager.storeBackupKey(seed)
mRecoveryCodeSaved.setEvent(true) mRecoveryCodeSaved.setEvent(true)
} }

View file

@ -2,19 +2,23 @@ package com.stevesoltys.seedvault.ui.storage
import android.app.Application import android.app.Application
import android.app.backup.BackupProgress import android.app.backup.BackupProgress
import android.app.backup.IBackupManager
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.util.Log import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.TRANSPORT_ID import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.requestBackup import com.stevesoltys.seedvault.transport.requestBackup
private val TAG = BackupStorageViewModel::class.java.simpleName private val TAG = BackupStorageViewModel::class.java.simpleName
internal class BackupStorageViewModel(private val app: Application) : StorageViewModel(app) { internal class BackupStorageViewModel(
private val app: Application,
private val backupManager: IBackupManager,
settingsManager: SettingsManager) : StorageViewModel(app, settingsManager) {
override val isRestoreOperation = false override val isRestoreOperation = false
@ -26,7 +30,7 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
// initialize the new location // initialize the new location
val observer = InitializationObserver() val observer = InitializationObserver()
Backup.backupManager.initializeTransportsForUser(UserHandle.myUserId(), arrayOf(TRANSPORT_ID), observer) backupManager.initializeTransportsForUser(UserHandle.myUserId(), arrayOf(TRANSPORT_ID), observer)
// if storage is on USB and this is not SetupWizard, do a backup right away // if storage is on USB and this is not SetupWizard, do a backup right away
if (isUsb && !isSetupWizard) Thread { if (isUsb && !isSetupWizard) Thread {

View file

@ -3,21 +3,22 @@ package com.stevesoltys.seedvault.ui.storage
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.transport.backup.plugins.DIRECTORY_ROOT import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.plugins.findFileBlocking import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT
import com.stevesoltys.seedvault.transport.restore.plugins.DocumentsProviderRestorePlugin import com.stevesoltys.seedvault.transport.restore.RestorePlugin
private val TAG = RestoreStorageViewModel::class.java.simpleName private val TAG = RestoreStorageViewModel::class.java.simpleName
internal class RestoreStorageViewModel(private val app: Application) : StorageViewModel(app) { internal class RestoreStorageViewModel(
private val app: Application,
private val restorePlugin: RestorePlugin,
settingsManager: SettingsManager) : StorageViewModel(app, settingsManager) {
override val isRestoreOperation = true override val isRestoreOperation = true
override fun onLocationSet(uri: Uri) = Thread { override fun onLocationSet(uri: Uri) = Thread {
if (hasBackup(uri)) { if (restorePlugin.hasBackup(uri)) {
saveStorage(uri) saveStorage(uri)
mLocationChecked.postEvent(LocationResult()) mLocationChecked.postEvent(LocationResult())
@ -30,21 +31,4 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
} }
}.start() }.start()
/**
* Searches if there's really a backup available in the given location.
* Returns true if at least one was found and false otherwise.
*
* This method is not plugin-agnostic and breaks encapsulation.
* It is specific to the (currently only) DocumentsProvider plugin.
*
* TODO maybe move this to the RestoreCoordinator once we can inject it
*/
@WorkerThread
private fun hasBackup(folderUri: Uri): Boolean {
val parent = DocumentFile.fromTreeUri(app, folderUri) ?: throw AssertionError()
val rootDir = parent.findFileBlocking(app, DIRECTORY_ROOT) ?: return false
val backupSets = DocumentsProviderRestorePlugin.getBackups(app, rootDir)
return backupSets.isNotEmpty()
}
} }

View file

@ -5,12 +5,12 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.BackupActivity import com.stevesoltys.seedvault.ui.BackupActivity
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
import com.stevesoltys.seedvault.ui.LiveEventHandler import com.stevesoltys.seedvault.ui.LiveEventHandler
import org.koin.androidx.viewmodel.ext.android.getViewModel
private val TAG = StorageActivity::class.java.name private val TAG = StorageActivity::class.java.name
@ -27,9 +27,9 @@ class StorageActivity : BackupActivity() {
setContentView(R.layout.activity_fragment_container) setContentView(R.layout.activity_fragment_container)
viewModel = if (isRestore()) { viewModel = if (isRestore()) {
ViewModelProviders.of(this).get(RestoreStorageViewModel::class.java) getViewModel<RestoreStorageViewModel>()
} else { } else {
ViewModelProviders.of(this).get(BackupStorageViewModel::class.java) getViewModel<BackupStorageViewModel>()
} }
viewModel.isSetupWizard = isSetupWizard() viewModel.isSetupWizard = isSetupWizard()

View file

@ -11,11 +11,11 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity.RESULT_OK import androidx.appcompat.app.AppCompatActivity.RESULT_OK
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
import com.stevesoltys.seedvault.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE import com.stevesoltys.seedvault.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE
import kotlinx.android.synthetic.main.fragment_storage_root.* import kotlinx.android.synthetic.main.fragment_storage_root.*
import org.koin.androidx.viewmodel.ext.android.getSharedViewModel
internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
@ -42,9 +42,9 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = if (arguments!!.getBoolean(INTENT_EXTRA_IS_RESTORE)) { viewModel = if (arguments!!.getBoolean(INTENT_EXTRA_IS_RESTORE)) {
ViewModelProviders.of(requireActivity()).get(RestoreStorageViewModel::class.java) getSharedViewModel<RestoreStorageViewModel>()
} else { } else {
ViewModelProviders.of(requireActivity()).get(BackupStorageViewModel::class.java) getSharedViewModel<BackupStorageViewModel>()
} }
if (viewModel.isRestoreOperation) { if (viewModel.isRestoreOperation) {

View file

@ -12,11 +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.seedvault.Backup
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.settings.BackupManagerSettings import com.stevesoltys.seedvault.settings.BackupManagerSettings
import com.stevesoltys.seedvault.settings.FlashDrive import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.settings.Storage
import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService
import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.LiveEvent
@ -24,9 +24,10 @@ import com.stevesoltys.seedvault.ui.MutableLiveEvent
private val TAG = StorageViewModel::class.java.simpleName 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,
protected val settingsManager = (app as Backup).settingsManager protected val settingsManager: SettingsManager
) : AndroidViewModel(app), RemovableStorageListener {
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
@ -44,8 +45,7 @@ internal abstract class StorageViewModel(private val app: Application) : Android
abstract val isRestoreOperation: Boolean abstract val isRestoreOperation: Boolean
companion object { companion object {
internal fun validLocationIsSet(context: Context): Boolean { internal fun validLocationIsSet(context: Context, settingsManager: SettingsManager): Boolean {
val settingsManager = (context.applicationContext as Backup).settingsManager
val storage = settingsManager.getStorage() ?: return false val storage = settingsManager.getStorage() ?: return false
if (storage.isUsb) return true if (storage.isUsb) return true
return storage.getDocumentFile(context).isDirectory return storage.getDocumentFile(context).isDirectory

View file

@ -50,7 +50,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
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, settingsManager, 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)

View file

@ -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, settingsManager, 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>()
@ -43,7 +43,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
androidVersion = Random.nextInt(), androidVersion = Random.nextInt(),
deviceName = getRandomString()) deviceName = getRandomString())
every { plugin.getAvailableBackups(context) } returns sequenceOf(encryptedMetadata, encryptedMetadata) every { plugin.getAvailableBackups() } returns sequenceOf(encryptedMetadata, encryptedMetadata)
every { metadataReader.readMetadata(inputStream, token) } returns metadata every { metadataReader.readMetadata(inputStream, token) } returns metadata
every { inputStream.close() } just Runs every { inputStream.close() } just Runs