Rename Storage to SafStorage

in preparation for generalization
This commit is contained in:
Torsten Grote 2024-04-10 17:29:13 -03:00
parent 6788d0d25a
commit 2489190824
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
19 changed files with 117 additions and 68 deletions

View file

@ -49,7 +49,7 @@ class PluginTest : KoinComponent {
@Before
fun setup() = runBlocking {
every { mockedSettingsManager.getStorage() } returns settingsManager.getStorage()
every { mockedSettingsManager.getSafStorage() } returns settingsManager.getSafStorage()
storage.rootBackupDir?.deleteContents(context)
?: error("Select a storage location in the app first!")
}
@ -76,7 +76,7 @@ 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 = settingsManager.getSafStorage() ?: error("no storage")
assertFalse(storagePlugin.hasBackup(s))
// prepare returned tokens requested when initializing device

View file

@ -17,7 +17,7 @@ internal class BackupRestoreTest : SeedvaultLargeTest() {
confirmCode()
}
if (settingsManager.getStorage() == null) {
if (settingsManager.getSafStorage() == null) {
chooseStorageLocation()
} else {
changeBackupLocation()

View file

@ -2,7 +2,7 @@ package com.stevesoltys.seedvault.plugins
import android.app.backup.RestoreSet
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.settings.Storage
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@ -59,7 +59,7 @@ interface StoragePlugin {
*/
@WorkerThread
@Throws(IOException::class)
suspend fun hasBackup(storage: Storage): Boolean
suspend fun hasBackup(safStorage: SafStorage): Boolean
/**
* Get the set of all backups currently available for restore.

View file

@ -9,7 +9,6 @@ import com.stevesoltys.seedvault.plugins.EncryptedMetadata
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.chunkFolderRegex
import com.stevesoltys.seedvault.plugins.tokenRegex
import com.stevesoltys.seedvault.settings.Storage
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
@ -28,7 +27,7 @@ internal class DocumentsProviderStoragePlugin(
*/
private val context: Context
get() = appContext.getStorageContext {
storage.storage?.isUsb == true
storage.safStorage?.isUsb == true
}
private val packageManager: PackageManager = appContext.packageManager
@ -79,10 +78,10 @@ internal class DocumentsProviderStoragePlugin(
}
@Throws(IOException::class)
override suspend fun hasBackup(storage: Storage): Boolean {
override suspend fun hasBackup(safStorage: SafStorage): Boolean {
// potentially get system user context if needed here
val c = appContext.getStorageContext { storage.isUsb }
val parent = DocumentFile.fromTreeUri(c, storage.uri) ?: throw AssertionError()
val c = appContext.getStorageContext { safStorage.isUsb }
val parent = DocumentFile.fromTreeUri(c, safStorage.uri) ?: throw AssertionError()
val rootDir = parent.findFileBlocking(c, DIRECTORY_ROOT) ?: return false
val backupSets = getBackups(c, rootDir)
return backupSets.isNotEmpty()

View file

@ -18,7 +18,6 @@ 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 kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
@ -45,9 +44,9 @@ internal class DocumentsStorage(
private val appContext: Context,
private val settingsManager: SettingsManager,
) {
internal var storage: Storage? = null
internal var safStorage: SafStorage? = null
get() {
if (field == null) field = settingsManager.getStorage()
if (field == null) field = settingsManager.getSafStorage()
return field
}
@ -56,14 +55,14 @@ internal class DocumentsStorage(
*/
private val context: Context
get() = appContext.getStorageContext {
storage?.isUsb == true
safStorage?.isUsb == true
}
private val contentResolver: ContentResolver get() = context.contentResolver
internal var rootBackupDir: DocumentFile? = null
get() = runBlocking {
if (field == null) {
val parent = storage?.getDocumentFile(context)
val parent = safStorage?.getDocumentFile(context)
?: return@runBlocking null
field = try {
parent.createOrGetDirectory(context, DIRECTORY_ROOT).apply {
@ -104,13 +103,13 @@ internal class DocumentsStorage(
* Resets this storage abstraction, forcing it to re-fetch cached values on next access.
*/
fun reset(newToken: Long?) {
storage = null
safStorage = null
currentToken = newToken
rootBackupDir = null
currentSetDir = null
}
fun getAuthority(): String? = storage?.uri?.authority
fun getAuthority(): String? = safStorage?.uri?.authority
@Throws(IOException::class)
suspend fun getSetDir(token: Long = currentToken ?: error("no token")): DocumentFile? {

View file

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.plugins.saf
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 SafStorage(
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, allowMetered: Boolean): Boolean {
return requiresNetwork && !hasUnmeteredInternet(context, allowMetered)
}
private fun hasUnmeteredInternet(context: Context, allowMetered: Boolean): Boolean {
val cm = context.getSystemService(ConnectivityManager::class.java) ?: return false
val isMetered = cm.isActiveNetworkMetered
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
(allowMetered || !isMetered)
}
}

View file

@ -13,7 +13,7 @@ import com.stevesoltys.seedvault.plugins.chunkFolderRegex
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.plugins.saf.FILE_NO_MEDIA
import com.stevesoltys.seedvault.plugins.tokenRegex
import com.stevesoltys.seedvault.settings.Storage
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.io.IOException
import java.io.InputStream
@ -135,7 +135,7 @@ internal class WebDavStoragePlugin(
}
@Throws(IOException::class)
override suspend fun hasBackup(storage: Storage): Boolean {
override suspend fun hasBackup(safStorage: SafStorage): Boolean {
// TODO this requires refactoring
return true
}

View file

@ -29,7 +29,7 @@ class SchedulingFragment : PreferenceFragmentCompat(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val storage = settingsManager.getStorage()
val storage = settingsManager.getSafStorage()
if (storage?.isUsb == true) {
findPreference<PreferenceCategory>("scheduling_category_conditions")?.isEnabled = false
}

View file

@ -22,6 +22,7 @@ import androidx.preference.TwoStatePreference
import androidx.work.WorkInfo
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.restore.RestoreActivity
import com.stevesoltys.seedvault.ui.toRelativeTime
import org.koin.android.ext.android.inject
@ -48,7 +49,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var menuBackupNow: MenuItem? = null
private var menuRestore: MenuItem? = null
private var storage: Storage? = null
private var safStorage: SafStorage? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
permitDiskReads {
@ -164,7 +165,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
// we need to re-set the title when returning to this fragment
activity?.setTitle(R.string.backup)
storage = settingsManager.getStorage()
safStorage = settingsManager.getSafStorage()
setBackupEnabledState()
setBackupLocationSummary()
setAutoRestoreState()
@ -241,7 +242,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
activity?.contentResolver?.let {
autoRestore.isChecked = Settings.Secure.getInt(it, BACKUP_AUTO_RESTORE, 1) == 1
}
val storage = this.storage
val storage = this.safStorage
if (storage?.isUsb == true) {
autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" +
getString(R.string.settings_auto_restore_summary_usb, storage.name)
@ -252,7 +253,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private fun setBackupLocationSummary() {
// get name of storage location
backupLocation.summary = storage?.name ?: getString(R.string.settings_backup_location_none)
backupLocation.summary = safStorage?.name ?: getString(R.string.settings_backup_location_none)
}
private fun setAppBackupStatusSummary(lastBackupInMillis: Long?) {
@ -271,7 +272,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
* says that nothing is scheduled which can happen when backup destination is on flash drive.
*/
private fun setAppBackupSchedulingSummary(workInfo: WorkInfo?) {
if (storage?.isUsb == true) {
if (safStorage?.isUsb == true) {
backupScheduling.summary = getString(R.string.settings_backup_status_next_backup_usb)
return
}

View file

@ -12,6 +12,7 @@ import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import com.stevesoltys.seedvault.getStorageContext
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import java.util.concurrent.ConcurrentSkipListSet
@ -88,24 +89,24 @@ class SettingsManager(private val context: Context) {
token = newToken
}
// FIXME Storage is currently plugin specific and not generic
fun setStorage(storage: Storage) {
// FIXME SafStorage is currently plugin specific and not generic
fun setSafStorage(safStorage: SafStorage) {
prefs.edit()
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
.putString(PREF_KEY_STORAGE_NAME, storage.name)
.putBoolean(PREF_KEY_STORAGE_IS_USB, storage.isUsb)
.putBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, storage.requiresNetwork)
.putString(PREF_KEY_STORAGE_URI, safStorage.uri.toString())
.putString(PREF_KEY_STORAGE_NAME, safStorage.name)
.putBoolean(PREF_KEY_STORAGE_IS_USB, safStorage.isUsb)
.putBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, safStorage.requiresNetwork)
.apply()
}
fun getStorage(): Storage? {
fun getSafStorage(): SafStorage? {
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
val uri = Uri.parse(uriStr)
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null)
?: throw IllegalStateException("no storage name")
val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false)
val requiresNetwork = prefs.getBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, false)
return Storage(uri, name, isUsb, requiresNetwork)
return SafStorage(uri, name, isUsb, requiresNetwork)
}
fun setFlashDrive(usb: FlashDrive?) {
@ -144,7 +145,7 @@ class SettingsManager(private val context: Context) {
*/
@WorkerThread
fun canDoBackupNow(): Boolean {
val storage = getStorage() ?: return false
val storage = getSafStorage() ?: return false
val systemContext = context.getStorageContext { storage.isUsb }
return !storage.isUnavailableUsb(systemContext) &&
!storage.isUnavailableNetwork(context, useMeteredNetwork)

View file

@ -131,7 +131,7 @@ internal class SettingsViewModel(
}
override fun onStorageLocationChanged() {
val storage = settingsManager.getStorage() ?: return
val storage = settingsManager.getSafStorage() ?: return
Log.i(TAG, "onStorageLocationChanged (isUsb: ${storage.isUsb}")
if (storage.isUsb) {
@ -156,7 +156,7 @@ internal class SettingsViewModel(
}
private fun onStoragePropertiesChanged() {
val storage = settingsManager.getStorage() ?: return
val storage = settingsManager.getSafStorage() ?: return
Log.d(TAG, "onStoragePropertiesChanged")
// register storage observer
@ -200,7 +200,7 @@ internal class SettingsViewModel(
i.putExtra(EXTRA_START_APP_BACKUP, true)
startForegroundService(app, i)
} else {
val isUsb = settingsManager.getStorage()?.isUsb ?: false
val isUsb = settingsManager.getSafStorage()?.isUsb ?: false
AppBackupWorker.scheduleNow(app, reschedule = !isUsb)
}
}
@ -280,14 +280,14 @@ internal class SettingsViewModel(
}
fun scheduleAppBackup(existingWorkPolicy: ExistingPeriodicWorkPolicy) {
val storage = settingsManager.getStorage() ?: error("no storage available")
val storage = settingsManager.getSafStorage() ?: error("no storage available")
if (!storage.isUsb && backupManager.isBackupEnabled) {
AppBackupWorker.schedule(app, settingsManager, existingWorkPolicy)
}
}
fun scheduleFilesBackup() {
val storage = settingsManager.getStorage() ?: error("no storage available")
val storage = settingsManager.getSafStorage() ?: error("no storage available")
if (!storage.isUsb && settingsManager.isStorageBackupEnabled()) {
BackupJobService.scheduleJob(
context = app,

View file

@ -18,7 +18,7 @@ internal class SeedvaultSafStoragePlugin(
*/
override val context: Context
get() = appContext.getStorageContext {
storage.storage?.isUsb == true
storage.safStorage?.isUsb == true
}
override val root: DocumentFile
get() = storage.rootBackupDir ?: error("No storage set")

View file

@ -43,7 +43,7 @@ internal class StorageBackupService : BackupService() {
override fun onBackupFinished(intent: Intent, success: Boolean) {
if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) {
val isUsb = settingsManager.getStorage()?.isUsb ?: false
val isUsb = settingsManager.getSafStorage()?.isUsb ?: false
AppBackupWorker.scheduleNow(applicationContext, reschedule = !isUsb)
}
}

View file

@ -411,7 +411,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 = settingsManager.getSafStorage() ?: return longBackoff
return when {
// back off if storage is removable and not available right now
storage.isUnavailableUsb(context) -> longBackoff

View file

@ -169,7 +169,7 @@ internal class RestoreCoordinator(
// check if we even have a backup of that app
if (metadataManager.getPackageMetadata(pmPackageName) != null) {
// remind user to plug in storage device
val storageName = settingsManager.getStorage()?.name
val storageName = settingsManager.getSafStorage()?.name
?: context.getString(R.string.settings_backup_location_none)
notificationManager.onRemovableStorageNotAvailableForRestore(
pmPackageName,
@ -365,7 +365,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 = settingsManager.getSafStorage() ?: return false
return storage.isUnavailableUsb(context)
}

View file

@ -74,7 +74,7 @@ internal class BackupStorageViewModel(
}
private fun scheduleBackupWorkers() {
val storage = settingsManager.getStorage() ?: error("no storage available")
val storage = settingsManager.getSafStorage() ?: error("no storage available")
if (!storage.isUsb) {
if (backupManager.isBackupEnabled) {
AppBackupWorker.schedule(app, settingsManager, CANCEL_AND_REENQUEUE)

View file

@ -16,10 +16,10 @@ import androidx.lifecycle.viewModelScope
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.SafStorage
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.ui.storage.StorageOption.SafOption
@ -47,7 +47,7 @@ internal abstract class StorageViewModel(
internal var isSetupWizard: Boolean = false
internal val hasStorageSet: Boolean
get() = settingsManager.getStorage() != null
get() = settingsManager.getSafStorage() != null
abstract val isRestoreOperation: Boolean
companion object {
@ -55,7 +55,7 @@ internal abstract class StorageViewModel(
context: Context,
settingsManager: SettingsManager,
): Boolean {
val storage = settingsManager.getStorage() ?: return false
val storage = settingsManager.getSafStorage() ?: return false
if (storage.isUsb) return true
return permitDiskReads {
storage.getDocumentFile(context).isDirectory
@ -106,20 +106,20 @@ internal abstract class StorageViewModel(
return saveStorage(storage)
}
protected fun createStorage(uri: Uri): Storage {
protected fun createStorage(uri: Uri): SafStorage {
val root = safOption ?: throw IllegalStateException("no storage root")
val name = if (root.isInternal()) {
"${root.title} (${app.getString(R.string.settings_backup_location_internal)})"
} else {
root.title
}
return Storage(uri, name, root.isUsb, root.requiresNetwork)
return SafStorage(uri, name, root.isUsb, root.requiresNetwork)
}
protected fun saveStorage(storage: Storage): Boolean {
settingsManager.setStorage(storage)
protected fun saveStorage(safStorage: SafStorage): Boolean {
settingsManager.setSafStorage(safStorage)
if (storage.isUsb) {
if (safStorage.isUsb) {
Log.d(TAG, "Selected storage is a removable USB device.")
val wasSaved = saveUsbDevice()
// reset stored flash drive, if we did not update it
@ -129,9 +129,9 @@ internal abstract class StorageViewModel(
}
BackupManagerSettings.resetDefaults(app.contentResolver)
Log.d(TAG, "New storage location saved: ${storage.uri}")
Log.d(TAG, "New storage location saved: ${safStorage.uri}")
return storage.isUsb
return safStorage.isUsb
}
private fun saveUsbDevice(): Boolean {

View file

@ -17,7 +17,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
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.plugins.saf.SafStorage
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.worker.ApkBackup
import io.mockk.Runs
@ -58,7 +58,7 @@ internal class BackupCoordinatorTest : BackupTest() {
private val metadataOutputStream = mockk<OutputStream>()
private val fileDescriptor: ParcelFileDescriptor = mockk()
private val packageMetadata: PackageMetadata = mockk()
private val storage = Storage(
private val safStorage = SafStorage(
uri = Uri.EMPTY,
name = getRandomString(),
isUsb = false,
@ -290,7 +290,7 @@ internal class BackupCoordinatorTest : BackupTest() {
)
} just Runs
coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs
every { settingsManager.getStorage() } returns storage
every { settingsManager.getSafStorage() } returns safStorage
every { settingsManager.useMeteredNetwork } returns false
every { metadataOutputStream.close() } just Runs
@ -340,7 +340,7 @@ internal class BackupCoordinatorTest : BackupTest() {
)
} just Runs
coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs
every { settingsManager.getStorage() } returns storage
every { settingsManager.getSafStorage() } returns safStorage
every { settingsManager.useMeteredNetwork } returns false
every { metadataOutputStream.close() } just Runs

View file

@ -16,7 +16,7 @@ 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.plugins.saf.SafStorage
import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.Runs
@ -57,7 +57,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
)
private val inputStream = mockk<InputStream>()
private val storage: Storage = mockk()
private val safStorage: SafStorage = mockk()
private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" }
private val packageInfoArray = arrayOf(packageInfo)
private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2)
@ -164,10 +164,10 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() optimized auto-restore with removed storage shows notification`() =
runBlocking {
every { settingsManager.getStorage() } returns storage
every { storage.isUnavailableUsb(context) } returns true
every { settingsManager.getSafStorage() } returns safStorage
every { safStorage.isUnavailableUsb(context) } returns true
every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L)
every { storage.name } returns storageName
every { safStorage.name } returns storageName
every {
notificationManager.onRemovableStorageNotAvailableForRestore(
packageName,
@ -188,8 +188,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() optimized auto-restore with available storage shows no notification`() =
runBlocking {
every { settingsManager.getStorage() } returns storage
every { storage.isUnavailableUsb(context) } returns false
every { settingsManager.getSafStorage() } returns safStorage
every { safStorage.isUnavailableUsb(context) } returns false
restore.beforeStartRestore(metadata)
assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
@ -204,8 +204,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() with removed storage shows no notification`() = runBlocking {
every { settingsManager.getStorage() } returns storage
every { storage.isUnavailableUsb(context) } returns true
every { settingsManager.getSafStorage() } returns safStorage
every { safStorage.isUnavailableUsb(context) } returns true
every { metadataManager.getPackageMetadata(packageName) } returns null
assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))