Use dependency injection with Koin
This commit is contained in:
parent
bb9d498ea8
commit
94c7663daf
59 changed files with 381 additions and 327 deletions
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
66
app/src/main/java/com/stevesoltys/seedvault/App.kt
Normal file
66
app/src/main/java/com/stevesoltys/seedvault/App.kt
Normal 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"
|
|
@ -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"
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()) }
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package com.stevesoltys.seedvault.header
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val headerModule = module {
|
||||
single<HeaderWriter> { HeaderWriterImpl() }
|
||||
single<HeaderReader> { HeaderReaderImpl() }
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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()) }
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()) }
|
||||
}
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) })
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ")
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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()) }
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) }
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue