Clean up code after refactorings

This commit is contained in:
Torsten Grote 2024-09-23 12:04:50 -03:00
parent 5c75574f65
commit 7b0e02c451
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
59 changed files with 84 additions and 240 deletions

View file

@ -10,7 +10,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
import com.stevesoltys.seedvault.backend.getAvailableBackupFileHandles
import com.stevesoltys.seedvault.backend.saf.DocumentsProviderLegacyPlugin
import com.stevesoltys.seedvault.backend.saf.DocumentsStorage
import com.stevesoltys.seedvault.settings.SettingsManager

View file

@ -13,7 +13,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.stevesoltys.seedvault.repo.BackupData
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.settings.SettingsManager
import io.mockk.CapturingSlot
import io.mockk.Runs
import io.mockk.coEvery
@ -34,7 +33,6 @@ import kotlin.test.assertEquals
@MediumTest
class KvBackupInstrumentationTest : KoinComponent {
private val settingsManager: SettingsManager by inject()
private val backupReceiver: BackupReceiver = mockk()
private val inputFactory: InputFactory = mockk()
private val dbManager: KvDbManager by inject()

View file

@ -13,9 +13,9 @@ import com.stevesoltys.seedvault.proto.SnapshotKt.blob
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.BackupData
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.repo.SnapshotCreatorFactory
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.repo.SnapshotCreatorFactory
import com.stevesoltys.seedvault.transport.backup.PackageService
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.every

View file

@ -86,11 +86,9 @@ open class App : Application() {
settingsManager = get(),
keyManager = get(),
backendManager = get(),
metadataManager = get(),
appListRetriever = get(),
storageBackup = get(),
backupManager = get(),
backupInitializer = get(),
backupStateManager = get(),
)
}

View file

@ -10,9 +10,9 @@ import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.getStorageContext
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.repo.BlobCache
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.StoragePluginType
import com.stevesoltys.seedvault.repo.BlobCache
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.BackendFactory
import org.calyxos.seedvault.core.backends.BackendProperties
@ -89,6 +89,7 @@ class BackendManager(
mBackend = backend
mBackendProperties = storageProperties
blobCache.clearLocalCache()
// TODO not critical, but nice to have: clear also local snapshot cache
}
/**

View file

@ -29,7 +29,6 @@ import org.calyxos.seedvault.core.backends.saf.SafProperties
import org.calyxos.seedvault.core.backends.saf.getTreeDocumentFile
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import kotlin.coroutines.resume
@Deprecated("")
@ -51,7 +50,7 @@ internal class DocumentsStorage(
private val context: Context get() = appContext.getStorageContext { safStorage.isUsb }
private val contentResolver: ContentResolver get() = context.contentResolver
internal var rootBackupDir: DocumentFile? = null
private var rootBackupDir: DocumentFile? = null
get() = runBlocking {
if (field == null) {
val parent = safStorage.getDocumentFile(context)
@ -94,16 +93,6 @@ internal class DocumentsStorage(
}
}
@Throws(IOException::class)
fun getOutputStream(file: DocumentFile): OutputStream {
return try {
contentResolver.openOutputStream(file.uri, "wt") ?: throw IOException()
} catch (e: Exception) {
// SAF can throw all sorts of exceptions, so wrap it in IOException
throw IOException(e)
}
}
}
/**
@ -192,7 +181,7 @@ suspend fun DocumentFile.findFileBlocking(context: Context, displayName: String)
@Throws(IOException::class, TimeoutCancellationException::class)
internal suspend fun getLoadedCursor(timeout: Long = 15_000, query: () -> Cursor?) =
withTimeout(timeout) {
suspendCancellableCoroutine<Cursor> { cont ->
suspendCancellableCoroutine { cont ->
val cursor = query() ?: throw IOException()
cont.invokeOnCancellation { cursor.close() }
val loading = cursor.extras.getBoolean(EXTRA_LOADING, false)

View file

@ -139,17 +139,6 @@ internal object StorageRootResolver {
return if (index != -1) getInt(index) else 0
}
private fun Cursor.getLong(columnName: String): Long? {
val index = getColumnIndex(columnName)
if (index == -1) return null
val value = getString(index) ?: return null
return try {
java.lang.Long.parseLong(value)
} catch (e: NumberFormatException) {
null
}
}
fun getIcon(context: Context, authority: String, rootId: String, icon: Int): Drawable? {
return getPackageIcon(context, authority, icon) ?: when {
authority == AUTHORITY_STORAGE && rootId == ROOT_ID_DEVICE -> {

View file

@ -102,17 +102,6 @@ internal interface Crypto {
@Deprecated("only for v1")
fun getNameForApk(salt: String, packageName: String, suffix: String = ""): String
/**
* Returns a [AesGcmHkdfStreaming] encrypting stream
* that gets encrypted and authenticated the given associated data.
*/
@Deprecated("only for v1")
@Throws(IOException::class, GeneralSecurityException::class)
fun newEncryptingStreamV1(
outputStream: OutputStream,
associatedData: ByteArray,
): OutputStream
/**
* Returns a [AesGcmHkdfStreaming] decrypting stream
* that gets decrypted and authenticated the given associated data.
@ -245,13 +234,6 @@ internal class CryptoImpl(
return messageDigest.digest()
}
@Deprecated("only for v1")
@Throws(IOException::class, GeneralSecurityException::class)
override fun newEncryptingStreamV1(
outputStream: OutputStream,
associatedData: ByteArray,
): OutputStream = CoreCrypto.newEncryptingStream(keyV1, outputStream, associatedData)
@Deprecated("only for v1")
@Throws(IOException::class, GeneralSecurityException::class)
override fun newDecryptingStreamV1(

View file

@ -33,12 +33,6 @@ internal class MetadataWriterImpl : MetadataWriter {
if (packageMetadata.size != null) {
put(JSON_PACKAGE_SIZE, packageMetadata.size)
}
if (packageMetadata.system) {
put(JSON_PACKAGE_SYSTEM, true)
}
if (packageMetadata.isLaunchableSystemApp) {
put(JSON_PACKAGE_SYSTEM_LAUNCHER, true)
}
})
}
return json.toString().toByteArray(Utf8)

View file

@ -144,6 +144,7 @@ internal class AppBackupManager(
@Throws(IOException::class)
suspend fun removeBackupRepo() {
blobCache.clearLocalCache()
// TODO not critical, but nice to have: clear also local snapshot cache
backendManager.backend.remove(TopLevelFolder(crypto.repoId))
}

View file

@ -19,6 +19,6 @@ val repoModule = module {
val snapshotFolder = File(androidContext().filesDir, FOLDER_SNAPSHOTS)
SnapshotManager(snapshotFolder, get(), get(), get())
}
factory { SnapshotCreatorFactory(androidContext(), get(), get(), get(), get()) }
factory { SnapshotCreatorFactory(androidContext(), get(), get(), get()) }
factory { Pruner(get(), get(), get()) }
}

View file

@ -23,7 +23,6 @@ import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Apk
import com.stevesoltys.seedvault.proto.Snapshot.App
import com.stevesoltys.seedvault.proto.Snapshot.Blob
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import io.github.oshai.kotlinlogging.KotlinLogging
@ -38,7 +37,6 @@ internal class SnapshotCreator(
private val context: Context,
private val clock: Clock,
private val packageService: PackageService,
private val settingsManager: SettingsManager,
private val metadataManager: MetadataManager,
) {

View file

@ -8,7 +8,6 @@ package com.stevesoltys.seedvault.repo
import android.content.Context
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService
/**
@ -18,9 +17,8 @@ internal class SnapshotCreatorFactory(
private val context: Context,
private val clock: Clock,
private val packageService: PackageService,
private val settingsManager: SettingsManager,
private val metadataManager: MetadataManager,
) {
fun createSnapshotCreator() =
SnapshotCreator(context, clock, packageService, settingsManager, metadataManager)
SnapshotCreator(context, clock, packageService, metadataManager)
}

View file

@ -27,7 +27,6 @@ import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.metadata.PackageState
import com.stevesoltys.seedvault.restore.install.isInstalled
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
@ -55,7 +54,6 @@ internal data class AppRestoreResult(
internal class AppDataRestoreManager(
private val context: Context,
private val backupManager: IBackupManager,
private val settingsManager: SettingsManager,
private val restoreCoordinator: RestoreCoordinator,
private val backendManager: BackendManager,
) {

View file

@ -96,7 +96,7 @@ internal class AppSelectionManager(
val backend = backendManager.backend
val token = restorableBackup.token
backend.load(LegacyAppBackupFile.IconsFile(token)).use {
iconManager.downloadIconsV1(restorableBackup.version, token, it)
iconManager.downloadIconsV1(token, it)
}
} else if (restorableBackup.version >= 2) {
val repoId = restorableBackup.repoId ?: error("No repoId in v2 backup")

View file

@ -83,7 +83,7 @@ internal class RestoreViewModel(
private val appSelectionManager =
AppSelectionManager(app, backendManager, iconManager, viewModelScope)
private val appDataRestoreManager = AppDataRestoreManager(
app, backupManager, settingsManager, restoreCoordinator, backendManager
app, backupManager, restoreCoordinator, backendManager
)
private val mDisplayFragment = MutableLiveEvent<DisplayFragment>()

View file

@ -21,15 +21,15 @@ import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.encodeBase64
import com.stevesoltys.seedvault.metadata.ApkSplit
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.repo.getBlobHandles
import com.stevesoltys.seedvault.restore.RestoreService
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.repo.getBlobHandles
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
import com.stevesoltys.seedvault.worker.hashSignature
import kotlinx.coroutines.TimeoutCancellationException

View file

@ -84,7 +84,7 @@ internal class InstallProgressAdapter(
if (item.icon == null) iconJob = scope.launch {
iconLoader(item, appIcon::setImageDrawable)
} else appIcon.setImageDrawable(item.icon)
appName.text = item.name ?: getAppName(v.context, item.packageName.toString())
appName.text = item.name ?: getAppName(v.context, item.packageName)
appInfo.visibility = GONE
when (item.state) {
IN_PROGRESS -> {

View file

@ -80,8 +80,7 @@ internal class AppListRetriever(
AppStatus(
packageName = packageName,
enabled = settingsManager.isBackupEnabled(packageName),
icon = data.iconRes?.let { getDrawable(context, it) }
?: getIconFromPackageManager(packageName),
icon = getDrawable(context, data.iconRes) ?: getIconFromPackageManager(packageName),
name = context.getString(data.nameRes),
time = metadata?.time ?: 0,
size = metadata?.size,

View file

@ -16,8 +16,8 @@ import androidx.preference.PreferenceManager
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.settings.preference.M3ListPreference
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel

View file

@ -35,12 +35,10 @@ import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
import androidx.work.WorkManager
import com.stevesoltys.seedvault.BackupStateManager
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.backend.BackendManager
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.storage.StorageBackupJobService
import com.stevesoltys.seedvault.transport.backup.BackupInitializer
import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
@ -68,11 +66,9 @@ internal class SettingsViewModel(
settingsManager: SettingsManager,
keyManager: KeyManager,
backendManager: BackendManager,
private val metadataManager: MetadataManager,
private val appListRetriever: AppListRetriever,
private val storageBackup: StorageBackup,
private val backupManager: IBackupManager,
private val backupInitializer: BackupInitializer,
backupStateManager: BackupStateManager,
) : RequireProvisioningViewModel(app, settingsManager, keyManager, backendManager) {

View file

@ -5,8 +5,8 @@
package com.stevesoltys.seedvault.storage
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.crypto.KeyManager
import org.calyxos.backup.storage.api.StorageBackup
import org.koin.dsl.module

View file

@ -17,7 +17,6 @@ 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 kotlinx.coroutines.runBlocking
@ -43,7 +42,6 @@ class ConfigurableBackupTransport internal constructor(private val context: Cont
private val backupCoordinator by inject<BackupCoordinator>()
private val restoreCoordinator by inject<RestoreCoordinator>()
private val settingsManager by inject<SettingsManager>()
override fun transportDirName(): String {
return TRANSPORT_DIRECTORY_NAME

View file

@ -20,7 +20,6 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.MetadataManager
@ -64,14 +63,12 @@ internal class BackupCoordinator(
private val appBackupManager: AppBackupManager,
private val kv: KVBackup,
private val full: FullBackup,
private val clock: Clock,
private val packageService: PackageService,
private val metadataManager: MetadataManager,
private val settingsManager: SettingsManager,
private val nm: BackupNotificationManager,
) {
private val backend get() = backendManager.backend
private val snapshotCreator
get() = appBackupManager.snapshotCreator ?: error("No SnapshotCreator")
private val state = CoordinatorState(
@ -92,6 +89,8 @@ internal class BackupCoordinator(
*
* If the transport returns anything other than [TRANSPORT_OK] from this method,
* the OS will halt the current initialize operation and schedule a retry in the near future.
* Attention: [finishBackup] will not be called in this case.
*
* Even if the transport is in a state
* such that attempting to "initialize" the backend storage is meaningless -
* for example, if there is no current live data-set at all,
@ -103,12 +102,8 @@ internal class BackupCoordinator(
* [TRANSPORT_ERROR] (to retry following network error or other failure).
*/
fun initializeDevice(): Int {
// we don't respect the intended system behavior here by always starting a new [RestoreSet]
// instead of simply deleting the current one
Log.i(TAG, "Initialize Device!")
// [finishBackup] will only be called when we return [TRANSPORT_OK] here
// so we remember that we initialized successfully
// we don't respect the intended system behavior of erasing all stored data
state.calledInitialize = true
return TRANSPORT_OK
}

