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 'commons-io:commons-io:2.6'
|
||||||
implementation 'io.github.novacrypto:BIP39:2019.01.27'
|
implementation 'io.github.novacrypto:BIP39:2019.01.27'
|
||||||
|
implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.0'
|
implementation 'androidx.preference:preference-ktx:1.1.0'
|
||||||
|
|
|
@ -3,23 +3,26 @@ package com.stevesoltys.seedvault
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.createOrGetFile
|
import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage
|
||||||
|
import com.stevesoltys.seedvault.plugins.saf.createOrGetFile
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
private const val filename = "test-file"
|
private const val filename = "test-file"
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class DocumentsStorageTest {
|
class DocumentsStorageTest : KoinComponent {
|
||||||
|
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
private val settingsManager = (context.applicationContext as Backup).settingsManager
|
private val settingsManager by inject<SettingsManager>()
|
||||||
private val storage = DocumentsStorage(context, settingsManager)
|
private val storage = DocumentsStorage(context, settingsManager)
|
||||||
|
|
||||||
private lateinit var file: DocumentFile
|
private lateinit var file: DocumentFile
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Backup"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
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
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
import android.util.Log.isLoggable
|
import android.util.Log.isLoggable
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
|
|
||||||
private val TAG = NotificationBackupObserver::class.java.simpleName
|
private val TAG = NotificationBackupObserver::class.java.simpleName
|
||||||
|
|
||||||
class NotificationBackupObserver(context: Context, private val userInitiated: Boolean) : IBackupObserver.Stub() {
|
class NotificationBackupObserver(context: Context, private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent {
|
||||||
|
|
||||||
private val pm = context.packageManager
|
private val pm = context.packageManager
|
||||||
private val nm = (context.applicationContext as Backup).notificationManager
|
private val nm: BackupNotificationManager by inject()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method could be called several times for packages with full data backup.
|
* This method could be called several times for packages with full data backup.
|
||||||
|
|
|
@ -12,19 +12,23 @@ import android.os.Handler
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.seedvault.settings.FlashDrive
|
import com.stevesoltys.seedvault.settings.FlashDrive
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.requestBackup
|
import com.stevesoltys.seedvault.transport.requestBackup
|
||||||
import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
|
import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit.HOURS
|
import java.util.concurrent.TimeUnit.HOURS
|
||||||
|
|
||||||
private val TAG = UsbIntentReceiver::class.java.simpleName
|
private val TAG = UsbIntentReceiver::class.java.simpleName
|
||||||
|
|
||||||
class UsbIntentReceiver : UsbMonitor() {
|
class UsbIntentReceiver : UsbMonitor(), KoinComponent {
|
||||||
|
|
||||||
|
private val settingsManager by inject<SettingsManager>()
|
||||||
|
|
||||||
override fun shouldMonitorStatus(context: Context, action: String, device: UsbDevice): Boolean {
|
override fun shouldMonitorStatus(context: Context, action: String, device: UsbDevice): Boolean {
|
||||||
if (action != ACTION_USB_DEVICE_ATTACHED) return false
|
if (action != ACTION_USB_DEVICE_ATTACHED) return false
|
||||||
Log.d(TAG, "Checking if this is the current backup drive.")
|
Log.d(TAG, "Checking if this is the current backup drive.")
|
||||||
val settingsManager = (context.applicationContext as Backup).settingsManager
|
|
||||||
val savedFlashDrive = settingsManager.getFlashDrive() ?: return false
|
val savedFlashDrive = settingsManager.getFlashDrive() ?: return false
|
||||||
val attachedFlashDrive = FlashDrive.from(device)
|
val attachedFlashDrive = FlashDrive.from(device)
|
||||||
return if (savedFlashDrive == attachedFlashDrive) {
|
return if (savedFlashDrive == attachedFlashDrive) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface CipherFactory {
|
||||||
fun createDecryptionCipher(iv: ByteArray): Cipher
|
fun createDecryptionCipher(iv: ByteArray): Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
class CipherFactoryImpl(private val keyManager: KeyManager) : CipherFactory {
|
internal class CipherFactoryImpl(private val keyManager: KeyManager) : CipherFactory {
|
||||||
|
|
||||||
override fun createEncryptionCipher(): Cipher {
|
override fun createEncryptionCipher(): Cipher {
|
||||||
return Cipher.getInstance(CIPHER_TRANSFORMATION).apply {
|
return Cipher.getInstance(CIPHER_TRANSFORMATION).apply {
|
||||||
|
|
|
@ -71,7 +71,7 @@ interface Crypto {
|
||||||
fun decryptSegment(inputStream: InputStream): ByteArray
|
fun decryptSegment(inputStream: InputStream): ByteArray
|
||||||
}
|
}
|
||||||
|
|
||||||
class CryptoImpl(
|
internal class CryptoImpl(
|
||||||
private val cipherFactory: CipherFactory,
|
private val cipherFactory: CipherFactory,
|
||||||
private val headerWriter: HeaderWriter,
|
private val headerWriter: HeaderWriter,
|
||||||
private val headerReader: HeaderReader) : Crypto {
|
private val headerReader: HeaderReader) : Crypto {
|
||||||
|
|
|
@ -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
|
fun getBackupKey(): SecretKey
|
||||||
}
|
}
|
||||||
|
|
||||||
class KeyManagerImpl : KeyManager {
|
internal class KeyManagerImpl : KeyManager {
|
||||||
|
|
||||||
private val keyStore by lazy {
|
private val keyStore by lazy {
|
||||||
KeyStore.getInstance(ANDROID_KEY_STORE).apply {
|
KeyStore.getInstance(ANDROID_KEY_STORE).apply {
|
||||||
|
|
|
@ -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_ANDROID_VERSION = "androidVersion"
|
||||||
internal const val JSON_DEVICE_NAME = "deviceName"
|
internal const val JSON_DEVICE_NAME = "deviceName"
|
||||||
|
|
||||||
class DecryptionFailedException(cause: Throwable) : Exception(cause)
|
internal class DecryptionFailedException(cause: Throwable) : Exception(cause)
|
||||||
|
|
||||||
class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) {
|
class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) {
|
||||||
constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false)
|
constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false)
|
||||||
|
|
|
@ -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)
|
@Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class)
|
||||||
override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata {
|
override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata {
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface MetadataWriter {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataWriterImpl(private val crypto: Crypto): MetadataWriter {
|
internal class MetadataWriterImpl(private val crypto: Crypto): MetadataWriter {
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun write(outputStream: OutputStream, token: Long) {
|
override fun write(outputStream: OutputStream, token: Long) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.stevesoltys.seedvault.transport.backup.plugins
|
package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
||||||
|
@ -7,7 +7,7 @@ import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
class DocumentsProviderBackupPlugin(
|
internal class DocumentsProviderBackupPlugin(
|
||||||
private val storage: DocumentsStorage,
|
private val storage: DocumentsStorage,
|
||||||
packageManager: PackageManager) : BackupPlugin {
|
packageManager: PackageManager) : BackupPlugin {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.stevesoltys.seedvault.transport.backup.plugins
|
package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -9,7 +9,7 @@ import java.io.OutputStream
|
||||||
|
|
||||||
private val TAG = DocumentsProviderFullBackup::class.java.simpleName
|
private val TAG = DocumentsProviderFullBackup::class.java.simpleName
|
||||||
|
|
||||||
class DocumentsProviderFullBackup(
|
internal class DocumentsProviderFullBackup(
|
||||||
private val storage: DocumentsStorage) : FullBackupPlugin {
|
private val storage: DocumentsStorage) : FullBackupPlugin {
|
||||||
|
|
||||||
override fun getQuota() = DEFAULT_QUOTA_FULL_BACKUP
|
override fun getQuota() = DEFAULT_QUOTA_FULL_BACKUP
|
|
@ -1,12 +1,11 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore.plugins
|
package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class DocumentsProviderFullRestorePlugin(
|
internal class DocumentsProviderFullRestorePlugin(
|
||||||
private val documentsStorage: DocumentsStorage) : FullRestorePlugin {
|
private val documentsStorage: DocumentsStorage) : FullRestorePlugin {
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
|
@ -1,4 +1,4 @@
|
||||||
package com.stevesoltys.seedvault.transport.backup.plugins
|
package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
@ -7,7 +7,7 @@ import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin {
|
internal class DocumentsProviderKVBackup(private val storage: DocumentsStorage) : KVBackupPlugin {
|
||||||
|
|
||||||
private var packageFile: DocumentFile? = null
|
private var packageFile: DocumentFile? = null
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore.plugins
|
package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.DocumentsStorage
|
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.assertRightFile
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin {
|
internal class DocumentsProviderKVRestorePlugin(private val storage: DocumentsStorage) : KVRestorePlugin {
|
||||||
|
|
||||||
private var packageDir: DocumentFile? = null
|
private var packageDir: DocumentFile? = null
|
||||||
|
|
|
@ -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.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -26,7 +26,9 @@ private const val MIME_TYPE = "application/octet-stream"
|
||||||
|
|
||||||
private val TAG = DocumentsStorage::class.java.simpleName
|
private val TAG = DocumentsStorage::class.java.simpleName
|
||||||
|
|
||||||
class DocumentsStorage(private val context: Context, private val settingsManager: SettingsManager) {
|
internal class DocumentsStorage(
|
||||||
|
private val context: Context,
|
||||||
|
private val settingsManager: SettingsManager) {
|
||||||
|
|
||||||
private val storage: Storage? = settingsManager.getStorage()
|
private val storage: Storage? = settingsManager.getStorage()
|
||||||
private val token: Long = settingsManager.getBackupToken()
|
private val token: Long = settingsManager.getBackupToken()
|
|
@ -3,19 +3,18 @@ package com.stevesoltys.seedvault.restore
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
|
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class RestoreActivity : RequireProvisioningActivity() {
|
class RestoreActivity : RequireProvisioningActivity() {
|
||||||
|
|
||||||
private lateinit var viewModel: RestoreViewModel
|
private val viewModel: RestoreViewModel by viewModel()
|
||||||
|
|
||||||
override fun getViewModel(): RequireProvisioningViewModel = viewModel
|
override fun getViewModel(): RequireProvisioningViewModel = viewModel
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
viewModel = ViewModelProviders.of(this).get(RestoreViewModel::class.java)
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
if (isSetupWizard) hideSystemUI()
|
if (isSetupWizard) hideSystemUI()
|
||||||
|
|
|
@ -10,16 +10,18 @@ import android.view.ViewGroup
|
||||||
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.getAppName
|
import com.stevesoltys.seedvault.getAppName
|
||||||
import com.stevesoltys.seedvault.isDebugBuild
|
import com.stevesoltys.seedvault.isDebugBuild
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class RestoreProgressFragment : Fragment() {
|
class RestoreProgressFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var viewModel: RestoreViewModel
|
private val viewModel: RestoreViewModel by sharedViewModel()
|
||||||
|
private val settingsManager: SettingsManager by inject()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
|
@ -32,8 +34,6 @@ class RestoreProgressFragment : Fragment() {
|
||||||
// decryption will fail when the device is locked, so keep the screen on to prevent locking
|
// decryption will fail when the device is locked, so keep the screen on to prevent locking
|
||||||
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
|
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(RestoreViewModel::class.java)
|
|
||||||
|
|
||||||
viewModel.chosenRestoreSet.observe(this, Observer { set ->
|
viewModel.chosenRestoreSet.observe(this, Observer { set ->
|
||||||
backupNameView.text = set.device
|
backupNameView.text = set.device
|
||||||
})
|
})
|
||||||
|
@ -50,7 +50,6 @@ class RestoreProgressFragment : Fragment() {
|
||||||
if (finished == 0) {
|
if (finished == 0) {
|
||||||
// success
|
// success
|
||||||
currentPackageView.text = getString(R.string.restore_finished_success)
|
currentPackageView.text = getString(R.string.restore_finished_success)
|
||||||
val settingsManager = (requireContext().applicationContext as Backup).settingsManager
|
|
||||||
warningView.text = if (settingsManager.getStorage()?.isUsb == true) {
|
warningView.text = if (settingsManager.getStorage()?.isUsb == true) {
|
||||||
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
|
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,13 +9,13 @@ import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import kotlinx.android.synthetic.main.fragment_restore_set.*
|
import kotlinx.android.synthetic.main.fragment_restore_set.*
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class RestoreSetFragment : Fragment() {
|
class RestoreSetFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var viewModel: RestoreViewModel
|
private val viewModel: RestoreViewModel by sharedViewModel()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
|
@ -24,7 +24,6 @@ class RestoreSetFragment : Fragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(RestoreViewModel::class.java)
|
|
||||||
|
|
||||||
viewModel.restoreSets.observe(this, Observer { result -> onRestoreSetsLoaded(result) })
|
viewModel.restoreSets.observe(this, Observer { result -> onRestoreSetsLoaded(result) })
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.stevesoltys.seedvault.restore
|
package com.stevesoltys.seedvault.restore
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.app.backup.IBackupManager
|
||||||
import android.app.backup.IRestoreObserver
|
import android.app.backup.IRestoreObserver
|
||||||
import android.app.backup.IRestoreSession
|
import android.app.backup.IRestoreSession
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
|
@ -9,20 +10,25 @@ import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.R
|
|
||||||
import com.stevesoltys.seedvault.BackupMonitor
|
import com.stevesoltys.seedvault.BackupMonitor
|
||||||
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||||
|
|
||||||
private val TAG = RestoreViewModel::class.java.simpleName
|
private val TAG = RestoreViewModel::class.java.simpleName
|
||||||
|
|
||||||
class RestoreViewModel(app: Application) : RequireProvisioningViewModel(app), RestoreSetClickListener {
|
internal class RestoreViewModel(
|
||||||
|
app: Application,
|
||||||
|
settingsManager: SettingsManager,
|
||||||
|
keyManager: KeyManager,
|
||||||
|
private val backupManager: IBackupManager
|
||||||
|
) : RequireProvisioningViewModel(app, settingsManager, keyManager), RestoreSetClickListener {
|
||||||
|
|
||||||
override val isRestoreOperation = true
|
override val isRestoreOperation = true
|
||||||
|
|
||||||
private val backupManager = Backup.backupManager
|
|
||||||
|
|
||||||
private var session: IRestoreSession? = null
|
private var session: IRestoreSession? = null
|
||||||
private var observer: RestoreObserver? = null
|
private var observer: RestoreObserver? = null
|
||||||
private val monitor = BackupMonitor()
|
private val monitor = BackupMonitor()
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.stevesoltys.seedvault.settings
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.TimeUnit.DAYS
|
import java.util.concurrent.TimeUnit.DAYS
|
||||||
|
|
||||||
private val SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS
|
private val SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS
|
||||||
|
|
|
@ -2,20 +2,21 @@ package com.stevesoltys.seedvault.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import com.stevesoltys.seedvault.BackupNotificationManager
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
|
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class SettingsActivity : RequireProvisioningActivity() {
|
class SettingsActivity : RequireProvisioningActivity() {
|
||||||
|
|
||||||
private lateinit var viewModel: SettingsViewModel
|
private val viewModel: SettingsViewModel by viewModel()
|
||||||
|
private val notificationManager: BackupNotificationManager by inject()
|
||||||
|
|
||||||
override fun getViewModel(): RequireProvisioningViewModel = viewModel
|
override fun getViewModel(): RequireProvisioningViewModel = viewModel
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
viewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.activity_fragment_container)
|
setContentView(R.layout.activity_fragment_container)
|
||||||
|
@ -36,7 +37,7 @@ class SettingsActivity : RequireProvisioningActivity() {
|
||||||
} else if (!viewModel.validLocationIsSet()) {
|
} else if (!viewModel.validLocationIsSet()) {
|
||||||
showStorageActivity()
|
showStorageActivity()
|
||||||
// remove potential error notifications
|
// remove potential error notifications
|
||||||
(application as Backup).notificationManager.onBackupErrorSeen()
|
notificationManager.onBackupErrorSeen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.stevesoltys.seedvault.settings
|
package com.stevesoltys.seedvault.settings
|
||||||
|
|
||||||
|
import android.app.backup.IBackupManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.BACKUP_SERVICE
|
import android.content.Context.BACKUP_SERVICE
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -17,26 +18,25 @@ import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.Preference.OnPreferenceChangeListener
|
import androidx.preference.Preference.OnPreferenceChangeListener
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.TwoStatePreference
|
import androidx.preference.TwoStatePreference
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.UsbMonitor
|
import com.stevesoltys.seedvault.UsbMonitor
|
||||||
import com.stevesoltys.seedvault.isMassStorage
|
import com.stevesoltys.seedvault.isMassStorage
|
||||||
import com.stevesoltys.seedvault.restore.RestoreActivity
|
import com.stevesoltys.seedvault.restore.RestoreActivity
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
private val TAG = SettingsFragment::class.java.name
|
private val TAG = SettingsFragment::class.java.name
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
private val backupManager = Backup.backupManager
|
private val viewModel: SettingsViewModel by sharedViewModel()
|
||||||
|
private val settingsManager: SettingsManager by inject()
|
||||||
private lateinit var viewModel: SettingsViewModel
|
private val backupManager: IBackupManager by inject()
|
||||||
private lateinit var settingsManager: SettingsManager
|
|
||||||
|
|
||||||
private lateinit var backup: TwoStatePreference
|
private lateinit var backup: TwoStatePreference
|
||||||
private lateinit var autoRestore: TwoStatePreference
|
private lateinit var autoRestore: TwoStatePreference
|
||||||
|
@ -63,9 +63,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
|
|
||||||
settingsManager = (requireContext().applicationContext as Backup).settingsManager
|
|
||||||
|
|
||||||
backup = findPreference<TwoStatePreference>("backup")!!
|
backup = findPreference<TwoStatePreference>("backup")!!
|
||||||
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||||
val enabled = newValue as Boolean
|
val enabled = newValue as Boolean
|
||||||
|
|
|
@ -18,12 +18,12 @@ private const val PREF_KEY_FLASH_DRIVE_PRODUCT_ID = "flashDriveProductId"
|
||||||
|
|
||||||
private const val PREF_KEY_BACKUP_TOKEN = "backupToken"
|
private const val PREF_KEY_BACKUP_TOKEN = "backupToken"
|
||||||
private const val PREF_KEY_BACKUP_TIME = "backupTime"
|
private const val PREF_KEY_BACKUP_TIME = "backupTime"
|
||||||
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
|
|
||||||
|
|
||||||
class SettingsManager(context: Context) {
|
class SettingsManager(context: Context) {
|
||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
// FIXME Storage is currently plugin specific and not generic
|
||||||
fun setStorage(storage: Storage) {
|
fun setStorage(storage: Storage) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
||||||
|
@ -108,11 +108,6 @@ class SettingsManager(context: Context) {
|
||||||
return prefs.getLong(PREF_KEY_BACKUP_TIME, 0L)
|
return prefs.getLong(PREF_KEY_BACKUP_TIME, 0L)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Replaced by KeyManager#getBackupKey()")
|
|
||||||
fun getBackupPassword(): String? {
|
|
||||||
return prefs.getString(PREF_KEY_BACKUP_PASSWORD, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Storage(
|
data class Storage(
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
package com.stevesoltys.seedvault.settings
|
package com.stevesoltys.seedvault.settings
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
import com.stevesoltys.seedvault.transport.requestBackup
|
import com.stevesoltys.seedvault.transport.requestBackup
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||||
|
|
||||||
class SettingsViewModel(app: Application) : RequireProvisioningViewModel(app) {
|
class SettingsViewModel(
|
||||||
|
app: Application,
|
||||||
|
settingsManager: SettingsManager,
|
||||||
|
keyManager: KeyManager
|
||||||
|
) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
|
||||||
|
|
||||||
override val isRestoreOperation = false
|
override val isRestoreOperation = false
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ import android.content.pm.PackageInfo
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.seedvault.settings.SettingsActivity
|
import com.stevesoltys.seedvault.settings.SettingsActivity
|
||||||
|
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
|
|
||||||
val TRANSPORT_ID: String = ConfigurableBackupTransport::class.java.name
|
val TRANSPORT_ID: String = ConfigurableBackupTransport::class.java.name
|
||||||
|
|
||||||
|
@ -20,11 +24,10 @@ private val TAG = ConfigurableBackupTransport::class.java.simpleName
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
* @author Torsten Grote
|
* @author Torsten Grote
|
||||||
*/
|
*/
|
||||||
class ConfigurableBackupTransport internal constructor(private val context: Context) : BackupTransport() {
|
class ConfigurableBackupTransport internal constructor(private val context: Context) : BackupTransport(), KoinComponent {
|
||||||
|
|
||||||
private val pluginManager = PluginManager(context)
|
private val backupCoordinator by inject<BackupCoordinator>()
|
||||||
private val backupCoordinator = pluginManager.backupCoordinator
|
private val restoreCoordinator by inject<RestoreCoordinator>()
|
||||||
private val restoreCoordinator = pluginManager.restoreCoordinator
|
|
||||||
|
|
||||||
override fun transportDirName(): String {
|
override fun transportDirName(): String {
|
||||||
return TRANSPORT_DIRECTORY_NAME
|
return TRANSPORT_DIRECTORY_NAME
|
||||||
|
|
|
@ -2,19 +2,21 @@ package com.stevesoltys.seedvault.transport
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
import android.content.Context
|
|
||||||
import android.content.Context.BACKUP_SERVICE
|
|
||||||
import android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP
|
import android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP
|
||||||
import android.app.backup.BackupTransport.FLAG_USER_INITIATED
|
import android.app.backup.BackupTransport.FLAG_USER_INITIATED
|
||||||
|
import android.app.backup.IBackupManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Context.BACKUP_SERVICE
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.Backup
|
import com.stevesoltys.seedvault.BackupMonitor
|
||||||
|
import com.stevesoltys.seedvault.BackupNotificationManager
|
||||||
import com.stevesoltys.seedvault.NotificationBackupObserver
|
import com.stevesoltys.seedvault.NotificationBackupObserver
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.BackupMonitor
|
import org.koin.core.context.GlobalContext.get
|
||||||
|
|
||||||
private val TAG = ConfigurableBackupTransportService::class.java.simpleName
|
private val TAG = ConfigurableBackupTransportService::class.java.simpleName
|
||||||
|
|
||||||
|
@ -50,17 +52,18 @@ class ConfigurableBackupTransportService : Service() {
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun requestBackup(context: Context) {
|
fun requestBackup(context: Context) {
|
||||||
// show notification
|
// show notification
|
||||||
val nm = (context.applicationContext as Backup).notificationManager
|
val nm: BackupNotificationManager = get().koin.get()
|
||||||
nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true)
|
nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true)
|
||||||
|
|
||||||
val observer = NotificationBackupObserver(context, true)
|
val observer = NotificationBackupObserver(context, true)
|
||||||
val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED
|
val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED
|
||||||
val packages = PackageService().eligiblePackages
|
val packages = PackageService.eligiblePackages
|
||||||
val result = try {
|
val result = try {
|
||||||
Backup.backupManager.requestBackup(packages, observer, BackupMonitor(), flags)
|
val backupManager: IBackupManager = get().koin.get()
|
||||||
|
backupManager.requestBackup(packages, observer, BackupMonitor(), flags)
|
||||||
} catch (e: RemoteException) {
|
} catch (e: RemoteException) {
|
||||||
// TODO show notification on backup error
|
|
||||||
Log.e(TAG, "Error during backup: ", e)
|
Log.e(TAG, "Error during backup: ", e)
|
||||||
|
nm.onBackupError()
|
||||||
}
|
}
|
||||||
if (result == BackupManager.SUCCESS) {
|
if (result == BackupManager.SUCCESS) {
|
||||||
Log.i(TAG, "Backup succeeded ")
|
Log.i(TAG, "Backup succeeded ")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.stevesoltys.seedvault.transport
|
package com.stevesoltys.seedvault.transport
|
||||||
|
|
||||||
|
import android.app.backup.IBackupManager
|
||||||
import android.content.pm.IPackageManager
|
import android.content.pm.IPackageManager
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
|
@ -7,8 +8,9 @@ import android.os.ServiceManager.getService
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.android.collect.Sets.newArraySet
|
import com.google.android.collect.Sets.newArraySet
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.inject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
private val TAG = PackageService::class.java.simpleName
|
private val TAG = PackageService::class.java.simpleName
|
||||||
|
@ -27,9 +29,9 @@ private val IGNORED_PACKAGES = newArraySet(
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
* @author Torsten Grote
|
* @author Torsten Grote
|
||||||
*/
|
*/
|
||||||
internal class PackageService {
|
internal object PackageService : KoinComponent {
|
||||||
|
|
||||||
private val backupManager = Backup.backupManager
|
private val backupManager: IBackupManager by inject()
|
||||||
private val packageManager: IPackageManager = IPackageManager.Stub.asInterface(getService("package"))
|
private val packageManager: IPackageManager = IPackageManager.Stub.asInterface(getService("package"))
|
||||||
|
|
||||||
val eligiblePackages: Array<String>
|
val eligiblePackages: Array<String>
|
||||||
|
|
|
@ -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 Steve Soltys
|
||||||
* @author Torsten Grote
|
* @author Torsten Grote
|
||||||
*/
|
*/
|
||||||
class BackupCoordinator(
|
internal class BackupCoordinator(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: BackupPlugin,
|
||||||
private val kv: KVBackup,
|
private val kv: KVBackup,
|
||||||
|
|
|
@ -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
|
private val TAG = FullBackup::class.java.simpleName
|
||||||
|
|
||||||
class FullBackup(
|
internal class FullBackup(
|
||||||
private val plugin: FullBackupPlugin,
|
private val plugin: FullBackupPlugin,
|
||||||
private val inputFactory: InputFactory,
|
private val inputFactory: InputFactory,
|
||||||
private val headerWriter: HeaderWriter,
|
private val headerWriter: HeaderWriter,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.io.InputStream
|
||||||
/**
|
/**
|
||||||
* This class exists for easier testing, so we can mock it and return custom data inputs.
|
* This class exists for easier testing, so we can mock it and return custom data inputs.
|
||||||
*/
|
*/
|
||||||
class InputFactory {
|
internal class InputFactory {
|
||||||
|
|
||||||
fun getBackupDataInput(inputFileDescriptor: ParcelFileDescriptor): BackupDataInput {
|
fun getBackupDataInput(inputFileDescriptor: ParcelFileDescriptor): BackupDataInput {
|
||||||
return BackupDataInput(inputFileDescriptor.fileDescriptor)
|
return BackupDataInput(inputFileDescriptor.fileDescriptor)
|
||||||
|
|
|
@ -17,7 +17,7 @@ const val DEFAULT_QUOTA_KEY_VALUE_BACKUP = (2 * (5 * 1024 * 1024)).toLong()
|
||||||
|
|
||||||
private val TAG = KVBackup::class.java.simpleName
|
private val TAG = KVBackup::class.java.simpleName
|
||||||
|
|
||||||
class KVBackup(
|
internal class KVBackup(
|
||||||
private val plugin: KVBackupPlugin,
|
private val plugin: KVBackupPlugin,
|
||||||
private val inputFactory: InputFactory,
|
private val inputFactory: InputFactory,
|
||||||
private val headerWriter: HeaderWriter,
|
private val headerWriter: HeaderWriter,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.io.OutputStream
|
||||||
/**
|
/**
|
||||||
* This class exists for easier testing, so we can mock it and return custom data outputs.
|
* This class exists for easier testing, so we can mock it and return custom data outputs.
|
||||||
*/
|
*/
|
||||||
class OutputFactory {
|
internal class OutputFactory {
|
||||||
|
|
||||||
fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput {
|
fun getBackupDataOutput(outputFileDescriptor: ParcelFileDescriptor): BackupDataOutput {
|
||||||
return BackupDataOutput(outputFileDescriptor.fileDescriptor)
|
return BackupDataOutput(outputFileDescriptor.fileDescriptor)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.app.backup.RestoreDescription
|
import android.app.backup.RestoreDescription
|
||||||
import android.app.backup.RestoreDescription.*
|
import android.app.backup.RestoreDescription.*
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -23,7 +22,6 @@ private class RestoreCoordinatorState(
|
||||||
private val TAG = RestoreCoordinator::class.java.simpleName
|
private val TAG = RestoreCoordinator::class.java.simpleName
|
||||||
|
|
||||||
internal class RestoreCoordinator(
|
internal class RestoreCoordinator(
|
||||||
private val context: Context,
|
|
||||||
private val settingsManager: SettingsManager,
|
private val settingsManager: SettingsManager,
|
||||||
private val plugin: RestorePlugin,
|
private val plugin: RestorePlugin,
|
||||||
private val kv: KVRestore,
|
private val kv: KVRestore,
|
||||||
|
@ -39,7 +37,7 @@ internal class RestoreCoordinator(
|
||||||
* or null if an error occurred (the attempt should be rescheduled).
|
* or null if an error occurred (the attempt should be rescheduled).
|
||||||
**/
|
**/
|
||||||
fun getAvailableRestoreSets(): Array<RestoreSet>? {
|
fun getAvailableRestoreSets(): Array<RestoreSet>? {
|
||||||
val availableBackups = plugin.getAvailableBackups(context) ?: return null
|
val availableBackups = plugin.getAvailableBackups() ?: return null
|
||||||
val restoreSets = ArrayList<RestoreSet>()
|
val restoreSets = ArrayList<RestoreSet>()
|
||||||
for (encryptedMetadata in availableBackups) {
|
for (encryptedMetadata in availableBackups) {
|
||||||
if (encryptedMetadata.error) continue
|
if (encryptedMetadata.error) continue
|
||||||
|
|
|
@ -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
|
package com.stevesoltys.seedvault.transport.restore
|
||||||
|
|
||||||
import android.content.Context
|
import android.net.Uri
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
|
import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
|
||||||
|
|
||||||
interface RestorePlugin {
|
interface RestorePlugin {
|
||||||
|
@ -15,6 +16,15 @@ interface RestorePlugin {
|
||||||
* @return metadata for the set of restore images available,
|
* @return metadata for the set of restore images available,
|
||||||
* or null if an error occurred (the attempt should be rescheduled).
|
* or null if an error occurred (the attempt should be rescheduled).
|
||||||
**/
|
**/
|
||||||
fun getAvailableBackups(context: Context): Sequence<EncryptedBackupMetadata>?
|
fun getAvailableBackups(): Sequence<EncryptedBackupMetadata>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches if there's really a backup available in the given location.
|
||||||
|
* Returns true if at least one was found and false otherwise.
|
||||||
|
*
|
||||||
|
* FIXME: Passing a Uri is maybe too plugin-specific?
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
fun hasBackup(uri: Uri): Boolean
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 android.app.Application
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import com.stevesoltys.seedvault.Backup
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.ui.storage.StorageViewModel
|
import com.stevesoltys.seedvault.ui.storage.StorageViewModel
|
||||||
|
|
||||||
abstract class RequireProvisioningViewModel(protected val app: Application) : AndroidViewModel(app) {
|
abstract class RequireProvisioningViewModel(
|
||||||
|
protected val app: Application,
|
||||||
|
private val settingsManager: SettingsManager,
|
||||||
|
private val keyManager: KeyManager
|
||||||
|
) : AndroidViewModel(app) {
|
||||||
|
|
||||||
abstract val isRestoreOperation: Boolean
|
abstract val isRestoreOperation: Boolean
|
||||||
|
|
||||||
|
@ -13,8 +18,8 @@ abstract class RequireProvisioningViewModel(protected val app: Application) : An
|
||||||
internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation
|
internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation
|
||||||
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
||||||
|
|
||||||
internal fun validLocationIsSet() = StorageViewModel.validLocationIsSet(app)
|
internal fun validLocationIsSet() = StorageViewModel.validLocationIsSet(app, settingsManager)
|
||||||
|
|
||||||
internal fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey()
|
internal fun recoveryCodeIsSet() = keyManager.hasBackupKey()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@ package com.stevesoltys.seedvault.ui.recoverycode
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.ui.BackupActivity
|
import com.stevesoltys.seedvault.ui.BackupActivity
|
||||||
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
||||||
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
|
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
|
||||||
import com.stevesoltys.seedvault.ui.LiveEventHandler
|
import com.stevesoltys.seedvault.ui.LiveEventHandler
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class RecoveryCodeActivity : BackupActivity() {
|
class RecoveryCodeActivity : BackupActivity() {
|
||||||
|
|
||||||
private lateinit var viewModel: RecoveryCodeViewModel
|
private val viewModel: RecoveryCodeViewModel by viewModel()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -20,7 +20,6 @@ class RecoveryCodeActivity : BackupActivity() {
|
||||||
|
|
||||||
setContentView(R.layout.activity_recovery_code)
|
setContentView(R.layout.activity_recovery_code)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this).get(RecoveryCodeViewModel::class.java)
|
|
||||||
viewModel.isRestore = isRestore()
|
viewModel.isRestore = isRestore()
|
||||||
viewModel.confirmButtonClicked.observeEvent(this, LiveEventHandler { clicked ->
|
viewModel.confirmButtonClicked.observeEvent(this, LiveEventHandler { clicked ->
|
||||||
if (clicked) showInput(true)
|
if (clicked) showInput(true)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import android.widget.AutoCompleteTextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.isDebugBuild
|
import com.stevesoltys.seedvault.isDebugBuild
|
||||||
import io.github.novacrypto.bip39.Validation.InvalidChecksumException
|
import io.github.novacrypto.bip39.Validation.InvalidChecksumException
|
||||||
|
@ -19,10 +18,11 @@ import io.github.novacrypto.bip39.Validation.WordNotFoundException
|
||||||
import io.github.novacrypto.bip39.wordlists.English
|
import io.github.novacrypto.bip39.wordlists.English
|
||||||
import kotlinx.android.synthetic.main.fragment_recovery_code_input.*
|
import kotlinx.android.synthetic.main.fragment_recovery_code_input.*
|
||||||
import kotlinx.android.synthetic.main.recovery_code_input.*
|
import kotlinx.android.synthetic.main.recovery_code_input.*
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class RecoveryCodeInputFragment : Fragment() {
|
class RecoveryCodeInputFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var viewModel: RecoveryCodeViewModel
|
private val viewModel: RecoveryCodeViewModel by sharedViewModel()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
|
@ -31,7 +31,6 @@ class RecoveryCodeInputFragment : Fragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(RecoveryCodeViewModel::class.java)
|
|
||||||
|
|
||||||
if (viewModel.isRestore) {
|
if (viewModel.isRestore) {
|
||||||
introText.setText(R.string.recovery_code_input_intro)
|
introText.setText(R.string.recovery_code_input_intro)
|
||||||
|
|
|
@ -6,15 +6,15 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import kotlinx.android.synthetic.main.fragment_recovery_code_output.*
|
import kotlinx.android.synthetic.main.fragment_recovery_code_output.*
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class RecoveryCodeOutputFragment : Fragment() {
|
class RecoveryCodeOutputFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var viewModel: RecoveryCodeViewModel
|
private val viewModel: RecoveryCodeViewModel by sharedViewModel()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
|
@ -23,7 +23,6 @@ class RecoveryCodeOutputFragment : Fragment() {
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(RecoveryCodeViewModel::class.java)
|
|
||||||
|
|
||||||
setGridParameters(wordList)
|
setGridParameters(wordList)
|
||||||
wordList.adapter = RecoveryCodeAdapter(viewModel.wordList)
|
wordList.adapter = RecoveryCodeAdapter(viewModel.wordList)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package com.stevesoltys.seedvault.ui.recoverycode
|
package com.stevesoltys.seedvault.ui.recoverycode
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import com.stevesoltys.seedvault.Backup
|
import com.stevesoltys.seedvault.App
|
||||||
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
import com.stevesoltys.seedvault.ui.LiveEvent
|
import com.stevesoltys.seedvault.ui.LiveEvent
|
||||||
import com.stevesoltys.seedvault.ui.MutableLiveEvent
|
import com.stevesoltys.seedvault.ui.MutableLiveEvent
|
||||||
import io.github.novacrypto.bip39.*
|
import io.github.novacrypto.bip39.*
|
||||||
|
@ -17,7 +17,7 @@ import java.util.*
|
||||||
internal const val WORD_NUM = 12
|
internal const val WORD_NUM = 12
|
||||||
internal const val WORD_LIST_SIZE = 2048
|
internal const val WORD_LIST_SIZE = 2048
|
||||||
|
|
||||||
class RecoveryCodeViewModel(application: Application) : AndroidViewModel(application) {
|
class RecoveryCodeViewModel(app: App, private val keyManager: KeyManager) : AndroidViewModel(app) {
|
||||||
|
|
||||||
internal val wordList: List<CharSequence> by lazy {
|
internal val wordList: List<CharSequence> by lazy {
|
||||||
val items: ArrayList<CharSequence> = ArrayList(WORD_NUM)
|
val items: ArrayList<CharSequence> = ArrayList(WORD_NUM)
|
||||||
|
@ -49,7 +49,7 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica
|
||||||
}
|
}
|
||||||
val mnemonic = input.joinToString(" ")
|
val mnemonic = input.joinToString(" ")
|
||||||
val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "")
|
val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "")
|
||||||
Backup.keyManager.storeBackupKey(seed)
|
keyManager.storeBackupKey(seed)
|
||||||
|
|
||||||
mRecoveryCodeSaved.setEvent(true)
|
mRecoveryCodeSaved.setEvent(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,23 @@ package com.stevesoltys.seedvault.ui.storage
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.backup.BackupProgress
|
import android.app.backup.BackupProgress
|
||||||
|
import android.app.backup.IBackupManager
|
||||||
import android.app.backup.IBackupObserver
|
import android.app.backup.IBackupObserver
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
||||||
import com.stevesoltys.seedvault.transport.requestBackup
|
import com.stevesoltys.seedvault.transport.requestBackup
|
||||||
|
|
||||||
private val TAG = BackupStorageViewModel::class.java.simpleName
|
private val TAG = BackupStorageViewModel::class.java.simpleName
|
||||||
|
|
||||||
internal class BackupStorageViewModel(private val app: Application) : StorageViewModel(app) {
|
internal class BackupStorageViewModel(
|
||||||
|
private val app: Application,
|
||||||
|
private val backupManager: IBackupManager,
|
||||||
|
settingsManager: SettingsManager) : StorageViewModel(app, settingsManager) {
|
||||||
|
|
||||||
override val isRestoreOperation = false
|
override val isRestoreOperation = false
|
||||||
|
|
||||||
|
@ -26,7 +30,7 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
|
||||||
|
|
||||||
// initialize the new location
|
// initialize the new location
|
||||||
val observer = InitializationObserver()
|
val observer = InitializationObserver()
|
||||||
Backup.backupManager.initializeTransportsForUser(UserHandle.myUserId(), arrayOf(TRANSPORT_ID), observer)
|
backupManager.initializeTransportsForUser(UserHandle.myUserId(), arrayOf(TRANSPORT_ID), observer)
|
||||||
|
|
||||||
// if storage is on USB and this is not SetupWizard, do a backup right away
|
// if storage is on USB and this is not SetupWizard, do a backup right away
|
||||||
if (isUsb && !isSetupWizard) Thread {
|
if (isUsb && !isSetupWizard) Thread {
|
||||||
|
|
|
@ -3,21 +3,22 @@ package com.stevesoltys.seedvault.ui.storage
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.DIRECTORY_ROOT
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.plugins.findFileBlocking
|
import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT
|
||||||
import com.stevesoltys.seedvault.transport.restore.plugins.DocumentsProviderRestorePlugin
|
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
||||||
|
|
||||||
private val TAG = RestoreStorageViewModel::class.java.simpleName
|
private val TAG = RestoreStorageViewModel::class.java.simpleName
|
||||||
|
|
||||||
internal class RestoreStorageViewModel(private val app: Application) : StorageViewModel(app) {
|
internal class RestoreStorageViewModel(
|
||||||
|
private val app: Application,
|
||||||
|
private val restorePlugin: RestorePlugin,
|
||||||
|
settingsManager: SettingsManager) : StorageViewModel(app, settingsManager) {
|
||||||
|
|
||||||
override val isRestoreOperation = true
|
override val isRestoreOperation = true
|
||||||
|
|
||||||
override fun onLocationSet(uri: Uri) = Thread {
|
override fun onLocationSet(uri: Uri) = Thread {
|
||||||
if (hasBackup(uri)) {
|
if (restorePlugin.hasBackup(uri)) {
|
||||||
saveStorage(uri)
|
saveStorage(uri)
|
||||||
|
|
||||||
mLocationChecked.postEvent(LocationResult())
|
mLocationChecked.postEvent(LocationResult())
|
||||||
|
@ -30,21 +31,4 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches if there's really a backup available in the given location.
|
|
||||||
* Returns true if at least one was found and false otherwise.
|
|
||||||
*
|
|
||||||
* This method is not plugin-agnostic and breaks encapsulation.
|
|
||||||
* It is specific to the (currently only) DocumentsProvider plugin.
|
|
||||||
*
|
|
||||||
* TODO maybe move this to the RestoreCoordinator once we can inject it
|
|
||||||
*/
|
|
||||||
@WorkerThread
|
|
||||||
private fun hasBackup(folderUri: Uri): Boolean {
|
|
||||||
val parent = DocumentFile.fromTreeUri(app, folderUri) ?: throw AssertionError()
|
|
||||||
val rootDir = parent.findFileBlocking(app, DIRECTORY_ROOT) ?: return false
|
|
||||||
val backupSets = DocumentsProviderRestorePlugin.getBackups(app, rootDir)
|
|
||||||
return backupSets.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.ui.BackupActivity
|
import com.stevesoltys.seedvault.ui.BackupActivity
|
||||||
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
||||||
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
|
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
|
||||||
import com.stevesoltys.seedvault.ui.LiveEventHandler
|
import com.stevesoltys.seedvault.ui.LiveEventHandler
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.getViewModel
|
||||||
|
|
||||||
private val TAG = StorageActivity::class.java.name
|
private val TAG = StorageActivity::class.java.name
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ class StorageActivity : BackupActivity() {
|
||||||
setContentView(R.layout.activity_fragment_container)
|
setContentView(R.layout.activity_fragment_container)
|
||||||
|
|
||||||
viewModel = if (isRestore()) {
|
viewModel = if (isRestore()) {
|
||||||
ViewModelProviders.of(this).get(RestoreStorageViewModel::class.java)
|
getViewModel<RestoreStorageViewModel>()
|
||||||
} else {
|
} else {
|
||||||
ViewModelProviders.of(this).get(BackupStorageViewModel::class.java)
|
getViewModel<BackupStorageViewModel>()
|
||||||
}
|
}
|
||||||
viewModel.isSetupWizard = isSetupWizard()
|
viewModel.isSetupWizard = isSetupWizard()
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
|
||||||
import com.stevesoltys.seedvault.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE
|
import com.stevesoltys.seedvault.ui.REQUEST_CODE_OPEN_DOCUMENT_TREE
|
||||||
import kotlinx.android.synthetic.main.fragment_storage_root.*
|
import kotlinx.android.synthetic.main.fragment_storage_root.*
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.getSharedViewModel
|
||||||
|
|
||||||
internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
|
internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
viewModel = if (arguments!!.getBoolean(INTENT_EXTRA_IS_RESTORE)) {
|
viewModel = if (arguments!!.getBoolean(INTENT_EXTRA_IS_RESTORE)) {
|
||||||
ViewModelProviders.of(requireActivity()).get(RestoreStorageViewModel::class.java)
|
getSharedViewModel<RestoreStorageViewModel>()
|
||||||
} else {
|
} else {
|
||||||
ViewModelProviders.of(requireActivity()).get(BackupStorageViewModel::class.java)
|
getSharedViewModel<BackupStorageViewModel>()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.isRestoreOperation) {
|
if (viewModel.isRestoreOperation) {
|
||||||
|
|
|
@ -12,11 +12,11 @@ import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.stevesoltys.seedvault.Backup
|
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.isMassStorage
|
import com.stevesoltys.seedvault.isMassStorage
|
||||||
import com.stevesoltys.seedvault.settings.BackupManagerSettings
|
import com.stevesoltys.seedvault.settings.BackupManagerSettings
|
||||||
import com.stevesoltys.seedvault.settings.FlashDrive
|
import com.stevesoltys.seedvault.settings.FlashDrive
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.settings.Storage
|
import com.stevesoltys.seedvault.settings.Storage
|
||||||
import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService
|
import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService
|
||||||
import com.stevesoltys.seedvault.ui.LiveEvent
|
import com.stevesoltys.seedvault.ui.LiveEvent
|
||||||
|
@ -24,9 +24,10 @@ import com.stevesoltys.seedvault.ui.MutableLiveEvent
|
||||||
|
|
||||||
private val TAG = StorageViewModel::class.java.simpleName
|
private val TAG = StorageViewModel::class.java.simpleName
|
||||||
|
|
||||||
internal abstract class StorageViewModel(private val app: Application) : AndroidViewModel(app), RemovableStorageListener {
|
internal abstract class StorageViewModel(
|
||||||
|
private val app: Application,
|
||||||
protected val settingsManager = (app as Backup).settingsManager
|
protected val settingsManager: SettingsManager
|
||||||
|
) : AndroidViewModel(app), RemovableStorageListener {
|
||||||
|
|
||||||
private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
|
private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
|
||||||
internal val storageRoots: LiveData<List<StorageRoot>> get() = mStorageRoots
|
internal val storageRoots: LiveData<List<StorageRoot>> get() = mStorageRoots
|
||||||
|
@ -44,8 +45,7 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
abstract val isRestoreOperation: Boolean
|
abstract val isRestoreOperation: Boolean
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun validLocationIsSet(context: Context): Boolean {
|
internal fun validLocationIsSet(context: Context, settingsManager: SettingsManager): Boolean {
|
||||||
val settingsManager = (context.applicationContext as Backup).settingsManager
|
|
||||||
val storage = settingsManager.getStorage() ?: return false
|
val storage = settingsManager.getStorage() ?: return false
|
||||||
if (storage.isUsb) return true
|
if (storage.isUsb) return true
|
||||||
return storage.getDocumentFile(context).isDirectory
|
return storage.getDocumentFile(context).isDirectory
|
||||||
|
|
|
@ -50,7 +50,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
||||||
private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||||
private val restore = RestoreCoordinator(context, settingsManager, restorePlugin, kvRestore, fullRestore, metadataReader)
|
private val restore = RestoreCoordinator(settingsManager, restorePlugin, kvRestore, fullRestore, metadataReader)
|
||||||
|
|
||||||
private val backupDataInput = mockk<BackupDataInput>()
|
private val backupDataInput = mockk<BackupDataInput>()
|
||||||
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
||||||
|
|
|
@ -27,7 +27,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
private val full = mockk<FullRestore>()
|
private val full = mockk<FullRestore>()
|
||||||
private val metadataReader = mockk<MetadataReader>()
|
private val metadataReader = mockk<MetadataReader>()
|
||||||
|
|
||||||
private val restore = RestoreCoordinator(context, settingsManager, plugin, kv, full, metadataReader)
|
private val restore = RestoreCoordinator(settingsManager, plugin, kv, full, metadataReader)
|
||||||
|
|
||||||
private val token = Random.nextLong()
|
private val token = Random.nextLong()
|
||||||
private val inputStream = mockk<InputStream>()
|
private val inputStream = mockk<InputStream>()
|
||||||
|
@ -43,7 +43,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
androidVersion = Random.nextInt(),
|
androidVersion = Random.nextInt(),
|
||||||
deviceName = getRandomString())
|
deviceName = getRandomString())
|
||||||
|
|
||||||
every { plugin.getAvailableBackups(context) } returns sequenceOf(encryptedMetadata, encryptedMetadata)
|
every { plugin.getAvailableBackups() } returns sequenceOf(encryptedMetadata, encryptedMetadata)
|
||||||
every { metadataReader.readMetadata(inputStream, token) } returns metadata
|
every { metadataReader.readMetadata(inputStream, token) } returns metadata
|
||||||
every { inputStream.close() } just Runs
|
every { inputStream.close() } just Runs
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue