diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 5edad4fd..665b7acc 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,11 +1,6 @@ <component name="ProjectCodeStyleConfiguration"> <code_scheme name="Project" version="173"> <JetCodeStyleSettings> - <option name="PACKAGES_TO_USE_STAR_IMPORTS"> - <value /> - </option> - <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> - <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="ALLOW_TRAILING_COMMA" value="true" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> </JetCodeStyleSettings> diff --git a/.idea/copyright/Apache_2_0.xml b/.idea/copyright/Apache_2_0.xml deleted file mode 100644 index b0f0e1ab..00000000 --- a/.idea/copyright/Apache_2_0.xml +++ /dev/null @@ -1,6 +0,0 @@ -<component name="CopyrightManager"> - <copyright> - <option name="notice" value="SPDX-FileCopyrightText: &#36;today.year The Calyx Institute SPDX-License-Identifier: Apache-2.0" /> - <option name="myName" value="Apache-2.0" /> - </copyright> -</component> \ No newline at end of file diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt index d3eff96d..6a27ed23 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt @@ -1,12 +1,12 @@ package com.stevesoltys.seedvault -import com.stevesoltys.seedvault.restore.RestoreViewModel -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.restore.FullRestore -import com.stevesoltys.seedvault.transport.restore.KVRestore -import com.stevesoltys.seedvault.transport.restore.OutputFactory +import com.stevesoltys.seedvault.ui.restore.RestoreViewModel +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.app.backup.InputFactory +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService +import com.stevesoltys.seedvault.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore +import com.stevesoltys.seedvault.service.app.restore.OutputFactory import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel @@ -26,8 +26,8 @@ class KoinInstrumentationTestApp : App() { val context = this@KoinInstrumentationTestApp single { spyk(BackupNotificationManager(context)) } - single { spyk(FullBackup(get(), get(), get(), get())) } - single { spyk(KVBackup(get(), get(), get(), get(), get())) } + single { spyk(FullBackupService(get(), get(), get(), get())) } + single { spyk(KVBackupService(get(), get(), get(), get(), get())) } single { spyk(InputFactory()) } single { spyk(FullRestore(get(), get(), get(), get(), get())) } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt index f5dddd70..e8a158fc 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt @@ -4,14 +4,14 @@ import androidx.test.core.content.pm.PackageInfoBuilder import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderLegacyPlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderStoragePlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA -import com.stevesoltys.seedvault.plugins.saf.deleteContents -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.DocumentsProviderLegacyPlugin +import com.stevesoltys.seedvault.service.storage.saf.DocumentsProviderStoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.DocumentsStorage +import com.stevesoltys.seedvault.service.storage.saf.FILE_BACKUP_METADATA +import com.stevesoltys.seedvault.service.storage.saf.deleteContents +import com.stevesoltys.seedvault.service.settings.SettingsService import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.Dispatchers @@ -33,9 +33,9 @@ import org.koin.core.component.inject class PluginTest : KoinComponent { private val context = InstrumentationRegistry.getInstrumentation().targetContext - private val settingsManager: SettingsManager by inject() - private val mockedSettingsManager: SettingsManager = mockk() - private val storage = DocumentsStorage(context, mockedSettingsManager) + private val settingsService: SettingsService by inject() + private val mockedSettingsService: SettingsService = mockk() + private val storage = DocumentsStorage(context, mockedSettingsService) private val storagePlugin: StoragePlugin = DocumentsProviderStoragePlugin(context, storage) @@ -49,7 +49,7 @@ class PluginTest : KoinComponent { @Before fun setup() = runBlocking { - every { mockedSettingsManager.getStorage() } returns settingsManager.getStorage() + every { mockedSettingsService.getStorage() } returns settingsService.getStorage() storage.rootBackupDir?.deleteContents(context) ?: error("Select a storage location in the app first!") } @@ -76,11 +76,11 @@ class PluginTest : KoinComponent { fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) { // no backups available initially assertEquals(0, storagePlugin.getAvailableBackups()?.toList()?.size) - val s = settingsManager.getStorage() ?: error("no storage") + val s = settingsService.getStorage() ?: error("no storage") assertFalse(storagePlugin.hasBackup(s)) // prepare returned tokens requested when initializing device - every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1) + every { mockedSettingsService.getToken() } returnsMany listOf(token, token + 1, token + 1) // start new restore set and initialize device afterwards storagePlugin.startNewRestoreSet(token) @@ -114,7 +114,7 @@ class PluginTest : KoinComponent { @Test fun testMetadataWriteRead() = runBlocking(Dispatchers.IO) { - every { mockedSettingsManager.getToken() } returns token + every { mockedSettingsService.getToken() } returns token storagePlugin.startNewRestoreSet(token) storagePlugin.initializeDevice() @@ -216,7 +216,7 @@ class PluginTest : KoinComponent { } private fun initStorage(token: Long) = runBlocking { - every { mockedSettingsManager.getToken() } returns token + every { mockedSettingsService.getToken() } returns token storagePlugin.initializeDevice() } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt index 0886c682..3d7f275e 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt @@ -5,9 +5,9 @@ import android.os.ParcelFileDescriptor import com.stevesoltys.seedvault.e2e.io.BackupDataInputIntercept import com.stevesoltys.seedvault.e2e.io.InputStreamIntercept import com.stevesoltys.seedvault.e2e.screen.impl.BackupScreen -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.service.app.backup.InputFactory +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.clearMocks import io.mockk.coEvery @@ -27,9 +27,9 @@ internal interface LargeBackupTestBase : LargeTestBase { val spyBackupNotificationManager: BackupNotificationManager get() = get() - val spyFullBackup: FullBackup get() = get() + val spyFullBackupService: FullBackupService get() = get() - val spyKVBackup: KVBackup get() = get() + val spyKVBackupService: KVBackupService get() = get() val spyInputFactory: InputFactory get() = get() @@ -72,7 +72,7 @@ internal interface LargeBackupTestBase : LargeTestBase { return backupResult.copy( backupResults = backupResult.allUserApps().associate { - it.packageName to spyMetadataManager.getPackageMetadata(it.packageName) + it.packageName to spyMetadataService.getPackageMetadata(it.packageName) }.toMutableMap() ) } @@ -88,7 +88,7 @@ internal interface LargeBackupTestBase : LargeTestBase { } private fun spyOnBackup(backupResult: SeedvaultLargeTestResult): AtomicBoolean { - clearMocks(spyInputFactory, spyKVBackup, spyFullBackup) + clearMocks(spyInputFactory, spyKVBackupService, spyFullBackupService) spyOnFullBackupData(backupResult) spyOnKVBackupData(backupResult) @@ -100,7 +100,7 @@ internal interface LargeBackupTestBase : LargeTestBase { var data = mutableMapOf<String, ByteArray>() coEvery { - spyKVBackup.performBackup(any(), any(), any(), any(), any()) + spyKVBackupService.performBackup(any(), any(), any(), any(), any()) } answers { packageName = firstArg<PackageInfo>().packageName callOriginal() @@ -117,7 +117,7 @@ internal interface LargeBackupTestBase : LargeTestBase { } coEvery { - spyKVBackup.finishBackup() + spyKVBackupService.finishBackup() } answers { backupResult.kv[packageName!!] = data .mapValues { entry -> entry.value.sha256() } @@ -134,7 +134,7 @@ internal interface LargeBackupTestBase : LargeTestBase { var dataIntercept = ByteArrayOutputStream() coEvery { - spyFullBackup.performFullBackup(any(), any(), any(), any(), any()) + spyFullBackupService.performFullBackup(any(), any(), any(), any(), any()) } answers { packageName = firstArg<PackageInfo>().packageName callOriginal() @@ -150,7 +150,7 @@ internal interface LargeBackupTestBase : LargeTestBase { } every { - spyFullBackup.finishBackup() + spyFullBackupService.finishBackup() } answers { val result = callOriginal() backupResult.full[packageName!!] = dataIntercept.toByteArray().sha256() diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt index be95877d..37df98a3 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt @@ -6,9 +6,9 @@ import com.stevesoltys.seedvault.e2e.io.BackupDataOutputIntercept import com.stevesoltys.seedvault.e2e.io.OutputStreamIntercept import com.stevesoltys.seedvault.e2e.screen.impl.RecoveryCodeScreen import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen -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.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore +import com.stevesoltys.seedvault.service.app.restore.OutputFactory import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.every diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt index 69d0cf62..968d7f6c 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt @@ -11,21 +11,21 @@ import androidx.preference.PreferenceManager import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until -import com.stevesoltys.seedvault.crypto.ANDROID_KEY_STORE -import com.stevesoltys.seedvault.crypto.KEY_ALIAS_BACKUP -import com.stevesoltys.seedvault.crypto.KEY_ALIAS_MAIN -import com.stevesoltys.seedvault.crypto.KeyManager +import com.stevesoltys.seedvault.service.crypto.ANDROID_KEY_STORE +import com.stevesoltys.seedvault.service.crypto.KEY_ALIAS_BACKUP +import com.stevesoltys.seedvault.service.crypto.KEY_ALIAS_MAIN +import com.stevesoltys.seedvault.service.crypto.KeyManager import com.stevesoltys.seedvault.currentRestoreStorageViewModel import com.stevesoltys.seedvault.currentRestoreViewModel import com.stevesoltys.seedvault.e2e.screen.impl.BackupScreen import com.stevesoltys.seedvault.e2e.screen.impl.DocumentPickerScreen import com.stevesoltys.seedvault.e2e.screen.impl.RecoveryCodeScreen -import com.stevesoltys.seedvault.metadata.MetadataManager +import com.stevesoltys.seedvault.service.metadata.MetadataService import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage -import com.stevesoltys.seedvault.restore.RestoreViewModel -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.transport.backup.PackageService +import com.stevesoltys.seedvault.service.storage.saf.DocumentsStorage +import com.stevesoltys.seedvault.ui.restore.RestoreViewModel +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.app.PackageService import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -65,13 +65,13 @@ internal interface LargeTestBase : KoinComponent { val packageService: PackageService get() = get() - val settingsManager: SettingsManager get() = get() + val settingsService: SettingsService get() = get() val keyManager: KeyManager get() = get() val documentsStorage: DocumentsStorage get() = get() - val spyMetadataManager: MetadataManager get() = get() + val spyMetadataService: MetadataService get() = get() val backupManager: IBackupManager get() = get() @@ -83,7 +83,7 @@ internal interface LargeTestBase : KoinComponent { fun resetApplicationState() { backupManager.setAutoRestore(false) - settingsManager.setNewToken(null) + settingsService.setNewToken(null) documentsStorage.reset(null) val sharedPreferences = permitDiskReads { diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt index e0e29f10..b20cb184 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt @@ -47,7 +47,7 @@ internal abstract class SeedvaultLargeTest : if (arguments.getString("d2d_backup_test") == "true") { println("Enabling D2D backups for test") - settingsManager.setD2dBackupsEnabled(true) + settingsService.setD2dBackupsEnabled(true) } } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTestResult.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTestResult.kt index 4c5e3b6c..b41dc61e 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTestResult.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTestResult.kt @@ -1,8 +1,8 @@ package com.stevesoltys.seedvault.e2e import android.content.pm.PackageInfo -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.restore.AppRestoreResult +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.ui.restore.AppRestoreResult /** * Contains maps of (package name -> SHA-256 hashes) of application data. diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt index 83e638b6..a528b21d 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt @@ -3,7 +3,7 @@ package com.stevesoltys.seedvault.e2e.impl import androidx.test.filters.LargeTest import com.stevesoltys.seedvault.e2e.SeedvaultLargeTest import com.stevesoltys.seedvault.e2e.SeedvaultLargeTestResult -import com.stevesoltys.seedvault.metadata.PackageState +import com.stevesoltys.seedvault.service.metadata.PackageState import org.junit.Test @LargeTest @@ -17,7 +17,7 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { confirmCode() } - if (settingsManager.getStorage() == null) { + if (settingsService.getStorage() == null) { chooseStorageLocation() } else { changeBackupLocation() diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorageTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorageTest.kt index 353d7680..8b6a1461 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorageTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorageTest.kt @@ -13,7 +13,11 @@ import com.stevesoltys.seedvault.assertReadEquals import com.stevesoltys.seedvault.coAssertThrows import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomByteArray -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.storage.saf.DocumentsStorage +import com.stevesoltys.seedvault.service.storage.saf.createOrGetFile +import com.stevesoltys.seedvault.service.storage.saf.findFileBlocking +import com.stevesoltys.seedvault.service.storage.saf.getLoadedCursor +import com.stevesoltys.seedvault.service.settings.SettingsService import com.stevesoltys.seedvault.writeAndClose import io.mockk.Runs import io.mockk.every @@ -44,8 +48,8 @@ import kotlin.random.Random class DocumentsStorageTest : KoinComponent { private val context = InstrumentationRegistry.getInstrumentation().targetContext - private val settingsManager by inject<SettingsManager>() - private val storage = DocumentsStorage(context, settingsManager) + private val settingsService by inject<SettingsService>() + private val storage = DocumentsStorage(context, settingsService) private val filename = getRandomBase64() private lateinit var file: DocumentFile diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt index f140eb22..8ecad933 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt @@ -3,6 +3,7 @@ package com.stevesoltys.seedvault.transport.backup import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest +import com.stevesoltys.seedvault.service.app.PackageService import org.junit.Test import org.junit.runner.RunWith import org.koin.core.component.KoinComponent diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index 0888d2f4..19305302 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -5,11 +5,11 @@ <application> <!-- Remove permission requirements only for debug versions to make development easier --> <activity - android:name="com.stevesoltys.seedvault.settings.SettingsActivity" + android:name="com.stevesoltys.seedvault.ui.settings.SettingsActivity" android:exported="true" tools:remove="android:permission" /> <activity - android:name="com.stevesoltys.seedvault.restore.RestoreActivity" + android:name="com.stevesoltys.seedvault.ui.restore.RestoreActivity" android:exported="true" tools:remove="android:permission" /> </application> diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 57c91c08..98bbc430 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -87,7 +87,7 @@ tools:ignore="GoogleAppIndexingWarning"> <activity - android:name=".settings.SettingsActivity" + android:name=".ui.settings.SettingsActivity" android:exported="true" android:permission="com.stevesoltys.seedvault.OPEN_SETTINGS" /> @@ -106,7 +106,7 @@ android:theme="@style/AppTheme.NoActionBar" /> <activity - android:name=".restore.RestoreActivity" + android:name=".ui.restore.RestoreActivity" android:exported="true" android:label="@string/restore_title" android:permission="com.stevesoltys.seedvault.RESTORE_BACKUP" @@ -137,7 +137,7 @@ </receiver> <receiver - android:name=".restore.RestoreErrorBroadcastReceiver" + android:name=".ui.restore.RestoreErrorBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="com.stevesoltys.seedvault.action.UNINSTALL" /> @@ -145,7 +145,7 @@ </receiver> <receiver - android:name=".SecretCodeReceiver" + android:name=".ui.restore.SecretCodeReceiver" android:exported="true"> <intent-filter> <action android:name="android.telephony.action.SECRET_CODE" /> @@ -158,19 +158,19 @@ <!-- Used to start actual BackupService depending on scheduling criteria --> <service - android:name=".storage.StorageBackupJobService" + android:name=".service.file.backup.FileBackupJobService" android:exported="false" android:label="BackupJobService" android:permission="android.permission.BIND_JOB_SERVICE" /> <!-- Does the actual backup work as a foreground service --> <service - android:name=".storage.StorageBackupService" + android:name=".service.file.backup.FileBackupService" android:exported="false" android:foregroundServiceType="dataSync" android:label="BackupService" /> <!-- Does restore as a foreground service --> <service - android:name=".storage.StorageRestoreService" + android:name=".service.file.restore.FileRestoreService" android:exported="false" android:foregroundServiceType="dataSync" android:label="RestoreService" /> diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index f3403de7..9f1d6460 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -10,24 +10,25 @@ import android.os.Build import android.os.ServiceManager.getService import android.os.StrictMode import android.os.UserManager -import com.stevesoltys.seedvault.crypto.cryptoModule -import com.stevesoltys.seedvault.header.headerModule -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.metadata.metadataModule -import com.stevesoltys.seedvault.plugins.saf.documentsProviderModule -import com.stevesoltys.seedvault.restore.RestoreViewModel -import com.stevesoltys.seedvault.restore.install.installModule -import com.stevesoltys.seedvault.settings.AppListRetriever -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.settings.SettingsViewModel -import com.stevesoltys.seedvault.storage.storageModule -import com.stevesoltys.seedvault.transport.backup.backupModule -import com.stevesoltys.seedvault.transport.restore.restoreModule +import com.stevesoltys.seedvault.service.crypto.cryptoModule +import com.stevesoltys.seedvault.service.header.headerModule +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.metadata.metadataModule +import com.stevesoltys.seedvault.service.storage.saf.documentsProviderModule +import com.stevesoltys.seedvault.ui.restore.RestoreViewModel +import com.stevesoltys.seedvault.ui.restore.apk.installModule +import com.stevesoltys.seedvault.ui.settings.AppListRetriever +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.ui.settings.SettingsViewModel +import com.stevesoltys.seedvault.service.file.filesModule +import com.stevesoltys.seedvault.service.app.backup.backupModule +import com.stevesoltys.seedvault.service.app.restore.restoreModule import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel +import com.stevesoltys.seedvault.util.TimeSource import org.koin.android.ext.android.inject import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -43,9 +44,9 @@ import org.koin.dsl.module open class App : Application() { private val appModule = module { - single { SettingsManager(this@App) } + single { SettingsService(this@App) } single { BackupNotificationManager(this@App) } - single { Clock() } + single { TimeSource() } factory<IBackupManager> { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) } factory { AppListRetriever(this@App, get(), get(), get()) } @@ -94,24 +95,24 @@ open class App : Application() { backupModule, restoreModule, installModule, - storageModule, + filesModule, appModule ) - private val settingsManager: SettingsManager by inject() - private val metadataManager: MetadataManager by inject() + private val settingsService: SettingsService by inject() + private val metadataService: MetadataService by inject() /** - * The responsibility for the current token was moved to the [SettingsManager] + * The responsibility for the current token was moved to the [SettingsService] * in the end of 2020. * This method migrates the token for existing installs and can be removed * after sufficient time has passed. */ private fun migrateTokenFromMetadataToSettingsManager() { @Suppress("DEPRECATION") - val token = metadataManager.getBackupToken() - if (token != 0L && settingsManager.getToken() == null) { - settingsManager.setNewToken(token) + val token = metadataService.getBackupToken() + if (token != 0L && settingsService.getToken() == null) { + settingsService.setNewToken(token) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt index 4800fcef..2e02a432 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt @@ -15,12 +15,12 @@ import android.os.Looper import android.provider.DocumentsContract import android.util.Log import androidx.core.content.ContextCompat.startForegroundService -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.settings.FlashDrive -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.storage.StorageBackupService -import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP -import com.stevesoltys.seedvault.transport.requestBackup +import com.stevesoltys.seedvault.service.app.backup.requestBackup +import com.stevesoltys.seedvault.service.file.backup.FileBackupService +import com.stevesoltys.seedvault.service.file.backup.FileBackupService.Companion.EXTRA_START_APP_BACKUP +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.settings.FlashDrive +import com.stevesoltys.seedvault.service.settings.SettingsService import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE import org.koin.core.context.GlobalContext.get import java.util.concurrent.TimeUnit.HOURS @@ -32,17 +32,17 @@ private const val HOURS_AUTO_BACKUP: Long = 24 class UsbIntentReceiver : UsbMonitor() { // using KoinComponent would crash robolectric tests :( - private val settingsManager: SettingsManager by lazy { get().get() } - private val metadataManager: MetadataManager by lazy { get().get() } + private val settingsService: SettingsService by lazy { get().get() } + private val metadataService: MetadataService by lazy { get().get() } 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 savedFlashDrive = settingsManager.getFlashDrive() ?: return false + val savedFlashDrive = settingsService.getFlashDrive() ?: return false val attachedFlashDrive = FlashDrive.from(device) return if (savedFlashDrive == attachedFlashDrive) { Log.d(TAG, "Matches stored device, checking backup time...") - val backupMillis = System.currentTimeMillis() - metadataManager.getLastBackupTime() + val backupMillis = System.currentTimeMillis() - metadataService.getLastBackupTime() if (backupMillis >= HOURS.toMillis(HOURS_AUTO_BACKUP)) { Log.d(TAG, "Last backup older than 24 hours, requesting a backup...") true @@ -57,8 +57,8 @@ class UsbIntentReceiver : UsbMonitor() { } override fun onStatusChanged(context: Context, action: String, device: UsbDevice) { - if (settingsManager.isStorageBackupEnabled()) { - val i = Intent(context, StorageBackupService::class.java) + if (settingsService.isStorageBackupEnabled()) { + val i = Intent(context, FileBackupService::class.java) // this starts an app backup afterwards i.putExtra(EXTRA_START_APP_BACKUP, true) startForegroundService(context, i) diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderModule.kt deleted file mode 100644 index 82d5a04d..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderModule.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.stevesoltys.seedvault.header - -import org.koin.dsl.module - -val headerModule = module { - single<HeaderReader> { HeaderReaderImpl() } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/BackupManagerOperationMonitor.kt similarity index 78% rename from app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/BackupManagerOperationMonitor.kt index 8e961916..5a1af5fc 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/BackupManagerOperationMonitor.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault +package com.stevesoltys.seedvault.service.app import android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY import android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_ID @@ -8,9 +8,9 @@ import android.os.Bundle import android.util.Log import android.util.Log.DEBUG -private val TAG = BackupMonitor::class.java.name +private val TAG = BackupManagerOperationMonitor::class.java.name -class BackupMonitor : IBackupManagerMonitor.Stub() { +class BackupManagerOperationMonitor : IBackupManagerMonitor.Stub() { override fun onEvent(bundle: Bundle) { if (!Log.isLoggable(TAG, DEBUG)) return @@ -18,5 +18,4 @@ class BackupMonitor : IBackupManagerMonitor.Stub() { Log.d(TAG, "CATEGORY: " + bundle.getInt(EXTRA_LOG_EVENT_CATEGORY, -1)) Log.d(TAG, "PACKAGE: " + bundle.getString(EXTRA_LOG_EVENT_PACKAGE_NAME, "?")) } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/PackageService.kt similarity index 92% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/PackageService.kt index ca2e8ab0..4daadebb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/PackageService.kt @@ -1,6 +1,5 @@ -package com.stevesoltys.seedvault.transport.backup +package com.stevesoltys.seedvault.service.app -import android.app.backup.IBackupManager import android.content.Context import android.content.pm.ApplicationInfo.FLAG_ALLOW_BACKUP import android.content.pm.ApplicationInfo.FLAG_STOPPED @@ -12,17 +11,12 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_INSTRUMENTATION import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.os.RemoteException -import android.os.UserHandle import android.util.Log import android.util.Log.INFO import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.settings.SettingsManager - -private val TAG = PackageService::class.java.simpleName - -private const val LOG_MAX_PACKAGES = 100 +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.settings.SettingsService /** * @author Steve Soltys @@ -30,13 +24,17 @@ private const val LOG_MAX_PACKAGES = 100 */ internal class PackageService( private val context: Context, - private val backupManager: IBackupManager, - private val settingsManager: SettingsManager, + private val settingsService: SettingsService, private val plugin: StoragePlugin, ) { + companion object { + private val TAG = PackageService::class.java.simpleName + + private const val LOG_MAX_PACKAGES = 100 + } + private val packageManager: PackageManager = context.packageManager - private val myUserId = UserHandle.myUserId() val eligiblePackages: Array<String> @WorkerThread @@ -96,7 +94,7 @@ internal class PackageService( @WorkerThread get() = packageManager.getInstalledPackages(GET_INSTRUMENTATION).filter { packageInfo -> packageInfo.isUserVisible(context) && - packageInfo.allowsBackup(settingsManager.d2dBackupsEnabled()) + packageInfo.allowsBackup(settingsService.d2dBackupsEnabled()) } /** @@ -105,7 +103,7 @@ internal class PackageService( val userNotAllowedApps: List<PackageInfo> @WorkerThread get() = packageManager.getInstalledPackages(0).filter { packageInfo -> - !packageInfo.allowsBackup(settingsManager.d2dBackupsEnabled()) && + !packageInfo.allowsBackup(settingsService.d2dBackupsEnabled()) && !packageInfo.isSystemApp() } @@ -140,7 +138,7 @@ internal class PackageService( // apps that we, or the user, want to exclude. // Check that the app is not excluded by user preference - val enabled = settingsManager.isBackupEnabled(packageName) + val enabled = settingsService.isBackupEnabled(packageName) // We need to explicitly exclude DocumentsProvider and Seedvault. // Otherwise, they get killed while backing them up, terminating our backup. diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/AppBackupService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/AppBackupService.kt new file mode 100644 index 00000000..4d329c9f --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/AppBackupService.kt @@ -0,0 +1,54 @@ +package com.stevesoltys.seedvault.service.app.backup + +import android.app.backup.BackupManager +import android.app.backup.IBackupManager +import android.content.Context +import android.os.RemoteException +import android.util.Log +import androidx.annotation.WorkerThread +import com.stevesoltys.seedvault.service.app.BackupManagerOperationMonitor +import com.stevesoltys.seedvault.service.app.PackageService +import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager +import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver +import org.koin.core.context.GlobalContext + +private val TAG = AppBackupService::class.java.simpleName + +internal class AppBackupService( + private val context: Context, +) { + + fun initiateBackup() { + requestBackup(context) + } +} + +/** + * TODO: Move to above service class. + */ +@WorkerThread +fun requestBackup(context: Context) { + val backupManager: IBackupManager = GlobalContext.get().get() + if (backupManager.isBackupEnabled) { + val packageService: PackageService = GlobalContext.get().get() + val packages = packageService.eligiblePackages + val appTotals = packageService.expectedAppTotals + + val result = try { + Log.d(TAG, "Backup is enabled, request backup...") + val observer = NotificationBackupObserver(context, packages.size, appTotals) + backupManager.requestBackup(packages, observer, BackupManagerOperationMonitor(), 0) + } catch (e: RemoteException) { + Log.e(TAG, "Error during backup: ", e) + val nm: BackupNotificationManager = GlobalContext.get().get() + nm.onBackupError() + } + if (result == BackupManager.SUCCESS) { + Log.i(TAG, "Backup succeeded ") + } else { + Log.e(TAG, "Backup failed: $result") + } + } else { + Log.i(TAG, "Backup is not enabled") + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/BackupModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/BackupModule.kt new file mode 100644 index 00000000..681da237 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/BackupModule.kt @@ -0,0 +1,62 @@ +package com.stevesoltys.seedvault.service.app.backup + +import com.stevesoltys.seedvault.service.app.PackageService +import com.stevesoltys.seedvault.service.app.backup.apk.ApkBackupService +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManager +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManagerImpl +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val backupModule = module { + single { InputFactory() } + single { + PackageService( + context = androidContext(), + settingsService = get(), + plugin = get() + ) + } + single { + ApkBackupService( + pm = androidContext().packageManager, + cryptoService = get(), + settingsService = get(), + metadataService = get() + ) + } + single<KvDbManager> { KvDbManagerImpl(androidContext()) } + single { + KVBackupService( + plugin = get(), + settingsService = get(), + inputFactory = get(), + cryptoService = get(), + dbManager = get() + ) + } + single { + FullBackupService( + plugin = get(), + settingsService = get(), + inputFactory = get(), + cryptoService = get() + ) + } + single { + BackupCoordinatorService( + context = androidContext(), + plugin = get(), + kv = get(), + full = get(), + apkBackupService = get(), + timeSource = get(), + packageService = get(), + metadataService = get(), + settingsService = get(), + nm = get() + ) + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/InputFactory.kt similarity index 91% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/backup/InputFactory.kt index 87ba7bc0..f06deecf 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/InputFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/InputFactory.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.transport.backup +package com.stevesoltys.seedvault.service.app.backup import android.app.backup.BackupDataInput import android.os.ParcelFileDescriptor diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/apk/ApkBackupService.kt similarity index 88% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/backup/apk/ApkBackupService.kt index 02208eb0..71a09306 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/apk/ApkBackupService.kt @@ -1,4 +1,12 @@ -package com.stevesoltys.seedvault.transport.backup +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.backup.apk import android.annotation.SuppressLint import android.content.pm.PackageInfo @@ -8,13 +16,15 @@ import android.content.pm.SigningInfo import android.util.Log import android.util.PackageUtils.computeSha256DigestBytes import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.encodeBase64 -import com.stevesoltys.seedvault.metadata.ApkSplit -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.util.encodeBase64 +import com.stevesoltys.seedvault.service.metadata.ApkSplit +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.app.isNotUpdatedSystemApp +import com.stevesoltys.seedvault.service.app.isTestOnly import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -23,14 +33,14 @@ import java.io.InputStream import java.io.OutputStream import java.security.MessageDigest -private val TAG = ApkBackup::class.java.simpleName +private val TAG = ApkBackupService::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") -internal class ApkBackup( +internal class ApkBackupService( private val pm: PackageManager, - private val crypto: Crypto, - private val settingsManager: SettingsManager, - private val metadataManager: MetadataManager, + private val cryptoService: CryptoService, + private val settingsService: SettingsService, + private val metadataService: MetadataService, ) { /** @@ -53,7 +63,7 @@ internal class ApkBackup( if (packageName == MAGIC_PACKAGE_MANAGER) return null // do not back up when setting is not enabled - if (!settingsManager.backupApks()) return null + if (!settingsService.backupApks()) return null // do not back up test-only apps as we can't re-install them anyway // see: https://commonsware.com/blog/2017/10/31/android-studio-3p0-flag-test-only.html @@ -82,7 +92,7 @@ internal class ApkBackup( } // get cached metadata about package - val packageMetadata = metadataManager.getPackageMetadata(packageName) + val packageMetadata = metadataService.getPackageMetadata(packageName) ?: PackageMetadata() // get version codes @@ -104,7 +114,7 @@ internal class ApkBackup( // get an InputStream for the APK val inputStream = getApkInputStream(packageInfo.applicationInfo.sourceDir) // copy the APK to the storage's output and calculate SHA-256 hash while at it - val name = crypto.getNameForApk(metadataManager.salt, packageName) + val name = cryptoService.getNameForApk(metadataService.salt, packageName) val sha256 = copyStreamsAndGetHash(inputStream, streamGetter(name)) // back up splits if they exist @@ -195,7 +205,7 @@ internal class ApkBackup( } } val sha256 = messageDigest.digest().encodeBase64() - val name = crypto.getNameForApk(metadataManager.salt, packageName, splitName) + val name = cryptoService.getNameForApk(metadataService.salt, packageName, splitName) // copy the split APK to the storage stream getApkInputStream(sourceDir).use { inputStream -> streamGetter(name).use { outputStream -> diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/coordinator/BackupCoordinatorService.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/backup/coordinator/BackupCoordinatorService.kt index 87a13020..9e38402d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/coordinator/BackupCoordinatorService.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.transport.backup +package com.stevesoltys.seedvault.service.app.backup.coordinator import android.app.backup.BackupTransport.FLAG_DATA_NOT_CHANGED import android.app.backup.BackupTransport.FLAG_INCREMENTAL @@ -18,62 +18,52 @@ import android.os.ParcelFileDescriptor import android.util.Log import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread -import com.stevesoltys.seedvault.Clock import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.metadata.BackupType -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.app.PackageService +import com.stevesoltys.seedvault.service.app.backup.apk.ApkBackupService +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService +import com.stevesoltys.seedvault.service.app.isStopped +import com.stevesoltys.seedvault.service.app.isSystemApp +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.metadata.PackageState +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.settings.SettingsService import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager +import com.stevesoltys.seedvault.util.TimeSource import java.io.IOException import java.io.OutputStream import java.util.concurrent.TimeUnit.DAYS import java.util.concurrent.TimeUnit.HOURS -private val TAG = BackupCoordinator::class.java.simpleName - -private class CoordinatorState( - var calledInitialize: Boolean, - var calledClearBackupData: Boolean, - var cancelReason: PackageState, -) { - val expectFinish: Boolean - get() = calledInitialize || calledClearBackupData - - fun onFinish() { - calledInitialize = false - calledClearBackupData = false - cancelReason = UNKNOWN_ERROR - } -} - /** * @author Steve Soltys * @author Torsten Grote */ @WorkerThread // entire class should always be accessed from a worker thread, so blocking is ok @Suppress("BlockingMethodInNonBlockingContext") -internal class BackupCoordinator( +internal class BackupCoordinatorService( private val context: Context, private val plugin: StoragePlugin, - private val kv: KVBackup, - private val full: FullBackup, - private val apkBackup: ApkBackup, - private val clock: Clock, + private val kv: KVBackupService, + private val full: FullBackupService, + private val apkBackupService: ApkBackupService, + private val timeSource: TimeSource, private val packageService: PackageService, - private val metadataManager: MetadataManager, - private val settingsManager: SettingsManager, + private val metadataService: MetadataService, + private val settingsService: SettingsService, private val nm: BackupNotificationManager, ) { - private val state = CoordinatorState( + private val TAG = BackupCoordinatorService::class.java.simpleName + + private val state = BackupCoordinatorState( calledInitialize = false, calledClearBackupData = false, cancelReason = UNKNOWN_ERROR @@ -92,9 +82,9 @@ internal class BackupCoordinator( */ @Throws(IOException::class) private suspend fun startNewRestoreSet(): Long { - val token = clock.time() + val token = timeSource.time() Log.i(TAG, "Starting new RestoreSet with token $token...") - settingsManager.setNewToken(token) + settingsService.setNewToken(token) plugin.startNewRestoreSet(token) return token } @@ -125,7 +115,7 @@ internal class BackupCoordinator( plugin.initializeDevice() Log.d(TAG, "Resetting backup metadata for token $token...") plugin.getMetadataOutputStream(token).use { - metadataManager.onDeviceInitialization(token, it) + metadataService.onDeviceInitialization(token, it) } // [finishBackup] will only be called when we return [TRANSPORT_OK] here // so we remember that we initialized successfully @@ -134,7 +124,7 @@ internal class BackupCoordinator( } catch (e: IOException) { Log.e(TAG, "Error initializing device", e) // Show error notification if we needed init or were ready for backups - if (metadataManager.requiresInit || settingsManager.canDoBackupNow()) nm.onBackupError() + if (metadataService.requiresInit || settingsService.canDoBackupNow()) nm.onBackupError() TRANSPORT_ERROR } @@ -230,7 +220,7 @@ internal class BackupCoordinator( flags: Int, ): Int { state.cancelReason = UNKNOWN_ERROR - if (metadataManager.requiresInit) { + if (metadataService.requiresInit) { // start a new restore set to upgrade from legacy format // by starting a clean backup with all files using the new version try { @@ -241,8 +231,8 @@ internal class BackupCoordinator( // this causes a backup error, but things should go back to normal afterwards return TRANSPORT_NOT_INITIALIZED } - val token = settingsManager.getToken() ?: error("no token in performFullBackup") - val salt = metadataManager.salt + val token = settingsService.getToken() ?: error("no token in performFullBackup") + val salt = metadataService.salt return kv.performBackup(packageInfo, data, flags, token, salt) } @@ -280,8 +270,8 @@ internal class BackupCoordinator( flags: Int, ): Int { state.cancelReason = UNKNOWN_ERROR - val token = settingsManager.getToken() ?: error("no token in performFullBackup") - val salt = metadataManager.salt + val token = settingsService.getToken() ?: error("no token in performFullBackup") + val salt = metadataService.salt return full.performFullBackup(targetPackage, fileDescriptor, flags, token, salt) } @@ -310,8 +300,8 @@ internal class BackupCoordinator( // don't bother with system apps that have no data val ignoreApp = state.cancelReason == NO_DATA && packageInfo.isSystemApp() if (!ignoreApp) onPackageBackupError(packageInfo, BackupType.FULL) - val token = settingsManager.getToken() ?: error("no token in cancelFullBackup") - val salt = metadataManager.salt + val token = settingsService.getToken() ?: error("no token in cancelFullBackup") + val salt = metadataService.salt full.cancelFullBackup(token, salt, ignoreApp) } @@ -329,8 +319,8 @@ internal class BackupCoordinator( suspend fun clearBackupData(packageInfo: PackageInfo): Int { val packageName = packageInfo.packageName Log.i(TAG, "Clear Backup Data of $packageName.") - val token = settingsManager.getToken() ?: error("no token in clearBackupData") - val salt = metadataManager.salt + val token = settingsService.getToken() ?: error("no token in clearBackupData") + val salt = metadataService.salt try { kv.clearBackupData(packageInfo, token, salt) } catch (e: IOException) { @@ -368,7 +358,7 @@ internal class BackupCoordinator( if (result == TRANSPORT_OK) { val isPmBackup = packageName == MAGIC_PACKAGE_MANAGER // call onPackageBackedUp for @pm@ only if we can do backups right now - if (!isPmBackup || settingsManager.canDoBackupNow()) { + if (!isPmBackup || settingsService.canDoBackupNow()) { try { onPackageBackedUp(packageInfo, BackupType.KV) } catch (e: Exception) { @@ -377,7 +367,7 @@ internal class BackupCoordinator( } } // hook in here to back up APKs of apps that are otherwise not allowed for backup - if (isPmBackup && settingsManager.canDoBackupNow()) { + if (isPmBackup && settingsService.canDoBackupNow()) { try { backUpApksOfNotBackedUpPackages() } catch (e: Exception) { @@ -426,7 +416,7 @@ internal class BackupCoordinator( val wasBackedUp = backUpApk(packageInfo, packageState) if (!wasBackedUp) { val packageMetadata = - metadataManager.getPackageMetadata(packageName) + metadataService.getPackageMetadata(packageName) val oldPackageState = packageMetadata?.state if (oldPackageState != packageState) { Log.i( @@ -434,7 +424,7 @@ internal class BackupCoordinator( ", update to $packageState" ) plugin.getMetadataOutputStream().use { - metadataManager.onPackageBackupError(packageInfo, packageState, it) + metadataService.onPackageBackupError(packageInfo, packageState, it) } } } @@ -455,12 +445,12 @@ internal class BackupCoordinator( ): Boolean { val packageName = packageInfo.packageName return try { - apkBackup.backupApkIfNecessary(packageInfo, packageState) { name -> - val token = settingsManager.getToken() ?: throw IOException("no current token") + apkBackupService.backupApkIfNecessary(packageInfo, packageState) { name -> + val token = settingsService.getToken() ?: throw IOException("no current token") plugin.getOutputStream(token, name) }?.let { packageMetadata -> plugin.getMetadataOutputStream().use { - metadataManager.onApkBackedUp(packageInfo, packageMetadata, it) + metadataService.onApkBackedUp(packageInfo, packageMetadata, it) } true } ?: false @@ -472,7 +462,7 @@ internal class BackupCoordinator( private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType) { plugin.getMetadataOutputStream().use { - metadataManager.onPackageBackedUp(packageInfo, type, it) + metadataService.onPackageBackedUp(packageInfo, type, it) } } @@ -480,7 +470,7 @@ internal class BackupCoordinator( val packageName = packageInfo.packageName try { plugin.getMetadataOutputStream().use { - metadataManager.onPackageBackupError(packageInfo, state.cancelReason, it, type) + metadataService.onPackageBackupError(packageInfo, state.cancelReason, it, type) } } catch (e: IOException) { Log.e(TAG, "Error while writing metadata for $packageName", e) @@ -491,7 +481,7 @@ internal class BackupCoordinator( val longBackoff = DAYS.toMillis(30) // back off if there's no storage set - val storage = settingsManager.getStorage() ?: return longBackoff + val storage = settingsService.getStorage() ?: return longBackoff return when { // back off if storage is removable and not available right now storage.isUnavailableUsb(context) -> longBackoff @@ -503,8 +493,10 @@ internal class BackupCoordinator( } private suspend fun StoragePlugin.getMetadataOutputStream(token: Long? = null): OutputStream { - val t = token ?: settingsManager.getToken() ?: throw IOException("no current token") - return getOutputStream(t, FILE_BACKUP_METADATA) + val t = token ?: settingsService.getToken() ?: throw IOException("no current token") + return getOutputStream(t, + com.stevesoltys.seedvault.service.storage.saf.FILE_BACKUP_METADATA + ) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/coordinator/BackupCoordinatorState.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/coordinator/BackupCoordinatorState.kt new file mode 100644 index 00000000..9bd623ca --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/coordinator/BackupCoordinatorState.kt @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.backup.coordinator + +import com.stevesoltys.seedvault.service.metadata.PackageState + +class BackupCoordinatorState( + var calledInitialize: Boolean, + var calledClearBackupData: Boolean, + var cancelReason: PackageState, +) { + val expectFinish: Boolean + get() = calledInitialize || calledClearBackupData + + fun onFinish() { + calledInitialize = false + calledClearBackupData = false + cancelReason = PackageState.UNKNOWN_ERROR + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/full/FullBackupService.kt similarity index 85% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/backup/full/FullBackupService.kt index 48a42d64..d505986e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/full/FullBackupService.kt @@ -1,4 +1,8 @@ -package com.stevesoltys.seedvault.transport.backup +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.backup.full import android.app.backup.BackupTransport.FLAG_USER_INITIATED import android.app.backup.BackupTransport.TRANSPORT_ERROR @@ -8,41 +12,26 @@ import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import android.util.Log -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.app.backup.InputFactory +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.getADForFull +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.storage.StoragePlugin import libcore.io.IoUtils.closeQuietly import java.io.EOFException import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -private class FullBackupState( - val packageInfo: PackageInfo, - val inputFileDescriptor: ParcelFileDescriptor, - val inputStream: InputStream, - var outputStreamInit: (suspend () -> OutputStream)?, -) { - /** - * This is an encrypted stream that can be written to directly. - */ - var outputStream: OutputStream? = null - val packageName: String = packageInfo.packageName - var size: Long = 0 -} const val DEFAULT_QUOTA_FULL_BACKUP = (2 * (25 * 1024 * 1024)).toLong() -private val TAG = FullBackup::class.java.simpleName +private val TAG = FullBackupService::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") -internal class FullBackup( +internal class FullBackupService( private val plugin: StoragePlugin, - private val settingsManager: SettingsManager, + private val settingsService: SettingsService, private val inputFactory: InputFactory, - private val crypto: Crypto, + private val cryptoService: CryptoService, ) { private var state: FullBackupState? = null @@ -52,7 +41,7 @@ internal class FullBackup( fun getCurrentPackage() = state?.packageInfo fun getQuota(): Long { - return if (settingsManager.isQuotaUnlimited()) Long.MAX_VALUE else DEFAULT_QUOTA_FULL_BACKUP + return if (settingsService.isQuotaUnlimited()) Long.MAX_VALUE else DEFAULT_QUOTA_FULL_BACKUP } fun checkFullBackupSize(size: Long): Int { @@ -114,7 +103,7 @@ internal class FullBackup( val inputStream = inputFactory.getInputStream(socket) state = FullBackupState(targetPackage, socket, inputStream) { Log.d(TAG, "Initializing OutputStream for $packageName.") - val name = crypto.getNameForPackage(salt, packageName) + val name = cryptoService.getNameForPackage(salt, packageName) // get OutputStream to write backup data into val outputStream = try { plugin.getOutputStream(token, name) @@ -122,12 +111,15 @@ internal class FullBackup( "Error getting OutputStream for full backup of $packageName".let { Log.e(TAG, it, e) } - throw(e) + throw (e) } // store version header val state = this.state ?: throw AssertionError() outputStream.write(ByteArray(1) { VERSION }) - crypto.newEncryptingStream(outputStream, getADForFull(VERSION, state.packageName)) + cryptoService.newEncryptingStream( + outputStream, + getADForFull(VERSION, state.packageName) + ) } // this lambda is only called before we actually write backup data the first time return TRANSPORT_OK } @@ -173,7 +165,7 @@ internal class FullBackup( @Throws(IOException::class) suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) { - val name = crypto.getNameForPackage(salt, packageInfo.packageName) + val name = cryptoService.getNameForPackage(salt, packageInfo.packageName) plugin.removeData(token, name) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/full/FullBackupState.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/full/FullBackupState.kt new file mode 100644 index 00000000..85d04450 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/full/FullBackupState.kt @@ -0,0 +1,20 @@ +package com.stevesoltys.seedvault.service.app.backup.full + +import android.content.pm.PackageInfo +import android.os.ParcelFileDescriptor +import java.io.InputStream +import java.io.OutputStream + +class FullBackupState( + val packageInfo: PackageInfo, + val inputFileDescriptor: ParcelFileDescriptor, + val inputStream: InputStream, + var outputStreamInit: (suspend () -> OutputStream)?, +) { + /** + * This is an encrypted stream that can be written to directly. + */ + var outputStream: OutputStream? = null + val packageName: String = packageInfo.packageName + var size: Long = 0 +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVBackupService.kt similarity index 89% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVBackupService.kt index 060f5431..a3bab241 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVBackupService.kt @@ -1,4 +1,8 @@ -package com.stevesoltys.seedvault.transport.backup +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.backup.kv import android.app.backup.BackupTransport.FLAG_DATA_NOT_CHANGED import android.app.backup.BackupTransport.FLAG_INCREMENTAL @@ -10,33 +14,25 @@ import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import android.util.Log import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.getADForKV +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.app.backup.InputFactory import java.io.IOException import java.util.zip.GZIPOutputStream -class KVBackupState( - internal val packageInfo: PackageInfo, - val token: Long, - val name: String, - val db: KVDb, -) { - var needsUpload: Boolean = false -} - const val DEFAULT_QUOTA_KEY_VALUE_BACKUP = (2 * (5 * 1024 * 1024)).toLong() -private val TAG = KVBackup::class.java.simpleName +private val TAG = KVBackupService::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") -internal class KVBackup( +internal class KVBackupService( private val plugin: StoragePlugin, - private val settingsManager: SettingsManager, + private val settingsService: SettingsService, private val inputFactory: InputFactory, - private val crypto: Crypto, + private val cryptoService: CryptoService, private val dbManager: KvDbManager, ) { @@ -46,7 +42,7 @@ internal class KVBackup( fun getCurrentPackage() = state?.packageInfo - fun getQuota(): Long = if (settingsManager.isQuotaUnlimited()) { + fun getQuota(): Long = if (settingsService.isQuotaUnlimited()) { Long.MAX_VALUE } else { DEFAULT_QUOTA_KEY_VALUE_BACKUP @@ -84,7 +80,7 @@ internal class KVBackup( if (state != null) { throw AssertionError("Have state for ${state.packageInfo.packageName}") } - val name = crypto.getNameForPackage(salt, packageName) + val name = cryptoService.getNameForPackage(salt, packageName) val db = dbManager.getDb(packageName) this.state = KVBackupState(packageInfo, token, name, db) @@ -134,7 +130,7 @@ internal class KVBackup( // K/V backups (typically starting with package manager metadata - @pm@) // are scheduled with JobInfo.Builder#setOverrideDeadline() // and thus do not respect backoff. - settingsManager.canDoBackupNow() + settingsService.canDoBackupNow() } else { // all other packages always need upload true @@ -194,7 +190,7 @@ internal class KVBackup( @Throws(IOException::class) suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) { Log.i(TAG, "Clearing K/V data of ${packageInfo.packageName}") - val name = state?.name ?: crypto.getNameForPackage(salt, packageInfo.packageName) + val name = state?.name ?: cryptoService.getNameForPackage(salt, packageInfo.packageName) plugin.removeData(token, name) if (!dbManager.deleteDb(packageInfo.packageName)) throw IOException() } @@ -244,7 +240,7 @@ internal class KVBackup( plugin.getOutputStream(token, name).use { outputStream -> outputStream.write(ByteArray(1) { VERSION }) val ad = getADForKV(VERSION, packageName) - crypto.newEncryptingStream(outputStream, ad).use { encryptedStream -> + cryptoService.newEncryptingStream(outputStream, ad).use { encryptedStream -> GZIPOutputStream(encryptedStream).use { gZipStream -> dbManager.getDbInputStream(packageName).use { inputStream -> inputStream.copyTo(gZipStream) diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVBackupState.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVBackupState.kt new file mode 100644 index 00000000..f92477dd --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVBackupState.kt @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.backup.kv + +import android.content.pm.PackageInfo + +class KVBackupState( + internal val packageInfo: PackageInfo, + val token: Long, + val name: String, + val db: KVDb, +) { + var needsUpload: Boolean = false +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVDbManager.kt similarity index 97% rename from app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVDbManager.kt index 95a026fb..055a2536 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/backup/kv/KVDbManager.kt @@ -1,4 +1,8 @@ -package com.stevesoltys.seedvault.transport.backup +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.backup.kv import android.content.ContentValues import android.content.Context diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/AppRestoreService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/AppRestoreService.kt new file mode 100644 index 00000000..103a822e --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/AppRestoreService.kt @@ -0,0 +1,10 @@ +package com.stevesoltys.seedvault.service.app.restore + +import com.stevesoltys.seedvault.ui.restore.RestorableBackup + +class AppRestoreService { + + fun initiateRestore(restorableBackup: RestorableBackup) { + + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/OutputFactory.kt similarity index 91% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/restore/OutputFactory.kt index 393cbfee..686b0cb1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/OutputFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/OutputFactory.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.transport.restore +package com.stevesoltys.seedvault.service.app.restore import android.app.backup.BackupDataOutput import android.os.ParcelFileDescriptor @@ -17,5 +17,4 @@ internal class OutputFactory { fun getOutputStream(outputFileDescriptor: ParcelFileDescriptor): OutputStream { return FileOutputStream(outputFileDescriptor.fileDescriptor) } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/RestoreModule.kt similarity index 58% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/restore/RestoreModule.kt index 7ea9248b..b3eb820a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/RestoreModule.kt @@ -1,5 +1,8 @@ -package com.stevesoltys.seedvault.transport.restore +package com.stevesoltys.seedvault.service.app.restore +import com.stevesoltys.seedvault.service.app.restore.coordinator.RestoreCoordinator +import com.stevesoltys.seedvault.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore import org.koin.android.ext.koin.androidContext import org.koin.dsl.module diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/coordinator/RestoreCoordinator.kt similarity index 91% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/restore/coordinator/RestoreCoordinator.kt index 68431258..f85930c4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/coordinator/RestoreCoordinator.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.transport.restore +package com.stevesoltys.seedvault.service.app.restore.coordinator import android.app.backup.BackupTransport.TRANSPORT_ERROR import android.app.backup.BackupTransport.TRANSPORT_OK @@ -13,15 +13,17 @@ import android.os.ParcelFileDescriptor import android.util.Log import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.header.UnsupportedVersionException -import com.stevesoltys.seedvault.metadata.BackupMetadata -import com.stevesoltys.seedvault.metadata.BackupType -import com.stevesoltys.seedvault.metadata.DecryptionFailedException -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.metadata.MetadataReader -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.DecryptionFailedException +import com.stevesoltys.seedvault.service.metadata.MetadataReader +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.storage.StoragePlugin import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager @@ -35,26 +37,14 @@ import java.io.IOException */ internal const val D2D_DEVICE_NAME = "D2D" -private data class RestoreCoordinatorState( - val token: Long, - val packages: Iterator<PackageInfo>, - /** - * Optional [PackageInfo] for single package restore, to reduce data needed to read for @pm@ - */ - val autoRestorePackageInfo: PackageInfo?, - val backupMetadata: BackupMetadata, -) { - var currentPackage: String? = null -} - private val TAG = RestoreCoordinator::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") internal class RestoreCoordinator( private val context: Context, - private val crypto: Crypto, - private val settingsManager: SettingsManager, - private val metadataManager: MetadataManager, + private val cryptoService: CryptoService, + private val settingsService: SettingsService, + private val metadataService: MetadataService, private val notificationManager: BackupNotificationManager, private val plugin: StoragePlugin, private val kv: KVRestore, @@ -126,7 +116,7 @@ internal class RestoreCoordinator( * or 0 if there is no backup set available corresponding to the current device state. */ fun getCurrentRestoreSet(): Long { - return (settingsManager.getToken() ?: 0L).apply { + return (settingsService.getToken() ?: 0L).apply { Log.i(TAG, "Got current restore set token: $this") } } @@ -138,7 +128,7 @@ internal class RestoreCoordinator( this.backupMetadata = backupMetadata if (backupMetadata.d2dBackup) { - settingsManager.setD2dBackupsEnabled(true) + settingsService.setD2dBackupsEnabled(true) } } @@ -167,9 +157,9 @@ internal class RestoreCoordinator( // check if the backup is on removable storage that is not plugged in if (isStorageRemovableAndNotAvailable()) { // check if we even have a backup of that app - if (metadataManager.getPackageMetadata(pmPackageName) != null) { + if (metadataService.getPackageMetadata(pmPackageName) != null) { // remind user to plug in storage device - val storageName = settingsManager.getStorage()?.name + val storageName = settingsService.getStorage()?.name ?: context.getString(R.string.settings_backup_location_none) notificationManager.onRemovableStorageNotAvailableForRestore( pmPackageName, @@ -231,7 +221,8 @@ internal class RestoreCoordinator( val type = try { when (state.backupMetadata.packageMetadataMap[packageName]?.backupType) { BackupType.KV -> { - val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName) + val name = + cryptoService.getNameForPackage(state.backupMetadata.salt, packageName) if (plugin.hasData(state.token, name)) { Log.i(TAG, "Found K/V data for $packageName.") kv.initializeState( @@ -247,7 +238,8 @@ internal class RestoreCoordinator( } BackupType.FULL -> { - val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName) + val name = + cryptoService.getNameForPackage(state.backupMetadata.salt, packageName) if (plugin.hasData(state.token, name)) { Log.i(TAG, "Found full backup data for $packageName.") full.initializeState(version, state.token, name, packageInfo) @@ -365,7 +357,7 @@ internal class RestoreCoordinator( // TODO this is plugin specific, needs to be factored out when supporting different plugins private fun isStorageRemovableAndNotAvailable(): Boolean { - val storage = settingsManager.getStorage() ?: return false + val storage = settingsService.getStorage() ?: return false return storage.isUnavailableUsb(context) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/coordinator/RestoreCoordinatorState.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/coordinator/RestoreCoordinatorState.kt new file mode 100644 index 00000000..6525000b --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/coordinator/RestoreCoordinatorState.kt @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.restore.coordinator + +import android.content.pm.PackageInfo +import com.stevesoltys.seedvault.service.metadata.BackupMetadata + +data class RestoreCoordinatorState( + val token: Long, + val packages: Iterator<PackageInfo>, + /** + * Optional [PackageInfo] for single package restore, to reduce data needed to read for @pm@ + */ + val autoRestorePackageInfo: PackageInfo?, + val backupMetadata: BackupMetadata, +) { + var currentPackage: String? = null +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/full/FullRestore.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/restore/full/FullRestore.kt index 91c83e34..cb06e655 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/full/FullRestore.kt @@ -1,4 +1,9 @@ -package com.stevesoltys.seedvault.transport.restore +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.restore.full import android.app.backup.BackupTransport.NO_MORE_DATA import android.app.backup.BackupTransport.TRANSPORT_ERROR @@ -7,29 +12,20 @@ import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import android.util.Log -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.header.HeaderReader -import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH -import com.stevesoltys.seedvault.header.UnsupportedVersionException -import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.app.restore.OutputFactory +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.HeaderDecodeService +import com.stevesoltys.seedvault.service.header.MAX_SEGMENT_LENGTH +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.header.getADForFull import libcore.io.IoUtils.closeQuietly import java.io.EOFException import java.io.IOException -import java.io.InputStream import java.io.OutputStream import java.security.GeneralSecurityException -private class FullRestoreState( - val version: Byte, - val token: Long, - val name: String, - val packageInfo: PackageInfo, -) { - var inputStream: InputStream? = null -} - private val TAG = FullRestore::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") @@ -38,8 +34,8 @@ internal class FullRestore( @Suppress("Deprecation") private val legacyPlugin: LegacyStoragePlugin, private val outputFactory: OutputFactory, - private val headerReader: HeaderReader, - private val crypto: Crypto, + private val headerDecodeService: HeaderDecodeService, + private val cryptoService: CryptoService, ) { private var state: FullRestoreState? = null @@ -104,15 +100,15 @@ internal class FullRestore( if (state.version == 0.toByte()) { val inputStream = legacyPlugin.getInputStreamForPackage(state.token, state.packageInfo) - val version = headerReader.readVersion(inputStream, state.version) + val version = headerDecodeService.readVersion(inputStream, state.version) @Suppress("deprecation") - crypto.decryptHeader(inputStream, version, packageName) + cryptoService.decryptHeader(inputStream, version, packageName) state.inputStream = inputStream } else { val inputStream = plugin.getInputStream(state.token, state.name) - val version = headerReader.readVersion(inputStream, state.version) + val version = headerDecodeService.readVersion(inputStream, state.version) val ad = getADForFull(version, packageName) - state.inputStream = crypto.newDecryptingStream(inputStream, ad) + state.inputStream = cryptoService.newDecryptingStream(inputStream, ad) } } catch (e: IOException) { Log.w(TAG, "Error getting input stream for $packageName", e) @@ -148,7 +144,7 @@ internal class FullRestore( // read segment from input stream and decrypt it val decrypted = try { @Suppress("deprecation") - crypto.decryptSegment(inputStream) + cryptoService.decryptSegment(inputStream) } catch (e: EOFException) { Log.i(TAG, " EOF") // close input stream here as we won't need it anymore diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/full/FullRestoreState.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/full/FullRestoreState.kt new file mode 100644 index 00000000..89c20d67 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/full/FullRestoreState.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.restore.full + +import android.content.pm.PackageInfo +import java.io.InputStream + +class FullRestoreState( + val version: Byte, + val token: Long, + val name: String, + val packageInfo: PackageInfo, +) { + var inputStream: InputStream? = null +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/kv/KVRestore.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/app/restore/kv/KVRestore.kt index c529c0ad..4f37393d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/kv/KVRestore.kt @@ -1,4 +1,9 @@ -package com.stevesoltys.seedvault.transport.restore +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.restore.kv import android.app.backup.BackupDataOutput import android.app.backup.BackupTransport.TRANSPORT_ERROR @@ -9,34 +14,23 @@ import android.util.Log import com.stevesoltys.seedvault.ANCESTRAL_RECORD_KEY import com.stevesoltys.seedvault.GLOBAL_METADATA_KEY import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.decodeBase64 -import com.stevesoltys.seedvault.header.HeaderReader -import com.stevesoltys.seedvault.header.UnsupportedVersionException -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.transport.backup.KVDb -import com.stevesoltys.seedvault.transport.backup.KvDbManager +import com.stevesoltys.seedvault.service.app.backup.kv.KVDb +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManager +import com.stevesoltys.seedvault.service.app.restore.OutputFactory +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.HeaderDecodeService +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.getADForKV +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.util.decodeBase64 import libcore.io.IoUtils.closeQuietly import java.io.IOException import java.security.GeneralSecurityException -import java.util.ArrayList import java.util.zip.GZIPInputStream import javax.crypto.AEADBadTagException -private class KVRestoreState( - val version: Byte, - val token: Long, - val name: String, - val packageInfo: PackageInfo, - /** - * Optional [PackageInfo] for single package restore, optimizes restore of @pm@ - */ - val autoRestorePackageInfo: PackageInfo?, -) - private val TAG = KVRestore::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") @@ -45,8 +39,8 @@ internal class KVRestore( @Suppress("Deprecation") private val legacyPlugin: LegacyStoragePlugin, private val outputFactory: OutputFactory, - private val headerReader: HeaderReader, - private val crypto: Crypto, + private val headerDecodeService: HeaderDecodeService, + private val cryptoService: CryptoService, private val dbManager: KvDbManager, ) { @@ -153,9 +147,9 @@ internal class KVRestore( private suspend fun downloadRestoreDb(state: KVRestoreState): KVDb { val packageName = state.packageInfo.packageName plugin.getInputStream(state.token, state.name).use { inputStream -> - headerReader.readVersion(inputStream, state.version) + headerDecodeService.readVersion(inputStream, state.version) val ad = getADForKV(VERSION, packageName) - crypto.newDecryptingStream(inputStream, ad).use { decryptedStream -> + cryptoService.newDecryptingStream(inputStream, ad).use { decryptedStream -> GZIPInputStream(decryptedStream).use { gzipStream -> dbManager.getDbOutputStream(packageName).use { outputStream -> gzipStream.copyTo(outputStream) @@ -241,10 +235,10 @@ internal class KVRestore( out: BackupDataOutput, ) = legacyPlugin.getInputStreamForRecord(state.token, state.packageInfo, dKey.base64Key) .use { inputStream -> - val version = headerReader.readVersion(inputStream, state.version) + val version = headerDecodeService.readVersion(inputStream, state.version) val packageName = state.packageInfo.packageName - crypto.decryptHeader(inputStream, version, packageName, dKey.key) - val value = crypto.decryptMultipleSegments(inputStream) + cryptoService.decryptHeader(inputStream, version, packageName, dKey.key) + val value = cryptoService.decryptMultipleSegments(inputStream) val size = value.size Log.v(TAG, " ... key=${dKey.key} size=$size") diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/kv/KVRestoreState.kt b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/kv/KVRestoreState.kt new file mode 100644 index 00000000..6372ab6f --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/app/restore/kv/KVRestoreState.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.app.restore.kv + +import android.content.pm.PackageInfo + +class KVRestoreState( + val version: Byte, + val token: Long, + val name: String, + val packageInfo: PackageInfo, + /** + * Optional [PackageInfo] for single package restore, optimizes restore of @pm@ + */ + val autoRestorePackageInfo: PackageInfo?, +) diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CipherFactory.kt similarity index 96% rename from app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/crypto/CipherFactory.kt index 52d7ef88..8758e8b1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/CipherFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CipherFactory.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.crypto +package com.stevesoltys.seedvault.service.crypto import java.security.Key import javax.crypto.Cipher diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoModule.kt similarity index 77% rename from app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoModule.kt index f484bf6d..cf127f6f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/CryptoModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoModule.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.crypto +package com.stevesoltys.seedvault.service.crypto import org.koin.dsl.module import java.security.KeyStore @@ -15,5 +15,5 @@ val cryptoModule = module { } KeyManagerImpl(keyStore) } - single<Crypto> { CryptoImpl(get(), get(), get()) } + single<CryptoService> { CryptoServiceImpl(get(), get(), get()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoService.kt new file mode 100644 index 00000000..274b2a73 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoService.kt @@ -0,0 +1,108 @@ +package com.stevesoltys.seedvault.service.crypto + +import com.google.crypto.tink.subtle.AesGcmHkdfStreaming +import com.stevesoltys.seedvault.service.header.SegmentHeader +import com.stevesoltys.seedvault.service.header.VersionHeader +import java.io.EOFException +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.security.GeneralSecurityException +import java.security.SecureRandom + +/** + * A version 1 backup stream uses [AesGcmHkdfStreaming] from the tink library. + * + * A version 0 backup stream starts with a version byte followed by an encrypted [VersionHeader]. + * + * The header will be encrypted with AES/GCM to provide authentication. + * It can be read using [decryptHeader] which throws a [SecurityException], + * if the expected version and package name do not match the encrypted header. + * + * After the header, follows one or more data segments. + * Each segment begins with a clear-text [SegmentHeader] + * that contains the length of the segment + * and a nonce acting as the initialization vector for the encryption. + * The segment can be read using [decryptSegment] which throws a [SecurityException], + * if the length of the segment is specified larger than allowed. + */ +internal interface CryptoService { + + /** + * Returns a ByteArray with bytes retrieved from [SecureRandom]. + */ + fun getRandomBytes(size: Int): ByteArray + + fun getNameForPackage(salt: String, packageName: String): String + + /** + * Returns the name that identifies an APK in the backup storage plugin. + * @param suffix empty string for normal APKs and the name of the split in case of an APK split + */ + fun getNameForApk(salt: String, packageName: String, suffix: String = ""): String + + /** + * Returns a [AesGcmHkdfStreaming] encrypting stream + * that gets encrypted and authenticated the given associated data. + */ + @Throws(IOException::class, GeneralSecurityException::class) + fun newEncryptingStream( + outputStream: OutputStream, + associatedData: ByteArray, + ): OutputStream + + /** + * Returns a [AesGcmHkdfStreaming] decrypting stream + * that gets decrypted and authenticated the given associated data. + */ + @Throws(IOException::class, GeneralSecurityException::class) + fun newDecryptingStream( + inputStream: InputStream, + associatedData: ByteArray, + ): InputStream + + /** + * Reads and decrypts a [VersionHeader] from the given [InputStream] + * and ensures that the expected version, package name and key match + * what is found in the header. + * If a mismatch is found, a [SecurityException] is thrown. + * + * @return The read [VersionHeader] present in the beginning of the given [InputStream]. + */ + @Suppress("Deprecation") + @Deprecated("Use newDecryptingStream instead") + @Throws(IOException::class, SecurityException::class) + fun decryptHeader( + inputStream: InputStream, + expectedVersion: Byte, + expectedPackageName: String, + expectedKey: String? = null, + ): VersionHeader + + /** + * Reads and decrypts a segment from the given [InputStream]. + * + * @return The decrypted segment payload. + */ + @Deprecated("Use newDecryptingStream instead") + @Throws(EOFException::class, IOException::class, SecurityException::class) + fun decryptSegment(inputStream: InputStream): ByteArray + + /** + * Like [decryptSegment], but decrypts multiple segments and does not throw [EOFException]. + */ + @Deprecated("Use newDecryptingStream instead") + @Throws(IOException::class, SecurityException::class) + fun decryptMultipleSegments(inputStream: InputStream): ByteArray + + /** + * Verify that the stored backup key was created from the given seed. + * + * @return true if the key was created from given seed, false otherwise. + */ + fun verifyBackupKey(seed: ByteArray): Boolean +} + +internal const val TYPE_METADATA: Byte = 0x00 +internal const val TYPE_BACKUP_KV: Byte = 0x01 +internal const val TYPE_BACKUP_FULL: Byte = 0x02 diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoServiceImpl.kt similarity index 53% rename from app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoServiceImpl.kt index 108bcc85..2acadbf5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/CryptoServiceImpl.kt @@ -1,14 +1,11 @@ -package com.stevesoltys.seedvault.crypto +package com.stevesoltys.seedvault.service.crypto -import com.google.crypto.tink.subtle.AesGcmHkdfStreaming -import com.stevesoltys.seedvault.encodeBase64 -import com.stevesoltys.seedvault.header.HeaderReader -import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH -import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE -import com.stevesoltys.seedvault.header.SegmentHeader -import com.stevesoltys.seedvault.header.VersionHeader +import com.stevesoltys.seedvault.service.header.HeaderDecodeService +import com.stevesoltys.seedvault.service.header.MAX_SEGMENT_LENGTH +import com.stevesoltys.seedvault.service.header.MAX_VERSION_HEADER_SIZE +import com.stevesoltys.seedvault.service.header.VersionHeader +import com.stevesoltys.seedvault.util.encodeBase64 import org.calyxos.backup.storage.crypto.StreamCrypto -import org.calyxos.backup.storage.crypto.StreamCrypto.deriveStreamKey import java.io.EOFException import java.io.IOException import java.io.InputStream @@ -19,111 +16,14 @@ import java.security.NoSuchAlgorithmException import java.security.SecureRandom import javax.crypto.spec.SecretKeySpec -/** - * A version 1 backup stream uses [AesGcmHkdfStreaming] from the tink library. - * - * A version 0 backup stream starts with a version byte followed by an encrypted [VersionHeader]. - * - * The header will be encrypted with AES/GCM to provide authentication. - * It can be read using [decryptHeader] which throws a [SecurityException], - * if the expected version and package name do not match the encrypted header. - * - * After the header, follows one or more data segments. - * Each segment begins with a clear-text [SegmentHeader] - * that contains the length of the segment - * and a nonce acting as the initialization vector for the encryption. - * The segment can be read using [decryptSegment] which throws a [SecurityException], - * if the length of the segment is specified larger than allowed. - */ -internal interface Crypto { - - /** - * Returns a ByteArray with bytes retrieved from [SecureRandom]. - */ - fun getRandomBytes(size: Int): ByteArray - - fun getNameForPackage(salt: String, packageName: String): String - - /** - * Returns the name that identifies an APK in the backup storage plugin. - * @param suffix empty string for normal APKs and the name of the split in case of an APK split - */ - fun getNameForApk(salt: String, packageName: String, suffix: String = ""): String - - /** - * Returns a [AesGcmHkdfStreaming] encrypting stream - * that gets encrypted and authenticated the given associated data. - */ - @Throws(IOException::class, GeneralSecurityException::class) - fun newEncryptingStream( - outputStream: OutputStream, - associatedData: ByteArray, - ): OutputStream - - /** - * Returns a [AesGcmHkdfStreaming] decrypting stream - * that gets decrypted and authenticated the given associated data. - */ - @Throws(IOException::class, GeneralSecurityException::class) - fun newDecryptingStream( - inputStream: InputStream, - associatedData: ByteArray, - ): InputStream - - /** - * Reads and decrypts a [VersionHeader] from the given [InputStream] - * and ensures that the expected version, package name and key match - * what is found in the header. - * If a mismatch is found, a [SecurityException] is thrown. - * - * @return The read [VersionHeader] present in the beginning of the given [InputStream]. - */ - @Suppress("Deprecation") - @Deprecated("Use newDecryptingStream instead") - @Throws(IOException::class, SecurityException::class) - fun decryptHeader( - inputStream: InputStream, - expectedVersion: Byte, - expectedPackageName: String, - expectedKey: String? = null, - ): VersionHeader - - /** - * Reads and decrypts a segment from the given [InputStream]. - * - * @return The decrypted segment payload. - */ - @Deprecated("Use newDecryptingStream instead") - @Throws(EOFException::class, IOException::class, SecurityException::class) - fun decryptSegment(inputStream: InputStream): ByteArray - - /** - * Like [decryptSegment], but decrypts multiple segments and does not throw [EOFException]. - */ - @Deprecated("Use newDecryptingStream instead") - @Throws(IOException::class, SecurityException::class) - fun decryptMultipleSegments(inputStream: InputStream): ByteArray - - /** - * Verify that the stored backup key was created from the given seed. - * - * @return true if the key was created from given seed, false otherwise. - */ - fun verifyBackupKey(seed: ByteArray): Boolean -} - -internal const val TYPE_METADATA: Byte = 0x00 -internal const val TYPE_BACKUP_KV: Byte = 0x01 -internal const val TYPE_BACKUP_FULL: Byte = 0x02 - -internal class CryptoImpl( +internal class CryptoServiceImpl( private val keyManager: KeyManager, private val cipherFactory: CipherFactory, - private val headerReader: HeaderReader, -) : Crypto { + private val headerDecodeService: HeaderDecodeService, +) : CryptoService { private val key: ByteArray by lazy { - deriveStreamKey(keyManager.getMainKey(), "app data key".toByteArray()) + StreamCrypto.deriveStreamKey(keyManager.getMainKey(), "app data key".toByteArray()) } private val secureRandom: SecureRandom by lazy { SecureRandom() } @@ -175,7 +75,7 @@ internal class CryptoImpl( expectedKey: String?, ): VersionHeader { val decrypted = decryptSegment(inputStream, MAX_VERSION_HEADER_SIZE) - val header = headerReader.getVersionHeader(decrypted) + val header = headerDecodeService.getVersionHeader(decrypted) if (header.version != expectedVersion) { throw SecurityException( @@ -219,7 +119,7 @@ internal class CryptoImpl( @Suppress("Deprecation") @Throws(EOFException::class, IOException::class, SecurityException::class) private fun decryptSegment(inputStream: InputStream, maxSegmentLength: Int): ByteArray { - val segmentHeader = headerReader.readSegmentHeader(inputStream) + val segmentHeader = headerDecodeService.readSegmentHeader(inputStream) if (segmentHeader.segmentLength > maxSegmentLength) throw SecurityException( "Segment length too long: ${segmentHeader.segmentLength} > $maxSegmentLength" ) diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/KeyManager.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/crypto/KeyManager.kt index 4b605fec..f41fbd5f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/crypto/KeyManager.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.crypto +package com.stevesoltys.seedvault.service.crypto import android.security.keystore.KeyProperties.BLOCK_MODE_GCM import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/file/FileBackupModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/file/FileBackupModule.kt new file mode 100644 index 00000000..41ef219a --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/file/FileBackupModule.kt @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.service.file + +import org.calyxos.backup.storage.api.StorageBackup +import org.calyxos.backup.storage.api.StoragePlugin +import org.koin.dsl.module + +val filesModule = module { + single<StoragePlugin> { FileBackupStoragePlugin(get(), get(), get()) } + single { StorageBackup(get(), get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/SeedvaultStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/service/file/FileBackupStoragePlugin.kt similarity index 76% rename from app/src/main/java/com/stevesoltys/seedvault/storage/SeedvaultStoragePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/file/FileBackupStoragePlugin.kt index d9657380..6ee51a1f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/SeedvaultStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/file/FileBackupStoragePlugin.kt @@ -1,14 +1,17 @@ -package com.stevesoltys.seedvault.storage +package com.stevesoltys.seedvault.service.file import android.content.Context import androidx.documentfile.provider.DocumentFile -import com.stevesoltys.seedvault.crypto.KeyManager import com.stevesoltys.seedvault.getStorageContext -import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage +import com.stevesoltys.seedvault.service.crypto.KeyManager +import com.stevesoltys.seedvault.service.storage.saf.DocumentsStorage import org.calyxos.backup.storage.plugin.saf.SafStoragePlugin import javax.crypto.SecretKey -internal class SeedvaultStoragePlugin( +/** + * [SafStoragePlugin] for backing up files. + */ +internal class FileBackupStoragePlugin( private val appContext: Context, private val storage: DocumentsStorage, private val keyManager: KeyManager, diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/file/backup/FileBackupJobService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/file/backup/FileBackupJobService.kt new file mode 100644 index 00000000..3f1ddac5 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/file/backup/FileBackupJobService.kt @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ +package com.stevesoltys.seedvault.service.file.backup + +import org.calyxos.backup.storage.backup.BackupJobService + +/* +test and debug with + + adb shell dumpsys jobscheduler | + grep -A 23 -B 4 "Service: com.stevesoltys.seedvault/.storage.StorageBackupJobService" + +force running with: + + adb shell cmd jobscheduler run -f com.stevesoltys.seedvault 0 + + */ +internal class FileBackupJobService : BackupJobService(FileBackupService::class.java) diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/file/backup/FileBackupService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/file/backup/FileBackupService.kt new file mode 100644 index 00000000..4993cc2c --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/file/backup/FileBackupService.kt @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ +package com.stevesoltys.seedvault.service.file.backup + +import android.content.Intent +import com.stevesoltys.seedvault.service.app.backup.requestBackup +import org.calyxos.backup.storage.api.BackupObserver +import org.calyxos.backup.storage.api.StorageBackup +import org.calyxos.backup.storage.backup.BackupService +import org.calyxos.backup.storage.backup.NotificationBackupObserver +import org.koin.android.ext.android.inject + +internal class FileBackupService : BackupService() { + + companion object { + internal const val EXTRA_START_APP_BACKUP = "startAppBackup" + } + + override val storageBackup: StorageBackup by inject() + + // use lazy delegate because context isn't available during construction time + override val backupObserver: BackupObserver by lazy { + NotificationBackupObserver(applicationContext) + } + + override fun onBackupFinished(intent: Intent, success: Boolean) { + if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) { + requestBackup(applicationContext) + } + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/file/restore/FileRestoreService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/file/restore/FileRestoreService.kt new file mode 100644 index 00000000..2e8cec8c --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/file/restore/FileRestoreService.kt @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ +package com.stevesoltys.seedvault.service.file.restore + +import org.calyxos.backup.storage.api.RestoreObserver +import org.calyxos.backup.storage.api.StorageBackup +import org.calyxos.backup.storage.restore.NotificationRestoreObserver +import org.calyxos.backup.storage.restore.RestoreService +import org.koin.android.ext.android.inject + +internal class FileRestoreService : RestoreService() { + override val storageBackup: StorageBackup by inject() + + // use lazy delegate because context isn't available during construction time + override val restoreObserver: RestoreObserver by lazy { + NotificationRestoreObserver(applicationContext) + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt b/app/src/main/java/com/stevesoltys/seedvault/service/header/Header.kt similarity index 90% rename from app/src/main/java/com/stevesoltys/seedvault/header/Header.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/header/Header.kt index 17be0016..150e9710 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/header/Header.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/header/Header.kt @@ -1,8 +1,8 @@ -package com.stevesoltys.seedvault.header +package com.stevesoltys.seedvault.service.header -import com.stevesoltys.seedvault.crypto.GCM_AUTHENTICATION_TAG_LENGTH -import com.stevesoltys.seedvault.crypto.TYPE_BACKUP_FULL -import com.stevesoltys.seedvault.crypto.TYPE_BACKUP_KV +import com.stevesoltys.seedvault.service.crypto.GCM_AUTHENTICATION_TAG_LENGTH +import com.stevesoltys.seedvault.service.crypto.TYPE_BACKUP_FULL +import com.stevesoltys.seedvault.service.crypto.TYPE_BACKUP_KV import java.nio.ByteBuffer internal const val VERSION: Byte = 1 diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderDecodeService.kt b/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderDecodeService.kt new file mode 100644 index 00000000..c7bd5b7e --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderDecodeService.kt @@ -0,0 +1,22 @@ +package com.stevesoltys.seedvault.service.header + +import java.io.EOFException +import java.io.IOException +import java.io.InputStream + +internal interface HeaderDecodeService { + @Throws(IOException::class, UnsupportedVersionException::class) + fun readVersion(inputStream: InputStream, expectedVersion: Byte): Byte + + @Suppress("Deprecation") + @Deprecated("For restoring v0 backups only") + @Throws(SecurityException::class) + fun getVersionHeader(byteArray: ByteArray): VersionHeader + + @Suppress("Deprecation") + @Deprecated("For restoring v0 backups only") + @Throws(EOFException::class, IOException::class) + fun readSegmentHeader(inputStream: InputStream): SegmentHeader +} + +class UnsupportedVersionException(val version: Byte) : IOException() diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt b/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderDecodeServiceImpl.kt similarity index 79% rename from app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderDecodeServiceImpl.kt index 1f6c77fb..29ce253a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderDecodeServiceImpl.kt @@ -1,28 +1,13 @@ -package com.stevesoltys.seedvault.header +package com.stevesoltys.seedvault.service.header -import com.stevesoltys.seedvault.Utf8 +import com.stevesoltys.seedvault.util.Utf8 import java.io.EOFException import java.io.IOException import java.io.InputStream import java.nio.ByteBuffer import java.security.GeneralSecurityException -internal interface HeaderReader { - @Throws(IOException::class, UnsupportedVersionException::class) - fun readVersion(inputStream: InputStream, expectedVersion: Byte): Byte - - @Suppress("Deprecation") - @Deprecated("For restoring v0 backups only") - @Throws(SecurityException::class) - fun getVersionHeader(byteArray: ByteArray): VersionHeader - - @Suppress("Deprecation") - @Deprecated("For restoring v0 backups only") - @Throws(EOFException::class, IOException::class) - fun readSegmentHeader(inputStream: InputStream): SegmentHeader -} - -internal class HeaderReaderImpl : HeaderReader { +internal class HeaderDecodeServiceImpl : HeaderDecodeService { @Throws(IOException::class, UnsupportedVersionException::class, GeneralSecurityException::class) override fun readVersion(inputStream: InputStream, expectedVersion: Byte): Byte { @@ -85,5 +70,3 @@ internal class HeaderReaderImpl : HeaderReader { } } - -class UnsupportedVersionException(val version: Byte) : IOException() diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderModule.kt new file mode 100644 index 00000000..8b38a1ec --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/header/HeaderModule.kt @@ -0,0 +1,7 @@ +package com.stevesoltys.seedvault.service.header + +import org.koin.dsl.module + +val headerModule = module { + single<HeaderDecodeService> { HeaderDecodeServiceImpl() } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/Metadata.kt similarity index 93% rename from app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/metadata/Metadata.kt index c8ad6aac..b2d56f70 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/Metadata.kt @@ -1,10 +1,10 @@ -package com.stevesoltys.seedvault.metadata +package com.stevesoltys.seedvault.service.metadata import android.content.pm.ApplicationInfo.FLAG_STOPPED import android.os.Build -import com.stevesoltys.seedvault.crypto.TYPE_METADATA -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.crypto.TYPE_METADATA +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR import org.calyxos.backup.storage.crypto.StreamCrypto.toByteArray import java.nio.ByteBuffer diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataModule.kt similarity index 67% rename from app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataModule.kt index 0d7ed1a2..8d231f89 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataModule.kt @@ -1,10 +1,10 @@ -package com.stevesoltys.seedvault.metadata +package com.stevesoltys.seedvault.service.metadata import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val metadataModule = module { - single { MetadataManager(androidContext(), get(), get(), get(), get(), get()) } + single { MetadataService(androidContext(), get(), get(), get(), get(), get()) } single<MetadataWriter> { MetadataWriterImpl(get()) } single<MetadataReader> { MetadataReaderImpl(get()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataReader.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataReader.kt index bbd6df19..eb5f046b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataReader.kt @@ -1,15 +1,15 @@ -package com.stevesoltys.seedvault.metadata +package com.stevesoltys.seedvault.service.metadata -import com.stevesoltys.seedvault.Utf8 -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.header.UnsupportedVersionException -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.util.Utf8 import org.json.JSONException import org.json.JSONObject import java.io.IOException @@ -36,7 +36,7 @@ interface MetadataReader { } -internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { +internal class MetadataReaderImpl(private val cryptoService: CryptoService) : MetadataReader { @Throws( SecurityException::class, @@ -51,7 +51,8 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { if (version == 0.toByte()) return readMetadataV0(inputStream, expectedToken) val metadataBytes = try { - crypto.newDecryptingStream(inputStream, getAD(version, expectedToken)).readBytes() + cryptoService.newDecryptingStream(inputStream, getAD(version, expectedToken)) + .readBytes() } catch (e: GeneralSecurityException) { throw DecryptionFailedException(e) } @@ -67,7 +68,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { @Suppress("Deprecation") private fun readMetadataV0(inputStream: InputStream, expectedToken: Long): BackupMetadata { val metadataBytes = try { - crypto.decryptMultipleSegments(inputStream) + cryptoService.decryptMultipleSegments(inputStream) } catch (e: AEADBadTagException) { throw DecryptionFailedException(e) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataService.kt similarity index 89% rename from app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataService.kt index f58a29a1..d9178221 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataService.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.metadata +package com.stevesoltys.seedvault.service.metadata import android.content.Context import android.content.Context.MODE_PRIVATE @@ -9,34 +9,34 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.distinctUntilChanged -import com.stevesoltys.seedvault.Clock -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.encodeBase64 -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.transport.backup.isSystemApp +import com.stevesoltys.seedvault.service.app.isSystemApp +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.util.TimeSource +import com.stevesoltys.seedvault.util.encodeBase64 import java.io.FileNotFoundException import java.io.IOException import java.io.OutputStream -private val TAG = MetadataManager::class.java.simpleName +private val TAG = MetadataService::class.java.simpleName @VisibleForTesting internal const val METADATA_CACHE_FILE = "metadata.cache" internal const val METADATA_SALT_SIZE = 32 @WorkerThread -internal class MetadataManager( +internal class MetadataService( private val context: Context, - private val clock: Clock, - private val crypto: Crypto, + private val timeSource: TimeSource, + private val cryptoService: CryptoService, private val metadataWriter: MetadataWriter, private val metadataReader: MetadataReader, - private val settingsManager: SettingsManager + private val settingsService: SettingsService ) { private val uninitializedMetadata = BackupMetadata(token = 0L, salt = "") @@ -63,7 +63,7 @@ internal class MetadataManager( @Synchronized @Throws(IOException::class) fun onDeviceInitialization(token: Long, metadataOutputStream: OutputStream) { - val salt = crypto.getRandomBytes(METADATA_SALT_SIZE).encodeBase64() + val salt = cryptoService.getRandomBytes(METADATA_SALT_SIZE).encodeBase64() modifyMetadata(metadataOutputStream) { metadata = BackupMetadata(token = token, salt = salt) } @@ -135,9 +135,9 @@ internal class MetadataManager( ) { val packageName = packageInfo.packageName modifyMetadata(metadataOutputStream) { - val now = clock.time() + val now = timeSource.time() metadata.time = now - metadata.d2dBackup = settingsManager.d2dBackupsEnabled() + metadata.d2dBackup = settingsService.d2dBackupsEnabled() if (metadata.packageMetadataMap.containsKey(packageName)) { metadata.packageMetadataMap[packageName]!!.time = now diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataWriter.kt similarity index 84% rename from app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataWriter.kt index bbed50c7..44839ffb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/metadata/MetadataWriter.kt @@ -1,8 +1,8 @@ -package com.stevesoltys.seedvault.metadata +package com.stevesoltys.seedvault.service.metadata -import com.stevesoltys.seedvault.Utf8 -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.util.Utf8 import org.json.JSONArray import org.json.JSONObject import java.io.IOException @@ -15,14 +15,15 @@ interface MetadataWriter { fun encode(metadata: BackupMetadata): ByteArray } -internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter { +internal class MetadataWriterImpl(private val cryptoService: CryptoService) : MetadataWriter { @Throws(IOException::class) override fun write(metadata: BackupMetadata, outputStream: OutputStream) { outputStream.write(ByteArray(1).apply { this[0] = metadata.version }) - crypto.newEncryptingStream(outputStream, getAD(metadata.version, metadata.token)).use { - it.write(encode(metadata)) - } + cryptoService.newEncryptingStream(outputStream, getAD(metadata.version, metadata.token)) + .use { + it.write(encode(metadata)) + } } override fun encode(metadata: BackupMetadata): ByteArray { diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/settings/FlashDrive.kt b/app/src/main/java/com/stevesoltys/seedvault/service/settings/FlashDrive.kt new file mode 100644 index 00000000..b64cabdc --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/settings/FlashDrive.kt @@ -0,0 +1,19 @@ +package com.stevesoltys.seedvault.service.settings + +import android.hardware.usb.UsbDevice + +data class FlashDrive( + val name: String, + val serialNumber: String?, + val vendorId: Int, + val productId: Int, +) { + companion object { + fun from(device: UsbDevice) = FlashDrive( + name = "${device.manufacturerName} ${device.productName}", + serialNumber = device.serialNumber, + vendorId = device.vendorId, + productId = device.productId + ) + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt b/app/src/main/java/com/stevesoltys/seedvault/service/settings/SettingsService.kt similarity index 74% rename from app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/settings/SettingsService.kt index 2647e57b..eba1ccca 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/settings/SettingsService.kt @@ -1,17 +1,14 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.service.settings import android.content.Context -import android.hardware.usb.UsbDevice -import android.net.ConnectivityManager -import android.net.NetworkCapabilities import android.net.Uri import androidx.annotation.UiThread import androidx.annotation.WorkerThread -import androidx.documentfile.provider.DocumentFile import androidx.preference.PreferenceManager import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.transport.backup.BackupCoordinator +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.ui.settings.AppStatus import java.util.concurrent.ConcurrentSkipListSet internal const val PREF_KEY_TOKEN = "token" @@ -34,7 +31,9 @@ private const val PREF_KEY_BACKUP_STORAGE = "backup_storage" private const val PREF_KEY_UNLIMITED_QUOTA = "unlimited_quota" internal const val PREF_KEY_D2D_BACKUPS = "d2d_backups" -class SettingsManager(private val context: Context) { +class SettingsService( + private val context: Context, +) { private val prefs = permitDiskReads { PreferenceManager.getDefaultSharedPreferences(context) @@ -59,7 +58,7 @@ class SettingsManager(private val context: Context) { /** * Sets a new RestoreSet token. - * Should only be called by the [BackupCoordinator] + * Should only be called by the [BackupCoordinatorService] * to ensure that related work is performed after moving to a new token. */ fun setNewToken(newToken: Long?) { @@ -162,54 +161,3 @@ class SettingsManager(private val context: Context) { .apply() } } - -data class Storage( - val uri: Uri, - val name: String, - val isUsb: Boolean, - val requiresNetwork: Boolean, -) { - fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri) - ?: throw AssertionError("Should only happen on API < 21.") - - /** - * Returns true if this is USB storage that is not available, false otherwise. - * - * Must be run off UI thread (ideally I/O). - */ - @WorkerThread - fun isUnavailableUsb(context: Context): Boolean { - return isUsb && !getDocumentFile(context).isDirectory - } - - /** - * Returns true if this is storage that requires network access, - * but it isn't available right now. - */ - fun isUnavailableNetwork(context: Context): Boolean { - return requiresNetwork && !hasUnmeteredInternet(context) - } - - private fun hasUnmeteredInternet(context: Context): Boolean { - val cm = context.getSystemService(ConnectivityManager::class.java) - val isMetered = cm.isActiveNetworkMetered - val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false - return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && !isMetered - } -} - -data class FlashDrive( - val name: String, - val serialNumber: String?, - val vendorId: Int, - val productId: Int, -) { - companion object { - fun from(device: UsbDevice) = FlashDrive( - name = "${device.manufacturerName} ${device.productName}", - serialNumber = device.serialNumber, - vendorId = device.vendorId, - productId = device.productId - ) - } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/settings/Storage.kt b/app/src/main/java/com/stevesoltys/seedvault/service/settings/Storage.kt new file mode 100644 index 00000000..c8d9957f --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/settings/Storage.kt @@ -0,0 +1,43 @@ +package com.stevesoltys.seedvault.service.settings + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.net.Uri +import androidx.annotation.WorkerThread +import androidx.documentfile.provider.DocumentFile + +data class Storage( + val uri: Uri, + val name: String, + val isUsb: Boolean, + val requiresNetwork: Boolean, +) { + fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri) + ?: throw AssertionError("Should only happen on API < 21.") + + /** + * Returns true if this is USB storage that is not available, false otherwise. + * + * Must be run off UI thread (ideally I/O). + */ + @WorkerThread + fun isUnavailableUsb(context: Context): Boolean { + return isUsb && !getDocumentFile(context).isDirectory + } + + /** + * Returns true if this is storage that requires network access, + * but it isn't available right now. + */ + fun isUnavailableNetwork(context: Context): Boolean { + return requiresNetwork && !hasUnmeteredInternet(context) + } + + private fun hasUnmeteredInternet(context: Context): Boolean { + val cm = context.getSystemService(ConnectivityManager::class.java) + val isMetered = cm.isActiveNetworkMetered + val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && !isMetered + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/service/storage/EncryptedBackupMetadata.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/EncryptedBackupMetadata.kt new file mode 100644 index 00000000..44980e57 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/EncryptedBackupMetadata.kt @@ -0,0 +1,8 @@ +package com.stevesoltys.seedvault.service.storage + +import java.io.InputStream + +class EncryptedBackupMetadata( + val token: Long, + val inputStreamRetriever: () -> InputStream, +) diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/StoragePlugin.kt similarity index 89% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/storage/StoragePlugin.kt index 53becfac..bd530628 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/StoragePlugin.kt @@ -1,8 +1,8 @@ -package com.stevesoltys.seedvault.plugins +package com.stevesoltys.seedvault.service.storage import android.app.backup.RestoreSet import androidx.annotation.WorkerThread -import com.stevesoltys.seedvault.settings.Storage +import com.stevesoltys.seedvault.service.settings.Storage import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -61,7 +61,7 @@ interface StoragePlugin { * @return metadata for the set of restore images available, * or null if an error occurred (the attempt should be rescheduled). **/ - suspend fun getAvailableBackups(): Sequence<EncryptedMetadata>? + suspend fun getAvailableBackups(): Sequence<EncryptedBackupMetadata>? /** * Returns the package name of the app that provides the backend storage @@ -74,5 +74,3 @@ interface StoragePlugin { val providerPackageName: String? } - -class EncryptedMetadata(val token: Long, val inputStreamRetriever: () -> InputStream) diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsProviderModule.kt similarity index 57% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsProviderModule.kt index 638ee990..51da8dd4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsProviderModule.kt @@ -1,7 +1,8 @@ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.service.storage.saf -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.DocumentsProviderLegacyPlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin import org.koin.android.ext.koin.androidContext import org.koin.dsl.module diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsProviderStoragePlugin.kt similarity index 94% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsProviderStoragePlugin.kt index 0dbc6c50..8069f9b6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsProviderStoragePlugin.kt @@ -1,13 +1,13 @@ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.service.storage.saf import android.content.Context import android.content.pm.PackageManager import android.util.Log import androidx.documentfile.provider.DocumentFile import com.stevesoltys.seedvault.getStorageContext -import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.settings.Storage +import com.stevesoltys.seedvault.service.storage.EncryptedBackupMetadata +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.settings.Storage import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream @@ -90,14 +90,14 @@ internal class DocumentsProviderStoragePlugin( return backupSets.isNotEmpty() } - override suspend fun getAvailableBackups(): Sequence<EncryptedMetadata>? { + override suspend 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() - EncryptedMetadata(backupSet.token) { + EncryptedBackupMetadata(backupSet.token) { storage.getInputStream(backupSet.metadataFile) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsStorage.kt similarity index 96% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsStorage.kt index 7e970a73..b9ca50fb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/DocumentsStorage.kt @@ -1,6 +1,6 @@ @file:Suppress("BlockingMethodInNonBlockingContext") -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.service.storage.saf import android.content.ContentResolver import android.content.Context @@ -17,8 +17,8 @@ import android.util.Log import androidx.annotation.VisibleForTesting import androidx.documentfile.provider.DocumentFile import com.stevesoltys.seedvault.getStorageContext -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.settings.Storage +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.settings.Storage import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.runBlocking import kotlinx.coroutines.suspendCancellableCoroutine @@ -43,11 +43,11 @@ private val TAG = DocumentsStorage::class.java.simpleName internal class DocumentsStorage( private val appContext: Context, - private val settingsManager: SettingsManager, + private val settingsService: SettingsService, ) { internal var storage: Storage? = null get() { - if (field == null) field = settingsManager.getStorage() + if (field == null) field = settingsService.getStorage() return field } @@ -81,7 +81,7 @@ internal class DocumentsStorage( private var currentToken: Long? = null get() { - if (field == null) field = settingsManager.getToken() + if (field == null) field = settingsService.getToken() return field } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderLegacyPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/legacy/DocumentsProviderLegacyPlugin.kt similarity index 90% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderLegacyPlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/legacy/DocumentsProviderLegacyPlugin.kt index 1a1d075f..d4300591 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderLegacyPlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/legacy/DocumentsProviderLegacyPlugin.kt @@ -1,16 +1,20 @@ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.service.storage.saf.legacy import android.content.Context import android.content.pm.PackageInfo import androidx.annotation.WorkerThread import androidx.documentfile.provider.DocumentFile -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.DocumentsStorage +import com.stevesoltys.seedvault.service.storage.saf.assertRightFile +import com.stevesoltys.seedvault.service.storage.saf.findFileBlocking +import com.stevesoltys.seedvault.service.storage.saf.listFilesBlocking import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream @WorkerThread @Suppress("BlockingMethodInNonBlockingContext", "Deprecation") // all methods do I/O +@Deprecated("Only for old v0 backup format") internal class DocumentsProviderLegacyPlugin( private val context: Context, private val storage: DocumentsStorage, diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/LegacyStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/legacy/LegacyStoragePlugin.kt similarity index 96% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/LegacyStoragePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/legacy/LegacyStoragePlugin.kt index 8082819b..f5f3fb7e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/LegacyStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/service/storage/saf/legacy/LegacyStoragePlugin.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.plugins +package com.stevesoltys.seedvault.service.storage.saf.legacy import android.content.pm.PackageInfo import java.io.IOException diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt deleted file mode 100644 index 1c54beb2..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.stevesoltys.seedvault.storage - -import android.content.Intent -import com.stevesoltys.seedvault.transport.requestBackup -import org.calyxos.backup.storage.api.BackupObserver -import org.calyxos.backup.storage.api.RestoreObserver -import org.calyxos.backup.storage.api.StorageBackup -import org.calyxos.backup.storage.backup.BackupJobService -import org.calyxos.backup.storage.backup.BackupService -import org.calyxos.backup.storage.backup.NotificationBackupObserver -import org.calyxos.backup.storage.restore.NotificationRestoreObserver -import org.calyxos.backup.storage.restore.RestoreService -import org.koin.android.ext.android.inject - -/* -test and debug with - - adb shell dumpsys jobscheduler | - grep -A 23 -B 4 "Service: com.stevesoltys.seedvault/.storage.StorageBackupJobService" - -force running with: - - adb shell cmd jobscheduler run -f com.stevesoltys.seedvault 0 - - */ -internal class StorageBackupJobService : BackupJobService(StorageBackupService::class.java) - -internal class StorageBackupService : BackupService() { - - companion object { - internal const val EXTRA_START_APP_BACKUP = "startAppBackup" - } - - override val storageBackup: StorageBackup by inject() - - // use lazy delegate because context isn't available during construction time - override val backupObserver: BackupObserver by lazy { - NotificationBackupObserver(applicationContext) - } - - override fun onBackupFinished(intent: Intent, success: Boolean) { - if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) { - requestBackup(applicationContext) - } - } -} - -internal class StorageRestoreService : RestoreService() { - override val storageBackup: StorageBackup by inject() - - // use lazy delegate because context isn't available during construction time - override val restoreObserver: RestoreObserver by lazy { - NotificationRestoreObserver(applicationContext) - } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt deleted file mode 100644 index 68254e88..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.stevesoltys.seedvault.storage - -import org.calyxos.backup.storage.api.StorageBackup -import org.calyxos.backup.storage.api.StoragePlugin -import org.koin.dsl.module - -val storageModule = module { - single<StoragePlugin> { SeedvaultStoragePlugin(get(), get(), get()) } - single { StorageBackup(get(), get()) } -} 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 cead1eab..04423495 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransport.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransport.kt @@ -11,10 +11,10 @@ import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import android.util.Log import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.settings.SettingsActivity -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.transport.backup.BackupCoordinator -import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.service.app.restore.coordinator.RestoreCoordinator +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.ui.settings.SettingsActivity import kotlinx.coroutines.runBlocking import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -36,9 +36,9 @@ private val TAG = ConfigurableBackupTransport::class.java.simpleName class ConfigurableBackupTransport internal constructor(private val context: Context) : BackupTransport(), KoinComponent { - private val backupCoordinator by inject<BackupCoordinator>() + private val backupCoordinatorService by inject<BackupCoordinatorService>() private val restoreCoordinator by inject<RestoreCoordinator>() - private val settingsManager by inject<SettingsManager>() + private val settingsService by inject<SettingsService>() override fun transportDirName(): String { return TRANSPORT_DIRECTORY_NAME @@ -58,7 +58,7 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont * This allows the agent to decide what to do based on properties of the transport. */ override fun getTransportFlags(): Int { - return if (settingsManager.d2dBackupsEnabled()) { + return if (settingsService.d2dBackupsEnabled()) { D2D_TRANSPORT_FLAGS } else { DEFAULT_TRANSPORT_FLAGS @@ -120,26 +120,26 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont // override fun initializeDevice(): Int = runBlocking { - backupCoordinator.initializeDevice() + backupCoordinatorService.initializeDevice() } override fun isAppEligibleForBackup( targetPackage: PackageInfo, isFullBackup: Boolean, ): Boolean { - return backupCoordinator.isAppEligibleForBackup(targetPackage, isFullBackup) + return backupCoordinatorService.isAppEligibleForBackup(targetPackage, isFullBackup) } override fun getBackupQuota(packageName: String, isFullBackup: Boolean): Long = runBlocking { - backupCoordinator.getBackupQuota(packageName, isFullBackup) + backupCoordinatorService.getBackupQuota(packageName, isFullBackup) } override fun clearBackupData(packageInfo: PackageInfo): Int = runBlocking { - backupCoordinator.clearBackupData(packageInfo) + backupCoordinatorService.clearBackupData(packageInfo) } override fun finishBackup(): Int = runBlocking { - backupCoordinator.finishBackup() + backupCoordinatorService.finishBackup() } // ------------------------------------------------------------------------------------ @@ -147,7 +147,7 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont // override fun requestBackupTime(): Long { - return backupCoordinator.requestBackupTime() + return backupCoordinatorService.requestBackupTime() } override fun performBackup( @@ -155,7 +155,7 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont inFd: ParcelFileDescriptor, flags: Int, ): Int = runBlocking { - backupCoordinator.performIncrementalBackup(packageInfo, inFd, flags) + backupCoordinatorService.performIncrementalBackup(packageInfo, inFd, flags) } override fun performBackup( @@ -171,11 +171,11 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont // override fun requestFullBackupTime(): Long { - return backupCoordinator.requestFullBackupTime() + return backupCoordinatorService.requestFullBackupTime() } override fun checkFullBackupSize(size: Long): Int { - return backupCoordinator.checkFullBackupSize(size) + return backupCoordinatorService.checkFullBackupSize(size) } override fun performFullBackup( @@ -183,7 +183,7 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont socket: ParcelFileDescriptor, flags: Int, ): Int = runBlocking { - backupCoordinator.performFullBackup(targetPackage, socket, flags) + backupCoordinatorService.performFullBackup(targetPackage, socket, flags) } override fun performFullBackup( @@ -191,15 +191,15 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont fileDescriptor: ParcelFileDescriptor, ): Int = runBlocking { Log.w(TAG, "Warning: Legacy performFullBackup() method called.") - backupCoordinator.performFullBackup(targetPackage, fileDescriptor, 0) + backupCoordinatorService.performFullBackup(targetPackage, fileDescriptor, 0) } override fun sendBackupData(numBytes: Int): Int = runBlocking { - backupCoordinator.sendBackupData(numBytes) + backupCoordinatorService.sendBackupData(numBytes) } override fun cancelFullBackup() = runBlocking { - backupCoordinator.cancelFullBackup() + backupCoordinatorService.cancelFullBackup() } // ------------------------------------------------------------------------------------ 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 1d67988d..900df27f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt @@ -1,22 +1,14 @@ package com.stevesoltys.seedvault.transport import android.app.Service -import android.app.backup.BackupManager import android.app.backup.IBackupManager -import android.content.Context import android.content.Intent import android.os.IBinder -import android.os.RemoteException import android.util.Log -import androidx.annotation.WorkerThread -import com.stevesoltys.seedvault.BackupMonitor -import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.transport.backup.PackageService +import com.stevesoltys.seedvault.service.crypto.KeyManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager -import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import org.koin.core.context.GlobalContext.get private val TAG = ConfigurableBackupTransportService::class.java.simpleName @@ -59,30 +51,3 @@ class ConfigurableBackupTransportService : Service(), KoinComponent { } } - -@WorkerThread -fun requestBackup(context: Context) { - val backupManager: IBackupManager = get().get() - if (backupManager.isBackupEnabled) { - val packageService: PackageService = get().get() - val packages = packageService.eligiblePackages - val appTotals = packageService.expectedAppTotals - - val result = try { - Log.d(TAG, "Backup is enabled, request backup...") - val observer = NotificationBackupObserver(context, packages.size, appTotals) - backupManager.requestBackup(packages, observer, BackupMonitor(), 0) - } catch (e: RemoteException) { - Log.e(TAG, "Error during backup: ", e) - val nm: BackupNotificationManager = get().get() - nm.onBackupError() - } - if (result == BackupManager.SUCCESS) { - Log.i(TAG, "Backup succeeded ") - } else { - Log.e(TAG, "Backup failed: $result") - } - } else { - Log.i(TAG, "Backup is not enabled") - } -} 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 deleted file mode 100644 index bf7d3272..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.stevesoltys.seedvault.transport.backup - -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module - -val backupModule = module { - single { InputFactory() } - single { - PackageService( - context = androidContext(), - backupManager = get(), - settingsManager = get(), - plugin = get() - ) - } - single { - ApkBackup( - pm = androidContext().packageManager, - crypto = get(), - settingsManager = get(), - metadataManager = get() - ) - } - single<KvDbManager> { KvDbManagerImpl(androidContext()) } - single { - KVBackup( - plugin = get(), - settingsManager = get(), - inputFactory = get(), - crypto = get(), - dbManager = get() - ) - } - single { - FullBackup( - plugin = get(), - settingsManager = get(), - inputFactory = get(), - crypto = get() - ) - } - single { - BackupCoordinator( - context = androidContext(), - plugin = get(), - kv = get(), - full = get(), - apkBackup = get(), - clock = get(), - packageService = get(), - metadataManager = get(), - settingsManager = get(), - nm = get() - ) - } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivityBase.kt similarity index 93% rename from app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivityBase.kt index 40d35fba..4c79bef9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivityBase.kt @@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import com.stevesoltys.seedvault.R -abstract class BackupActivity : AppCompatActivity() { +abstract class BackupActivityBase : AppCompatActivity() { @CallSuper override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/files/FileSelectionFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/files/FileSelectionFragment.kt index cde9e8dc..21e5b7e8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/files/FileSelectionFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/files/FileSelectionFragment.kt @@ -5,7 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.settings.SettingsViewModel +import com.stevesoltys.seedvault.ui.settings.SettingsViewModel import org.calyxos.backup.storage.ui.backup.BackupContentFragment import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.viewModel diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/LiveEvent.kt similarity index 89% rename from app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/LiveEvent.kt index 7dac0f91..a06fdb72 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEvent.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/LiveEvent.kt @@ -1,9 +1,9 @@ -package com.stevesoltys.seedvault.ui +package com.stevesoltys.seedvault.ui.liveevent import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import com.stevesoltys.seedvault.ui.LiveEvent.ConsumableEvent +import com.stevesoltys.seedvault.ui.liveevent.LiveEvent.ConsumableEvent open class LiveEvent<T> : LiveData<ConsumableEvent<T>>() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEventHandler.java b/app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/LiveEventHandler.java similarity index 57% rename from app/src/main/java/com/stevesoltys/seedvault/ui/LiveEventHandler.java rename to app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/LiveEventHandler.java index 858a5ef9..9be03cba 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/LiveEventHandler.java +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/LiveEventHandler.java @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.ui; +package com.stevesoltys.seedvault.ui.liveevent; public interface LiveEventHandler<T> { void onEvent(T t); diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/MutableLiveEvent.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/MutableLiveEvent.kt similarity index 82% rename from app/src/main/java/com/stevesoltys/seedvault/ui/MutableLiveEvent.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/MutableLiveEvent.kt index 993a2c18..5ffd0422 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/MutableLiveEvent.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/liveevent/MutableLiveEvent.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.ui +package com.stevesoltys.seedvault.ui.liveevent class MutableLiveEvent<T> : LiveEvent<T>() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt index 35b412fa..d1e12e87 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt @@ -20,12 +20,12 @@ import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT import androidx.core.app.NotificationCompat.PRIORITY_HIGH import androidx.core.app.NotificationCompat.PRIORITY_LOW import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.restore.ACTION_RESTORE_ERROR_UNINSTALL -import com.stevesoltys.seedvault.restore.EXTRA_PACKAGE_NAME -import com.stevesoltys.seedvault.restore.REQUEST_CODE_UNINSTALL -import com.stevesoltys.seedvault.settings.ACTION_APP_STATUS_LIST -import com.stevesoltys.seedvault.settings.SettingsActivity -import com.stevesoltys.seedvault.transport.backup.ExpectedAppTotals +import com.stevesoltys.seedvault.ui.restore.ACTION_RESTORE_ERROR_UNINSTALL +import com.stevesoltys.seedvault.ui.restore.EXTRA_PACKAGE_NAME +import com.stevesoltys.seedvault.ui.restore.REQUEST_CODE_UNINSTALL +import com.stevesoltys.seedvault.ui.settings.ACTION_APP_STATUS_LIST +import com.stevesoltys.seedvault.ui.settings.SettingsActivity +import com.stevesoltys.seedvault.service.app.ExpectedAppTotals private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver" private const val CHANNEL_ID_ERROR = "NotificationError" diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt index d35971fc..ae23551c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt @@ -9,8 +9,8 @@ import android.util.Log.INFO import android.util.Log.isLoggable import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.transport.backup.ExpectedAppTotals +import com.stevesoltys.seedvault.service.app.ExpectedAppTotals +import com.stevesoltys.seedvault.service.metadata.MetadataService import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -23,7 +23,7 @@ internal class NotificationBackupObserver( ) : IBackupObserver.Stub(), KoinComponent { private val nm: BackupNotificationManager by inject() - private val metadataManager: MetadataManager by inject() + private val metadataService: MetadataService by inject() private var currentPackage: String? = null private var numPackages: Int = 0 @@ -77,7 +77,7 @@ internal class NotificationBackupObserver( Log.i(TAG, "Backup finished $numPackages/$expectedPackages. Status: $status") } val success = status == 0 - val numBackedUp = if (success) metadataManager.getPackagesNumBackedUp() else null + val numBackedUp = if (success) metadataService.getPackagesNumBackedUp() else null nm.onBackupFinished(success, numBackedUp) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/provision/RequireProvisioningActivity.kt similarity index 94% rename from app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/provision/RequireProvisioningActivity.kt index 393eb2ef..0fa26042 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/provision/RequireProvisioningActivity.kt @@ -1,10 +1,11 @@ -package com.stevesoltys.seedvault.ui +package com.stevesoltys.seedvault.ui.provision import android.content.Intent import android.os.Bundle import android.util.Log import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.annotation.CallSuper +import com.stevesoltys.seedvault.ui.BackupActivityBase import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeActivity import com.stevesoltys.seedvault.ui.storage.StorageActivity @@ -19,7 +20,7 @@ private val TAG = RequireProvisioningActivity::class.java.name * An Activity that requires the recovery code and the backup location to be set up * before starting. */ -abstract class RequireProvisioningActivity : BackupActivity() { +abstract class RequireProvisioningActivity : BackupActivityBase() { private val recoveryCodeRequest = registerForActivityResult(StartActivityForResult()) { result -> diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/provision/RequireProvisioningViewModel.kt similarity index 68% rename from app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/provision/RequireProvisioningViewModel.kt index 28064441..ebfdfeb9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/provision/RequireProvisioningViewModel.kt @@ -1,14 +1,16 @@ -package com.stevesoltys.seedvault.ui +package com.stevesoltys.seedvault.ui.provision import android.app.Application import androidx.lifecycle.AndroidViewModel -import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.crypto.KeyManager +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.ui.liveevent.LiveEvent +import com.stevesoltys.seedvault.ui.liveevent.MutableLiveEvent import com.stevesoltys.seedvault.ui.storage.StorageViewModel abstract class RequireProvisioningViewModel( protected val app: Application, - protected val settingsManager: SettingsManager, + protected val settingsService: SettingsService, protected val keyManager: KeyManager, ) : AndroidViewModel(app) { @@ -18,7 +20,7 @@ abstract class RequireProvisioningViewModel( internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true) - internal fun validLocationIsSet() = StorageViewModel.validLocationIsSet(app, settingsManager) + internal fun validLocationIsSet() = StorageViewModel.validLocationIsSet(app, settingsService) 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 5923a145..60704696 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 @@ -5,11 +5,11 @@ import android.view.MenuItem import android.view.WindowManager.LayoutParams.FLAG_SECURE import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.isDebugBuild -import com.stevesoltys.seedvault.ui.BackupActivity -import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE +import com.stevesoltys.seedvault.ui.BackupActivityBase +import com.stevesoltys.seedvault.ui.provision.INTENT_EXTRA_IS_RESTORE import org.koin.androidx.viewmodel.ext.android.viewModel -class RecoveryCodeActivity : BackupActivity() { +class RecoveryCodeActivity : BackupActivityBase() { private val viewModel: RecoveryCodeViewModel by viewModel() 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 5dbc82ef..0bf30c01 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 @@ -10,12 +10,12 @@ import cash.z.ecc.android.bip39.Mnemonics.InvalidWordException import cash.z.ecc.android.bip39.Mnemonics.WordCountException import cash.z.ecc.android.bip39.toSeed import com.stevesoltys.seedvault.App -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.crypto.KeyManager +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.crypto.KeyManager import com.stevesoltys.seedvault.transport.TRANSPORT_ID -import com.stevesoltys.seedvault.transport.backup.BackupCoordinator -import com.stevesoltys.seedvault.ui.LiveEvent -import com.stevesoltys.seedvault.ui.MutableLiveEvent +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.ui.liveevent.LiveEvent +import com.stevesoltys.seedvault.ui.liveevent.MutableLiveEvent import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -29,17 +29,17 @@ private val TAG = RecoveryCodeViewModel::class.java.simpleName internal class RecoveryCodeViewModel( app: App, - private val crypto: Crypto, + private val cryptoService: CryptoService, private val keyManager: KeyManager, private val backupManager: IBackupManager, - private val backupCoordinator: BackupCoordinator, + private val backupCoordinatorService: BackupCoordinatorService, private val notificationManager: BackupNotificationManager, private val storageBackup: StorageBackup, ) : AndroidViewModel(app) { internal val wordList: List<CharArray> by lazy { // we use our own entropy to not having to trust the library to use SecureRandom - val entropy = crypto.getRandomBytes(Mnemonics.WordCount.COUNT_12.bitLength / 8) + val entropy = cryptoService.getRandomBytes(Mnemonics.WordCount.COUNT_12.bitLength / 8) // create the words from the entropy Mnemonics.MnemonicCode(entropy).words } @@ -73,7 +73,7 @@ internal class RecoveryCodeViewModel( fun verifyExistingCode(input: List<CharSequence>) { // we validate the code again, just in case val seed = validateCode(input).toSeed() - val verified = crypto.verifyBackupKey(seed) + val verified = cryptoService.verifyBackupKey(seed) // store main key at this opportunity if it is still missing if (verified && !keyManager.hasMainKey()) keyManager.storeMainKey(seed) mExistingCodeChecked.setEvent(verified) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestorableBackup.kt similarity index 77% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestorableBackup.kt index 2c3abb3c..73eb1ccd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestorableBackup.kt @@ -1,7 +1,7 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore -import com.stevesoltys.seedvault.metadata.BackupMetadata -import com.stevesoltys.seedvault.metadata.PackageMetadataMap +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.PackageMetadataMap data class RestorableBackup(val backupMetadata: BackupMetadata) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreActivity.kt similarity index 74% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreActivity.kt index e0afb7b0..de4b251b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreActivity.kt @@ -1,15 +1,15 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.os.Bundle import androidx.annotation.CallSuper import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES_STARTED -import com.stevesoltys.seedvault.restore.install.InstallProgressFragment -import com.stevesoltys.seedvault.ui.RequireProvisioningActivity -import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_APPS +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_BACKUP +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_FILES +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_FILES_STARTED +import com.stevesoltys.seedvault.ui.restore.apk.InstallProgressFragment +import com.stevesoltys.seedvault.ui.provision.RequireProvisioningActivity +import com.stevesoltys.seedvault.ui.provision.RequireProvisioningViewModel import org.koin.androidx.viewmodel.ext.android.viewModel class RestoreActivity : RequireProvisioningActivity() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreErrorBroadcastReceiver.kt similarity index 96% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreErrorBroadcastReceiver.kt index 1b6dc87b..f482420b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreErrorBroadcastReceiver.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.content.BroadcastReceiver import android.content.Context diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreFilesFragment.kt similarity index 97% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreFilesFragment.kt index 531ce034..ca25fe39 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreFilesFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.app.Activity.RESULT_OK import android.os.Bundle diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreProgressAdapter.kt similarity index 95% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreProgressAdapter.kt index 802004af..0d323bd8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreProgressAdapter.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.content.pm.PackageManager.NameNotFoundException import android.view.LayoutInflater @@ -8,7 +8,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView.Adapter import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder +import com.stevesoltys.seedvault.ui.restore.RestoreProgressAdapter.PackageViewHolder import com.stevesoltys.seedvault.ui.AppBackupState import com.stevesoltys.seedvault.ui.AppViewHolder import java.util.LinkedList diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreProgressFragment.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreProgressFragment.kt index 17bf9fc6..78969011 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreProgressFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreSetAdapter.kt similarity index 93% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreSetAdapter.kt index f7e7cbb4..3a29bded 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreSetAdapter.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE import android.text.format.DateUtils.HOUR_IN_MILLIS @@ -10,7 +10,7 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder +import com.stevesoltys.seedvault.ui.restore.RestoreSetAdapter.RestoreSetViewHolder internal class RestoreSetAdapter( private val listener: RestorableBackupClickListener, diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreSetFragment.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreSetFragment.kt index 6959565c..3d7b7536 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreSetFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreViewModel.kt similarity index 89% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreViewModel.kt index 9992a15c..5ad3c363 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/RestoreViewModel.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore +package com.stevesoltys.seedvault.ui.restore import android.app.Application import android.app.backup.BackupManager @@ -18,28 +18,28 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations.switchMap import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.stevesoltys.seedvault.BackupMonitor +import com.stevesoltys.seedvault.service.app.BackupManagerOperationMonitor import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES -import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES_STARTED -import com.stevesoltys.seedvault.restore.install.ApkRestore -import com.stevesoltys.seedvault.restore.install.InstallIntentCreator -import com.stevesoltys.seedvault.restore.install.InstallResult -import com.stevesoltys.seedvault.restore.install.isInstalled -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.storage.StorageRestoreService +import com.stevesoltys.seedvault.service.crypto.KeyManager +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_APPS +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_BACKUP +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_FILES +import com.stevesoltys.seedvault.ui.restore.DisplayFragment.RESTORE_FILES_STARTED +import com.stevesoltys.seedvault.ui.restore.apk.ApkRestore +import com.stevesoltys.seedvault.ui.restore.apk.InstallIntentCreator +import com.stevesoltys.seedvault.ui.restore.apk.InstallResult +import com.stevesoltys.seedvault.ui.restore.apk.isInstalled +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.file.restore.FileRestoreService import com.stevesoltys.seedvault.transport.TRANSPORT_ID -import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator +import com.stevesoltys.seedvault.service.app.restore.coordinator.RestoreCoordinator import com.stevesoltys.seedvault.ui.AppBackupState import com.stevesoltys.seedvault.ui.AppBackupState.FAILED import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NOT_ALLOWED @@ -49,9 +49,9 @@ import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_QUOTA_EXCEEDED import com.stevesoltys.seedvault.ui.AppBackupState.IN_PROGRESS import com.stevesoltys.seedvault.ui.AppBackupState.NOT_YET_BACKED_UP import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED -import com.stevesoltys.seedvault.ui.LiveEvent -import com.stevesoltys.seedvault.ui.MutableLiveEvent -import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel +import com.stevesoltys.seedvault.ui.liveevent.LiveEvent +import com.stevesoltys.seedvault.ui.liveevent.MutableLiveEvent +import com.stevesoltys.seedvault.ui.provision.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.notification.getAppName import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers @@ -74,20 +74,20 @@ internal const val PACKAGES_PER_CHUNK = 100 internal class RestoreViewModel( app: Application, - settingsManager: SettingsManager, + settingsService: SettingsService, keyManager: KeyManager, private val backupManager: IBackupManager, private val restoreCoordinator: RestoreCoordinator, private val apkRestore: ApkRestore, storageBackup: StorageBackup, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, -) : RequireProvisioningViewModel(app, settingsManager, keyManager), +) : RequireProvisioningViewModel(app, settingsService, keyManager), RestorableBackupClickListener, SnapshotViewModel { override val isRestoreOperation = true private var session: IRestoreSession? = null - private val monitor = BackupMonitor() + private val monitor = BackupManagerOperationMonitor() private val mDisplayFragment = MutableLiveEvent<DisplayFragment>() internal val displayFragment: LiveEvent<DisplayFragment> = mDisplayFragment @@ -193,8 +193,8 @@ internal class RestoreViewModel( // if we had no token before (i.e. restore from setup wizard), // use the token of the current restore set from now on - if (settingsManager.getToken() == null) { - settingsManager.setNewToken(token) + if (settingsService.getToken() == null) { + settingsService.setNewToken(token) } // start a new restore session @@ -316,7 +316,7 @@ internal class RestoreViewModel( private val restorableBackup: RestorableBackup, private val session: IRestoreSession, private val packages: List<String>, - private val monitor: BackupMonitor, + private val monitor: BackupManagerOperationMonitor, ) : IRestoreObserver.Stub() { /** @@ -441,7 +441,7 @@ internal class RestoreViewModel( @UiThread internal fun startFilesRestore(item: SnapshotItem) { - val i = Intent(app, StorageRestoreService::class.java) + val i = Intent(app, FileRestoreService::class.java) i.putExtra(EXTRA_USER_ID, item.storedSnapshot.userId) i.putExtra(EXTRA_TIMESTAMP_START, item.time) app.startForegroundService(i) diff --git a/app/src/main/java/com/stevesoltys/seedvault/SecretCodeReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/SecretCodeReceiver.kt similarity index 61% rename from app/src/main/java/com/stevesoltys/seedvault/SecretCodeReceiver.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/SecretCodeReceiver.kt index 63563609..1f2004be 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/SecretCodeReceiver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/SecretCodeReceiver.kt @@ -1,22 +1,23 @@ -package com.stevesoltys.seedvault +package com.stevesoltys.seedvault.ui.restore import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.util.Log -import com.stevesoltys.seedvault.restore.RestoreActivity - -private val TAG = BroadcastReceiver::class.java.simpleName -private const val RESTORE_SECRET_CODE = "7378673" class SecretCodeReceiver : BroadcastReceiver() { + companion object { + private val TAG = BroadcastReceiver::class.java.simpleName + + private const val RESTORE_SECRET_CODE = "7378673" + } + override fun onReceive(context: Context, intent: Intent) { if (intent.data?.host != RESTORE_SECRET_CODE) return Log.d(TAG, "Restore secret code received.") val i = Intent(context, RestoreActivity::class.java).apply { - flags = FLAG_ACTIVITY_NEW_TASK + flags = Intent.FLAG_ACTIVITY_NEW_TASK } context.startActivity(i) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkInstaller.kt similarity index 96% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkInstaller.kt index 4affa6d9..a98089b5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkInstaller.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkInstaller.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.annotation.SuppressLint import android.app.PendingIntent @@ -20,8 +20,8 @@ import android.content.pm.PackageInstaller.SessionParams import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL import android.content.pm.PackageManager import android.util.Log -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.SUCCEEDED import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkRestore.kt similarity index 90% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkRestore.kt index 59600b3d..63dd4720 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkRestore.kt @@ -1,23 +1,23 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.content.Context import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_SIGNATURES import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.util.Log -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.metadata.ApkSplit -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.restore.RestorableBackup -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP -import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS -import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED -import com.stevesoltys.seedvault.transport.backup.copyStreamsAndGetHash -import com.stevesoltys.seedvault.transport.backup.getSignatures -import com.stevesoltys.seedvault.transport.backup.isSystemApp +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.metadata.ApkSplit +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.ui.restore.RestorableBackup +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED_SYSTEM_APP +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.IN_PROGRESS +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.QUEUED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.SUCCEEDED +import com.stevesoltys.seedvault.service.app.backup.apk.copyStreamsAndGetHash +import com.stevesoltys.seedvault.service.app.backup.apk.getSignatures +import com.stevesoltys.seedvault.service.app.isSystemApp import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow @@ -31,7 +31,7 @@ internal class ApkRestore( private val storagePlugin: StoragePlugin, @Suppress("Deprecation") private val legacyStoragePlugin: LegacyStoragePlugin, - private val crypto: Crypto, + private val cryptoService: CryptoService, private val splitCompatChecker: ApkSplitCompatibilityChecker, private val apkInstaller: ApkInstaller, ) { @@ -222,7 +222,7 @@ internal class ApkRestore( @Suppress("Deprecation") legacyStoragePlugin.getApkInputStream(token, packageName, suffix) } else { - val name = crypto.getNameForApk(salt, packageName, suffix) + val name = cryptoService.getNameForApk(salt, packageName, suffix) storagePlugin.getInputStream(token, name) } val sha256 = copyStreamsAndGetHash(inputStream, cachedApk.outputStream()) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityChecker.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkSplitCompatibilityChecker.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityChecker.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkSplitCompatibilityChecker.kt index a7be09df..e3bea2fd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityChecker.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/ApkSplitCompatibilityChecker.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.util.Log diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/DeviceInfo.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/DeviceInfo.kt similarity index 95% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/DeviceInfo.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/DeviceInfo.kt index 614e2c87..1e72768e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/DeviceInfo.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/DeviceInfo.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.content.Context import android.os.Build diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallIntentCreator.kt similarity index 97% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallIntentCreator.kt index 1f9b13ae..62f0b708 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallIntentCreator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallIntentCreator.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.content.Intent import android.content.Intent.ACTION_VIEW diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallModule.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallModule.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallModule.kt index 33e640b3..d9b391fc 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallModule.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import org.koin.android.ext.koin.androidContext import org.koin.dsl.module diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallProgressAdapter.kt similarity index 91% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressAdapter.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallProgressAdapter.kt index b3cc5cab..b08ab4b9 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallProgressAdapter.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.view.LayoutInflater import android.view.View @@ -10,11 +10,11 @@ import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedListAdapterCallback import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP -import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS -import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED_SYSTEM_APP +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.IN_PROGRESS +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.QUEUED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.SUCCEEDED import com.stevesoltys.seedvault.ui.AppViewHolder import com.stevesoltys.seedvault.ui.notification.getAppName diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallProgressFragment.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallProgressFragment.kt index 62948cbc..70e034c6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallProgressFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.content.ActivityNotFoundException import android.content.Context @@ -18,7 +18,7 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.restore.RestoreViewModel +import com.stevesoltys.seedvault.ui.restore.RestoreViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel class InstallProgressFragment : Fragment(), InstallItemListener { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallResult.kt similarity index 92% rename from app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallResult.kt index 982311e7..ce1e54c8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallResult.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/restore/apk/InstallResult.kt @@ -1,11 +1,11 @@ -package com.stevesoltys.seedvault.restore.install +package com.stevesoltys.seedvault.ui.restore.apk import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS -import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.IN_PROGRESS +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.QUEUED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.SUCCEEDED import java.util.concurrent.ConcurrentHashMap internal interface InstallResult { diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AboutDialogFragment.kt similarity index 93% rename from app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/AboutDialogFragment.kt index 81738a29..1afb4aa0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AboutDialogFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AboutDialogFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.os.Bundle import android.text.method.LinkMovementMethod @@ -8,7 +8,7 @@ import android.view.ViewGroup import android.widget.TextView import androidx.fragment.app.Fragment import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.transport.backup.PackageService +import com.stevesoltys.seedvault.service.app.PackageService import org.koin.android.ext.android.inject class AboutDialogFragment : Fragment() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppListRetriever.kt similarity index 89% rename from app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppListRetriever.kt index e185b79b..1e4d6a14 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppListRetriever.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.annotation.StringRes import android.content.Context @@ -8,9 +8,10 @@ import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.transport.backup.PackageService +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.metadata.PackageState +import com.stevesoltys.seedvault.service.app.PackageService +import com.stevesoltys.seedvault.service.settings.SettingsService import com.stevesoltys.seedvault.ui.AppBackupState import com.stevesoltys.seedvault.ui.AppBackupState.FAILED import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NOT_ALLOWED @@ -47,8 +48,8 @@ class AppSectionTitle(@StringRes val titleRes: Int) : AppListItem() internal class AppListRetriever( private val context: Context, private val packageService: PackageService, - private val settingsManager: SettingsManager, - private val metadataManager: MetadataManager, + private val settingsService: SettingsService, + private val metadataService: MetadataService, ) { private val pm: PackageManager = context.packageManager @@ -75,7 +76,7 @@ internal class AppListRetriever( Pair(PACKAGE_NAME_CONTACTS, R.string.backup_contacts) ) return specialPackages.map { (packageName, stringId) -> - val metadata = metadataManager.getPackageMetadata(packageName) + val metadata = metadataService.getPackageMetadata(packageName) val status = if (packageName == PACKAGE_NAME_CONTACTS && metadata?.state == null) { // handle local contacts backup specially as it might not be installed if (packageService.getVersionName(packageName) == null) FAILED_NOT_INSTALLED @@ -83,7 +84,7 @@ internal class AppListRetriever( } else metadata?.state.toAppBackupState() AppStatus( packageName = packageName, - enabled = settingsManager.isBackupEnabled(packageName), + enabled = settingsService.isBackupEnabled(packageName), icon = getIcon(packageName), name = context.getString(stringId), time = metadata?.time ?: 0, @@ -96,7 +97,7 @@ internal class AppListRetriever( private fun getUserApps(): List<AppStatus> { val locale = Locale.getDefault() return packageService.userApps.map { - val metadata = metadataManager.getPackageMetadata(it.packageName) + val metadata = metadataService.getPackageMetadata(it.packageName) val time = metadata?.time ?: 0 val status = metadata?.state.toAppBackupState() if (status == NOT_YET_BACKED_UP) { @@ -107,7 +108,7 @@ internal class AppListRetriever( } AppStatus( packageName = it.packageName, - enabled = settingsManager.isBackupEnabled(it.packageName), + enabled = settingsService.isBackupEnabled(it.packageName), icon = getIcon(it.packageName), name = getAppName(context, it.packageName).toString(), time = time, @@ -121,7 +122,7 @@ internal class AppListRetriever( return packageService.userNotAllowedApps.map { AppStatus( packageName = it.packageName, - enabled = settingsManager.isBackupEnabled(it.packageName), + enabled = settingsService.isBackupEnabled(it.packageName), icon = getIcon(it.packageName), name = getAppName(context, it.packageName).toString(), time = 0, diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppStatusAdapter.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppStatusAdapter.kt index 5536f6b5..ce15ff1c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppStatusAdapter.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.content.Intent import android.net.Uri @@ -21,7 +21,7 @@ import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_NOT_ALLOWED import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED import com.stevesoltys.seedvault.ui.AppViewHolder -import com.stevesoltys.seedvault.ui.toRelativeTime +import com.stevesoltys.seedvault.util.toRelativeTime internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) : Adapter<RecyclerView.ViewHolder>() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppStatusFragment.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppStatusFragment.kt index 85c18982..6126b056 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/AppStatusFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/BackupManagerSettings.kt similarity index 95% rename from app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/BackupManagerSettings.kt index 622693c1..6bec6b16 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/BackupManagerSettings.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/BackupManagerSettings.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.content.ContentResolver import android.provider.Settings diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/ExpertSettingsFragment.kt similarity index 90% rename from app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/ExpertSettingsFragment.kt index 05607375..e5ae4507 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/ExpertSettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/ExpertSettingsFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.os.Bundle import androidx.activity.result.contract.ActivityResultContracts.CreateDocument @@ -7,7 +7,8 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.transport.backup.PackageService +import com.stevesoltys.seedvault.service.app.PackageService +import com.stevesoltys.seedvault.service.settings.PREF_KEY_D2D_BACKUPS import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsActivity.kt similarity index 93% rename from app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsActivity.kt index 68194e29..0202947e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsActivity.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.os.Bundle import androidx.annotation.CallSuper @@ -6,8 +6,8 @@ import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.ui.RequireProvisioningActivity -import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel +import com.stevesoltys.seedvault.ui.provision.RequireProvisioningActivity +import com.stevesoltys.seedvault.ui.provision.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.recoverycode.ARG_FOR_NEW_CODE import org.koin.android.ext.android.inject diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsFragment.kt similarity index 95% rename from app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsFragment.kt index 38aaf805..a00b4ccd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsFragment.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.app.backup.IBackupManager import android.content.Intent @@ -21,8 +21,12 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.TwoStatePreference import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.restore.RestoreActivity -import com.stevesoltys.seedvault.ui.toRelativeTime +import com.stevesoltys.seedvault.service.settings.PREF_KEY_AUTO_RESTORE +import com.stevesoltys.seedvault.service.settings.PREF_KEY_BACKUP_APK +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.settings.Storage +import com.stevesoltys.seedvault.ui.restore.RestoreActivity +import com.stevesoltys.seedvault.util.toRelativeTime import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel @@ -31,7 +35,7 @@ private val TAG = SettingsFragment::class.java.name class SettingsFragment : PreferenceFragmentCompat() { private val viewModel: SettingsViewModel by sharedViewModel() - private val settingsManager: SettingsManager by inject() + private val settingsService: SettingsService by inject() private val backupManager: IBackupManager by inject() private lateinit var backup: TwoStatePreference @@ -155,7 +159,7 @@ class SettingsFragment : PreferenceFragmentCompat() { // we need to re-set the title when returning to this fragment activity?.setTitle(R.string.backup) - storage = settingsManager.getStorage() + storage = settingsService.getStorage() setBackupEnabledState() setBackupLocationSummary() setAutoRestoreState() diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsViewModel.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsViewModel.kt index 3c190651..cce2c56c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/settings/SettingsViewModel.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.settings +package com.stevesoltys.seedvault.ui.settings import android.app.Application import android.app.backup.IBackupManager @@ -25,14 +25,15 @@ import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import androidx.recyclerview.widget.DiffUtil.calculateDiff import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.storage.StorageBackupJobService -import com.stevesoltys.seedvault.storage.StorageBackupService -import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP -import com.stevesoltys.seedvault.transport.requestBackup -import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel +import com.stevesoltys.seedvault.service.app.backup.requestBackup +import com.stevesoltys.seedvault.service.crypto.KeyManager +import com.stevesoltys.seedvault.service.file.backup.FileBackupJobService +import com.stevesoltys.seedvault.service.file.backup.FileBackupService +import com.stevesoltys.seedvault.service.file.backup.FileBackupService.Companion.EXTRA_START_APP_BACKUP +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.ui.provision.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -48,14 +49,14 @@ private const val USER_FULL_DATA_BACKUP_AWARE = "user_full_data_backup_aware" internal class SettingsViewModel( app: Application, - settingsManager: SettingsManager, + settingsService: SettingsService, keyManager: KeyManager, private val notificationManager: BackupNotificationManager, - private val metadataManager: MetadataManager, + private val metadataService: MetadataService, private val appListRetriever: AppListRetriever, private val storageBackup: StorageBackup, private val backupManager: IBackupManager, -) : RequireProvisioningViewModel(app, settingsManager, keyManager) { +) : RequireProvisioningViewModel(app, settingsService, keyManager) { private val contentResolver = app.contentResolver private val connectivityManager = app.getSystemService(ConnectivityManager::class.java) @@ -65,7 +66,7 @@ internal class SettingsViewModel( private val mBackupPossible = MutableLiveData(false) val backupPossible: LiveData<Boolean> = mBackupPossible - internal val lastBackupTime = metadataManager.lastBackupTime + internal val lastBackupTime = metadataService.lastBackupTime private val mAppStatusList = switchMap(lastBackupTime) { // updates app list when lastBackupTime changes @@ -106,14 +107,14 @@ internal class SettingsViewModel( } scope.launch { // ensures the lastBackupTime LiveData gets set - metadataManager.getLastBackupTime() + metadataService.getLastBackupTime() } onStorageLocationChanged() loadFilesSummary() } override fun onStorageLocationChanged() { - val storage = settingsManager.getStorage() ?: return + val storage = settingsService.getStorage() ?: return // register storage observer try { @@ -137,7 +138,7 @@ internal class SettingsViewModel( networkCallback.registered = true } - if (settingsManager.isStorageBackupEnabled()) { + if (settingsService.isStorageBackupEnabled()) { // disable storage backup if new storage is on USB if (storage.isUsb) disableStorageBackup() // enable it, just in case the previous storage was on USB, @@ -146,7 +147,7 @@ internal class SettingsViewModel( } viewModelScope.launch(Dispatchers.IO) { - val canDo = settingsManager.canDoBackupNow() + val canDo = settingsService.canDoBackupNow() mBackupPossible.postValue(canDo) } } @@ -166,8 +167,8 @@ internal class SettingsViewModel( } else if (!backupManager.isBackupEnabled) { Toast.makeText(app, R.string.notification_backup_disabled, LENGTH_LONG).show() } else viewModelScope.launch(Dispatchers.IO) { - if (settingsManager.isStorageBackupEnabled()) { - val i = Intent(app, StorageBackupService::class.java) + if (settingsService.isStorageBackupEnabled()) { + val i = Intent(app, FileBackupService::class.java) // this starts an app backup afterwards i.putExtra(EXTRA_START_APP_BACKUP, true) startForegroundService(app, i) @@ -191,7 +192,7 @@ internal class SettingsViewModel( @UiThread fun onAppStatusToggled(status: AppStatus) { - settingsManager.onAppBackupStatusChanged(status) + settingsService.onAppBackupStatusChanged(status) } @UiThread @@ -221,10 +222,10 @@ internal class SettingsViewModel( } fun enableStorageBackup() { - val storage = settingsManager.getStorage() ?: error("no storage available") + val storage = settingsService.getStorage() ?: error("no storage available") if (!storage.isUsb) BackupJobService.scheduleJob( context = app, - jobServiceClass = StorageBackupJobService::class.java, + jobServiceClass = FileBackupJobService::class.java, periodMillis = HOURS.toMillis(24), networkType = if (storage.requiresNetwork) NETWORK_TYPE_UNMETERED else NETWORK_TYPE_NONE, 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 4595468b..69a9fbc5 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 @@ -10,10 +10,10 @@ import android.util.Log import androidx.annotation.WorkerThread import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.settings.SettingsService import com.stevesoltys.seedvault.transport.TRANSPORT_ID -import com.stevesoltys.seedvault.transport.backup.BackupCoordinator -import com.stevesoltys.seedvault.transport.requestBackup +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.service.app.backup.requestBackup import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.calyxos.backup.storage.api.StorageBackup @@ -24,10 +24,10 @@ private val TAG = BackupStorageViewModel::class.java.simpleName internal class BackupStorageViewModel( private val app: Application, private val backupManager: IBackupManager, - private val backupCoordinator: BackupCoordinator, + private val backupCoordinatorService: BackupCoordinatorService, private val storageBackup: StorageBackup, - settingsManager: SettingsManager, -) : StorageViewModel(app, settingsManager) { + settingsService: SettingsService, +) : StorageViewModel(app, settingsService) { override val isRestoreOperation = false 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 916ca3e0..e7c69d47 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 @@ -5,9 +5,9 @@ import android.net.Uri import android.util.Log import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.DIRECTORY_ROOT +import com.stevesoltys.seedvault.service.settings.SettingsService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.IOException @@ -17,8 +17,8 @@ private val TAG = RestoreStorageViewModel::class.java.simpleName internal class RestoreStorageViewModel( private val app: Application, private val storagePlugin: StoragePlugin, - settingsManager: SettingsManager, -) : StorageViewModel(app, settingsManager) { + settingsService: SettingsService, +) : StorageViewModel(app, settingsService) { override val isRestoreOperation = true 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 ca743c6f..56d63ed1 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 @@ -14,14 +14,14 @@ import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTre import androidx.annotation.CallSuper import androidx.appcompat.app.AlertDialog 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.BackupActivityBase +import com.stevesoltys.seedvault.ui.provision.INTENT_EXTRA_IS_RESTORE +import com.stevesoltys.seedvault.ui.provision.INTENT_EXTRA_IS_SETUP_WIZARD import org.koin.androidx.viewmodel.ext.android.getViewModel private val TAG = StorageActivity::class.java.name -class StorageActivity : BackupActivity() { +class StorageActivity : BackupActivityBase() { private lateinit var viewModel: StorageViewModel diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt index fc84248c..1360398c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt @@ -23,7 +23,7 @@ import androidx.annotation.RequiresPermission import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE +import com.stevesoltys.seedvault.ui.provision.INTENT_EXTRA_IS_RESTORE import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import org.koin.androidx.viewmodel.ext.android.getSharedViewModel 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 0d0e5f11..99dbba26 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 @@ -14,19 +14,19 @@ import androidx.lifecycle.MutableLiveData import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.permitDiskReads -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.ui.LiveEvent -import com.stevesoltys.seedvault.ui.MutableLiveEvent +import com.stevesoltys.seedvault.service.settings.FlashDrive +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.settings.Storage +import com.stevesoltys.seedvault.ui.settings.BackupManagerSettings +import com.stevesoltys.seedvault.ui.liveevent.LiveEvent +import com.stevesoltys.seedvault.ui.liveevent.MutableLiveEvent import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption private val TAG = StorageViewModel::class.java.simpleName internal abstract class StorageViewModel( private val app: Application, - protected val settingsManager: SettingsManager, + protected val settingsService: SettingsService, ) : AndroidViewModel(app), RemovableStorageListener { private val mStorageOptions = MutableLiveData<List<StorageOption>>() @@ -43,15 +43,15 @@ internal abstract class StorageViewModel( internal var isSetupWizard: Boolean = false internal val hasStorageSet: Boolean - get() = settingsManager.getStorage() != null + get() = settingsService.getStorage() != null abstract val isRestoreOperation: Boolean companion object { internal fun validLocationIsSet( context: Context, - settingsManager: SettingsManager, + settingsService: SettingsService, ): Boolean { - val storage = settingsManager.getStorage() ?: return false + val storage = settingsService.getStorage() ?: return false if (storage.isUsb) return true return permitDiskReads { storage.getDocumentFile(context).isDirectory @@ -113,15 +113,15 @@ internal abstract class StorageViewModel( } protected fun saveStorage(storage: Storage): Boolean { - settingsManager.setStorage(storage) + settingsService.setStorage(storage) if (storage.isUsb) { Log.d(TAG, "Selected storage is a removable USB device.") val wasSaved = saveUsbDevice() // reset stored flash drive, if we did not update it - if (!wasSaved) settingsManager.setFlashDrive(null) + if (!wasSaved) settingsService.setFlashDrive(null) } else { - settingsManager.setFlashDrive(null) + settingsService.setFlashDrive(null) } BackupManagerSettings.resetDefaults(app.contentResolver) @@ -135,7 +135,7 @@ internal abstract class StorageViewModel( manager.deviceList.values.forEach { device -> if (device.isMassStorage()) { val flashDrive = FlashDrive.from(device) - settingsManager.setFlashDrive(flashDrive) + settingsService.setFlashDrive(flashDrive) Log.d(TAG, "Saved flash drive: $flashDrive") return true } diff --git a/app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt b/app/src/main/java/com/stevesoltys/seedvault/util/Base64Utils.kt similarity index 90% rename from app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt rename to app/src/main/java/com/stevesoltys/seedvault/util/Base64Utils.kt index 69571f51..74863128 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/Base64Utils.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/util/Base64Utils.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault +package com.stevesoltys.seedvault.util import java.nio.charset.Charset import java.util.Base64 diff --git a/app/src/main/java/com/stevesoltys/seedvault/Clock.kt b/app/src/main/java/com/stevesoltys/seedvault/util/TimeSource.kt similarity index 79% rename from app/src/main/java/com/stevesoltys/seedvault/Clock.kt rename to app/src/main/java/com/stevesoltys/seedvault/util/TimeSource.kt index 3a39ffe1..9e437a61 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/Clock.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/util/TimeSource.kt @@ -1,9 +1,9 @@ -package com.stevesoltys.seedvault +package com.stevesoltys.seedvault.util /** * This class only exists, so we can mock the time in tests. */ -class Clock { +class TimeSource { /** * Returns the current time in milliseconds (Unix time). */ diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/UiUtils.kt b/app/src/main/java/com/stevesoltys/seedvault/util/UiUtils.kt similarity index 92% rename from app/src/main/java/com/stevesoltys/seedvault/ui/UiUtils.kt rename to app/src/main/java/com/stevesoltys/seedvault/util/UiUtils.kt index 26378dbf..4dd8350d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/UiUtils.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/util/UiUtils.kt @@ -1,4 +1,4 @@ -package com.stevesoltys.seedvault.ui +package com.stevesoltys.seedvault.util import android.content.Context import android.text.format.DateUtils.MINUTE_IN_MILLIS diff --git a/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt b/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt index 1bb0c7c5..2bb3d537 100644 --- a/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt +++ b/app/src/sharedTest/java/com/stevesoltys/seedvault/crypto/KeyManagerTestImpl.kt @@ -1,5 +1,7 @@ package com.stevesoltys.seedvault.crypto +import com.stevesoltys.seedvault.service.crypto.KEY_SIZE +import com.stevesoltys.seedvault.service.crypto.KeyManager import javax.crypto.KeyGenerator import javax.crypto.SecretKey diff --git a/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt b/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt index cdf03aea..d5edac29 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt @@ -1,18 +1,19 @@ package com.stevesoltys.seedvault -import com.stevesoltys.seedvault.crypto.CipherFactory -import com.stevesoltys.seedvault.crypto.CipherFactoryImpl -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.crypto.CryptoImpl -import com.stevesoltys.seedvault.crypto.KeyManager +import com.stevesoltys.seedvault.service.crypto.CipherFactory +import com.stevesoltys.seedvault.service.crypto.CipherFactoryImpl +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl +import com.stevesoltys.seedvault.service.crypto.KeyManager import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl -import com.stevesoltys.seedvault.header.headerModule -import com.stevesoltys.seedvault.metadata.metadataModule -import com.stevesoltys.seedvault.plugins.saf.documentsProviderModule -import com.stevesoltys.seedvault.restore.install.installModule -import com.stevesoltys.seedvault.settings.SettingsManager -import com.stevesoltys.seedvault.transport.backup.backupModule -import com.stevesoltys.seedvault.transport.restore.restoreModule +import com.stevesoltys.seedvault.service.header.headerModule +import com.stevesoltys.seedvault.service.metadata.metadataModule +import com.stevesoltys.seedvault.service.storage.saf.documentsProviderModule +import com.stevesoltys.seedvault.ui.restore.apk.installModule +import com.stevesoltys.seedvault.service.settings.SettingsService +import com.stevesoltys.seedvault.service.app.backup.backupModule +import com.stevesoltys.seedvault.service.app.restore.restoreModule +import com.stevesoltys.seedvault.util.TimeSource import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import org.koin.dsl.module @@ -22,11 +23,11 @@ class TestApp : App() { private val testCryptoModule = module { factory<CipherFactory> { CipherFactoryImpl(get()) } single<KeyManager> { KeyManagerTestImpl() } - single<Crypto> { CryptoImpl(get(), get(), get()) } + single<CryptoService> { CryptoServiceImpl(get(), get(), get()) } } private val appModule = module { - single { Clock() } - single { SettingsManager(this@TestApp) } + single { TimeSource() } + single { SettingsService(this@TestApp) } } override fun startKoin() = startKoin { diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceImplTest.kt similarity index 86% rename from app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceImplTest.kt index 879ee745..629fc3f5 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceImplTest.kt @@ -2,8 +2,11 @@ package com.stevesoltys.seedvault.crypto import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.header.HeaderReaderImpl -import com.stevesoltys.seedvault.metadata.METADATA_SALT_SIZE +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.metadata.METADATA_SALT_SIZE +import com.stevesoltys.seedvault.service.crypto.CipherFactory +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl +import com.stevesoltys.seedvault.service.crypto.KeyManager import io.mockk.mockk import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals @@ -15,13 +18,13 @@ import java.io.ByteArrayInputStream import java.io.IOException @TestInstance(PER_METHOD) -class CryptoImplTest { +class CryptoServiceImplTest { private val keyManager = mockk<KeyManager>() private val cipherFactory = mockk<CipherFactory>() - private val headerReader = HeaderReaderImpl() + private val headerReader = HeaderDecodeServiceImpl() - private val crypto = CryptoImpl(keyManager, cipherFactory, headerReader) + private val crypto = CryptoServiceImpl(keyManager, cipherFactory, headerReader) @Test fun `decrypting multiple segments on empty stream throws`() { diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceIntegrationTest.kt similarity index 84% rename from app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceIntegrationTest.kt index 7eedba18..847947d3 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceIntegrationTest.kt @@ -1,7 +1,9 @@ package com.stevesoltys.seedvault.crypto import com.stevesoltys.seedvault.assertReadEquals -import com.stevesoltys.seedvault.header.HeaderReaderImpl +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.crypto.CipherFactoryImpl +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.not @@ -15,12 +17,12 @@ import java.io.IOException import kotlin.random.Random @TestInstance(PER_METHOD) -class CryptoIntegrationTest { +class CryptoServiceIntegrationTest { private val keyManager = KeyManagerTestImpl() private val cipherFactory = CipherFactoryImpl(keyManager) - private val headerReader = HeaderReaderImpl() - private val crypto = CryptoImpl(keyManager, cipherFactory, headerReader) + private val headerReader = HeaderDecodeServiceImpl() + private val crypto = CryptoServiceImpl(keyManager, cipherFactory, headerReader) private val cleartext = Random.nextBytes(Random.nextInt(1, 422300)) diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceTest.kt similarity index 72% rename from app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceTest.kt index 3480283e..b3537d3c 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoServiceTest.kt @@ -3,15 +3,18 @@ package com.stevesoltys.seedvault.crypto import com.stevesoltys.seedvault.assertContains import com.stevesoltys.seedvault.getRandomByteArray import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.header.HeaderReader -import com.stevesoltys.seedvault.header.IV_SIZE -import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE -import com.stevesoltys.seedvault.header.MAX_PACKAGE_LENGTH_SIZE -import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH -import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE -import com.stevesoltys.seedvault.header.SegmentHeader -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.VersionHeader +import com.stevesoltys.seedvault.service.crypto.CipherFactory +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl +import com.stevesoltys.seedvault.service.crypto.KeyManager +import com.stevesoltys.seedvault.service.header.HeaderDecodeService +import com.stevesoltys.seedvault.service.header.IV_SIZE +import com.stevesoltys.seedvault.service.header.MAX_KEY_LENGTH_SIZE +import com.stevesoltys.seedvault.service.header.MAX_PACKAGE_LENGTH_SIZE +import com.stevesoltys.seedvault.service.header.MAX_SEGMENT_LENGTH +import com.stevesoltys.seedvault.service.header.MAX_VERSION_HEADER_SIZE +import com.stevesoltys.seedvault.service.header.SegmentHeader +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.VersionHeader import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.Assertions.assertArrayEquals @@ -29,13 +32,13 @@ import javax.crypto.Cipher import kotlin.random.Random @TestInstance(PER_METHOD) -class CryptoTest { +class CryptoServiceTest { private val keyManager = mockk<KeyManager>() private val cipherFactory = mockk<CipherFactory>() - private val headerReader = mockk<HeaderReader>() + private val headerDecodeService = mockk<HeaderDecodeService>() - private val crypto = CryptoImpl(keyManager, cipherFactory, headerReader) + private val crypto = CryptoServiceImpl(keyManager, cipherFactory, headerDecodeService) private val cipher = mockk<Cipher>() @@ -59,10 +62,12 @@ class CryptoTest { @Test fun `decrypting header works as expected`() { - every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { + headerDecodeService.readSegmentHeader(versionInputStream) + } returns versionSegmentHeader every { cipherFactory.createDecryptionCipher(iv) } returns cipher every { cipher.doFinal(versionCiphertext) } returns cleartext - every { headerReader.getVersionHeader(cleartext) } returns versionHeader + every { headerDecodeService.getVersionHeader(cleartext) } returns versionHeader assertEquals( versionHeader, @@ -82,7 +87,9 @@ class CryptoTest { val versionInputStream = ByteArrayInputStream(versionCiphertext) val versionSegmentHeader = SegmentHeader(size.toShort(), iv) - every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { + headerDecodeService.readSegmentHeader(versionInputStream) + } returns versionSegmentHeader val e = assertThrows(SecurityException::class.java) { crypto.decryptHeader( @@ -97,10 +104,12 @@ class CryptoTest { @Test fun `decrypting header throws because of different version`() { - every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { + headerDecodeService.readSegmentHeader(versionInputStream) + } returns versionSegmentHeader every { cipherFactory.createDecryptionCipher(iv) } returns cipher every { cipher.doFinal(versionCiphertext) } returns cleartext - every { headerReader.getVersionHeader(cleartext) } returns versionHeader + every { headerDecodeService.getVersionHeader(cleartext) } returns versionHeader val version = (VERSION + 1).toByte() val e = assertThrows(SecurityException::class.java) { @@ -116,10 +125,12 @@ class CryptoTest { @Test fun `decrypting header throws because of different package name`() { - every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { + headerDecodeService.readSegmentHeader(versionInputStream) + } returns versionSegmentHeader every { cipherFactory.createDecryptionCipher(iv) } returns cipher every { cipher.doFinal(versionCiphertext) } returns cleartext - every { headerReader.getVersionHeader(cleartext) } returns versionHeader + every { headerDecodeService.getVersionHeader(cleartext) } returns versionHeader val packageName = getRandomString(MAX_PACKAGE_LENGTH_SIZE) val e = assertThrows(SecurityException::class.java) { @@ -135,10 +146,12 @@ class CryptoTest { @Test fun `decrypting header throws because of different key`() { - every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader + every { + headerDecodeService.readSegmentHeader(versionInputStream) + } returns versionSegmentHeader every { cipherFactory.createDecryptionCipher(iv) } returns cipher every { cipher.doFinal(versionCiphertext) } returns cleartext - every { headerReader.getVersionHeader(cleartext) } returns versionHeader + every { headerDecodeService.getVersionHeader(cleartext) } returns versionHeader val e = assertThrows(SecurityException::class.java) { crypto.decryptHeader( @@ -154,7 +167,7 @@ class CryptoTest { @Test fun `decrypting data segment header works as expected`() { - every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { headerDecodeService.readSegmentHeader(inputStream) } returns segmentHeader every { cipherFactory.createDecryptionCipher(iv) } returns cipher every { cipher.doFinal(ciphertext) } returns cleartext @@ -166,7 +179,7 @@ class CryptoTest { val inputStream = mockk<InputStream>() val buffer = ByteArray(segmentHeader.segmentLength.toInt()) - every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { headerDecodeService.readSegmentHeader(inputStream) } returns segmentHeader every { inputStream.read(buffer) } returns 0 assertThrows(IOException::class.java) { @@ -179,7 +192,7 @@ class CryptoTest { val inputStream = mockk<InputStream>() val buffer = ByteArray(segmentHeader.segmentLength.toInt()) - every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { headerDecodeService.readSegmentHeader(inputStream) } returns segmentHeader every { inputStream.read(buffer) } returns -1 assertThrows(EOFException::class.java) { @@ -192,7 +205,7 @@ class CryptoTest { val inputStream = mockk<InputStream>() val buffer = ByteArray(segmentHeader.segmentLength.toInt()) - every { headerReader.readSegmentHeader(inputStream) } returns segmentHeader + every { headerDecodeService.readSegmentHeader(inputStream) } returns segmentHeader every { inputStream.read(buffer) } returns buffer.size - 1 assertThrows(IOException::class.java) { diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/KeyManagerImplTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/KeyManagerImplTest.kt index 7f7ee538..9d83446c 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/crypto/KeyManagerImplTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/KeyManagerImplTest.kt @@ -1,6 +1,7 @@ package com.stevesoltys.seedvault.crypto import com.stevesoltys.seedvault.getRandomByteArray +import com.stevesoltys.seedvault.service.crypto.KeyManagerImpl import io.mockk.Runs import io.mockk.every import io.mockk.just diff --git a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderDecodeServiceTest.kt similarity index 92% rename from app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/header/HeaderDecodeServiceTest.kt index 7765d1ee..6f1b9315 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderDecodeServiceTest.kt @@ -1,8 +1,18 @@ package com.stevesoltys.seedvault.header -import com.stevesoltys.seedvault.Utf8 +import com.stevesoltys.seedvault.util.Utf8 import com.stevesoltys.seedvault.assertContains import com.stevesoltys.seedvault.getRandomString +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.header.IV_SIZE +import com.stevesoltys.seedvault.service.header.MAX_KEY_LENGTH_SIZE +import com.stevesoltys.seedvault.service.header.MAX_PACKAGE_LENGTH_SIZE +import com.stevesoltys.seedvault.service.header.MAX_SEGMENT_LENGTH +import com.stevesoltys.seedvault.service.header.MAX_VERSION_HEADER_SIZE +import com.stevesoltys.seedvault.service.header.SEGMENT_HEADER_SIZE +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.VersionHeader import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows @@ -15,9 +25,9 @@ import java.nio.ByteBuffer import kotlin.random.Random @TestInstance(PER_CLASS) -internal class HeaderReaderTest { +internal class HeaderDecodeServiceTest { - private val reader = HeaderReaderImpl() + private val reader = HeaderDecodeServiceImpl() // Version Tests diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt index fdd6aff8..73792ee5 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReadWriteTest.kt @@ -1,15 +1,20 @@ package com.stevesoltys.seedvault.metadata -import com.stevesoltys.seedvault.crypto.CipherFactoryImpl -import com.stevesoltys.seedvault.crypto.CryptoImpl -import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES +import com.stevesoltys.seedvault.service.crypto.CipherFactoryImpl +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl +import com.stevesoltys.seedvault.service.crypto.KEY_SIZE_BYTES import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.header.HeaderReaderImpl -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.MetadataReaderImpl +import com.stevesoltys.seedvault.service.metadata.MetadataWriterImpl +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -27,11 +32,11 @@ internal class MetadataReadWriteTest { ) private val keyManager = KeyManagerTestImpl(secretKey) private val cipherFactory = CipherFactoryImpl(keyManager) - private val headerReader = HeaderReaderImpl() - private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader) + private val headerReader = HeaderDecodeServiceImpl() + private val cryptoServiceImpl = CryptoServiceImpl(keyManager, cipherFactory, headerReader) - private val writer = MetadataWriterImpl(cryptoImpl) - private val reader = MetadataReaderImpl(cryptoImpl) + private val writer = MetadataWriterImpl(cryptoServiceImpl) + private val reader = MetadataReaderImpl(cryptoServiceImpl) private val packages = HashMap<String, PackageMetadata>().apply { put(getRandomString(), PackageMetadata(Random.nextLong(), APK_AND_DATA, BackupType.FULL)) diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt index 140d5915..de837bcf 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt @@ -1,11 +1,29 @@ package com.stevesoltys.seedvault.metadata -import com.stevesoltys.seedvault.Utf8 -import com.stevesoltys.seedvault.crypto.Crypto +import com.stevesoltys.seedvault.util.Utf8 +import com.stevesoltys.seedvault.service.crypto.CryptoService import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.JSON_METADATA +import com.stevesoltys.seedvault.service.metadata.JSON_METADATA_SDK_INT +import com.stevesoltys.seedvault.service.metadata.JSON_METADATA_TOKEN +import com.stevesoltys.seedvault.service.metadata.JSON_METADATA_VERSION +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_BACKUP_TYPE +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_INSTALLER +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_SHA256 +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_SIGNATURES +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_STATE +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_TIME +import com.stevesoltys.seedvault.service.metadata.JSON_PACKAGE_VERSION +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.METADATA_SALT_SIZE +import com.stevesoltys.seedvault.service.metadata.MetadataReaderImpl +import com.stevesoltys.seedvault.service.metadata.MetadataWriterImpl +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageMetadataMap import io.mockk.mockk import org.json.JSONArray import org.json.JSONObject @@ -22,10 +40,10 @@ import kotlin.random.Random @TestInstance(PER_CLASS) class MetadataReaderTest { - private val crypto = mockk<Crypto>() + private val cryptoService = mockk<CryptoService>() - private val encoder = MetadataWriterImpl(crypto) - private val decoder = MetadataReaderImpl(crypto) + private val encoder = MetadataWriterImpl(cryptoService) + private val decoder = MetadataReaderImpl(cryptoService) private val metadata = getMetadata() private val metadataByteArray = encoder.encode(metadata) diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataServiceTest.kt similarity index 85% rename from app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataServiceTest.kt index 521aac65..295e0295 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataServiceTest.kt @@ -7,19 +7,28 @@ import android.content.pm.ApplicationInfo.FLAG_ALLOW_BACKUP import android.content.pm.ApplicationInfo.FLAG_SYSTEM import android.content.pm.PackageInfo import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.stevesoltys.seedvault.Clock +import com.stevesoltys.seedvault.util.TimeSource import com.stevesoltys.seedvault.TestApp -import com.stevesoltys.seedvault.crypto.Crypto -import com.stevesoltys.seedvault.encodeBase64 +import com.stevesoltys.seedvault.service.crypto.CryptoService +import com.stevesoltys.seedvault.util.encodeBase64 import com.stevesoltys.seedvault.getRandomByteArray import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.metadata.METADATA_CACHE_FILE +import com.stevesoltys.seedvault.service.metadata.METADATA_SALT_SIZE +import com.stevesoltys.seedvault.service.metadata.MetadataReader +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.metadata.MetadataWriter +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageMetadataMap +import com.stevesoltys.seedvault.service.settings.SettingsService import io.mockk.Runs import io.mockk.every import io.mockk.just @@ -46,29 +55,29 @@ import kotlin.random.Random sdk = [33], // robolectric does not support 34, yet application = TestApp::class ) -class MetadataManagerTest { +class MetadataServiceTest { private val context: Context = mockk() - private val clock: Clock = mockk() - private val crypto: Crypto = mockk() + private val timeSource: TimeSource = mockk() + private val cryptoService: CryptoService = mockk() private val metadataWriter: MetadataWriter = mockk() private val metadataReader: MetadataReader = mockk() - private val settingsManager: SettingsManager = mockk() + private val settingsService: SettingsService = mockk() - private val manager = MetadataManager( + private val manager = MetadataService( context = context, - clock = clock, - crypto = crypto, + timeSource = timeSource, + cryptoService = cryptoService, metadataWriter = metadataWriter, metadataReader = metadataReader, - settingsManager = settingsManager + settingsService = settingsService ) private val time = 42L private val token = Random.nextLong() private val packageName = getRandomString() private val packageInfo = PackageInfo().apply { - packageName = this@MetadataManagerTest.packageName + packageName = this@MetadataServiceTest.packageName applicationInfo = ApplicationInfo().apply { flags = FLAG_ALLOW_BACKUP } } private val saltBytes = Random.nextBytes(METADATA_SALT_SIZE) @@ -81,7 +90,7 @@ class MetadataManagerTest { @Before fun beforeEachTest() { - every { settingsManager.d2dBackupsEnabled() } returns false + every { settingsService.d2dBackupsEnabled() } returns false } @After @@ -91,8 +100,8 @@ class MetadataManagerTest { @Test fun `test onDeviceInitialization()`() { - every { clock.time() } returns time - every { crypto.getRandomBytes(METADATA_SALT_SIZE) } returns saltBytes + every { timeSource.time() } returns time + every { cryptoService.getRandomBytes(METADATA_SALT_SIZE) } returns saltBytes expectReadFromCache() expectModifyMetadata(initialMetadata) @@ -251,7 +260,7 @@ class MetadataManagerTest { updatedMetadata.packageMetadataMap[packageName] = packageMetadata expectReadFromCache() - every { clock.time() } returns time + every { timeSource.time() } returns time expectModifyMetadata(initialMetadata) manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream) @@ -279,7 +288,7 @@ class MetadataManagerTest { PackageMetadata(updateTime, APK_AND_DATA, BackupType.KV) expectReadFromCache() - every { clock.time() } returns updateTime + every { timeSource.time() } returns updateTime every { metadataWriter.write(updatedMetadata, storageOutputStream) } throws IOException() try { @@ -313,7 +322,7 @@ class MetadataManagerTest { PackageMetadata(time, state = APK_AND_DATA) expectReadFromCache() - every { clock.time() } returns time + every { timeSource.time() } returns time expectModifyMetadata(updatedMetadata) manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream) diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt index d0e11aca..ee8bd43f 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataV0ReadTest.kt @@ -1,12 +1,15 @@ package com.stevesoltys.seedvault.metadata -import com.stevesoltys.seedvault.crypto.CipherFactoryImpl -import com.stevesoltys.seedvault.crypto.CryptoImpl -import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES +import com.stevesoltys.seedvault.service.crypto.CipherFactoryImpl +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl +import com.stevesoltys.seedvault.service.crypto.KEY_SIZE_BYTES import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl -import com.stevesoltys.seedvault.header.HeaderReaderImpl -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.MetadataReaderImpl +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED import com.stevesoltys.seedvault.toByteArrayFromHex import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -26,10 +29,10 @@ internal class MetadataV0ReadTest { ) private val keyManager = KeyManagerTestImpl(secretKey) private val cipherFactory = CipherFactoryImpl(keyManager) - private val headerReader = HeaderReaderImpl() - private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader) + private val headerReader = HeaderDecodeServiceImpl() + private val cryptoServiceImpl = CryptoServiceImpl(keyManager, cipherFactory, headerReader) - private val reader = MetadataReaderImpl(cryptoImpl) + private val reader = MetadataReaderImpl(cryptoServiceImpl) private val packages = HashMap<String, PackageMetadata>().apply { put("org.example", PackageMetadata(23L, APK_AND_DATA)) diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt index 4712551a..0e74de30 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt @@ -1,13 +1,19 @@ package com.stevesoltys.seedvault.metadata -import com.stevesoltys.seedvault.crypto.Crypto +import com.stevesoltys.seedvault.service.crypto.CryptoService import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.metadata.ApkSplit +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.MetadataReaderImpl +import com.stevesoltys.seedvault.service.metadata.MetadataWriterImpl +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState.APK_AND_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED import io.mockk.mockk import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -18,10 +24,10 @@ import kotlin.random.Random @TestInstance(PER_CLASS) internal class MetadataWriterDecoderTest { - private val crypto = mockk<Crypto>() + private val cryptoService = mockk<CryptoService>() - private val encoder = MetadataWriterImpl(crypto) - private val decoder = MetadataReaderImpl(crypto) + private val encoder = MetadataWriterImpl(cryptoService) + private val decoder = MetadataReaderImpl(cryptoService) @Test fun `encoded metadata matches decoded metadata (no packages)`() { diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt index 3fd6491b..7dc1e6ea 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt @@ -7,6 +7,7 @@ import android.provider.DocumentsContract import androidx.documentfile.provider.DocumentFile import androidx.test.ext.junit.runners.AndroidJUnit4 import com.stevesoltys.seedvault.TestApp +import com.stevesoltys.seedvault.service.storage.saf.getTreeDocumentFile import io.mockk.every import io.mockk.mockk import org.junit.After diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/StoragePluginTest.kt b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/StoragePluginTest.kt index d06004fd..0b3a23a0 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/StoragePluginTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/StoragePluginTest.kt @@ -1,6 +1,9 @@ package com.stevesoltys.seedvault.plugins.saf import androidx.documentfile.provider.DocumentFile +import com.stevesoltys.seedvault.service.storage.saf.DocumentsProviderStoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.DocumentsStorage +import com.stevesoltys.seedvault.service.storage.saf.listFilesBlocking import com.stevesoltys.seedvault.transport.backup.BackupTest import io.mockk.Runs import io.mockk.coEvery @@ -38,7 +41,7 @@ internal class StoragePluginTest : BackupTest() { fun `test initializeDevice`() = runBlocking { // get current set dir and for that the current token every { storage getProperty "currentToken" } returns token - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token every { storage getProperty "storage" } returns null // just to check if isUsb coEvery { storage.getSetDir(token) } returns setDir // delete contents of current set dir diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupServiceRestoreTest.kt similarity index 77% rename from app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupServiceRestoreTest.kt index f8805c78..4ec66a24 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupServiceRestoreTest.kt @@ -7,15 +7,21 @@ import android.graphics.drawable.Drawable import android.util.PackageUtils import com.stevesoltys.seedvault.assertReadEquals import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.ApkSplit -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.restore.RestorableBackup +import com.stevesoltys.seedvault.service.app.backup.apk.ApkBackupService +import com.stevesoltys.seedvault.service.metadata.ApkSplit +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageMetadataMap +import com.stevesoltys.seedvault.service.metadata.PackageState +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin import com.stevesoltys.seedvault.transport.TransportTest -import com.stevesoltys.seedvault.transport.backup.ApkBackup +import com.stevesoltys.seedvault.ui.restore.RestorableBackup +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallResult +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstaller +import com.stevesoltys.seedvault.ui.restore.apk.ApkRestore +import com.stevesoltys.seedvault.ui.restore.apk.ApkSplitCompatibilityChecker +import com.stevesoltys.seedvault.ui.restore.apk.MutableInstallResult import io.mockk.coEvery import io.mockk.every import io.mockk.mockk @@ -40,7 +46,7 @@ import kotlin.random.Random @ExperimentalCoroutinesApi @Suppress("BlockingMethodInNonBlockingContext") -internal class ApkBackupRestoreTest : TransportTest() { +internal class ApkBackupServiceRestoreTest : TransportTest() { private val pm: PackageManager = mockk() private val strictContext: Context = mockk<Context>().apply { @@ -53,12 +59,13 @@ internal class ApkBackupRestoreTest : TransportTest() { private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk() private val apkInstaller: ApkInstaller = mockk() - private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager) + private val apkBackupService = + ApkBackupService(pm, cryptoService, settingsService, metadataService) private val apkRestore: ApkRestore = ApkRestore( context = strictContext, storagePlugin = storagePlugin, legacyStoragePlugin = legacyStoragePlugin, - crypto = crypto, + cryptoService = cryptoService, splitCompatChecker = splitCompatChecker, apkInstaller = apkInstaller ) @@ -107,20 +114,24 @@ internal class ApkBackupRestoreTest : TransportTest() { writeBytes(splitBytes) }.absolutePath) - every { settingsManager.backupApks() } returns true + every { settingsService.backupApks() } returns true every { sigInfo.hasMultipleSigners() } returns false every { sigInfo.signingCertificateHistory } returns sigs every { PackageUtils.computeSha256DigestBytes(signatureBytes) } returns signatureHash every { - metadataManager.getPackageMetadata(packageInfo.packageName) + metadataService.getPackageMetadata(packageInfo.packageName) } returns packageMetadata every { pm.getInstallSourceInfo(packageInfo.packageName) } returns mockk(relaxed = true) - every { metadataManager.salt } returns salt - every { crypto.getNameForApk(salt, packageName) } returns name - every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName + every { metadataService.salt } returns salt + every { cryptoService.getNameForApk(salt, packageName) } returns name + every { cryptoService.getNameForApk(salt, packageName, splitName) } returns suffixName every { storagePlugin.providerPackageName } returns storageProviderPackageName - apkBackup.backupApkIfNecessary(packageInfo, PackageState.APK_AND_DATA, outputStreamGetter) + apkBackupService.backupApkIfNecessary( + packageInfo, + PackageState.APK_AND_DATA, + outputStreamGetter + ) assertArrayEquals(apkBytes, outputStream.toByteArray()) assertArrayEquals(splitBytes, splitOutputStream.toByteArray()) @@ -131,7 +142,7 @@ internal class ApkBackupRestoreTest : TransportTest() { val cacheFiles = slot<List<File>>() every { strictContext.cacheDir } returns tmpFile - every { crypto.getNameForApk(salt, packageName, "") } returns name + every { cryptoService.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns inputStream every { pm.getPackageArchiveInfo(capture(apkPath), any<Int>()) } returns packageInfo every { applicationInfo.loadIcon(pm) } returns icon @@ -139,7 +150,7 @@ internal class ApkBackupRestoreTest : TransportTest() { every { splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName)) } returns true - every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName + every { cryptoService.getNameForApk(salt, packageName, splitName) } returns suffixName coEvery { storagePlugin.getInputStream(token, suffixName) } returns splitInputStream coEvery { apkInstaller.install(capture(cacheFiles), packageName, installerName, any()) diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt index 6eb1e969..4504be43 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt @@ -10,18 +10,24 @@ import android.graphics.drawable.Drawable import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomByteArray import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.ApkSplit -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.restore.RestorableBackup -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP -import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS -import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED -import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.metadata.ApkSplit +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageMetadataMap import com.stevesoltys.seedvault.transport.TransportTest +import com.stevesoltys.seedvault.ui.restore.RestorableBackup +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallResult +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.FAILED_SYSTEM_APP +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.IN_PROGRESS +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.QUEUED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstallState.SUCCEEDED +import com.stevesoltys.seedvault.ui.restore.apk.ApkInstaller +import com.stevesoltys.seedvault.ui.restore.apk.ApkRestore +import com.stevesoltys.seedvault.ui.restore.apk.ApkSplitCompatibilityChecker +import com.stevesoltys.seedvault.ui.restore.apk.InstallResult +import com.stevesoltys.seedvault.ui.restore.apk.MutableInstallResult import io.mockk.coEvery import io.mockk.every import io.mockk.mockk @@ -58,7 +64,7 @@ internal class ApkRestoreTest : TransportTest() { strictContext, storagePlugin, legacyStoragePlugin, - crypto, + cryptoService, splitCompatChecker, apkInstaller ) @@ -94,7 +100,7 @@ internal class ApkRestoreTest : TransportTest() { val backup = swapPackages(hashMapOf(packageName to packageMetadata)) every { strictContext.cacheDir } returns File(tmpDir.toString()) - every { crypto.getNameForApk(salt, packageName, "") } returns name + every { cryptoService.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream every { storagePlugin.providerPackageName } returns storageProviderPackageName @@ -109,7 +115,7 @@ internal class ApkRestoreTest : TransportTest() { packageInfo.packageName = getRandomString() every { strictContext.cacheDir } returns File(tmpDir.toString()) - every { crypto.getNameForApk(salt, packageName, "") } returns name + every { cryptoService.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo every { storagePlugin.providerPackageName } returns storageProviderPackageName @@ -297,7 +303,7 @@ internal class ApkRestoreTest : TransportTest() { cacheBaseApkAndGetInfo(tmpDir) every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true - every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName + every { cryptoService.getNameForApk(salt, packageName, splitName) } returns suffixName coEvery { storagePlugin.getInputStream(token, suffixName) } returns ByteArrayInputStream(getRandomByteArray()) @@ -322,7 +328,7 @@ internal class ApkRestoreTest : TransportTest() { cacheBaseApkAndGetInfo(tmpDir) every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true - every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName + every { cryptoService.getNameForApk(salt, packageName, splitName) } returns suffixName coEvery { storagePlugin.getInputStream(token, suffixName) } throws IOException() every { storagePlugin.providerPackageName } returns storageProviderPackageName @@ -359,9 +365,9 @@ internal class ApkRestoreTest : TransportTest() { val split2InputStream = ByteArrayInputStream(split2Bytes) val suffixName1 = getRandomString() val suffixName2 = getRandomString() - every { crypto.getNameForApk(salt, packageName, split1Name) } returns suffixName1 + every { cryptoService.getNameForApk(salt, packageName, split1Name) } returns suffixName1 coEvery { storagePlugin.getInputStream(token, suffixName1) } returns split1InputStream - every { crypto.getNameForApk(salt, packageName, split2Name) } returns suffixName2 + every { cryptoService.getNameForApk(salt, packageName, split2Name) } returns suffixName2 coEvery { storagePlugin.getInputStream(token, suffixName2) } returns split2InputStream every { storagePlugin.providerPackageName } returns storageProviderPackageName @@ -410,7 +416,7 @@ internal class ApkRestoreTest : TransportTest() { private fun cacheBaseApkAndGetInfo(tmpDir: Path) { every { strictContext.cacheDir } returns File(tmpDir.toString()) - every { crypto.getNameForApk(salt, packageName, "") } returns name + every { cryptoService.getNameForApk(salt, packageName, "") } returns name coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo every { applicationInfo.loadIcon(pm) } returns icon diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityCheckerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityCheckerTest.kt index 6d10afb8..1a3cd74a 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityCheckerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkSplitCompatibilityCheckerTest.kt @@ -2,6 +2,8 @@ package com.stevesoltys.seedvault.restore.install import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.transport.TransportTest +import com.stevesoltys.seedvault.ui.restore.apk.ApkSplitCompatibilityChecker +import com.stevesoltys.seedvault.ui.restore.apk.DeviceInfo import io.mockk.every import io.mockk.mockk import org.junit.Assert.assertEquals diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt index 2ee5f7c2..baab1ea0 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/DeviceInfoTest.kt @@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.TestApp import com.stevesoltys.seedvault.getRandomString +import com.stevesoltys.seedvault.ui.restore.apk.DeviceInfo import io.mockk.every import io.mockk.mockk import org.junit.After 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 824230e8..574218f8 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt @@ -7,29 +7,29 @@ import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.RestoreDescription import android.app.backup.RestoreDescription.TYPE_FULL_STREAM import android.os.ParcelFileDescriptor -import com.stevesoltys.seedvault.crypto.CipherFactoryImpl -import com.stevesoltys.seedvault.crypto.CryptoImpl +import com.stevesoltys.seedvault.service.crypto.CipherFactoryImpl +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl -import com.stevesoltys.seedvault.header.HeaderReaderImpl -import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH -import com.stevesoltys.seedvault.metadata.BackupType -import com.stevesoltys.seedvault.metadata.MetadataReaderImpl -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA -import com.stevesoltys.seedvault.transport.backup.ApkBackup -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.PackageService +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.header.MAX_SEGMENT_CLEARTEXT_LENGTH +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.MetadataReaderImpl +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.FILE_BACKUP_METADATA +import com.stevesoltys.seedvault.service.app.backup.apk.ApkBackupService +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.app.backup.InputFactory +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService +import com.stevesoltys.seedvault.service.app.PackageService import com.stevesoltys.seedvault.transport.backup.TestKvDbManager -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.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore +import com.stevesoltys.seedvault.service.app.restore.OutputFactory +import com.stevesoltys.seedvault.service.app.restore.coordinator.RestoreCoordinator import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.CapturingSlot import io.mockk.Runs @@ -54,30 +54,31 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val outputFactory = mockk<OutputFactory>() private val keyManager = KeyManagerTestImpl() private val cipherFactory = CipherFactoryImpl(keyManager) - private val headerReader = HeaderReaderImpl() - private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader) - private val metadataReader = MetadataReaderImpl(cryptoImpl) + private val headerReader = HeaderDecodeServiceImpl() + private val cryptoServiceImpl = CryptoServiceImpl(keyManager, cipherFactory, headerReader) + private val metadataReader = MetadataReaderImpl(cryptoServiceImpl) private val notificationManager = mockk<BackupNotificationManager>() private val dbManager = TestKvDbManager() @Suppress("Deprecation") private val legacyPlugin = mockk<LegacyStoragePlugin>() private val backupPlugin = mockk<StoragePlugin>() - private val kvBackup = - KVBackup(backupPlugin, settingsManager, inputFactory, cryptoImpl, dbManager) - private val fullBackup = FullBackup(backupPlugin, settingsManager, inputFactory, cryptoImpl) - private val apkBackup = mockk<ApkBackup>() + private val kvBackupService = + KVBackupService(backupPlugin, settingsService, inputFactory, cryptoServiceImpl, dbManager) + private val fullBackupService = + FullBackupService(backupPlugin, settingsService, inputFactory, cryptoServiceImpl) + private val apkBackupService = mockk<ApkBackupService>() private val packageService: PackageService = mockk() - private val backup = BackupCoordinator( + private val backup = BackupCoordinatorService( context, backupPlugin, - kvBackup, - fullBackup, - apkBackup, - clock, + kvBackupService, + fullBackupService, + apkBackupService, + timeSource, packageService, - metadataManager, - settingsManager, + metadataService, + settingsService, notificationManager ) @@ -86,16 +87,16 @@ internal class CoordinatorIntegrationTest : TransportTest() { legacyPlugin, outputFactory, headerReader, - cryptoImpl, + cryptoServiceImpl, dbManager ) private val fullRestore = - FullRestore(backupPlugin, legacyPlugin, outputFactory, headerReader, cryptoImpl) + FullRestore(backupPlugin, legacyPlugin, outputFactory, headerReader, cryptoServiceImpl) private val restore = RestoreCoordinator( context, - crypto, - settingsManager, - metadataManager, + cryptoService, + settingsService, + metadataService, notificationManager, backupPlugin, kvRestore, @@ -113,7 +114,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val key2 = "RestoreKey2" // as we use real crypto, we need a real name for packageInfo - private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName) + private val realName = cryptoServiceImpl.getNameForPackage(salt, packageInfo.packageName) @Test fun `test key-value backup and restore with 2 records`() = runBlocking { @@ -121,9 +122,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { val value2 = CapturingSlot<ByteArray>() val bOutputStream = ByteArrayOutputStream() - every { metadataManager.requiresInit } returns false - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { metadataService.requiresInit } returns false + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt // read one key/value record and write it to output stream every { inputFactory.getBackupDataInput(fileDescriptor) } returns backupDataInput every { backupDataInput.readNextHeader() } returns true andThen true andThen false @@ -138,16 +139,16 @@ internal class CoordinatorIntegrationTest : TransportTest() { appData2.size } coEvery { - apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) + apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata coEvery { backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream) + metadataService.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream) } just Runs every { - metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream) + metadataService.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream) } just Runs // start K/V backup @@ -164,7 +165,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) // find data for K/V backup - every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name + every { + cryptoService.getNameForPackage(metadata.salt, packageInfo.packageName) + } returns name coEvery { backupPlugin.hasData(token, name) } returns true val restoreDescription = restore.nextRestorePackage() ?: fail() @@ -198,9 +201,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { val appData = ByteArray(size).apply { Random.nextBytes(this) } val bOutputStream = ByteArrayOutputStream() - every { metadataManager.requiresInit } returns false - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { metadataService.requiresInit } returns false + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt // read one key/value record and write it to output stream every { inputFactory.getBackupDataInput(fileDescriptor) } returns backupDataInput every { backupDataInput.readNextHeader() } returns true andThen false @@ -210,13 +213,19 @@ internal class CoordinatorIntegrationTest : TransportTest() { appData.copyInto(value.captured) // write the app data into the passed ByteArray appData.size } - coEvery { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns null - every { settingsManager.getToken() } returns token + coEvery { + apkBackupService.backupApkIfNecessary( + packageInfo, + UNKNOWN_ERROR, + any() + ) + } returns null + every { settingsService.getToken() } returns token coEvery { backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream) + metadataService.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream) } just Runs // start K/V backup @@ -233,7 +242,12 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) // find data for K/V backup - every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name + every { + cryptoService.getNameForPackage( + metadata.salt, + packageInfo.packageName + ) + } returns name coEvery { backupPlugin.hasData(token, name) } returns true val restoreDescription = restore.nextRestorePackage() ?: fail() @@ -268,28 +282,28 @@ internal class CoordinatorIntegrationTest : TransportTest() { val bInputStream = ByteArrayInputStream(appData) coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false coEvery { - apkBackup.backupApkIfNecessary( + apkBackupService.backupApkIfNecessary( packageInfo, UNKNOWN_ERROR, any() ) } returns packageMetadata - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onApkBackedUp( + metadataService.onApkBackedUp( packageInfo, packageMetadata, metadataOutputStream ) } just Runs every { - metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream) + metadataService.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream) } just Runs // perform backup to output stream @@ -303,7 +317,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) // finds data for full backup - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { backupPlugin.hasData(token, name) } returns true val restoreDescription = restore.nextRestorePackage() ?: fail() diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt index 0af1caa2..50d97e1c 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt @@ -7,18 +7,18 @@ import android.content.pm.ApplicationInfo.FLAG_INSTALLED import android.content.pm.PackageInfo import android.content.pm.SigningInfo import android.util.Log -import com.stevesoltys.seedvault.Clock +import com.stevesoltys.seedvault.util.TimeSource import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.crypto.Crypto +import com.stevesoltys.seedvault.service.crypto.CryptoService import com.stevesoltys.seedvault.getRandomBase64 import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.BackupMetadata -import com.stevesoltys.seedvault.metadata.BackupType -import com.stevesoltys.seedvault.metadata.METADATA_SALT_SIZE -import com.stevesoltys.seedvault.metadata.MetadataManager -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.service.metadata.BackupMetadata +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.METADATA_SALT_SIZE +import com.stevesoltys.seedvault.service.metadata.MetadataService +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageMetadataMap +import com.stevesoltys.seedvault.service.settings.SettingsService import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -29,10 +29,10 @@ import kotlin.random.Random @TestInstance(PER_METHOD) internal abstract class TransportTest { - protected val clock: Clock = mockk() - protected val crypto = mockk<Crypto>() - protected val settingsManager = mockk<SettingsManager>() - protected val metadataManager = mockk<MetadataManager>() + protected val timeSource: TimeSource = mockk() + protected val cryptoService = mockk<CryptoService>() + protected val settingsService = mockk<SettingsService>() + protected val metadataService = mockk<MetadataService>() protected val context = mockk<Context>(relaxed = true) protected val sigInfo: SigningInfo = mockk() diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupServiceTest.kt similarity index 76% rename from app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupServiceTest.kt index 2137e565..3f1ede35 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupServiceTest.kt @@ -10,9 +10,10 @@ import android.content.pm.Signature import android.util.PackageUtils import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.ApkSplit -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.ApkSplit +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.app.backup.apk.ApkBackupService import io.mockk.coEvery import io.mockk.every import io.mockk.mockk @@ -33,12 +34,13 @@ import java.nio.file.Path import kotlin.random.Random @Suppress("BlockingMethodInNonBlockingContext") -internal class ApkBackupTest : BackupTest() { +internal class ApkBackupServiceTest : BackupTest() { private val pm: PackageManager = mockk() private val streamGetter: suspend (name: String) -> OutputStream = mockk() - private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager) + private val apkBackupService = + ApkBackupService(pm, cryptoService, settingsService, metadataService) private val signatureBytes = byteArrayOf(0x01, 0x02, 0x03) private val signatureHash = byteArrayOf(0x03, 0x02, 0x01) @@ -56,32 +58,32 @@ internal class ApkBackupTest : BackupTest() { @Test fun `does not back up @pm@`() = runBlocking { val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER } - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull(apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) } @Test fun `does not back up when setting disabled`() = runBlocking { - every { settingsManager.backupApks() } returns false + every { settingsService.backupApks() } returns false - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull(apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) } @Test fun `does not back up test-only apps`() = runBlocking { packageInfo.applicationInfo.flags = FLAG_TEST_ONLY - every { settingsManager.backupApks() } returns true + every { settingsService.backupApks() } returns true - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull(apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) } @Test fun `does not back up system apps`() = runBlocking { packageInfo.applicationInfo.flags = FLAG_SYSTEM - every { settingsManager.backupApks() } returns true + every { settingsService.backupApks() } returns true - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull(apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) } @Test @@ -93,7 +95,7 @@ internal class ApkBackupTest : BackupTest() { expectChecks(packageMetadata) - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull(apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) } @Test @@ -104,21 +106,27 @@ internal class ApkBackupTest : BackupTest() { assertThrows(IOException::class.java) { runBlocking { - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull( + apkBackupService.backupApkIfNecessary( + packageInfo, + UNKNOWN_ERROR, + streamGetter + ) + ) } } } @Test fun `do not accept empty signature`() = runBlocking { - every { settingsManager.backupApks() } returns true + every { settingsService.backupApks() } returns true every { - metadataManager.getPackageMetadata(packageInfo.packageName) + metadataService.getPackageMetadata(packageInfo.packageName) } returns packageMetadata every { sigInfo.hasMultipleSigners() } returns false every { sigInfo.signingCertificateHistory } returns emptyArray() - assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) + assertNull(apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter)) } @Test @@ -140,8 +148,8 @@ internal class ApkBackupTest : BackupTest() { ) expectChecks() - every { metadataManager.salt } returns salt - every { crypto.getNameForApk(salt, packageInfo.packageName) } returns name + every { metadataService.salt } returns salt + every { cryptoService.getNameForApk(salt, packageInfo.packageName) } returns name coEvery { streamGetter.invoke(name) } returns apkOutputStream every { pm.getInstallSourceInfo(packageInfo.packageName) @@ -149,7 +157,7 @@ internal class ApkBackupTest : BackupTest() { assertEquals( updatedMetadata, - apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter) + apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter) ) assertArrayEquals(apkBytes, apkOutputStream.toByteArray()) } @@ -203,13 +211,13 @@ internal class ApkBackupTest : BackupTest() { val suffixName2 = getRandomString() expectChecks() - every { metadataManager.salt } returns salt - every { crypto.getNameForApk(salt, packageInfo.packageName) } returns name + every { metadataService.salt } returns salt + every { cryptoService.getNameForApk(salt, packageInfo.packageName) } returns name every { - crypto.getNameForApk(salt, packageInfo.packageName, split1Name) + cryptoService.getNameForApk(salt, packageInfo.packageName, split1Name) } returns suffixName1 every { - crypto.getNameForApk(salt, packageInfo.packageName, split2Name) + cryptoService.getNameForApk(salt, packageInfo.packageName, split2Name) } returns suffixName2 coEvery { streamGetter.invoke(name) } returns apkOutputStream coEvery { streamGetter.invoke(suffixName1) } returns split1OutputStream @@ -221,7 +229,7 @@ internal class ApkBackupTest : BackupTest() { assertEquals( updatedMetadata, - apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter) + apkBackupService.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter) ) assertArrayEquals(apkBytes, apkOutputStream.toByteArray()) assertArrayEquals(split1Bytes, split1OutputStream.toByteArray()) @@ -229,9 +237,9 @@ internal class ApkBackupTest : BackupTest() { } private fun expectChecks(packageMetadata: PackageMetadata = this.packageMetadata) { - every { settingsManager.backupApks() } returns true + every { settingsService.backupApks() } returns true every { - metadataManager.getPackageMetadata(packageInfo.packageName) + metadataService.getPackageMetadata(packageInfo.packageName) } returns packageMetadata every { PackageUtils.computeSha256DigestBytes(signatureBytes) } returns signatureHash every { sigInfo.hasMultipleSigners() } returns false diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorServiceTest.kt similarity index 74% rename from app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorServiceTest.kt index bb9de194..96cb731d 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorServiceTest.kt @@ -12,16 +12,22 @@ import android.os.ParcelFileDescriptor import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.coAssertThrows import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.metadata.BackupType -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED -import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA -import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA -import com.stevesoltys.seedvault.settings.Storage +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.metadata.PackageState.NOT_ALLOWED +import com.stevesoltys.seedvault.service.metadata.PackageState.NO_DATA +import com.stevesoltys.seedvault.service.metadata.PackageState.QUOTA_EXCEEDED +import com.stevesoltys.seedvault.service.metadata.PackageState.UNKNOWN_ERROR +import com.stevesoltys.seedvault.service.metadata.PackageState.WAS_STOPPED +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.FILE_BACKUP_METADATA +import com.stevesoltys.seedvault.service.app.PackageService +import com.stevesoltys.seedvault.service.app.backup.coordinator.BackupCoordinatorService +import com.stevesoltys.seedvault.service.settings.Storage +import com.stevesoltys.seedvault.service.app.backup.apk.ApkBackupService +import com.stevesoltys.seedvault.service.app.backup.full.DEFAULT_QUOTA_FULL_BACKUP +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.Runs import io.mockk.coEvery @@ -38,25 +44,25 @@ import java.io.OutputStream import kotlin.random.Random @Suppress("BlockingMethodInNonBlockingContext") -internal class BackupCoordinatorTest : BackupTest() { +internal class BackupCoordinatorServiceTest : BackupTest() { private val plugin = mockk<StoragePlugin>() - private val kv = mockk<KVBackup>() - private val full = mockk<FullBackup>() - private val apkBackup = mockk<ApkBackup>() + private val kv = mockk<KVBackupService>() + private val full = mockk<FullBackupService>() + private val apkBackupService = mockk<ApkBackupService>() private val packageService: PackageService = mockk() private val notificationManager = mockk<BackupNotificationManager>() - private val backup = BackupCoordinator( + private val backup = BackupCoordinatorService( context, plugin, kv, full, - apkBackup, - clock, + apkBackupService, + timeSource, packageService, - metadataManager, - settingsManager, + metadataService, + settingsService, notificationManager ) @@ -75,7 +81,7 @@ internal class BackupCoordinatorTest : BackupTest() { expectStartNewRestoreSet() coEvery { plugin.initializeDevice() } just Runs coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream - every { metadataManager.onDeviceInitialization(token, metadataOutputStream) } just Runs + every { metadataService.onDeviceInitialization(token, metadataOutputStream) } just Runs every { kv.hasState() } returns false every { full.hasState() } returns false every { metadataOutputStream.close() } just Runs @@ -87,8 +93,8 @@ internal class BackupCoordinatorTest : BackupTest() { } private suspend fun expectStartNewRestoreSet() { - every { clock.time() } returns token - every { settingsManager.setNewToken(token) } just Runs + every { timeSource.time() } returns token + every { settingsService.setNewToken(token) } just Runs coEvery { plugin.startNewRestoreSet(token) } just Runs } @@ -98,8 +104,8 @@ internal class BackupCoordinatorTest : BackupTest() { expectStartNewRestoreSet() coEvery { plugin.initializeDevice() } throws IOException() - every { metadataManager.requiresInit } returns maybeTrue - every { settingsManager.canDoBackupNow() } returns !maybeTrue + every { metadataService.requiresInit } returns maybeTrue + every { settingsService.canDoBackupNow() } returns !maybeTrue every { notificationManager.onBackupError() } just Runs assertEquals(TRANSPORT_ERROR, backup.initializeDevice()) @@ -117,8 +123,8 @@ internal class BackupCoordinatorTest : BackupTest() { runBlocking { expectStartNewRestoreSet() coEvery { plugin.initializeDevice() } throws IOException() - every { metadataManager.requiresInit } returns false - every { settingsManager.canDoBackupNow() } returns false + every { metadataService.requiresInit } returns false + every { settingsService.canDoBackupNow() } returns false assertEquals(TRANSPORT_ERROR, backup.initializeDevice()) @@ -134,12 +140,12 @@ internal class BackupCoordinatorTest : BackupTest() { fun `performIncrementalBackup of @pm@ causes re-init when legacy format`() = runBlocking { val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER } - every { settingsManager.canDoBackupNow() } returns true - every { metadataManager.requiresInit } returns true + every { settingsService.canDoBackupNow() } returns true + every { metadataService.requiresInit } returns true // start new restore set - every { clock.time() } returns token + 1 - every { settingsManager.setNewToken(token + 1) } just Runs + every { timeSource.time() } returns token + 1 + every { settingsService.setNewToken(token + 1) } just Runs coEvery { plugin.startNewRestoreSet(token + 1) } just Runs every { data.close() } just Runs @@ -170,8 +176,8 @@ internal class BackupCoordinatorTest : BackupTest() { @Test fun `clearing KV backup data throws`() = runBlocking { - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { kv.clearBackupData(packageInfo, token, salt) } throws IOException() assertEquals(TRANSPORT_ERROR, backup.clearBackupData(packageInfo)) @@ -179,8 +185,8 @@ internal class BackupCoordinatorTest : BackupTest() { @Test fun `clearing full backup data throws`() = runBlocking { - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { kv.clearBackupData(packageInfo, token, salt) } just Runs coEvery { full.clearBackupData(packageInfo, token, salt) } throws IOException() @@ -189,8 +195,8 @@ internal class BackupCoordinatorTest : BackupTest() { @Test fun `clearing backup data succeeds`() = runBlocking { - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { kv.clearBackupData(packageInfo, token, salt) } just Runs coEvery { full.clearBackupData(packageInfo, token, salt) } just Runs @@ -208,10 +214,10 @@ internal class BackupCoordinatorTest : BackupTest() { every { full.hasState() } returns false every { kv.getCurrentPackage() } returns packageInfo coEvery { kv.finishBackup() } returns TRANSPORT_OK - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream) + metadataService.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream) } just Runs every { metadataOutputStream.close() } just Runs @@ -227,7 +233,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { kv.getCurrentPackage() } returns pmPackageInfo coEvery { kv.finishBackup() } returns TRANSPORT_OK - every { settingsManager.canDoBackupNow() } returns false + every { settingsService.canDoBackupNow() } returns false assertEquals(TRANSPORT_OK, backup.finishBackup()) } @@ -240,10 +246,10 @@ internal class BackupCoordinatorTest : BackupTest() { every { full.hasState() } returns true every { full.getCurrentPackage() } returns packageInfo every { full.finishBackup() } returns result - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream) + metadataService.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream) } just Runs every { metadataOutputStream.close() } just Runs @@ -254,20 +260,26 @@ internal class BackupCoordinatorTest : BackupTest() { @Test fun `metadata does not get updated when no APK was backed up`() = runBlocking { - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { full.performFullBackup(packageInfo, fileDescriptor, 0, token, salt) } returns TRANSPORT_OK - coEvery { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns null + coEvery { + apkBackupService.backupApkIfNecessary( + packageInfo, + UNKNOWN_ERROR, + any() + ) + } returns null assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0)) } @Test fun `app exceeding quota gets cancelled and reason written to metadata`() = runBlocking { - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { full.performFullBackup(packageInfo, fileDescriptor, 0, token, salt) } returns TRANSPORT_OK @@ -278,7 +290,7 @@ internal class BackupCoordinatorTest : BackupTest() { } returns TRANSPORT_QUOTA_EXCEEDED every { full.getCurrentPackage() } returns packageInfo every { - metadataManager.onPackageBackupError( + metadataService.onPackageBackupError( packageInfo, QUOTA_EXCEEDED, metadataOutputStream, @@ -286,7 +298,7 @@ internal class BackupCoordinatorTest : BackupTest() { ) } just Runs coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs - every { settingsManager.getStorage() } returns storage + every { settingsService.getStorage() } returns storage every { metadataOutputStream.close() } just Runs assertEquals( @@ -305,7 +317,7 @@ internal class BackupCoordinatorTest : BackupTest() { assertEquals(0L, backup.requestFullBackupTime()) verify(exactly = 1) { - metadataManager.onPackageBackupError( + metadataService.onPackageBackupError( packageInfo, QUOTA_EXCEEDED, metadataOutputStream, @@ -317,8 +329,8 @@ internal class BackupCoordinatorTest : BackupTest() { @Test fun `app with no data gets cancelled and reason written to metadata`() = runBlocking { - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt coEvery { full.performFullBackup(packageInfo, fileDescriptor, 0, token, salt) } returns TRANSPORT_OK @@ -327,7 +339,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { full.checkFullBackupSize(0) } returns TRANSPORT_PACKAGE_REJECTED every { full.getCurrentPackage() } returns packageInfo every { - metadataManager.onPackageBackupError( + metadataService.onPackageBackupError( packageInfo, NO_DATA, metadataOutputStream, @@ -335,7 +347,7 @@ internal class BackupCoordinatorTest : BackupTest() { ) } just Runs coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs - every { settingsManager.getStorage() } returns storage + every { settingsService.getStorage() } returns storage every { metadataOutputStream.close() } just Runs assertEquals( @@ -351,7 +363,7 @@ internal class BackupCoordinatorTest : BackupTest() { assertEquals(0L, backup.requestFullBackupTime()) verify(exactly = 1) { - metadataManager.onPackageBackupError( + metadataService.onPackageBackupError( packageInfo, NO_DATA, metadataOutputStream, @@ -376,10 +388,10 @@ internal class BackupCoordinatorTest : BackupTest() { ) val packageMetadata: PackageMetadata = mockk() - every { settingsManager.canDoBackupNow() } returns true - every { metadataManager.requiresInit } returns false - every { settingsManager.getToken() } returns token - every { metadataManager.salt } returns salt + every { settingsService.canDoBackupNow() } returns true + every { metadataService.requiresInit } returns false + every { settingsService.getToken() } returns token + every { metadataService.salt } returns salt // do actual @pm@ backup coEvery { kv.performBackup(packageInfo, fileDescriptor, 0, token, salt) @@ -395,7 +407,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { full.hasState() } returns false every { kv.getCurrentPackage() } returns pmPackageInfo every { - metadataManager.onPackageBackedUp(pmPackageInfo, BackupType.KV, metadataOutputStream) + metadataService.onPackageBackedUp(pmPackageInfo, BackupType.KV, metadataOutputStream) } just Runs coEvery { kv.finishBackup() } returns TRANSPORT_OK @@ -411,11 +423,11 @@ internal class BackupCoordinatorTest : BackupTest() { } just Runs // no backup needed coEvery { - apkBackup.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any()) + apkBackupService.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any()) } returns null // check old metadata for state changes, because we won't update it otherwise every { - metadataManager.getPackageMetadata(notAllowedPackages[0].packageName) + metadataService.getPackageMetadata(notAllowedPackages[0].packageName) } returns packageMetadata every { packageMetadata.state } returns NOT_ALLOWED // no change @@ -429,12 +441,12 @@ internal class BackupCoordinatorTest : BackupTest() { } just Runs // was backed up, get new packageMetadata coEvery { - apkBackup.backupApkIfNecessary(notAllowedPackages[1], WAS_STOPPED, any()) + apkBackupService.backupApkIfNecessary(notAllowedPackages[1], WAS_STOPPED, any()) } returns packageMetadata - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onApkBackedUp( + metadataService.onApkBackedUp( notAllowedPackages[1], packageMetadata, metadataOutputStream @@ -445,8 +457,8 @@ internal class BackupCoordinatorTest : BackupTest() { assertEquals(TRANSPORT_OK, backup.finishBackup()) coVerify { - apkBackup.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any()) - apkBackup.backupApkIfNecessary(notAllowedPackages[1], WAS_STOPPED, any()) + apkBackupService.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any()) + apkBackupService.backupApkIfNecessary(notAllowedPackages[1], WAS_STOPPED, any()) metadataOutputStream.close() } } @@ -459,16 +471,22 @@ internal class BackupCoordinatorTest : BackupTest() { every { notificationManager.onOptOutAppBackup(packageInfo.packageName, 1, 1) } just Runs - coEvery { apkBackup.backupApkIfNecessary(packageInfo, NOT_ALLOWED, any()) } returns null + coEvery { + apkBackupService.backupApkIfNecessary( + packageInfo, + NOT_ALLOWED, + any() + ) + } returns null every { - metadataManager.getPackageMetadata(packageInfo.packageName) + metadataService.getPackageMetadata(packageInfo.packageName) } returns oldPackageMetadata // state differs now, was stopped before every { oldPackageMetadata.state } returns WAS_STOPPED - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onPackageBackupError( + metadataService.onPackageBackupError( packageInfo, NOT_ALLOWED, metadataOutputStream @@ -479,7 +497,7 @@ internal class BackupCoordinatorTest : BackupTest() { backup.backUpApksOfNotBackedUpPackages() verify { - metadataManager.onPackageBackupError(packageInfo, NOT_ALLOWED, metadataOutputStream) + metadataService.onPackageBackupError(packageInfo, NOT_ALLOWED, metadataOutputStream) metadataOutputStream.close() } } @@ -490,14 +508,20 @@ internal class BackupCoordinatorTest : BackupTest() { every { notificationManager.onOptOutAppBackup(packageInfo.packageName, 1, 1) } just Runs - coEvery { apkBackup.backupApkIfNecessary(packageInfo, NOT_ALLOWED, any()) } returns null - every { - metadataManager.getPackageMetadata(packageInfo.packageName) + coEvery { + apkBackupService.backupApkIfNecessary( + packageInfo, + NOT_ALLOWED, + any() + ) } returns null - every { settingsManager.getToken() } returns token + every { + metadataService.getPackageMetadata(packageInfo.packageName) + } returns null + every { settingsService.getToken() } returns token coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onPackageBackupError( + metadataService.onPackageBackupError( packageInfo, NOT_ALLOWED, metadataOutputStream @@ -508,23 +532,23 @@ internal class BackupCoordinatorTest : BackupTest() { backup.backUpApksOfNotBackedUpPackages() verify { - metadataManager.onPackageBackupError(packageInfo, NOT_ALLOWED, metadataOutputStream) + metadataService.onPackageBackupError(packageInfo, NOT_ALLOWED, metadataOutputStream) metadataOutputStream.close() } } private fun expectApkBackupAndMetadataWrite() { coEvery { - apkBackup.backupApkIfNecessary( + apkBackupService.backupApkIfNecessary( any(), UNKNOWN_ERROR, any() ) } returns packageMetadata - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream every { - metadataManager.onApkBackedUp( + metadataService.onApkBackedUp( any(), packageMetadata, metadataOutputStream diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupTest.kt index b1f85b7c..6cdb6353 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupTest.kt @@ -1,7 +1,9 @@ package com.stevesoltys.seedvault.transport.backup import android.os.ParcelFileDescriptor +import com.stevesoltys.seedvault.service.app.backup.InputFactory import com.stevesoltys.seedvault.transport.TransportTest +import com.stevesoltys.seedvault.service.app.backup.full.DEFAULT_QUOTA_FULL_BACKUP import io.mockk.mockk import java.io.OutputStream diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupServiceTest.kt similarity index 84% rename from app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupServiceTest.kt index 2d03dd7e..6bb0a01f 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupServiceTest.kt @@ -4,9 +4,11 @@ import android.app.backup.BackupTransport.TRANSPORT_ERROR import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.StoragePlugin +import com.stevesoltys.seedvault.service.app.backup.full.DEFAULT_QUOTA_FULL_BACKUP +import com.stevesoltys.seedvault.service.app.backup.full.FullBackupService +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.getADForFull +import com.stevesoltys.seedvault.service.storage.StoragePlugin import io.mockk.Runs import io.mockk.coEvery import io.mockk.every @@ -22,10 +24,10 @@ import java.io.IOException import kotlin.random.Random @Suppress("BlockingMethodInNonBlockingContext") -internal class FullBackupTest : BackupTest() { +internal class FullBackupServiceTest : BackupTest() { private val plugin = mockk<StoragePlugin>() - private val backup = FullBackup(plugin, settingsManager, inputFactory, crypto) + private val backup = FullBackupService(plugin, settingsService, inputFactory, cryptoService) private val bytes = ByteArray(23).apply { Random.nextBytes(this) } private val inputStream = mockk<FileInputStream>() @@ -38,7 +40,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `checkFullBackupSize exceeds quota`() { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false assertEquals( TRANSPORT_QUOTA_EXCEEDED, @@ -48,7 +50,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `checkFullBackupSize does not exceed quota when unlimited`() { - every { settingsManager.isQuotaUnlimited() } returns true + every { settingsService.isQuotaUnlimited() } returns true assertEquals(TRANSPORT_OK, backup.checkFullBackupSize(quota + 1)) } @@ -65,14 +67,14 @@ internal class FullBackupTest : BackupTest() { @Test fun `checkFullBackupSize accepts min data`() { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false assertEquals(TRANSPORT_OK, backup.checkFullBackupSize(1)) } @Test fun `checkFullBackupSize accepts max data`() { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false assertEquals(TRANSPORT_OK, backup.checkFullBackupSize(quota)) } @@ -90,7 +92,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `sendBackupData first call over quota`() = runBlocking { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() val numBytes = (quota + 1).toInt() @@ -107,7 +109,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `sendBackupData second call over quota`() = runBlocking { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() val numBytes1 = quota.toInt() @@ -130,8 +132,8 @@ internal class FullBackupTest : BackupTest() { fun `sendBackupData throws exception when reading from InputStream`() = runBlocking { every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() - every { settingsManager.isQuotaUnlimited() } returns false - every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream + every { settingsService.isQuotaUnlimited() } returns false + every { cryptoService.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream every { inputStream.read(any(), any(), bytes.size) } throws IOException() expectClearState() @@ -147,8 +149,8 @@ internal class FullBackupTest : BackupTest() { fun `sendBackupData throws exception when getting outputStream`() = runBlocking { every { inputFactory.getInputStream(data) } returns inputStream - every { settingsManager.isQuotaUnlimited() } returns false - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { settingsService.isQuotaUnlimited() } returns false + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { plugin.getOutputStream(token, name) } throws IOException() expectClearState() @@ -164,8 +166,8 @@ internal class FullBackupTest : BackupTest() { fun `sendBackupData throws exception when writing header`() = runBlocking { every { inputFactory.getInputStream(data) } returns inputStream - every { settingsManager.isQuotaUnlimited() } returns false - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { settingsService.isQuotaUnlimited() } returns false + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { plugin.getOutputStream(token, name) } returns outputStream every { inputFactory.getInputStream(data) } returns inputStream every { outputStream.write(ByteArray(1) { VERSION }) } throws IOException() @@ -184,8 +186,13 @@ internal class FullBackupTest : BackupTest() { runBlocking { every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() - every { settingsManager.isQuotaUnlimited() } returns false - every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream + every { settingsService.isQuotaUnlimited() } returns false + every { + cryptoService.newEncryptingStream( + outputStream, + ad + ) + } returns encryptedOutputStream every { inputStream.read(any(), any(), bytes.size) } returns bytes.size every { encryptedOutputStream.write(any<ByteArray>()) } throws IOException() expectClearState() @@ -200,7 +207,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `sendBackupData runs ok`() = runBlocking { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() val numBytes1 = (quota / 2).toInt() @@ -221,7 +228,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `clearBackupData delegates to plugin`() = runBlocking { - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { plugin.removeData(token, name) } just Runs backup.clearBackupData(packageInfo, token, salt) @@ -232,7 +239,7 @@ internal class FullBackupTest : BackupTest() { every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() expectClearState() - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { plugin.removeData(token, name) } just Runs assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt)) @@ -246,7 +253,7 @@ internal class FullBackupTest : BackupTest() { every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() expectClearState() - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { plugin.removeData(token, name) } throws IOException() assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt)) @@ -257,7 +264,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `clearState throws exception when flushing OutputStream`() = runBlocking { - every { settingsManager.isQuotaUnlimited() } returns false + every { settingsService.isQuotaUnlimited() } returns false every { inputFactory.getInputStream(data) } returns inputStream expectInitializeOutputStream() val numBytes = 42 @@ -317,14 +324,14 @@ internal class FullBackupTest : BackupTest() { } private fun expectInitializeOutputStream() { - every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name + every { cryptoService.getNameForPackage(salt, packageInfo.packageName) } returns name coEvery { plugin.getOutputStream(token, name) } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } just Runs } private fun expectSendData(numBytes: Int, readBytes: Int = numBytes) { every { inputStream.read(any(), any(), numBytes) } returns readBytes - every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream + every { cryptoService.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream every { encryptedOutputStream.write(any<ByteArray>()) } just Runs } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupServiceTest.kt similarity index 90% rename from app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupServiceTest.kt index e370f080..b0b81c7f 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupServiceTest.kt @@ -9,10 +9,13 @@ import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUI import android.app.backup.BackupTransport.TRANSPORT_OK import android.content.pm.PackageInfo import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.StoragePlugin +import com.stevesoltys.seedvault.service.header.MAX_KEY_LENGTH_SIZE +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.getADForKV +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.app.backup.kv.KVBackupService +import com.stevesoltys.seedvault.service.app.backup.kv.KVDb +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManager import io.mockk.CapturingSlot import io.mockk.Runs import io.mockk.coEvery @@ -31,13 +34,14 @@ import java.io.IOException import kotlin.random.Random @Suppress("BlockingMethodInNonBlockingContext") -internal class KVBackupTest : BackupTest() { +internal class KVBackupServiceTest : BackupTest() { private val plugin = mockk<StoragePlugin>() private val dataInput = mockk<BackupDataInput>() private val dbManager = mockk<KvDbManager>() - private val backup = KVBackup(plugin, settingsManager, inputFactory, crypto, dbManager) + private val backup = + KVBackupService(plugin, settingsService, inputFactory, cryptoService, dbManager) private val db = mockk<KVDb>() private val packageName = packageInfo.packageName @@ -106,7 +110,7 @@ internal class KVBackupTest : BackupTest() { @Test fun `package with no new data comes back ok right away`() = runBlocking { - every { crypto.getNameForPackage(salt, packageName) } returns name + every { cryptoService.getNameForPackage(salt, packageName) } returns name every { dbManager.getDb(packageName) } returns db every { data.close() } just Runs @@ -214,7 +218,7 @@ internal class KVBackupTest : BackupTest() { coEvery { plugin.getOutputStream(token, name) } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } just Runs val ad = getADForKV(VERSION, packageInfo.packageName) - every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream + every { cryptoService.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream every { encryptedOutputStream.write(any<ByteArray>()) } throws IOException() assertEquals(TRANSPORT_ERROR, backup.finishBackup()) @@ -229,9 +233,9 @@ internal class KVBackupTest : BackupTest() { @Test fun `no upload when we back up @pm@ while we can't do backups`() = runBlocking { every { dbManager.existsDb(pmPackageInfo.packageName) } returns false - every { crypto.getNameForPackage(salt, pmPackageInfo.packageName) } returns name + every { cryptoService.getNameForPackage(salt, pmPackageInfo.packageName) } returns name every { dbManager.getDb(pmPackageInfo.packageName) } returns db - every { settingsManager.canDoBackupNow() } returns false + every { settingsService.canDoBackupNow() } returns false every { db.put(key, dataValue) } just Runs getDataInput(listOf(true, false)) @@ -258,7 +262,7 @@ internal class KVBackupTest : BackupTest() { private fun initPlugin(hasDataForPackage: Boolean = false, pi: PackageInfo = packageInfo) { every { dbManager.existsDb(pi.packageName) } returns hasDataForPackage - every { crypto.getNameForPackage(salt, pi.packageName) } returns name + every { cryptoService.getNameForPackage(salt, pi.packageName) } returns name every { dbManager.getDb(pi.packageName) } returns db } @@ -285,7 +289,7 @@ internal class KVBackupTest : BackupTest() { coEvery { plugin.getOutputStream(token, name) } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } just Runs val ad = getADForKV(VERSION, packageInfo.packageName) - every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream + every { cryptoService.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream every { encryptedOutputStream.write(any<ByteArray>()) } just Runs // gzip header every { encryptedOutputStream.write(any(), any(), any()) } just Runs // stream copy every { dbManager.getDbInputStream(packageName) } returns inputStream diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt index 7173f2ff..f70d3e6b 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt @@ -3,6 +3,8 @@ package com.stevesoltys.seedvault.transport.backup import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.toByteArrayFromHex import com.stevesoltys.seedvault.toHexString +import com.stevesoltys.seedvault.service.app.backup.kv.KVDb +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManager import org.json.JSONObject import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt index 73044e0d..fa407a28 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt @@ -6,13 +6,14 @@ import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import com.stevesoltys.seedvault.coAssertThrows import com.stevesoltys.seedvault.getRandomByteArray -import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH -import com.stevesoltys.seedvault.header.UnsupportedVersionException -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.VersionHeader -import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin +import com.stevesoltys.seedvault.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.header.MAX_SEGMENT_LENGTH +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.VersionHeader +import com.stevesoltys.seedvault.service.header.getADForFull +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin import io.mockk.CapturingSlot import io.mockk.Runs import io.mockk.coEvery @@ -36,7 +37,8 @@ internal class FullRestoreTest : RestoreTest() { private val plugin = mockk<StoragePlugin>() private val legacyPlugin = mockk<LegacyStoragePlugin>() - private val restore = FullRestore(plugin, legacyPlugin, outputFactory, headerReader, crypto) + private val restore = + FullRestore(plugin, legacyPlugin, outputFactory, headerDecodeService, cryptoService) private val encrypted = getRandomByteArray() private val outputStream = ByteArrayOutputStream() @@ -88,7 +90,7 @@ internal class FullRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } throws IOException() + every { headerDecodeService.readVersion(inputStream, VERSION) } throws IOException() every { fileDescriptor.close() } just Runs assertEquals( @@ -103,7 +105,7 @@ internal class FullRestoreTest : RestoreTest() { coEvery { plugin.getInputStream(token, name) } returns inputStream every { - headerReader.readVersion(inputStream, VERSION) + headerDecodeService.readVersion(inputStream, VERSION) } throws UnsupportedVersionException(unsupportedVersion) every { fileDescriptor.close() } just Runs @@ -118,8 +120,8 @@ internal class FullRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } throws IOException() + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { cryptoService.newDecryptingStream(inputStream, ad) } throws IOException() every { fileDescriptor.close() } just Runs assertEquals( @@ -134,8 +136,13 @@ internal class FullRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { + cryptoService.newDecryptingStream( + inputStream, + ad + ) + } throws GeneralSecurityException() every { fileDescriptor.close() } just Runs assertEquals(TRANSPORT_ERROR, restore.getNextFullRestoreDataChunk(fileDescriptor)) @@ -161,11 +168,11 @@ internal class FullRestoreTest : RestoreTest() { restore.initializeState(0.toByte(), token, name, packageInfo) coEvery { legacyPlugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream, 0.toByte()) } returns 0.toByte() + every { headerDecodeService.readVersion(inputStream, 0.toByte()) } returns 0.toByte() every { - crypto.decryptHeader(inputStream, 0.toByte(), packageInfo.packageName) + cryptoService.decryptHeader(inputStream, 0.toByte(), packageInfo.packageName) } returns VersionHeader(0.toByte(), packageInfo.packageName) - every { crypto.decryptSegment(inputStream) } returns encrypted + every { cryptoService.decryptSegment(inputStream) } returns encrypted every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream every { fileDescriptor.close() } just Runs @@ -183,7 +190,7 @@ internal class FullRestoreTest : RestoreTest() { coEvery { plugin.getInputStream(token, name) } returns inputStream every { - headerReader.readVersion(inputStream, Byte.MAX_VALUE) + headerDecodeService.readVersion(inputStream, Byte.MAX_VALUE) } throws GeneralSecurityException() every { inputStream.close() } just Runs every { fileDescriptor.close() } just Runs @@ -200,8 +207,8 @@ internal class FullRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { cryptoService.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream every { fileDescriptor.close() } just Runs every { inputStream.close() } just Runs @@ -233,8 +240,8 @@ internal class FullRestoreTest : RestoreTest() { private fun initInputStream() { coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { cryptoService.newDecryptingStream(inputStream, ad) } returns decryptedInputStream } private fun readAndEncryptInputStream(encryptedBytes: ByteArray) { diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt index 678d6b63..bf654998 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt @@ -4,16 +4,17 @@ import android.app.backup.BackupDataOutput import android.app.backup.BackupTransport.TRANSPORT_ERROR import android.app.backup.BackupTransport.TRANSPORT_OK import com.stevesoltys.seedvault.coAssertThrows -import com.stevesoltys.seedvault.encodeBase64 +import com.stevesoltys.seedvault.util.encodeBase64 import com.stevesoltys.seedvault.getRandomByteArray -import com.stevesoltys.seedvault.header.UnsupportedVersionException -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.header.VersionHeader -import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.transport.backup.KVDb -import com.stevesoltys.seedvault.transport.backup.KvDbManager +import com.stevesoltys.seedvault.service.header.UnsupportedVersionException +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.header.VersionHeader +import com.stevesoltys.seedvault.service.header.getADForKV +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin +import com.stevesoltys.seedvault.service.app.backup.kv.KVDb +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManager +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore import io.mockk.Runs import io.mockk.coEvery import io.mockk.every @@ -40,8 +41,14 @@ internal class KVRestoreTest : RestoreTest() { private val legacyPlugin = mockk<LegacyStoragePlugin>() private val dbManager = mockk<KvDbManager>() private val output = mockk<BackupDataOutput>() - private val restore = - KVRestore(plugin, legacyPlugin, outputFactory, headerReader, crypto, dbManager) + private val restore = KVRestore( + plugin, + legacyPlugin, + outputFactory, + headerDecodeService, + cryptoService, + dbManager + ) private val db = mockk<KVDb>() private val ad = getADForKV(VERSION, packageInfo.packageName) @@ -75,7 +82,7 @@ internal class KVRestoreTest : RestoreTest() { coEvery { plugin.getInputStream(token, name) } returns inputStream every { - headerReader.readVersion(inputStream, VERSION) + headerDecodeService.readVersion(inputStream, VERSION) } throws UnsupportedVersionException(Byte.MAX_VALUE) every { dbManager.deleteDb(packageInfo.packageName, true) } returns true streamsGetClosed() @@ -89,8 +96,13 @@ internal class KVRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { + cryptoService.newDecryptingStream( + inputStream, + ad + ) + } throws GeneralSecurityException() every { dbManager.deleteDb(packageInfo.packageName, true) } returns true streamsGetClosed() @@ -107,8 +119,8 @@ internal class KVRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { cryptoService.newDecryptingStream(inputStream, ad) } returns decryptInputStream every { dbManager.getDbOutputStream(packageInfo.packageName) } returns ByteArrayOutputStream() @@ -132,8 +144,8 @@ internal class KVRestoreTest : RestoreTest() { restore.initializeState(VERSION, token, name, packageInfo) coEvery { plugin.getInputStream(token, name) } returns inputStream - every { headerReader.readVersion(inputStream, VERSION) } returns VERSION - every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream + every { headerDecodeService.readVersion(inputStream, VERSION) } returns VERSION + every { cryptoService.newDecryptingStream(inputStream, ad) } returns decryptInputStream every { dbManager.getDbOutputStream(packageInfo.packageName) } returns ByteArrayOutputStream() @@ -198,7 +210,7 @@ internal class KVRestoreTest : RestoreTest() { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream every { - headerReader.readVersion(inputStream, 0x00) + headerDecodeService.readVersion(inputStream, 0x00) } throws UnsupportedVersionException(unsupportedVersion) streamsGetClosed() @@ -214,7 +226,7 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0x00) } throws IOException() + every { headerDecodeService.readVersion(inputStream, 0x00) } throws IOException() streamsGetClosed() assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) @@ -230,9 +242,9 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0x00) } returns 0x00 + every { headerDecodeService.readVersion(inputStream, 0x00) } returns 0x00 every { - crypto.decryptHeader(inputStream, 0x00, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0x00, packageInfo.packageName, key) } throws IOException() streamsGetClosed() @@ -249,11 +261,11 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0x00) } returns 0x00 + every { headerDecodeService.readVersion(inputStream, 0x00) } returns 0x00 every { - crypto.decryptHeader(inputStream, 0x00, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0x00, packageInfo.packageName, key) } returns VersionHeader(0x00, packageInfo.packageName, key) - every { crypto.decryptMultipleSegments(inputStream) } throws IOException() + every { cryptoService.decryptMultipleSegments(inputStream) } throws IOException() streamsGetClosed() assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) @@ -269,11 +281,11 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0) } returns 0 + every { headerDecodeService.readVersion(inputStream, 0) } returns 0 every { - crypto.decryptHeader(inputStream, 0x00, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0x00, packageInfo.packageName, key) } returns VersionHeader(0x00, packageInfo.packageName, key) - every { crypto.decryptMultipleSegments(inputStream) } returns data + every { cryptoService.decryptMultipleSegments(inputStream) } returns data every { output.writeEntityHeader(key, data.size) } throws IOException() streamsGetClosed() @@ -290,11 +302,11 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0) } returns 0 + every { headerDecodeService.readVersion(inputStream, 0) } returns 0 every { - crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0, packageInfo.packageName, key) } returns VersionHeader(0, packageInfo.packageName, key) - every { crypto.decryptMultipleSegments(inputStream) } returns data + every { cryptoService.decryptMultipleSegments(inputStream) } returns data every { output.writeEntityHeader(key, data.size) } returns 42 every { output.writeEntityData(data, data.size) } throws IOException() streamsGetClosed() @@ -312,11 +324,11 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0) } returns 0 + every { headerDecodeService.readVersion(inputStream, 0) } returns 0 every { - crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0, packageInfo.packageName, key) } returns VersionHeader(0, packageInfo.packageName, key) - every { crypto.decryptMultipleSegments(inputStream) } returns data + every { cryptoService.decryptMultipleSegments(inputStream) } returns data every { output.writeEntityHeader(key, data.size) } returns 42 every { output.writeEntityData(data, data.size) } returns data.size streamsGetClosed() @@ -334,11 +346,11 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0) } returns 0 + every { headerDecodeService.readVersion(inputStream, 0) } returns 0 every { - crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0, packageInfo.packageName, key) } returns VersionHeader(VERSION, packageInfo.packageName, key) - every { crypto.decryptMultipleSegments(inputStream) } returns data + every { cryptoService.decryptMultipleSegments(inputStream) } returns data every { output.writeEntityHeader(key, data.size) } returns 42 every { output.writeEntityData(data, data.size) } returns data.size streamsGetClosed() @@ -359,22 +371,22 @@ internal class KVRestoreTest : RestoreTest() { coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream, 0) } returns 0 + every { headerDecodeService.readVersion(inputStream, 0) } returns 0 every { - crypto.decryptHeader(inputStream, 0, packageInfo.packageName, key) + cryptoService.decryptHeader(inputStream, 0, packageInfo.packageName, key) } returns VersionHeader(0, packageInfo.packageName, key) - every { crypto.decryptMultipleSegments(inputStream) } returns data + every { cryptoService.decryptMultipleSegments(inputStream) } returns data every { output.writeEntityHeader(key, data.size) } returns 42 every { output.writeEntityData(data, data.size) } returns data.size // second key/value coEvery { legacyPlugin.getInputStreamForRecord(token, packageInfo, key264) } returns inputStream2 - every { headerReader.readVersion(inputStream2, 0) } returns 0 + every { headerDecodeService.readVersion(inputStream2, 0) } returns 0 every { - crypto.decryptHeader(inputStream2, 0, packageInfo.packageName, key2) + cryptoService.decryptHeader(inputStream2, 0, packageInfo.packageName, key2) } returns VersionHeader(0, packageInfo.packageName, key2) - every { crypto.decryptMultipleSegments(inputStream2) } returns data2 + every { cryptoService.decryptMultipleSegments(inputStream2) } returns data2 every { output.writeEntityHeader(key2, data2.size) } returns 42 every { output.writeEntityData(data2, data2.size) } returns data2.size every { inputStream2.close() } just Runs 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 88ba4c15..89157119 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 @@ -10,13 +10,17 @@ import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import com.stevesoltys.seedvault.coAssertThrows import com.stevesoltys.seedvault.getRandomString -import com.stevesoltys.seedvault.header.VERSION -import com.stevesoltys.seedvault.metadata.BackupType -import com.stevesoltys.seedvault.metadata.MetadataReader -import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.settings.Storage +import com.stevesoltys.seedvault.service.app.restore.coordinator.D2D_DEVICE_NAME +import com.stevesoltys.seedvault.service.app.restore.coordinator.RestoreCoordinator +import com.stevesoltys.seedvault.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore +import com.stevesoltys.seedvault.service.header.VERSION +import com.stevesoltys.seedvault.service.metadata.BackupType +import com.stevesoltys.seedvault.service.metadata.MetadataReader +import com.stevesoltys.seedvault.service.metadata.PackageMetadata +import com.stevesoltys.seedvault.service.settings.Storage +import com.stevesoltys.seedvault.service.storage.EncryptedBackupMetadata +import com.stevesoltys.seedvault.service.storage.StoragePlugin import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.Runs @@ -46,9 +50,9 @@ internal class RestoreCoordinatorTest : TransportTest() { private val restore = RestoreCoordinator( context, - crypto, - settingsManager, - metadataManager, + cryptoService, + settingsService, + metadataService, notificationManager, plugin, kv, @@ -75,11 +79,11 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking { - val encryptedMetadata = EncryptedMetadata(token) { inputStream } + val encryptedBackupMetadata = EncryptedBackupMetadata(token) { inputStream } coEvery { plugin.getAvailableBackups() } returns sequenceOf( - encryptedMetadata, - EncryptedMetadata(token + 1) { inputStream } + encryptedBackupMetadata, + EncryptedBackupMetadata(token + 1) { inputStream } ) every { metadataReader.readMetadata(inputStream, token) } returns metadata every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata @@ -103,7 +107,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `getCurrentRestoreSet() delegates to plugin`() { - every { settingsManager.getToken() } returns token + every { settingsService.getToken() } returns token assertEquals(token, restore.getCurrentRestoreSet()) } @@ -116,8 +120,8 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() fetches metadata if missing`() = runBlocking { coEvery { plugin.getAvailableBackups() } returns sequenceOf( - EncryptedMetadata(token) { inputStream }, - EncryptedMetadata(token + 1) { inputStream } + EncryptedBackupMetadata(token) { inputStream }, + EncryptedBackupMetadata(token + 1) { inputStream } ) every { metadataReader.readMetadata(inputStream, token) } returns metadata every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata @@ -129,7 +133,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() errors if metadata is not matching token`() = runBlocking { coEvery { plugin.getAvailableBackups() } returns sequenceOf( - EncryptedMetadata(token + 42) { inputStream } + EncryptedBackupMetadata(token + 42) { inputStream } ) every { metadataReader.readMetadata(inputStream, token + 42) } returns metadata every { inputStream.close() } just Runs @@ -164,9 +168,9 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() optimized auto-restore with removed storage shows notification`() = runBlocking { - every { settingsManager.getStorage() } returns storage + every { settingsService.getStorage() } returns storage every { storage.isUnavailableUsb(context) } returns true - every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L) + every { metadataService.getPackageMetadata(packageName) } returns PackageMetadata(42L) every { storage.name } returns storageName every { notificationManager.onRemovableStorageNotAvailableForRestore( @@ -188,7 +192,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() optimized auto-restore with available storage shows no notification`() = runBlocking { - every { settingsManager.getStorage() } returns storage + every { settingsService.getStorage() } returns storage every { storage.isUnavailableUsb(context) } returns false restore.beforeStartRestore(metadata) @@ -204,9 +208,9 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() with removed storage shows no notification`() = runBlocking { - every { settingsManager.getStorage() } returns storage + every { settingsService.getStorage() } returns storage every { storage.isUnavailableUsb(context) } returns true - every { metadataManager.getPackageMetadata(packageName) } returns null + every { metadataService.getPackageMetadata(packageName) } returns null assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray)) @@ -230,7 +234,7 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.beforeStartRestore(metadata) restore.startRestore(token, packageInfoArray) - every { crypto.getNameForPackage(metadata.salt, packageName) } returns name + every { cryptoService.getNameForPackage(metadata.salt, packageName) } returns name coEvery { plugin.hasData(token, name) } returns true every { kv.initializeState(VERSION, token, name, packageInfo) } just Runs @@ -270,9 +274,14 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.beforeStartRestore(metadata) restore.startRestore(token, packageInfoArray2) - every { crypto.getNameForPackage(metadata.salt, packageName) } returns name + every { cryptoService.getNameForPackage(metadata.salt, packageName) } returns name coEvery { plugin.hasData(token, name) } returns false - every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2 + every { + cryptoService.getNameForPackage( + metadata.salt, + packageInfo2.packageName + ) + } returns name2 coEvery { plugin.hasData(token, name2) } returns false assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage()) @@ -285,7 +294,12 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.beforeStartRestore(metadata) restore.startRestore(token, packageInfoArray2) - every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2 + every { + cryptoService.getNameForPackage( + metadata.salt, + packageInfo2.packageName + ) + } returns name2 coEvery { plugin.hasData(token, name2) } returns true every { full.initializeState(VERSION, token, name2, packageInfo2) } just Runs @@ -300,14 +314,19 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.beforeStartRestore(metadata) restore.startRestore(token, packageInfoArray2) - every { crypto.getNameForPackage(metadata.salt, packageName) } returns name + every { cryptoService.getNameForPackage(metadata.salt, packageName) } returns name coEvery { plugin.hasData(token, name) } returns true every { kv.initializeState(VERSION, token, name, packageInfo) } just Runs val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE) assertEquals(expected, restore.nextRestorePackage()) - every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2 + every { + cryptoService.getNameForPackage( + metadata.salt, + packageInfo2.packageName + ) + } returns name2 coEvery { plugin.hasData(token, name2) } returns true every { full.initializeState(VERSION, token, name2, packageInfo2) } just Runs @@ -356,9 +375,14 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.beforeStartRestore(metadata) restore.startRestore(token, packageInfoArray2) - every { crypto.getNameForPackage(metadata.salt, packageName) } returns name + every { cryptoService.getNameForPackage(metadata.salt, packageName) } returns name coEvery { plugin.hasData(token, name) } returns false - every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2 + every { + cryptoService.getNameForPackage( + metadata.salt, + packageInfo2.packageName + ) + } returns name2 coEvery { plugin.hasData(token, name2) } throws IOException() assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage()) diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreTest.kt index e720bc79..157c8ffb 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreTest.kt @@ -2,8 +2,9 @@ package com.stevesoltys.seedvault.transport.restore import android.os.ParcelFileDescriptor import com.stevesoltys.seedvault.getRandomByteArray -import com.stevesoltys.seedvault.header.HeaderReader -import com.stevesoltys.seedvault.header.VERSION +import com.stevesoltys.seedvault.service.app.restore.OutputFactory +import com.stevesoltys.seedvault.service.header.HeaderDecodeService +import com.stevesoltys.seedvault.service.header.VERSION import com.stevesoltys.seedvault.transport.TransportTest import io.mockk.mockk import java.io.InputStream @@ -11,7 +12,7 @@ import java.io.InputStream internal abstract class RestoreTest : TransportTest() { protected val outputFactory = mockk<OutputFactory>() - protected val headerReader = mockk<HeaderReader>() + protected val headerDecodeService = mockk<HeaderDecodeService>() protected val fileDescriptor = mockk<ParcelFileDescriptor>() protected val data = getRandomByteArray() diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt index 07df8f34..86899bc9 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt @@ -6,18 +6,22 @@ import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.RestoreDescription import android.app.backup.RestoreDescription.TYPE_FULL_STREAM import android.os.ParcelFileDescriptor -import com.stevesoltys.seedvault.crypto.CipherFactoryImpl -import com.stevesoltys.seedvault.crypto.CryptoImpl -import com.stevesoltys.seedvault.crypto.KEY_SIZE_BYTES +import com.stevesoltys.seedvault.service.crypto.CipherFactoryImpl +import com.stevesoltys.seedvault.service.crypto.CryptoServiceImpl +import com.stevesoltys.seedvault.service.crypto.KEY_SIZE_BYTES import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl -import com.stevesoltys.seedvault.encodeBase64 -import com.stevesoltys.seedvault.header.HeaderReaderImpl -import com.stevesoltys.seedvault.metadata.MetadataReaderImpl -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin +import com.stevesoltys.seedvault.util.encodeBase64 +import com.stevesoltys.seedvault.service.header.HeaderDecodeServiceImpl +import com.stevesoltys.seedvault.service.metadata.MetadataReaderImpl +import com.stevesoltys.seedvault.service.storage.saf.legacy.LegacyStoragePlugin +import com.stevesoltys.seedvault.service.storage.StoragePlugin import com.stevesoltys.seedvault.toByteArrayFromHex import com.stevesoltys.seedvault.transport.TransportTest -import com.stevesoltys.seedvault.transport.backup.KvDbManager +import com.stevesoltys.seedvault.service.app.backup.kv.KvDbManager +import com.stevesoltys.seedvault.service.app.restore.OutputFactory +import com.stevesoltys.seedvault.service.app.restore.coordinator.RestoreCoordinator +import com.stevesoltys.seedvault.service.app.restore.full.FullRestore +import com.stevesoltys.seedvault.service.app.restore.kv.KVRestore import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.coEvery import io.mockk.every @@ -44,10 +48,10 @@ internal class RestoreV0IntegrationTest : TransportTest() { ) private val keyManager = KeyManagerTestImpl(secretKey) private val cipherFactory = CipherFactoryImpl(keyManager) - private val headerReader = HeaderReaderImpl() - private val cryptoImpl = CryptoImpl(keyManager, cipherFactory, headerReader) + private val headerReader = HeaderDecodeServiceImpl() + private val cryptoServiceImpl = CryptoServiceImpl(keyManager, cipherFactory, headerReader) private val dbManager = mockk<KvDbManager>() - private val metadataReader = MetadataReaderImpl(cryptoImpl) + private val metadataReader = MetadataReaderImpl(cryptoServiceImpl) private val notificationManager = mockk<BackupNotificationManager>() @Suppress("Deprecation") @@ -58,16 +62,16 @@ internal class RestoreV0IntegrationTest : TransportTest() { legacyPlugin, outputFactory, headerReader, - cryptoImpl, + cryptoServiceImpl, dbManager ) private val fullRestore = - FullRestore(backupPlugin, legacyPlugin, outputFactory, headerReader, cryptoImpl) + FullRestore(backupPlugin, legacyPlugin, outputFactory, headerReader, cryptoServiceImpl) private val restore = RestoreCoordinator( context, - crypto, - settingsManager, - metadataManager, + cryptoService, + settingsService, + metadataService, notificationManager, backupPlugin, kvRestore,