View file

@ -41,7 +41,6 @@ val backupModule = module {
appBackupManager = get(),
kv = get(),
full = get(),
clock = get(),
packageService = get(),
metadataManager = get(),
settingsManager = get(),

View file

@ -138,14 +138,15 @@ internal class FullBackup(
suspend fun cancelFullBackup() {
val state = this.state ?: error("No state when canceling")
Log.i(TAG, "Cancel full backup for ${state.packageName}")
// TODO check if worth keeping the blobs. they've been uploaded already and may be re-usable
// so we could add them to the snapshot's blobMap or just let prune remove them at the end
// finalize the receiver
try {
backupReceiver.finalize(getOwner(state.packageName))
} catch (e: Exception) {
// as the backup was cancelled anyway, we don't care if finalizing had an error
Log.e(TAG, "Error finalizing backup in cancelFullBackup().", e)
}
// If the transport receives this callback, it will *not* receive a call to [finishBackup].
// It needs to tear down any ongoing backup state here.
clearState()
}

View file

@ -125,11 +125,6 @@ internal class PackageService(
packageInfo.allowsBackup()
}
/**
* A list of apps that do not allow backup.
*/
val userNotAllowedApps: List<PackageInfo> = emptyList()
val launchableSystemApps: List<ResolveInfo>
@WorkerThread
get() {

View file

@ -18,11 +18,10 @@ import android.util.Log.isLoggable
import com.stevesoltys.seedvault.ERROR_BACKUP_CANCELLED
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.repo.hexFromProto
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.worker.AppBackupPruneWorker
import com.stevesoltys.seedvault.worker.BackupRequester
import kotlinx.coroutines.runBlocking
@ -38,7 +37,6 @@ internal class NotificationBackupObserver(
) : IBackupObserver.Stub(), KoinComponent {
private val nm: BackupNotificationManager by inject()
private val metadataManager: MetadataManager by inject()
private val packageService: PackageService by inject()
private val settingsManager: SettingsManager by inject()
private val appBackupManager: AppBackupManager by inject()
@ -153,7 +151,7 @@ internal class NotificationBackupObserver(
success = snapshot != null
snapshot
}
val size = if (snapshot != null) { // TODO count size of APKs separately
val size = if (snapshot != null) { // TODO for later: count size of APKs separately
val chunkIds = snapshot.appsMap.values.flatMap { it.chunkIdsList }
chunkIds.sumOf {
snapshot.blobsMap[it.hexFromProto()]?.uncompressedLength?.toLong() ?: 0L

View file

@ -16,7 +16,6 @@ import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.backend.saf.SafHandler
import com.stevesoltys.seedvault.backend.webdav.WebDavHandler
import org.calyxos.seedvault.core.backends.webdav.WebDavProperties
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.storage.StorageBackupJobService
import com.stevesoltys.seedvault.transport.backup.BackupInitializer
@ -28,6 +27,7 @@ import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.backup.BackupJobService
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.saf.SafProperties
import org.calyxos.seedvault.core.backends.webdav.WebDavProperties
import java.io.IOException
import java.util.concurrent.TimeUnit

View file

@ -24,8 +24,6 @@ import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE
import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD
import org.koin.androidx.viewmodel.ext.android.getViewModel
private val TAG = StorageActivity::class.java.name
class StorageActivity : BackupActivity() {
private lateinit var viewModel: StorageViewModel

View file

@ -16,7 +16,6 @@ import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.backend.saf.SafHandler
import com.stevesoltys.seedvault.backend.webdav.WebDavHandler
import org.calyxos.seedvault.core.backends.webdav.WebDavProperties
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent
@ -26,6 +25,7 @@ import kotlinx.coroutines.launch
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.saf.SafProperties
import org.calyxos.seedvault.core.backends.webdav.WebDavConfig
import org.calyxos.seedvault.core.backends.webdav.WebDavProperties
internal abstract class StorageViewModel(
private val app: Application,
@ -48,8 +48,6 @@ internal abstract class StorageViewModel(
private var safOption: SafOption? = null
internal var isSetupWizard: Boolean = false
internal val hasStorageSet: Boolean
get() = backendManager.backendProperties != null
abstract val isRestoreOperation: Boolean
internal fun loadStorageRoots() {

View file

@ -17,11 +17,11 @@ import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Apk
import com.stevesoltys.seedvault.proto.Snapshot.Blob
import com.stevesoltys.seedvault.proto.SnapshotKt.split
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.repo.forProto
import com.stevesoltys.seedvault.repo.hexFromProto
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.isNotUpdatedSystemApp
import com.stevesoltys.seedvault.transport.backup.isTestOnly
import org.calyxos.seedvault.core.toHexString

View file

@ -8,7 +8,7 @@ package com.stevesoltys.seedvault.worker
import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.util.Log
import androidx.work.BackoffPolicy
import androidx.work.BackoffPolicy.EXPONENTIAL
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy.REPLACE
import androidx.work.ForegroundInfo
@ -36,7 +36,7 @@ class AppBackupPruneWorker(
fun scheduleNow(context: Context) {
val workRequest = OneTimeWorkRequestBuilder<AppBackupPruneWorker>()
.setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(10))
.setBackoffCriteria(EXPONENTIAL, Duration.ofSeconds(10))
.build()
val workManager = WorkManager.getInstance(context)
Log.i(TAG, "Asking to prune app backups now...")

View file

@ -24,8 +24,8 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_OBSERVER
import org.koin.core.component.KoinComponent

View file

@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.crypto.TYPE_ICONS
import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.transport.backup.PackageService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.calyxos.backup.storage.crypto.StreamCrypto.toByteArray
@ -150,13 +150,13 @@ internal class IconManager(
*/
@Suppress("DEPRECATION")
@Throws(IOException::class, SecurityException::class, GeneralSecurityException::class)
fun downloadIconsV1(version: Byte, token: Long, inputStream: InputStream): Set<String> {
fun downloadIconsV1(token: Long, inputStream: InputStream): Set<String> {
Log.d(TAG, "Start downloading icons")
val folder = File(context.cacheDir, CACHE_FOLDER)
if (!folder.isDirectory && !folder.mkdirs())
throw IOException("Can't create cache folder for icons")
val set = mutableSetOf<String>()
crypto.newDecryptingStreamV1(inputStream, getAD(version, token)).use { cryptoStream ->
crypto.newDecryptingStreamV1(inputStream, getAD(1.toByte(), token)).use { cryptoStream ->
ZipInputStream(cryptoStream).use { zip ->
var entry = zip.nextEntry
while (entry != null) {

View file

@ -21,7 +21,6 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import kotlin.random.Random
@Suppress("DEPRECATION")
@TestInstance(PER_METHOD)
class CryptoIntegrationTest {
@ -41,17 +40,6 @@ class CryptoIntegrationTest {
assertThat(crypto.getRandomBytes(42), not(equalTo(crypto.getRandomBytes(42))))
}
@Test
fun `decrypting encrypted cleartext works v1`() {
val ad = Random.nextBytes(42)
val outputStream = ByteArrayOutputStream()
crypto.newEncryptingStreamV1(outputStream, ad).use { it.write(cleartext) }
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
crypto.newDecryptingStreamV1(inputStream, ad).use {
assertReadEquals(cleartext, it)
}
}
@Test
fun `decrypting encrypted cleartext works v2`() {
val ad = Random.nextBytes(42)
@ -63,18 +51,6 @@ class CryptoIntegrationTest {
}
}
@Test
fun `decrypting encrypted cleartext fails with different AD v1`() {
val outputStream = ByteArrayOutputStream()
crypto.newEncryptingStreamV1(outputStream, Random.nextBytes(42)).use { it.write(cleartext) }
val inputStream = ByteArrayInputStream(outputStream.toByteArray())
assertThrows(IOException::class.java) {
crypto.newDecryptingStreamV1(inputStream, Random.nextBytes(41)).use {
it.read()
}
}
}
@Test
fun `decrypting encrypted cleartext fails with different AD v2`() {
val outputStream = ByteArrayOutputStream()

View file

@ -11,8 +11,8 @@ import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import junit.framework.Assert.assertTrue
import org.junit.Assert.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD

View file

@ -21,7 +21,6 @@ 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 io.mockk.Runs
import io.mockk.every
import io.mockk.just
@ -49,7 +48,6 @@ class MetadataManagerTest {
private val clock: Clock = mockk()
private val metadataWriter: MetadataWriter = mockk()
private val metadataReader: MetadataReader = mockk()
private val settingsManager: SettingsManager = mockk()
private val manager = MetadataManager(
context = context,

View file

@ -86,7 +86,7 @@ internal class BackupReceiverTest : TransportTest() {
}
@Test
fun `readFromStream`() = runBlocking {
fun readFromStream() = runBlocking {
val bytes = getRandomByteArray()
val chunkBytes1 = getRandomByteArray()
val chunkBytes2 = getRandomByteArray()

View file

@ -38,8 +38,7 @@ internal class SnapshotCreatorTest : TransportTest() {
private val ctx: Context = ApplicationProvider.getApplicationContext()
private val packageService: PackageService = mockk()
private val snapshotCreator =
SnapshotCreator(ctx, clock, packageService, settingsManager, metadataManager)
private val snapshotCreator = SnapshotCreator(ctx, clock, packageService, metadataManager)
@Test
fun `test onApkBackedUp`() {

View file

@ -22,16 +22,16 @@ import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.repo.SnapshotCreator
import com.stevesoltys.seedvault.repo.SnapshotManager
import com.stevesoltys.seedvault.repo.hexFromProto
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.repo.SnapshotManager
import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.repo.SnapshotCreator
import com.stevesoltys.seedvault.repo.hexFromProto
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
import com.stevesoltys.seedvault.worker.ApkBackup
import io.mockk.Runs
@ -41,7 +41,6 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.slot
import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.AppBackupFileType
import org.calyxos.seedvault.core.backends.Backend

View file

@ -29,15 +29,15 @@ import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.proto.SnapshotKt.blob
import com.stevesoltys.seedvault.proto.SnapshotKt.split
import com.stevesoltys.seedvault.proto.copy
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.repo.hexFromProto
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.transport.TransportTest
import com.stevesoltys.seedvault.repo.hexFromProto
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.every

View file

@ -26,14 +26,14 @@ 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.transport.restore.RestorableBackup
import com.stevesoltys.seedvault.repo.Loader
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.transport.TransportTest
import com.stevesoltys.seedvault.repo.Loader
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.every

View file

@ -9,9 +9,9 @@ import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.transport.TransportTest
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import kotlin.random.Random

View file

@ -93,16 +93,15 @@ internal class CoordinatorIntegrationTest : TransportTest() {
)
private val packageService: PackageService = mockk()
private val backup = BackupCoordinator(
context,
backendManager,
appBackupManager,
kvBackup,
fullBackup,
clock,
packageService,
metadataManager,
settingsManager,
notificationManager
context = context,
backendManager = backendManager,
appBackupManager = appBackupManager,
kv = kvBackup,
full = fullBackup,
packageService = packageService,
metadataManager = metadataManager,
settingsManager = settingsManager,
nm = notificationManager,
)
private val kvRestore = KVRestore(

View file

@ -13,7 +13,6 @@ import android.os.ParcelFileDescriptor
import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
@ -51,7 +50,6 @@ internal class BackupCoordinatorTest : BackupTest() {
appBackupManager = appBackupManager,
kv = kv,
full = full,
clock = clock,
packageService = packageService,
metadataManager = metadataManager,
settingsManager = settingsManager,
@ -60,7 +58,6 @@ internal class BackupCoordinatorTest : BackupTest() {
private val backend = mockk<Backend>()
private val fileDescriptor: ParcelFileDescriptor = mockk()
private val packageMetadata: PackageMetadata = mockk()
private val safProperties = SafProperties(
config = Uri.EMPTY,
name = getRandomString(),

View file

@ -66,7 +66,7 @@ internal class BackupCreationTest : BackupTest() {
private val appBackupManager = mockk<AppBackupManager>()
private val packageService = mockk<PackageService>()
private val snapshotCreator =
SnapshotCreator(context, clock, packageService, settingsManager, mockk(relaxed = true))
SnapshotCreator(context, clock, packageService, mockk(relaxed = true))
private val notificationManager = mockk<BackupNotificationManager>()
private val db = TestKvDbManager()
@ -80,7 +80,6 @@ internal class BackupCreationTest : BackupTest() {
appBackupManager = appBackupManager,
kv = kvBackup,
full = fullBackup,
clock = clock,
packageService = packageService,
metadataManager = metadataManager,
settingsManager = settingsManager,

View file

@ -31,7 +31,6 @@ import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.AppBackupFileType
@ -90,28 +89,17 @@ internal class RestoreCoordinatorTest : TransportTest() {
chunkIds = listOf(chunkId2),
)
mockkStatic("com.stevesoltys.seedvault.backend.BackendExtKt")
every { backendManager.backend } returns backend
}
@Test
fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking {
val info1 = FileInfo(LegacyAppBackupFile.Metadata(token), 1)
val info2 = FileInfo(LegacyAppBackupFile.Metadata(token + 1), 2)
val handle1 = LegacyAppBackupFile.Metadata(token)
val handle2 = LegacyAppBackupFile.Metadata(token + 1)
coEvery {
backend.list(
topLevelFolder = null,
AppBackupFileType.Snapshot::class, LegacyAppBackupFile.Metadata::class,
callback = captureLambda<(FileInfo) -> Unit>()
)
} answers {
val callback = lambda<(FileInfo) -> Unit>().captured
callback(info1)
callback(info2)
}
coEvery { backend.load(info1.fileHandle) } returns inputStream
coEvery { backend.load(info2.fileHandle) } returns inputStream
coEvery { backend.getAvailableBackupFileHandles() } returns listOf(handle1, handle2)
coEvery { backend.load(handle1) } returns inputStream
coEvery { backend.load(handle2) } returns inputStream
every { metadataReader.readMetadata(inputStream, token) } returns metadata
every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata
every { inputStream.close() } just Runs
@ -146,22 +134,12 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() fetches metadata if missing`() = runBlocking {
val info1 = FileInfo(LegacyAppBackupFile.Metadata(token), 1)
val info2 = FileInfo(LegacyAppBackupFile.Metadata(token + 1), 2)
val handle1 = LegacyAppBackupFile.Metadata(token)
val handle2 = LegacyAppBackupFile.Metadata(token + 1)
coEvery {
backend.list(
topLevelFolder = null,
AppBackupFileType.Snapshot::class, LegacyAppBackupFile.Metadata::class,
callback = captureLambda<(FileInfo) -> Unit>()
)
} answers {
val callback = lambda<(FileInfo) -> Unit>().captured
callback(info1)
callback(info2)
}
coEvery { backend.load(info1.fileHandle) } returns inputStream
coEvery { backend.load(info2.fileHandle) } returns inputStream
coEvery { backend.getAvailableBackupFileHandles() } returns listOf(handle1, handle2)
coEvery { backend.load(handle1) } returns inputStream
coEvery { backend.load(handle2) } returns inputStream
every { metadataReader.readMetadata(inputStream, token) } returns metadata
every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata
every { inputStream.close() } just Runs
@ -273,21 +251,10 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() errors when it can't find snapshots`() = runBlocking {
val handle = AppBackupFileType.Snapshot(repoId, getRandomByteArray(32).toHexString())
val info = FileInfo(handle, 1)
every { backendManager.backendProperties } returns safStorage
every { safStorage.isUnavailableUsb(context) } returns false
coEvery {
backend.list(
topLevelFolder = null,
AppBackupFileType.Snapshot::class, LegacyAppBackupFile.Metadata::class,
callback = captureLambda<(FileInfo) -> Unit>()
)
} answers {
val callback = lambda<(FileInfo) -> Unit>().captured
callback(info)
}
coEvery { backend.getAvailableBackupFileHandles() } returns listOf(handle)
coEvery { snapshotManager.loadSnapshot(handle) } returns snapshot.copy {
token = this@RestoreCoordinatorTest.token - 1 // unexpected token
}

View file

@ -23,8 +23,8 @@ import com.stevesoltys.seedvault.proto.copy
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.BackupData
import com.stevesoltys.seedvault.repo.BackupReceiver
import com.stevesoltys.seedvault.transport.backup.BackupTest
import com.stevesoltys.seedvault.repo.SnapshotCreator
import com.stevesoltys.seedvault.transport.backup.BackupTest
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify

View file

@ -23,6 +23,5 @@ public object Constants {
public val appSnapshotRegex: Regex = Regex("(^[a-f0-9]{64})\\.snapshot$")
public val fileSnapshotRegex: Regex = Regex("(^[0-9]{13})\\.SeedSnap$") // good until year 2286
public const val MIME_TYPE: String = "application/octet-stream"
public const val CHUNK_FOLDER_COUNT: Int = 256
}

View file

@ -16,7 +16,6 @@ import org.calyxos.backup.storage.api.BackupFile
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.backup.NotificationBackupObserver
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.toDuration
data class BackupProgress(
@ -91,7 +90,6 @@ internal class BackupStats(
liveData.postValue(BackupProgress(filesProcessed, totalFiles, null))
}
@OptIn(ExperimentalTime::class)
override suspend fun onBackupComplete(backupDuration: Long?) {
super.onBackupComplete(backupDuration)
@ -162,7 +160,6 @@ internal class BackupStats(
liveData.postValue(BackupProgress(filesProcessed, totalFiles, text))
}
@OptIn(ExperimentalTime::class)
override suspend fun onPruneComplete(pruneDuration: Long) {
super.onPruneComplete(pruneDuration)

View file

@ -10,17 +10,17 @@ import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
import android.security.keystore.KeyProperties.PURPOSE_SIGN
import android.security.keystore.KeyProperties.PURPOSE_VERIFY
import android.security.keystore.KeyProtection
import org.calyxos.seedvault.core.crypto.CoreCrypto.ALGORITHM_HMAC
import java.security.KeyStore
import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
object KeyManager: org.calyxos.seedvault.core.crypto.KeyManager {
object KeyManager : org.calyxos.seedvault.core.crypto.KeyManager {
private const val KEY_SIZE = 256
internal const val KEY_SIZE_BYTES = KEY_SIZE / 8
private const val KEY_SIZE_BYTES = KEY_SIZE / 8
private const val KEY_ALIAS_MASTER = "com.stevesoltys.seedvault.master"
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
private const val ALGORITHM_HMAC = "HmacSHA256"
private const val FAKE_SEED = "This is a legacy backup key 1234"

View file

@ -14,10 +14,8 @@ import org.calyxos.backup.storage.api.BackupFile
import org.calyxos.backup.storage.scanner.DocumentScanner
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
@OptIn(ExperimentalTime::class)
fun scanTree(context: Context, documentScanner: DocumentScanner, treeUri: Uri): String {
val sb = StringBuilder()
val timedResult = measureTimedValue {

View file

@ -13,7 +13,6 @@ import org.calyxos.backup.storage.api.BackupFile
import org.calyxos.backup.storage.scanner.MediaScanner
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.time.ExperimentalTime
import kotlin.time.TimedValue
import kotlin.time.measureTimedValue
@ -27,7 +26,6 @@ data class ScanResult(
}
}
@OptIn(ExperimentalTime::class)
fun scanUri(context: Context, mediaScanner: MediaScanner, uri: Uri): String {
val sb = StringBuilder()
val timedResult = measureTimedValue {
@ -57,7 +55,6 @@ fun dump(context: Context, mediaFiles: List<BackupFile>, sb: StringBuilder? = nu
return ScanResult(itemsFound, totalSize)
}
@OptIn(ExperimentalTime::class)
fun appendStats(
context: Context,
sb: StringBuilder,

View file

@ -17,7 +17,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import android.widget.Toast.LENGTH_SHORT
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import de.grobox.storagebackuptester.MainViewModel

View file

@ -6,4 +6,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -14,7 +14,7 @@
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="colorAccent">@color/matrix</item>
</style>

View file

@ -4,8 +4,6 @@
SPDX-License-Identifier: Apache-2.0
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>

View file

@ -11,7 +11,6 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.toDuration
/**
@ -20,7 +19,7 @@ import kotlin.time.toDuration
* So when building with AOSP 11, things will blow up.
*/
@OptIn(ExperimentalContracts::class, ExperimentalTime::class)
@OptIn(ExperimentalContracts::class)
internal inline fun measure(block: () -> Unit): Duration {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
@ -30,7 +29,7 @@ internal inline fun measure(block: () -> Unit): Duration {
return (System.currentTimeMillis() - start).toDuration(DurationUnit.MILLISECONDS)
}
@OptIn(ExperimentalContracts::class, ExperimentalTime::class)
@OptIn(ExperimentalContracts::class)
internal inline fun <T> measure(text: String, block: () -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)