diff --git a/app/build.gradle b/app/build.gradle index a3ea567e..01efa568 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/DocumentsStorageTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/DocumentsStorageTest.kt index 588bbe42..069e48b0 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/DocumentsStorageTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/DocumentsStorageTest.kt @@ -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() private val storage = DocumentsStorage(context, settingsManager) private lateinit var file: DocumentFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 88fb2c03..a792b726 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,7 +25,7 @@ tools:ignore="ProtectedPermissions" /> { 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" diff --git a/app/src/main/java/com/stevesoltys/seedvault/Backup.kt b/app/src/main/java/com/stevesoltys/seedvault/Backup.kt deleted file mode 100644 index b238bdfa..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/Backup.kt +++ /dev/null @@ -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" diff --git a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt index 9806f558..5a8bcda3 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt @@ -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. diff --git a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt index 9bdb3529..7ca3ab0c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt @@ -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() 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) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt index 833bdf36..249bb275 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt index d859e91d..58e9932e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt new file mode 100644 index 00000000..7beb019c --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt @@ -0,0 +1,9 @@ +package com.stevesoltys.seedvault.crypto + +import org.koin.dsl.module + +val cryptoModule = module { + factory { CipherFactoryImpl(get()) } + single { KeyManagerImpl() } + single { CryptoImpl(get(), get(), get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt index 92264116..157c7e65 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderModule.kt new file mode 100644 index 00000000..ae1f56c8 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderModule.kt @@ -0,0 +1,8 @@ +package com.stevesoltys.seedvault.header + +import org.koin.dsl.module + +val headerModule = module { + single { HeaderWriterImpl() } + single { HeaderReaderImpl() } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt index 54322224..9a68e713 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt new file mode 100644 index 00000000..2c03a999 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt @@ -0,0 +1,8 @@ +package com.stevesoltys.seedvault.metadata + +import org.koin.dsl.module + +val metadataModule = module { + single { MetadataWriterImpl(get()) } + single { MetadataReaderImpl(get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt index e39249be..bd8beb9a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt index 4a01c61c..1ebbd5d7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt @@ -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) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderBackupPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt similarity index 94% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderBackupPlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt index 21ba83b2..c201cef1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderBackupPlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderFullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullBackup.kt similarity index 92% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderFullBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullBackup.kt index c6524148..0e6105ae 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderFullBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullBackup.kt @@ -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 diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt similarity index 83% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt index fbbd91ee..3ed21176 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderFullRestorePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderKVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVBackup.kt similarity index 93% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderKVBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVBackup.kt index 650077bb..b0bc45d9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsProviderKVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVBackup.kt @@ -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 diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt similarity index 82% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt index 4d710b4a..7cb54f17 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderKVRestorePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt @@ -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 diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt new file mode 100644 index 00000000..7392bed9 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt @@ -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 { DocumentsProviderBackupPlugin(get(), androidContext().packageManager) } + single { DocumentsProviderRestorePlugin(androidContext(), get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderRestorePlugin.kt new file mode 100644 index 00000000..7270e85d --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderRestorePlugin.kt @@ -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? { + 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 { + val backupSets = ArrayList() + 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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt similarity index 97% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsStorage.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt index d401c179..e57658fb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/plugins/DocumentsStorage.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt @@ -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() diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt index 5b1b67c6..5f4a87bf 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt @@ -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() diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt index 71e5d894..faab9c0f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt index 6f15c2c9..fe4852dd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt @@ -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) }) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index 3549aa63..e6f1b684 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -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() diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt index 7deac73f..af95d992 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt @@ -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 diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt index fa95640a..ef4430c2 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt @@ -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() } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt index 82ed0246..bbacac54 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt @@ -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("backup")!! backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue -> val enabled = newValue as Boolean diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt index a1f2d7a7..c52252bf 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt @@ -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( diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index 3d1788fb..50f39677 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -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 diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransport.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransport.kt index 2f11fd0a..06cd2745 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransport.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransport.kt @@ -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() + private val restoreCoordinator by inject() override fun transportDirName(): String { return TRANSPORT_DIRECTORY_NAME diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt index c6b8ec52..96607514 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt @@ -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 ") diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/PackageService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/PackageService.kt index c6853b0e..f9881a2a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/PackageService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/PackageService.kt @@ -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 diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/PluginManager.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/PluginManager.kt deleted file mode 100644 index 9f8b3563..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/PluginManager.kt +++ /dev/null @@ -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) - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 857bf0ee..1d3c4bc5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -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, diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt new file mode 100644 index 00000000..d01835f1 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt @@ -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().kvBackupPlugin, get(), get(), get()) } + single { FullBackup(get().fullBackupPlugin, get(), get(), get()) } + single { BackupCoordinator(androidContext(), get(), get(), get(), get(), get(), get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt index 63a5960c..a42c006c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt @@ -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, diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt index 836265fd..87ba7bc0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt index dd020784..292f38e0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt @@ -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, diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt index bdd0d212..e196b9a4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt index 75ea3a24..cfa3eca6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt @@ -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? { - val availableBackups = plugin.getAvailableBackups(context) ?: return null + val availableBackups = plugin.getAvailableBackups() ?: return null val restoreSets = ArrayList() for (encryptedMetadata in availableBackups) { if (encryptedMetadata.error) continue diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt new file mode 100644 index 00000000..6dfd5b27 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt @@ -0,0 +1,10 @@ +package com.stevesoltys.seedvault.transport.restore + +import org.koin.dsl.module + +val restoreModule = module { + single { OutputFactory() } + single { KVRestore(get().kvRestorePlugin, get(), get(), get()) } + single { FullRestore(get().fullRestorePlugin, get(), get(), get()) } + single { RestoreCoordinator(get(), get(), get(), get(), get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorePlugin.kt index 4a643438..607469d8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorePlugin.kt @@ -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? + fun getAvailableBackups(): Sequence? + + /** + * 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 } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderRestorePlugin.kt deleted file mode 100644 index ee451ecc..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/plugins/DocumentsProviderRestorePlugin.kt +++ /dev/null @@ -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? { - 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 { - val backupSets = ArrayList() - 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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt index eea28603..3f2559e4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt @@ -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 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() } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt index 925d47bb..210f2640 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeActivity.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt index 1adf407f..cc5f8dbe 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeOutputFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeOutputFragment.kt index 0ceba673..7f1b9dcb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeOutputFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeOutputFragment.kt @@ -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) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt index dc620924..146dbab3 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt @@ -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 by lazy { val items: ArrayList = 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) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index 8a406264..1271d42c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -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 { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt index 944e94b7..44864b92 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt @@ -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() - } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt index 2eab058a..7534c19b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt @@ -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() } else { - ViewModelProviders.of(this).get(BackupStorageViewModel::class.java) + getViewModel() } viewModel.isSetupWizard = isSetupWizard() diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt index 076d2d4a..299df5a3 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt @@ -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() } else { - ViewModelProviders.of(requireActivity()).get(BackupStorageViewModel::class.java) + getSharedViewModel() } if (viewModel.isRestoreOperation) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt index 60d38376..fa3a7cb0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt @@ -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>() internal val storageRoots: LiveData> 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 diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt index 12331375..89904481 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt @@ -50,7 +50,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl) private val fullRestorePlugin = mockk() 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() private val fileDescriptor = mockk(relaxed = true) diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt index 46fd3ff0..b857d785 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt @@ -27,7 +27,7 @@ internal class RestoreCoordinatorTest : TransportTest() { private val full = mockk() private val metadataReader = mockk() - 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() @@ -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