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 '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.preference:preference-ktx:1.1.0'

View file

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

View file

@ -25,7 +25,7 @@
tools:ignore="ProtectedPermissions" />
<application
android:name=".Backup"
android:name=".App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
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.INFO
import android.util.Log.isLoggable
import org.koin.core.KoinComponent
import org.koin.core.inject
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 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.

View file

@ -12,19 +12,23 @@ import android.os.Handler
import android.provider.DocumentsContract
import android.util.Log
import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
import java.util.concurrent.TimeUnit.HOURS
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 {
if (action != ACTION_USB_DEVICE_ATTACHED) return false
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 attachedFlashDrive = FlashDrive.from(device)
return if (savedFlashDrive == attachedFlashDrive) {

View file

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

View file

@ -71,7 +71,7 @@ interface Crypto {
fun decryptSegment(inputStream: InputStream): ByteArray
}
class CryptoImpl(
internal class CryptoImpl(
private val cipherFactory: CipherFactory,
private val headerWriter: HeaderWriter,
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
}
class KeyManagerImpl : KeyManager {
internal class KeyManagerImpl : KeyManager {
private val keyStore by lazy {
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_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) {
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)
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)
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 com.stevesoltys.seedvault.transport.backup.BackupPlugin
@ -7,7 +7,7 @@ import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import java.io.IOException
import java.io.OutputStream
class DocumentsProviderBackupPlugin(
internal class DocumentsProviderBackupPlugin(
private val storage: DocumentsStorage,
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.util.Log
@ -9,7 +9,7 @@ import java.io.OutputStream
private val TAG = DocumentsProviderFullBackup::class.java.simpleName
class DocumentsProviderFullBackup(
internal class DocumentsProviderFullBackup(
private val storage: DocumentsStorage) : FullBackupPlugin {
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 com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import java.io.IOException
import java.io.InputStream
class DocumentsProviderFullRestorePlugin(
internal class DocumentsProviderFullRestorePlugin(
private val documentsStorage: DocumentsStorage) : FullRestorePlugin {
@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 androidx.documentfile.provider.DocumentFile
@ -7,7 +7,7 @@ import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import java.io.IOException
import java.io.OutputStream
class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin {
internal class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin {
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 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 java.io.IOException
import java.io.InputStream
class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin {
internal class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin {
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.content.Context
@ -26,7 +26,9 @@ private const val MIME_TYPE = "application/octet-stream"
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 token: Long = settingsManager.getBackupToken()

View file

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

View file

@ -10,16 +10,18 @@ import android.view.ViewGroup
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.seedvault.Backup
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.getAppName
import com.stevesoltys.seedvault.isDebugBuild
import com.stevesoltys.seedvault.settings.SettingsManager
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() {
private lateinit var viewModel: RestoreViewModel
private val viewModel: RestoreViewModel by sharedViewModel()
private val settingsManager: SettingsManager by inject()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
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
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
viewModel = ViewModelProviders.of(requireActivity()).get(RestoreViewModel::class.java)
viewModel.chosenRestoreSet.observe(this, Observer { set ->
backupNameView.text = set.device
})
@ -50,7 +50,6 @@ class RestoreProgressFragment : Fragment() {
if (finished == 0) {
// success
currentPackageView.text = getString(R.string.restore_finished_success)
val settingsManager = (requireContext().applicationContext as Backup).settingsManager
warningView.text = if (settingsManager.getStorage()?.isUsb == true) {
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
} else {

View file

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

View file

@ -1,6 +1,7 @@
package com.stevesoltys.seedvault.restore
import android.app.Application
import android.app.backup.IBackupManager
import android.app.backup.IRestoreObserver
import android.app.backup.IRestoreSession
import android.app.backup.RestoreSet
@ -9,20 +10,25 @@ import android.util.Log
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R
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.restore.RestorePlugin
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
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
private val backupManager = Backup.backupManager
private var session: IRestoreSession? = null
private var observer: RestoreObserver? = null
private val monitor = BackupMonitor()

View file

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

View file

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

View file

@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.settings
import android.app.backup.IBackupManager
import android.content.Context
import android.content.Context.BACKUP_SERVICE
import android.content.Intent
@ -17,26 +18,25 @@ import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.lifecycle.ViewModelProviders
import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceChangeListener
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.UsbMonitor
import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.restore.RestoreActivity
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import java.util.*
private val TAG = SettingsFragment::class.java.name
class SettingsFragment : PreferenceFragmentCompat() {
private val backupManager = Backup.backupManager
private lateinit var viewModel: SettingsViewModel
private lateinit var settingsManager: SettingsManager
private val viewModel: SettingsViewModel by sharedViewModel()
private val settingsManager: SettingsManager by inject()
private val backupManager: IBackupManager by inject()
private lateinit var backup: TwoStatePreference
private lateinit var autoRestore: TwoStatePreference
@ -63,9 +63,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.settings, rootKey)
setHasOptionsMenu(true)
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
settingsManager = (requireContext().applicationContext as Backup).settingsManager
backup = findPreference<TwoStatePreference>("backup")!!
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
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_TIME = "backupTime"
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
class SettingsManager(context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// FIXME Storage is currently plugin specific and not generic
fun setStorage(storage: Storage) {
prefs.edit()
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
@ -108,11 +108,6 @@ class SettingsManager(context: Context) {
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(

View file

@ -1,10 +1,15 @@
package com.stevesoltys.seedvault.settings
import android.app.Application
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.transport.requestBackup
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

View file

@ -10,6 +10,10 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
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
@ -20,11 +24,10 @@ private val TAG = ConfigurableBackupTransport::class.java.simpleName
* @author Steve Soltys
* @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 = pluginManager.backupCoordinator
private val restoreCoordinator = pluginManager.restoreCoordinator
private val backupCoordinator by inject<BackupCoordinator>()
private val restoreCoordinator by inject<RestoreCoordinator>()
override fun transportDirName(): String {
return TRANSPORT_DIRECTORY_NAME

View file

@ -2,19 +2,21 @@ package com.stevesoltys.seedvault.transport
import android.app.Service
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.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.os.IBinder
import android.os.RemoteException
import android.util.Log
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.R
import com.stevesoltys.seedvault.BackupMonitor
import org.koin.core.context.GlobalContext.get
private val TAG = ConfigurableBackupTransportService::class.java.simpleName
@ -50,17 +52,18 @@ class ConfigurableBackupTransportService : Service() {
@WorkerThread
fun requestBackup(context: Context) {
// 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)
val observer = NotificationBackupObserver(context, true)
val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED
val packages = PackageService().eligiblePackages
val packages = PackageService.eligiblePackages
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) {
// TODO show notification on backup error
Log.e(TAG, "Error during backup: ", e)
nm.onBackupError()
}
if (result == BackupManager.SUCCESS) {
Log.i(TAG, "Backup succeeded ")

View file

@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.transport
import android.app.backup.IBackupManager
import android.content.pm.IPackageManager
import android.content.pm.PackageInfo
import android.os.RemoteException
@ -7,8 +8,9 @@ import android.os.ServiceManager.getService
import android.os.UserHandle
import android.util.Log
import com.google.android.collect.Sets.newArraySet
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
private val TAG = PackageService::class.java.simpleName
@ -27,9 +29,9 @@ private val IGNORED_PACKAGES = newArraySet(
* @author Steve Soltys
* @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"))
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 Torsten Grote
*/
class BackupCoordinator(
internal class BackupCoordinator(
private val context: Context,
private val plugin: BackupPlugin,
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
class FullBackup(
internal class FullBackup(
private val plugin: FullBackupPlugin,
private val inputFactory: InputFactory,
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.
*/
class InputFactory {
internal class InputFactory {
fun getBackupDataInput(inputFileDescriptor: ParcelFileDescriptor): BackupDataInput {
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
class KVBackup(
internal class KVBackup(
private val plugin: KVBackupPlugin,
private val inputFactory: InputFactory,
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.
*/
class OutputFactory {
internal class OutputFactory {
fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput {
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.RestoreSet
import android.content.Context
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
@ -23,7 +22,6 @@ 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,
@ -39,7 +37,7 @@ internal class RestoreCoordinator(
* or null if an error occurred (the attempt should be rescheduled).
**/
fun getAvailableRestoreSets(): Array<RestoreSet>? {
val availableBackups = plugin.getAvailableBackups(context) ?: return null
val availableBackups = plugin.getAvailableBackups() ?: return null
val restoreSets = ArrayList<RestoreSet>()
for (encryptedMetadata in availableBackups) {
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
import android.content.Context
import android.net.Uri
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
interface RestorePlugin {
@ -15,6 +16,15 @@ interface RestorePlugin {
* @return metadata for the set of restore images available,
* 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 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
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
@ -13,8 +18,8 @@ abstract class RequireProvisioningViewModel(protected val app: Application) : An
internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation
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.view.MenuItem
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.BackupActivity
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
import com.stevesoltys.seedvault.ui.LiveEventHandler
import org.koin.androidx.viewmodel.ext.android.viewModel
class RecoveryCodeActivity : BackupActivity() {
private lateinit var viewModel: RecoveryCodeViewModel
private val viewModel: RecoveryCodeViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -20,7 +20,6 @@ class RecoveryCodeActivity : BackupActivity() {
setContentView(R.layout.activity_recovery_code)
viewModel = ViewModelProviders.of(this).get(RecoveryCodeViewModel::class.java)
viewModel.isRestore = isRestore()
viewModel.confirmButtonClicked.observeEvent(this, LiveEventHandler { clicked ->
if (clicked) showInput(true)

View file

@ -11,7 +11,6 @@ import android.widget.AutoCompleteTextView
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isDebugBuild
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 kotlinx.android.synthetic.main.fragment_recovery_code_input.*
import kotlinx.android.synthetic.main.recovery_code_input.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RecoveryCodeInputFragment : Fragment() {
private lateinit var viewModel: RecoveryCodeViewModel
private val viewModel: RecoveryCodeViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
@ -31,7 +31,6 @@ class RecoveryCodeInputFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(RecoveryCodeViewModel::class.java)
if (viewModel.isRestore) {
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.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.stevesoltys.seedvault.R
import kotlinx.android.synthetic.main.fragment_recovery_code_output.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class RecoveryCodeOutputFragment : Fragment() {
private lateinit var viewModel: RecoveryCodeViewModel
private val viewModel: RecoveryCodeViewModel by sharedViewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
@ -23,7 +23,6 @@ class RecoveryCodeOutputFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(RecoveryCodeViewModel::class.java)
setGridParameters(wordList)
wordList.adapter = RecoveryCodeAdapter(viewModel.wordList)

View file

@ -1,8 +1,8 @@
package com.stevesoltys.seedvault.ui.recoverycode
import android.app.Application
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.MutableLiveEvent
import io.github.novacrypto.bip39.*
@ -17,7 +17,7 @@ import java.util.*
internal const val WORD_NUM = 12
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 {
val items: ArrayList<CharSequence> = ArrayList(WORD_NUM)
@ -49,7 +49,7 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica
}
val mnemonic = input.joinToString(" ")
val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "")
Backup.keyManager.storeBackupKey(seed)
keyManager.storeBackupKey(seed)
mRecoveryCodeSaved.setEvent(true)
}

View file

@ -2,19 +2,23 @@ package com.stevesoltys.seedvault.ui.storage
import android.app.Application
import android.app.backup.BackupProgress
import android.app.backup.IBackupManager
import android.app.backup.IBackupObserver
import android.net.Uri
import android.os.UserHandle
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.requestBackup
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
@ -26,7 +30,7 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
// initialize the new location
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 (isUsb && !isSetupWizard) Thread {

View file

@ -3,21 +3,22 @@ package com.stevesoltys.seedvault.ui.storage
import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.transport.backup.plugins.DIRECTORY_ROOT
import com.stevesoltys.seedvault.transport.backup.plugins.findFileBlocking
import com.stevesoltys.seedvault.transport.restore.plugins.DocumentsProviderRestorePlugin
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
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 fun onLocationSet(uri: Uri) = Thread {
if (hasBackup(uri)) {
if (restorePlugin.hasBackup(uri)) {
saveStorage(uri)
mLocationChecked.postEvent(LocationResult())
@ -30,21 +31,4 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
}
}.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 androidx.annotation.CallSuper
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProviders
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.BackupActivity
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
import com.stevesoltys.seedvault.ui.LiveEventHandler
import org.koin.androidx.viewmodel.ext.android.getViewModel
private val TAG = StorageActivity::class.java.name
@ -27,9 +27,9 @@ class StorageActivity : BackupActivity() {
setContentView(R.layout.activity_fragment_container)
viewModel = if (isRestore()) {
ViewModelProviders.of(this).get(RestoreStorageViewModel::class.java)
getViewModel<RestoreStorageViewModel>()
} else {
ViewModelProviders.of(this).get(BackupStorageViewModel::class.java)
getViewModel<BackupStorageViewModel>()
}
viewModel.isSetupWizard = isSetupWizard()

View file

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

View file

@ -12,11 +12,11 @@ import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.stevesoltys.seedvault.Backup
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.settings.BackupManagerSettings
import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.Storage
import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService
import com.stevesoltys.seedvault.ui.LiveEvent
@ -24,9 +24,10 @@ import com.stevesoltys.seedvault.ui.MutableLiveEvent
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
internal abstract class StorageViewModel(
private val app: Application,
protected val settingsManager: SettingsManager
) : AndroidViewModel(app), RemovableStorageListener {
private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
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
companion object {
internal fun validLocationIsSet(context: Context): Boolean {
val settingsManager = (context.applicationContext as Backup).settingsManager
internal fun validLocationIsSet(context: Context, settingsManager: SettingsManager): Boolean {
val storage = settingsManager.getStorage() ?: return false
if (storage.isUsb) return true
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 fullRestorePlugin = mockk<FullRestorePlugin>()
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 fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)

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, settingsManager, plugin, kv, full, metadataReader)
private val restore = RestoreCoordinator(settingsManager, plugin, kv, full, metadataReader)
private val token = Random.nextLong()
private val inputStream = mockk<InputStream>()
@ -43,7 +43,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
androidVersion = Random.nextInt(),
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 { inputStream.close() } just Runs