From 43184f4d237ef2005d0acb299e8f3221d9f3e879 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Fri, 3 Jan 2020 09:34:15 -0300
Subject: [PATCH 1/5] Add note to auto-restore setting in case removable
 storage is used

This warns the user that auto-restore will only work when their storage
is plugged in.
---
 .../com/stevesoltys/seedvault/settings/SettingsFragment.kt  | 6 ++++++
 app/src/main/res/values/strings.xml                         | 3 ++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
index 0afc43fe..b9a60120 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
@@ -180,6 +180,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
         activity?.contentResolver?.let {
             autoRestore.isChecked = Settings.Secure.getInt(it, BACKUP_AUTO_RESTORE, 1) == 1
         }
+        if (storage?.isUsb == true) {
+            autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" +
+                    getString(R.string.settings_auto_restore_summary_usb)
+        } else {
+            autoRestore.setSummary(R.string.settings_auto_restore_summary)
+        }
     }
 
     private fun setBackupLocationSummary(lastBackupInMillis: Long) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ceaeb399..cd845216 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,7 +17,8 @@
     <string name="settings_backup_location_summary">%1$s · Last Backup %2$s</string>
     <string name="settings_info">All backups are encrypted on your phone. To restore from backup you will need your 12-word recovery code.</string>
     <string name="settings_auto_restore_title">Automatic restore</string>
-    <string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string>
+    <string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data.</string>
+    <string name="settings_auto_restore_summary_usb">Note: Your USB flash drive needs to be plugged in for this to work.</string>
     <string name="settings_backup_apk_title">App backup</string>
     <string name="settings_backup_apk_summary">Back up the apps themselves. Otherwise, only app data would get backed up.</string>
     <string name="settings_backup_apk_dialog_title">Really disable app backup?</string>

From 783e676be2ceac77e262c14c74de37a5cba63923 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Fri, 3 Jan 2020 10:58:27 -0300
Subject: [PATCH 2/5] Optimize the restore of a single application (e.g. auto
 restore)

This restores only the @pm@ keys that are really needed
and thus speeds up installation with auto restore considerably
when using cloud storage such as NextCloud for example.
---
 .../java/com/stevesoltys/seedvault/App.kt     |  2 ++
 .../seedvault/transport/restore/KVRestore.kt  | 26 +++++++++++++++----
 .../transport/restore/RestoreCoordinator.kt   | 21 ++++++++++++---
 3 files changed, 40 insertions(+), 9 deletions(-)

diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt
index 3cebd5d3..bc4e03c2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/App.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt
@@ -63,5 +63,7 @@ class App : Application() {
 }
 
 const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
+const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
+const val GLOBAL_METADATA_KEY = "@meta@"
 
 fun isDebugBuild() = Build.TYPE == "userdebug"
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt
index 7142d85f..38c5d8b4 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt
@@ -6,6 +6,9 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 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
@@ -17,7 +20,11 @@ import javax.crypto.AEADBadTagException
 
 private class KVRestoreState(
         internal val token: Long,
-        internal val packageInfo: PackageInfo)
+        internal val packageInfo: PackageInfo,
+        /**
+         * Optional [PackageInfo] for single package restore, optimizes restore of @pm@
+         */
+        internal val pmPackageInfo: PackageInfo?)
 
 private val TAG = KVRestore::class.java.simpleName
 
@@ -42,9 +49,11 @@ internal class KVRestore(
      *
      * It is possible that the system decides to not restore the package.
      * Then a new state will be initialized right away without calling other methods.
+     *
+     * @param pmPackageInfo single optional [PackageInfo] to optimize restore of @pm@
      */
-    fun initializeState(token: Long, packageInfo: PackageInfo) {
-        state = KVRestoreState(token, packageInfo)
+    fun initializeState(token: Long, packageInfo: PackageInfo, pmPackageInfo: PackageInfo? = null) {
+        state = KVRestoreState(token, packageInfo, pmPackageInfo)
     }
 
     /**
@@ -111,8 +120,15 @@ internal class KVRestore(
         // Decode the key filenames into keys then sort lexically by key
         val contents = ArrayList<DecodedKey>()
         for (recordKey in records) contents.add(DecodedKey(recordKey))
-        contents.sort()
-        return contents
+        // remove keys that are not needed for single package @pm@ restore
+        val pmPackageName = state?.pmPackageInfo?.packageName
+        val sortedKeys = if (packageInfo.packageName == MAGIC_PACKAGE_MANAGER && pmPackageName != null) {
+            val keys = listOf(ANCESTRAL_RECORD_KEY, GLOBAL_METADATA_KEY, pmPackageName)
+            Log.d(TAG, "Single package restore, restrict restore keys to $pmPackageName")
+            contents.filterTo(ArrayList()) { it.key in keys }
+        } else contents
+        sortedKeys.sort()
+        return sortedKeys
     }
 
     /**
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
index 711a82fe..0ca8c373 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
@@ -10,6 +10,7 @@ import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 import android.util.Log
 import androidx.collection.LongSparseArray
+import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.header.UnsupportedVersionException
 import com.stevesoltys.seedvault.metadata.BackupMetadata
 import com.stevesoltys.seedvault.metadata.DecryptionFailedException
@@ -21,7 +22,12 @@ import java.io.IOException
 private class RestoreCoordinatorState(
         internal val token: Long,
         internal val packages: Iterator<PackageInfo>,
-        internal var currentPackage: String? = null)
+        /**
+         * Optional [PackageInfo] for single package restore, to reduce data needed to read for @pm@
+         */
+        internal val pmPackageInfo: PackageInfo?) {
+    internal var currentPackage: String? = null
+}
 
 private val TAG = RestoreCoordinator::class.java.simpleName
 
@@ -104,7 +110,14 @@ internal class RestoreCoordinator(
     fun startRestore(token: Long, packages: Array<out PackageInfo>): Int {
         check(state == null) { "Started new restore with existing state" }
         Log.i(TAG, "Start restore with ${packages.map { info -> info.packageName }}")
-        state = RestoreCoordinatorState(token, packages.iterator())
+
+        // If there's only one package to restore (Auto Restore feature), add it to the state
+        val pmPackageInfo = if (packages.size == 2 && packages[0].packageName == MAGIC_PACKAGE_MANAGER) {
+            Log.d(TAG, "Optimize for single package restore of ${packages[1].packageName}")
+            packages[1]
+        } else null
+
+        state = RestoreCoordinatorState(token, packages.iterator(), pmPackageInfo)
         failedPackages.clear()
         return TRANSPORT_OK
     }
@@ -148,7 +161,7 @@ internal class RestoreCoordinator(
                 // check key/value data first and if available, don't even check for full data
                 kv.hasDataForPackage(state.token, packageInfo) -> {
                     Log.i(TAG, "Found K/V data for $packageName.")
-                    kv.initializeState(state.token, packageInfo)
+                    kv.initializeState(state.token, packageInfo, state.pmPackageInfo)
                     state.currentPackage = packageName
                     TYPE_KEY_VALUE
                 }
@@ -174,7 +187,7 @@ internal class RestoreCoordinator(
     /**
      * Get the data for the application returned by [nextRestorePackage],
      * if that method reported [TYPE_KEY_VALUE] as its delivery type.
-     * If the package has only TYPE_FULL_STREAM data, then this method will return an error.
+     * If the package has only [TYPE_FULL_STREAM] data, then this method will return an error.
      *
      * @param data An open, writable file into which the key/value backup data should be stored.
      * @return the same error codes as [startRestore].

From 2bcf82d607f72c1580665812506282011e7d2678 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Fri, 3 Jan 2020 12:51:44 -0300
Subject: [PATCH 3/5] Show heads-up notification when auto-restore fails due to
 removed storage

---
 app/src/main/AndroidManifest.xml              | 12 ++++
 .../seedvault/BackupNotificationManager.kt    | 49 ++++++++++++++-
 .../seedvault/UsbIntentReceiver.kt            |  1 +
 .../restore/RestoreErrorBroadcastReceiver.kt  | 34 +++++++++++
 .../seedvault/settings/SettingsFragment.kt    |  3 +-
 .../transport/restore/RestoreCoordinator.kt   | 27 +++++++-
 .../transport/restore/RestoreModule.kt        |  2 +-
 app/src/main/res/values/strings.xml           |  7 ++-
 .../transport/CoordinatorIntegrationTest.kt   |  2 +-
 .../restore/RestoreCoordinatorTest.kt         | 61 +++++++++++++++++--
 10 files changed, 186 insertions(+), 12 deletions(-)
 create mode 100644 app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dc86fdff..e1e882d9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,10 @@
         android:name="android.permission.INSTALL_PACKAGES"
         tools:ignore="ProtectedPermissions" />
 
+    <!-- This is needed when using auto-restore with removable storage
+         to allow the user to uninstall an app when storage was not plugged in during install -->
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+
     <application
         android:name=".App"
         android:allowBackup="false"
@@ -87,5 +91,13 @@
                 android:resource="@xml/device_filter" />
         </receiver>
 
+        <receiver
+            android:name=".restore.RestoreErrorBroadcastReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.stevesoltys.seedvault.action.UNINSTALL" />
+            </intent-filter>
+        </receiver>
+
     </application>
 </manifest>
diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
index 4d8645b1..d48b196e 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
@@ -2,25 +2,32 @@ package com.stevesoltys.seedvault
 
 import android.app.NotificationChannel
 import android.app.NotificationManager
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
-import android.app.NotificationManager.IMPORTANCE_LOW
+import android.app.NotificationManager.*
 import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
 import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager.NameNotFoundException
 import androidx.core.app.NotificationCompat.*
+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.SettingsActivity
 
 private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
 private const val CHANNEL_ID_ERROR = "NotificationError"
+private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
 private const val NOTIFICATION_ID_OBSERVER = 1
 private const val NOTIFICATION_ID_ERROR = 2
+private const val NOTIFICATION_ID_RESTORE_ERROR = 3
 
 class BackupNotificationManager(private val context: Context) {
 
     private val nm = context.getSystemService(NotificationManager::class.java)!!.apply {
         createNotificationChannel(getObserverChannel())
         createNotificationChannel(getErrorChannel())
+        createNotificationChannel(getRestoreErrorChannel())
     }
 
     private fun getObserverChannel(): NotificationChannel {
@@ -35,6 +42,11 @@ class BackupNotificationManager(private val context: Context) {
         return NotificationChannel(CHANNEL_ID_ERROR, title, IMPORTANCE_DEFAULT)
     }
 
+    private fun getRestoreErrorChannel(): NotificationChannel {
+        val title = context.getString(R.string.notification_restore_error_channel_title)
+        return NotificationChannel(CHANNEL_ID_RESTORE_ERROR, title, IMPORTANCE_HIGH)
+    }
+
     private val observerBuilder = Builder(context, CHANNEL_ID_OBSERVER).apply {
         setSmallIcon(R.drawable.ic_cloud_upload)
     }
@@ -43,6 +55,10 @@ class BackupNotificationManager(private val context: Context) {
         setSmallIcon(R.drawable.ic_cloud_error)
     }
 
+    private val restoreErrorBuilder = Builder(context, CHANNEL_ID_RESTORE_ERROR).apply {
+        setSmallIcon(R.drawable.ic_cloud_error)
+    }
+
     fun onBackupUpdate(app: CharSequence, transferred: Int, expected: Int, userInitiated: Boolean) {
         val notification = observerBuilder.apply {
             setContentTitle(context.getString(R.string.notification_title))
@@ -93,4 +109,33 @@ class BackupNotificationManager(private val context: Context) {
         nm.cancel(NOTIFICATION_ID_ERROR)
     }
 
+    fun onRemovableStorageNotAvailableForRestore(packageName: String, storageName: String) {
+        val appName = try {
+            val appInfo = context.packageManager.getApplicationInfo(packageName, 0)
+            context.packageManager.getApplicationLabel(appInfo)
+        } catch (e: NameNotFoundException) {
+            packageName
+        }
+        val intent = Intent(ACTION_RESTORE_ERROR_UNINSTALL).apply {
+            setPackage(context.packageName)
+            putExtra(EXTRA_PACKAGE_NAME, packageName)
+        }
+        val pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_UNINSTALL, intent, FLAG_UPDATE_CURRENT)
+        val actionText = context.getString(R.string.notification_restore_error_action)
+        val action = Action(R.drawable.ic_warning, actionText, pendingIntent)
+        val notification = restoreErrorBuilder.apply {
+            setContentTitle(context.getString(R.string.notification_restore_error_title, appName))
+            setContentText(context.getString(R.string.notification_restore_error_text, storageName))
+            setWhen(System.currentTimeMillis())
+            setAutoCancel(true)
+            priority = PRIORITY_HIGH
+            mActions = arrayListOf(action)
+        }.build()
+        nm.notify(NOTIFICATION_ID_RESTORE_ERROR, notification)
+    }
+
+    fun onRestoreErrorSeen() {
+        nm.cancel(NOTIFICATION_ID_RESTORE_ERROR)
+    }
+
 }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
index 4ec749e9..8642a2e2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
@@ -23,6 +23,7 @@ private val TAG = UsbIntentReceiver::class.java.simpleName
 
 class UsbIntentReceiver : UsbMonitor() {
 
+    // using KoinComponent would crash robolectric tests :(
     private val settingsManager: SettingsManager by lazy { get().koin.get<SettingsManager>() }
     private val metadataManager: MetadataManager by lazy { get().koin.get<MetadataManager>() }
 
diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt
new file mode 100644
index 00000000..4d24c841
--- /dev/null
+++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreErrorBroadcastReceiver.kt
@@ -0,0 +1,34 @@
+package com.stevesoltys.seedvault.restore
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import androidx.core.net.toUri
+import com.stevesoltys.seedvault.BackupNotificationManager
+import org.koin.core.context.GlobalContext.get
+
+internal const val ACTION_RESTORE_ERROR_UNINSTALL = "com.stevesoltys.seedvault.action.UNINSTALL"
+internal const val EXTRA_PACKAGE_NAME = "com.stevesoltys.seedvault.extra.PACKAGE_NAME"
+internal const val REQUEST_CODE_UNINSTALL = 4576841
+
+class RestoreErrorBroadcastReceiver : BroadcastReceiver() {
+
+    // using KoinComponent would crash robolectric tests :(
+    private val notificationManager: BackupNotificationManager by lazy { get().koin.get<BackupNotificationManager>() }
+
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.action != ACTION_RESTORE_ERROR_UNINSTALL) return
+
+        notificationManager.onRestoreErrorSeen()
+
+        val packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME)!!
+        @Suppress("DEPRECATION")  // the alternative doesn't work for us
+        val i = Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
+            data = "package:$packageName".toUri()
+            flags = FLAG_ACTIVITY_NEW_TASK
+        }
+        context.startActivity(i)
+    }
+
+}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
index b9a60120..ae0c5e9a 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
@@ -180,9 +180,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
         activity?.contentResolver?.let {
             autoRestore.isChecked = Settings.Secure.getInt(it, BACKUP_AUTO_RESTORE, 1) == 1
         }
+        val storage = this.storage
         if (storage?.isUsb == true) {
             autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" +
-                    getString(R.string.settings_auto_restore_summary_usb)
+                    getString(R.string.settings_auto_restore_summary_usb, storage.name)
         } else {
             autoRestore.setSummary(R.string.settings_auto_restore_summary)
         }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
index 0ca8c373..29cbebf8 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
@@ -6,16 +6,20 @@ import android.app.backup.IBackupManager
 import android.app.backup.RestoreDescription
 import android.app.backup.RestoreDescription.*
 import android.app.backup.RestoreSet
+import android.content.Context
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 import android.util.Log
 import androidx.collection.LongSparseArray
+import com.stevesoltys.seedvault.BackupNotificationManager
 import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
+import com.stevesoltys.seedvault.R
 import com.stevesoltys.seedvault.header.UnsupportedVersionException
 import com.stevesoltys.seedvault.metadata.BackupMetadata
 import com.stevesoltys.seedvault.metadata.DecryptionFailedException
 import com.stevesoltys.seedvault.metadata.MetadataManager
 import com.stevesoltys.seedvault.metadata.MetadataReader
+import com.stevesoltys.seedvault.settings.SettingsManager
 import libcore.io.IoUtils.closeQuietly
 import java.io.IOException
 
@@ -32,7 +36,10 @@ private class RestoreCoordinatorState(
 private val TAG = RestoreCoordinator::class.java.simpleName
 
 internal class RestoreCoordinator(
+        private val context: Context,
+        private val settingsManager: SettingsManager,
         private val metadataManager: MetadataManager,
+        private val notificationManager: BackupNotificationManager,
         private val plugin: RestorePlugin,
         private val kv: KVRestore,
         private val full: FullRestore,
@@ -113,7 +120,19 @@ internal class RestoreCoordinator(
 
         // If there's only one package to restore (Auto Restore feature), add it to the state
         val pmPackageInfo = if (packages.size == 2 && packages[0].packageName == MAGIC_PACKAGE_MANAGER) {
-            Log.d(TAG, "Optimize for single package restore of ${packages[1].packageName}")
+            val pmPackageName = packages[1].packageName
+            Log.d(TAG, "Optimize for single package restore of $pmPackageName")
+            // 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) {
+                    // remind user to plug in storage device
+                    val storageName = settingsManager.getStorage()?.name
+                            ?: context.getString(R.string.settings_backup_location_none)
+                    notificationManager.onRemovableStorageNotAvailableForRestore(pmPackageName, storageName)
+                }
+                return TRANSPORT_ERROR
+            }
             packages[1]
         } else null
 
@@ -245,4 +264,10 @@ internal class RestoreCoordinator(
 
     fun isFailedPackage(packageName: String) = packageName in failedPackages
 
+    // TODO this is plugin specific, needs to be factored out when supporting different plugins
+    private fun isStorageRemovableAndNotAvailable(): Boolean {
+        val storage = settingsManager.getStorage() ?: return false
+        return storage.isUsb && !storage.getDocumentFile(context).isDirectory
+    }
+
 }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt
index a4d9f4a3..0bda5c71 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt
@@ -8,5 +8,5 @@ val restoreModule = module {
     factory { ApkRestore(androidContext(), get()) }
     single { KVRestore(get<RestorePlugin>().kvRestorePlugin, get(), get(), get()) }
     single { FullRestore(get<RestorePlugin>().fullRestorePlugin, get(), get(), get()) }
-    single { RestoreCoordinator(get(), get(), get(), get(), get()) }
+    single { RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
 }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cd845216..cde0d8c1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,7 +18,7 @@
     <string name="settings_info">All backups are encrypted on your phone. To restore from backup you will need your 12-word recovery code.</string>
     <string name="settings_auto_restore_title">Automatic restore</string>
     <string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data.</string>
-    <string name="settings_auto_restore_summary_usb">Note: Your USB flash drive needs to be plugged in for this to work.</string>
+    <string name="settings_auto_restore_summary_usb">Note: Your %1$s needs to be plugged in for this to work.</string>
     <string name="settings_backup_apk_title">App backup</string>
     <string name="settings_backup_apk_summary">Back up the apps themselves. Otherwise, only app data would get backed up.</string>
     <string name="settings_backup_apk_dialog_title">Really disable app backup?</string>
@@ -80,6 +80,11 @@
     <string name="notification_error_text">A device backup failed to run.</string>
     <string name="notification_error_action">Fix</string>
 
+    <string name="notification_restore_error_channel_title">Auto Restore Flash Drive Error</string>
+    <string name="notification_restore_error_title">Could not restore data for %1$s</string>
+    <string name="notification_restore_error_text">Plug in your %1$s before installing the app to restore its data from backup.</string>
+    <string name="notification_restore_error_action">Uninstall App</string>
+
     <!-- Restore -->
     <string name="restore_title">Restore from Backup</string>
     <string name="restore_choose_restore_set">Choose a backup to restore</string>
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 f497acbd..112ae974 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
@@ -53,7 +53,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
     private val kvRestore = KVRestore(kvRestorePlugin, outputFactory, headerReader, cryptoImpl)
     private val fullRestorePlugin = mockk<FullRestorePlugin>()
     private val fullRestore = FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
-    private val restore = RestoreCoordinator(metadataManager, restorePlugin, kvRestore, fullRestore, metadataReader)
+    private val restore = RestoreCoordinator(context, settingsManager, metadataManager, notificationManager, restorePlugin, kvRestore, fullRestore, metadataReader)
 
     private val backupDataInput = mockk<BackupDataInput>()
     private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt
index 50117f4f..09594e46 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
@@ -1,19 +1,21 @@
 package com.stevesoltys.seedvault.transport.restore
 
+import android.app.backup.BackupTransport.TRANSPORT_ERROR
 import android.app.backup.BackupTransport.TRANSPORT_OK
 import android.app.backup.RestoreDescription
 import android.app.backup.RestoreDescription.*
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
+import androidx.documentfile.provider.DocumentFile
+import com.stevesoltys.seedvault.BackupNotificationManager
 import com.stevesoltys.seedvault.getRandomString
 import com.stevesoltys.seedvault.metadata.BackupMetadata
 import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata
 import com.stevesoltys.seedvault.metadata.MetadataReader
+import com.stevesoltys.seedvault.metadata.PackageMetadata
+import com.stevesoltys.seedvault.settings.Storage
 import com.stevesoltys.seedvault.transport.TransportTest
-import io.mockk.Runs
-import io.mockk.every
-import io.mockk.just
-import io.mockk.mockk
+import io.mockk.*
 import org.junit.jupiter.api.Assertions.*
 import org.junit.jupiter.api.Test
 import java.io.IOException
@@ -22,18 +24,27 @@ import kotlin.random.Random
 
 internal class RestoreCoordinatorTest : TransportTest() {
 
+    private val notificationManager: BackupNotificationManager = mockk()
     private val plugin = mockk<RestorePlugin>()
     private val kv = mockk<KVRestore>()
     private val full = mockk<FullRestore>()
     private val metadataReader = mockk<MetadataReader>()
 
-    private val restore = RestoreCoordinator(metadataManager, plugin, kv, full, metadataReader)
+    private val restore = RestoreCoordinator(context, settingsManager, metadataManager, notificationManager, plugin, kv, full, metadataReader)
 
     private val token = Random.nextLong()
     private val inputStream = mockk<InputStream>()
+    private val storage: Storage = mockk()
+    private val documentFile: DocumentFile = mockk()
     private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" }
     private val packageInfoArray = arrayOf(packageInfo)
     private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2)
+    private val pmPackageInfoArray = arrayOf(
+            PackageInfo().apply { packageName = "@pm@" },
+            packageInfo
+    )
+    private val packageName = packageInfo.packageName
+    private val storageName = getRandomString()
 
     @Test
     fun `getAvailableRestoreSets() builds set from plugin response`() {
@@ -74,6 +85,46 @@ internal class RestoreCoordinatorTest : TransportTest() {
         }
     }
 
+    @Test
+    fun `startRestore() optimized auto-restore with removed storage shows notification`() {
+        every { settingsManager.getStorage() } returns storage
+        every { storage.isUsb } returns true
+        every { storage.getDocumentFile(context) } returns documentFile
+        every { documentFile.isDirectory } returns false
+        every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L)
+        every { storage.name } returns storageName
+        every { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) } just Runs
+
+        assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))
+
+        verify(exactly = 1) { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) }
+    }
+
+    @Test
+    fun `startRestore() optimized auto-restore with available storage shows no notification`() {
+        every { settingsManager.getStorage() } returns storage
+        every { storage.isUsb } returns true
+        every { storage.getDocumentFile(context) } returns documentFile
+        every { documentFile.isDirectory } returns true
+
+        assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
+
+        verify(exactly = 0) { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) }
+    }
+
+    @Test
+    fun `startRestore() optimized auto-restore with removed storage but no backup shows no notification`() {
+        every { settingsManager.getStorage() } returns storage
+        every { storage.isUsb } returns true
+        every { storage.getDocumentFile(context) } returns documentFile
+        every { documentFile.isDirectory } returns false
+        every { metadataManager.getPackageMetadata(packageName) } returns null
+
+        assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))
+
+        verify(exactly = 0) { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) }
+    }
+
     @Test
     fun `nextRestorePackage() throws without startRestore()`() {
         assertThrows(IllegalStateException::class.javaObjectType) {

From 6ed522bfb7d04d7d1f886a2ba3b61796128545ad Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Tue, 14 Jan 2020 15:11:17 -0300
Subject: [PATCH 4/5] Show one single progress bar in the notification

Also don't show individual package results,
but a single dismissible status notification in the end.

Closes #59, #61
---
 .../seedvault/BackupNotificationManager.kt    | 25 +++++++++++--
 .../seedvault/NotificationBackupObserver.kt   | 36 +++++++++++++++----
 .../seedvault/metadata/MetadataManager.kt     |  7 ++++
 .../ConfigurableBackupTransportService.kt     | 12 +++----
 app/src/main/res/values/strings.xml           |  5 ++-
 5 files changed, 67 insertions(+), 18 deletions(-)

diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
index d48b196e..561f44cb 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
@@ -63,6 +63,8 @@ class BackupNotificationManager(private val context: Context) {
         val notification = observerBuilder.apply {
             setContentTitle(context.getString(R.string.notification_title))
             setContentText(app)
+            setOngoing(true)
+            setShowWhen(false)
             setWhen(System.currentTimeMillis())
             setProgress(expected, transferred, false)
             priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
@@ -79,14 +81,33 @@ class BackupNotificationManager(private val context: Context) {
         val notification = observerBuilder.apply {
             setContentTitle(title)
             setContentText(app)
+            setOngoing(true)
+            setShowWhen(false)
             setWhen(System.currentTimeMillis())
             priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
         }.build()
         nm.notify(NOTIFICATION_ID_OBSERVER, notification)
     }
 
-    fun onBackupFinished() {
-        nm.cancel(NOTIFICATION_ID_OBSERVER)
+    fun onBackupFinished(success: Boolean, notBackedUp: Int?, userInitiated: Boolean) {
+        if (!userInitiated) {
+            nm.cancel(NOTIFICATION_ID_OBSERVER)
+            return
+        }
+        val titleRes = if (success) R.string.notification_success_title else R.string.notification_failed_title
+        val contentText = if (notBackedUp == null) null else {
+            context.getString(R.string.notification_success_num_not_backed_up, notBackedUp)
+        }
+        val notification = observerBuilder.apply {
+            setContentTitle(context.getString(titleRes))
+            setContentText(contentText)
+            setOngoing(false)
+            setShowWhen(true)
+            setWhen(System.currentTimeMillis())
+            setProgress(0, 0, false)
+            priority = PRIORITY_LOW
+        }.build()
+        nm.notify(NOTIFICATION_ID_OBSERVER, notification)
     }
 
     fun onBackupError() {
diff --git a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt
index c3394065..b579d4f7 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager.NameNotFoundException
 import android.util.Log
 import android.util.Log.INFO
 import android.util.Log.isLoggable
+import com.stevesoltys.seedvault.metadata.MetadataManager
 import org.koin.core.KoinComponent
 import org.koin.core.inject
 
@@ -14,9 +15,18 @@ private val TAG = NotificationBackupObserver::class.java.simpleName
 
 class NotificationBackupObserver(
         private val context: Context,
+        private val expectedPackages: Int,
         private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent {
 
     private val nm: BackupNotificationManager by inject()
+    private val metadataManager: MetadataManager by inject()
+    private var currentPackage: String? = null
+    private var numPackages: Int = 0
+
+    init {
+        // we need to show this manually as [onUpdate] isn't called for first @pm@ package
+        nm.onBackupUpdate(getAppName(MAGIC_PACKAGE_MANAGER), 0, expectedPackages, userInitiated)
+    }
 
     /**
      * This method could be called several times for packages with full data backup.
@@ -26,10 +36,7 @@ class NotificationBackupObserver(
      * @param backupProgress Current progress of backup for the package.
      */
     override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) {
-        val transferred = backupProgress.bytesTransferred.toInt()
-        val expected = backupProgress.bytesExpected.toInt()
-        val app = getAppName(currentBackupPackage)
-        nm.onBackupUpdate(app, transferred, expected, userInitiated)
+        showProgressNotification(currentBackupPackage)
     }
 
     /**
@@ -46,7 +53,8 @@ class NotificationBackupObserver(
         if (isLoggable(TAG, INFO)) {
             Log.i(TAG, "Completed. Target: $target, status: $status")
         }
-        nm.onBackupResult(getAppName(target), status, userInitiated)
+        // often [onResult] gets called right away without any [onUpdate] call
+        showProgressNotification(target)
     }
 
     /**
@@ -58,9 +66,23 @@ class NotificationBackupObserver(
      */
     override fun backupFinished(status: Int) {
         if (isLoggable(TAG, INFO)) {
-            Log.i(TAG, "Backup finished. Status: $status")
+            Log.i(TAG, "Backup finished $numPackages/$expectedPackages. Status: $status")
         }
-        nm.onBackupFinished()
+        val success = status == 0
+        val notBackedUp = if (success) metadataManager.getPackagesNumNotBackedUp() else null
+        nm.onBackupFinished(success, notBackedUp, userInitiated)
+    }
+
+    private fun showProgressNotification(packageName: String) {
+        if (currentPackage == packageName) return
+
+        if (isLoggable(TAG, INFO)) {
+            Log.i(TAG, "Showing progress notification for $currentPackage $numPackages/$expectedPackages")
+        }
+        currentPackage = packageName
+        val app = getAppName(packageName)
+        numPackages += 1
+        nm.onBackupUpdate(app, numPackages, expectedPackages, userInitiated)
     }
 
     private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId)
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
index 799b13cd..2ef14ef5 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
@@ -178,6 +178,13 @@ class MetadataManager(
         return metadata.packageMetadataMap[packageName]?.copy()
     }
 
+    @Synchronized
+    fun getPackagesNumNotBackedUp(): Int {
+        return metadata.packageMetadataMap.filter { (_, packageMetadata) ->
+            !packageMetadata.system && packageMetadata.state != APK_AND_DATA
+        }.count()
+    }
+
     @Synchronized
     @VisibleForTesting
     private fun getMetadataFromCache(): BackupMetadata? {
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 f4392134..688f1026 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt
@@ -15,7 +15,6 @@ import androidx.annotation.WorkerThread
 import com.stevesoltys.seedvault.BackupMonitor
 import com.stevesoltys.seedvault.BackupNotificationManager
 import com.stevesoltys.seedvault.NotificationBackupObserver
-import com.stevesoltys.seedvault.R
 import com.stevesoltys.seedvault.transport.backup.PackageService
 import org.koin.core.context.GlobalContext.get
 
@@ -52,19 +51,16 @@ class ConfigurableBackupTransportService : Service() {
 
 @WorkerThread
 fun requestBackup(context: Context) {
-    // show notification
-    val nm: BackupNotificationManager = get().koin.get()
-    nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true)
-
     val packageService: PackageService = get().koin.get()
-    val observer = NotificationBackupObserver(context, true)
-    val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED
     val packages = packageService.eligiblePackages
+
+    val observer = NotificationBackupObserver(context, packages.size, true)
     val result = try {
         val backupManager: IBackupManager = get().koin.get()
-        backupManager.requestBackup(packages, observer, BackupMonitor(), flags)
+        backupManager.requestBackup(packages, observer, BackupMonitor(), FLAG_USER_INITIATED)
     } catch (e: RemoteException) {
         Log.e(TAG, "Error during backup: ", e)
+        val nm: BackupNotificationManager = get().koin.get()
         nm.onBackupError()
     }
     if (result == BackupManager.SUCCESS) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cde0d8c1..bc3132ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -70,11 +70,14 @@
     <!-- Notification -->
     <string name="notification_channel_title">Backup Notification</string>
     <string name="notification_title">Backup running</string>
-    <string name="notification_backup_starting">Starting Backup…</string>
     <string name="notification_backup_result_complete">Backup complete</string>
     <string name="notification_backup_result_rejected">Not backed up</string>
     <string name="notification_backup_result_error">Backup failed</string>
 
+    <string name="notification_success_title">Backup finished</string>
+    <string name="notification_success_num_not_backed_up">%1$d apps could not get backed up</string>
+    <string name="notification_failed_title">Backup failed</string>
+
     <string name="notification_error_channel_title">Error Notification</string>
     <string name="notification_error_title">Backup Error</string>
     <string name="notification_error_text">A device backup failed to run.</string>

From de160329056f95653723e9f1d1ac5f25f2b7fb39 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Tue, 14 Jan 2020 15:24:33 -0300
Subject: [PATCH 5/5] Don't use wildcard imports, because they are considered
 harmful

---
 .../stevesoltys/seedvault/BackupMonitor.kt    |  4 ++-
 .../seedvault/BackupNotificationManager.kt    | 10 +++++--
 .../seedvault/UsbIntentReceiver.kt            |  4 ++-
 .../stevesoltys/seedvault/crypto/Crypto.kt    |  8 ++++-
 .../seedvault/crypto/KeyManager.kt            |  5 +++-
 .../seedvault/metadata/MetadataReader.kt      |  6 +++-
 .../seedvault/plugins/saf/DocumentsStorage.kt | 10 +++++--
 .../restore/InstallProgressAdapter.kt         |  5 +++-
 .../restore/RestoreProgressAdapter.kt         | 12 ++++++--
 .../seedvault/restore/RestoreSetAdapter.kt    |  4 ++-
 .../seedvault/restore/RestoreViewModel.kt     | 14 +++++++--
 .../seedvault/transport/backup/ApkBackup.kt   |  6 +++-
 .../transport/backup/BackupCoordinator.kt     | 10 +++++--
 .../seedvault/transport/backup/FullBackup.kt  |  6 +++-
 .../seedvault/transport/backup/KVBackup.kt    |  6 +++-
 .../transport/restore/ApkInstaller.kt         | 12 ++++++--
 .../seedvault/transport/restore/ApkRestore.kt |  8 +++--
 .../transport/restore/FullRestore.kt          |  5 +++-
 .../transport/restore/RestoreCoordinator.kt   |  4 ++-
 .../ui/recoverycode/RecoveryCodeViewModel.kt  |  6 +++-
 .../ui/storage/StorageRootFetcher.kt          | 11 ++++++-
 .../ui/storage/StorageRootsFragment.kt        |  4 ++-
 .../seedvault/crypto/CryptoTest.kt            | 28 +++++++++++++++---
 .../seedvault/header/HeaderReaderTest.kt      |  4 ++-
 .../header/HeaderWriterReaderTest.kt          |  4 ++-
 .../seedvault/metadata/MetadataManagerTest.kt | 12 ++++++--
 .../seedvault/metadata/MetadataReaderTest.kt  |  6 +++-
 .../metadata/MetadataWriterDecoderTest.kt     |  5 +++-
 .../transport/CoordinatorIntegrationTest.kt   | 29 ++++++++++++++++---
 .../transport/backup/ApkBackupTest.kt         | 12 ++++++--
 .../transport/backup/BackupCoordinatorTest.kt | 16 ++++++++--
 .../transport/backup/FullBackupTest.kt        |  9 ++++--
 .../transport/backup/KVBackupTest.kt          | 10 +++++--
 .../transport/restore/ApkRestoreTest.kt       | 10 +++++--
 .../transport/restore/FullRestoreTest.kt      | 11 +++++--
 .../transport/restore/KVRestoreTest.kt        |  6 +++-
 .../restore/RestoreCoordinatorTest.kt         | 16 ++++++++--
 .../transport/restore/RestoreTest.kt          |  2 +-
 38 files changed, 278 insertions(+), 62 deletions(-)

diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt
index 0049fcef..8e961916 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt
@@ -1,6 +1,8 @@
 package com.stevesoltys.seedvault
 
-import android.app.backup.BackupManagerMonitor.*
+import android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY
+import android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_ID
+import android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME
 import android.app.backup.IBackupManagerMonitor
 import android.os.Bundle
 import android.util.Log
diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
index 561f44cb..d5cc7297 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt
@@ -2,14 +2,20 @@ package com.stevesoltys.seedvault
 
 import android.app.NotificationChannel
 import android.app.NotificationManager
-import android.app.NotificationManager.*
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.NotificationManager.IMPORTANCE_LOW
 import android.app.PendingIntent
 import android.app.PendingIntent.FLAG_UPDATE_CURRENT
 import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager.NameNotFoundException
-import androidx.core.app.NotificationCompat.*
+import androidx.core.app.NotificationCompat.Action
+import androidx.core.app.NotificationCompat.Builder
+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.restore.ACTION_RESTORE_ERROR_UNINSTALL
 import com.stevesoltys.seedvault.restore.EXTRA_PACKAGE_NAME
 import com.stevesoltys.seedvault.restore.REQUEST_CODE_UNINSTALL
diff --git a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
index 8642a2e2..5cd5e940 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
@@ -6,7 +6,9 @@ import android.content.Intent
 import android.database.ContentObserver
 import android.hardware.usb.UsbDevice
 import android.hardware.usb.UsbInterface
-import android.hardware.usb.UsbManager.*
+import android.hardware.usb.UsbManager.ACTION_USB_DEVICE_ATTACHED
+import android.hardware.usb.UsbManager.ACTION_USB_DEVICE_DETACHED
+import android.hardware.usb.UsbManager.EXTRA_DEVICE
 import android.net.Uri
 import android.os.Handler
 import android.provider.DocumentsContract
diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt
index 9494178d..7d2415af 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt
@@ -1,6 +1,12 @@
 package com.stevesoltys.seedvault.crypto
 
-import com.stevesoltys.seedvault.header.*
+import com.stevesoltys.seedvault.header.HeaderReader
+import com.stevesoltys.seedvault.header.HeaderWriter
+import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH
+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 java.io.EOFException
 import java.io.IOException
 import java.io.InputStream
diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt
index 157c7e65..9785857e 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/KeyManager.kt
@@ -1,7 +1,10 @@
 package com.stevesoltys.seedvault.crypto
 
 import android.os.Build.VERSION.SDK_INT
-import android.security.keystore.KeyProperties.*
+import android.security.keystore.KeyProperties.BLOCK_MODE_GCM
+import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE
+import android.security.keystore.KeyProperties.PURPOSE_DECRYPT
+import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
 import android.security.keystore.KeyProtection
 import java.security.KeyStore
 import java.security.KeyStore.SecretKeyEntry
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
index 9ee4f0b0..7fd90a04 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
@@ -4,7 +4,11 @@ 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.*
+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 org.json.JSONException
 import org.json.JSONObject
 import java.io.IOException
diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt
index 28c83435..ff91fa9d 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt
@@ -5,8 +5,14 @@ import android.content.Context
 import android.content.pm.PackageInfo
 import android.database.ContentObserver
 import android.net.Uri
-import android.provider.DocumentsContract.*
-import android.provider.DocumentsContract.Document.*
+import android.provider.DocumentsContract.Document.COLUMN_DOCUMENT_ID
+import android.provider.DocumentsContract.Document.COLUMN_MIME_TYPE
+import android.provider.DocumentsContract.Document.MIME_TYPE_DIR
+import android.provider.DocumentsContract.EXTRA_LOADING
+import android.provider.DocumentsContract.buildChildDocumentsUriUsingTree
+import android.provider.DocumentsContract.buildDocumentUriUsingTree
+import android.provider.DocumentsContract.buildTreeDocumentUri
+import android.provider.DocumentsContract.getDocumentId
 import android.util.Log
 import androidx.documentfile.provider.DocumentFile
 import com.stevesoltys.seedvault.metadata.MetadataManager
diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt
index 3e4b6c45..0b576fb7 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt
@@ -14,7 +14,10 @@ import androidx.recyclerview.widget.SortedList
 import androidx.recyclerview.widget.SortedListAdapterCallback
 import com.stevesoltys.seedvault.R
 import com.stevesoltys.seedvault.transport.restore.ApkRestoreResult
-import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.*
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.FAILED
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.IN_PROGRESS
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.QUEUED
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.SUCCEEDED
 
 internal class InstallProgressAdapter : Adapter<AppViewHolder>() {
 
diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt
index c1a1d575..6e8e0711 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressAdapter.kt
@@ -3,7 +3,9 @@ package com.stevesoltys.seedvault.restore
 import android.content.pm.PackageManager.NameNotFoundException
 import android.view.LayoutInflater
 import android.view.View
-import android.view.View.*
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.ProgressBar
@@ -13,7 +15,13 @@ import androidx.recyclerview.widget.RecyclerView.Adapter
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
 import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.R
-import com.stevesoltys.seedvault.restore.AppRestoreStatus.*
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_INSTALLED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
 import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder
 import java.util.*
 
diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt
index f31ab1e1..8acb0eef 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt
@@ -1,6 +1,8 @@
 package com.stevesoltys.seedvault.restore
 
-import android.text.format.DateUtils.*
+import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE
+import android.text.format.DateUtils.HOUR_IN_MILLIS
+import android.text.format.DateUtils.getRelativeTimeSpanString
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt
index 0dffd8fd..fbc414ca 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt
@@ -20,8 +20,18 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.R
 import com.stevesoltys.seedvault.crypto.KeyManager
 import com.stevesoltys.seedvault.getAppName
-import com.stevesoltys.seedvault.metadata.PackageState.*
-import com.stevesoltys.seedvault.restore.AppRestoreStatus.*
+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.restore.AppRestoreStatus.FAILED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_INSTALLED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
+import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
 import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
 import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
 import com.stevesoltys.seedvault.settings.SettingsManager
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
index f5711f49..4ab5f396 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
@@ -8,7 +8,11 @@ import android.util.Log
 import android.util.PackageUtils.computeSha256DigestBytes
 import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.encodeBase64
-import com.stevesoltys.seedvault.metadata.*
+import com.stevesoltys.seedvault.metadata.MetadataManager
+import com.stevesoltys.seedvault.metadata.PackageMetadata
+import com.stevesoltys.seedvault.metadata.PackageState
+import com.stevesoltys.seedvault.metadata.isSystemApp
+import com.stevesoltys.seedvault.metadata.isUpdatedSystemApp
 import com.stevesoltys.seedvault.settings.SettingsManager
 import java.io.File
 import java.io.FileNotFoundException
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
index 12f1ee11..03f5f3d5 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
@@ -1,6 +1,9 @@
 package com.stevesoltys.seedvault.transport.backup
 
-import android.app.backup.BackupTransport.*
+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 android.content.Context
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
@@ -10,7 +13,10 @@ import com.stevesoltys.seedvault.Clock
 import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.metadata.MetadataManager
 import com.stevesoltys.seedvault.metadata.PackageState
-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.isSystemApp
 import com.stevesoltys.seedvault.settings.SettingsManager
 import java.io.IOException
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt
index 198e7bb5..0782bcbe 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt
@@ -1,6 +1,10 @@
 package com.stevesoltys.seedvault.transport.backup
 
-import android.app.backup.BackupTransport.*
+import android.app.backup.BackupTransport.FLAG_USER_INITIATED
+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 android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 import android.util.Log
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt
index fc43701f..8c1dceff 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt
@@ -1,6 +1,10 @@
 package com.stevesoltys.seedvault.transport.backup
 
-import android.app.backup.BackupTransport.*
+import android.app.backup.BackupTransport.FLAG_INCREMENTAL
+import android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL
+import android.app.backup.BackupTransport.TRANSPORT_ERROR
+import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED
+import android.app.backup.BackupTransport.TRANSPORT_OK
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 import android.util.Log
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt
index b32e13b5..cd8662c2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkInstaller.kt
@@ -2,10 +2,18 @@ package com.stevesoltys.seedvault.transport.restore
 
 import android.app.PendingIntent
 import android.app.PendingIntent.FLAG_UPDATE_CURRENT
-import android.content.*
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
 import android.content.Intent.FLAG_RECEIVER_FOREGROUND
+import android.content.IntentFilter
+import android.content.IntentSender
 import android.content.pm.PackageInstaller
-import android.content.pm.PackageInstaller.*
+import android.content.pm.PackageInstaller.EXTRA_PACKAGE_NAME
+import android.content.pm.PackageInstaller.EXTRA_STATUS
+import android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE
+import android.content.pm.PackageInstaller.STATUS_SUCCESS
+import android.content.pm.PackageInstaller.SessionParams
 import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
 import android.content.pm.PackageManager
 import android.util.Log
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt
index bf090c70..8a1ff785 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt
@@ -1,7 +1,9 @@
 package com.stevesoltys.seedvault.transport.restore
 
 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.content.pm.PackageManager.NameNotFoundException
 import android.graphics.drawable.Drawable
 import android.util.Log
 import com.stevesoltys.seedvault.encodeBase64
@@ -9,7 +11,9 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata
 import com.stevesoltys.seedvault.metadata.PackageMetadataMap
 import com.stevesoltys.seedvault.metadata.isSystemApp
 import com.stevesoltys.seedvault.transport.backup.getSignatures
-import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.*
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.FAILED
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.IN_PROGRESS
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.QUEUED
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.flow.collect
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt
index 3cc7fc6c..a233a490 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt
@@ -1,6 +1,9 @@
 package com.stevesoltys.seedvault.transport.restore
 
-import android.app.backup.BackupTransport.*
+import android.app.backup.BackupTransport.NO_MORE_DATA
+import android.app.backup.BackupTransport.TRANSPORT_ERROR
+import android.app.backup.BackupTransport.TRANSPORT_OK
+import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 import android.util.Log
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
index 29cbebf8..a13a6822 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt
@@ -4,7 +4,9 @@ import android.app.backup.BackupTransport.TRANSPORT_ERROR
 import android.app.backup.BackupTransport.TRANSPORT_OK
 import android.app.backup.IBackupManager
 import android.app.backup.RestoreDescription
-import android.app.backup.RestoreDescription.*
+import android.app.backup.RestoreDescription.NO_MORE_PACKAGES
+import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
+import android.app.backup.RestoreDescription.TYPE_KEY_VALUE
 import android.app.backup.RestoreSet
 import android.content.Context
 import android.content.pm.PackageInfo
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 146dbab3..aa27f64e 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
@@ -5,11 +5,15 @@ import com.stevesoltys.seedvault.App
 import com.stevesoltys.seedvault.crypto.KeyManager
 import com.stevesoltys.seedvault.ui.LiveEvent
 import com.stevesoltys.seedvault.ui.MutableLiveEvent
-import io.github.novacrypto.bip39.*
+import io.github.novacrypto.bip39.JavaxPBKDF2WithHmacSHA512
+import io.github.novacrypto.bip39.MnemonicGenerator
+import io.github.novacrypto.bip39.MnemonicValidator
+import io.github.novacrypto.bip39.SeedCalculator
 import io.github.novacrypto.bip39.Validation.InvalidChecksumException
 import io.github.novacrypto.bip39.Validation.InvalidWordCountException
 import io.github.novacrypto.bip39.Validation.UnexpectedWhiteSpaceException
 import io.github.novacrypto.bip39.Validation.WordNotFoundException
+import io.github.novacrypto.bip39.Words
 import io.github.novacrypto.bip39.wordlists.English
 import java.security.SecureRandom
 import java.util.*
diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt
index 6617f4cf..ef6431b2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt
@@ -14,7 +14,16 @@ import android.net.Uri
 import android.os.Handler
 import android.provider.DocumentsContract
 import android.provider.DocumentsContract.PROVIDER_INTERFACE
-import android.provider.DocumentsContract.Root.*
+import android.provider.DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
+import android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID
+import android.provider.DocumentsContract.Root.COLUMN_FLAGS
+import android.provider.DocumentsContract.Root.COLUMN_ICON
+import android.provider.DocumentsContract.Root.COLUMN_ROOT_ID
+import android.provider.DocumentsContract.Root.COLUMN_SUMMARY
+import android.provider.DocumentsContract.Root.COLUMN_TITLE
+import android.provider.DocumentsContract.Root.FLAG_REMOVABLE_USB
+import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_CREATE
+import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
 import android.util.Log
 import com.stevesoltys.seedvault.R
 import java.lang.Long.parseLong
diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt
index 299df5a3..a7de2dcd 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt
@@ -1,7 +1,9 @@
 package com.stevesoltys.seedvault.ui.storage
 
 import android.content.Intent
-import android.content.Intent.*
+import android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
+import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt
index 2b27d675..cf25f040 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoTest.kt
@@ -3,13 +3,33 @@ 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.*
-import io.mockk.*
-import org.junit.jupiter.api.Assertions.*
+import com.stevesoltys.seedvault.header.HeaderReader
+import com.stevesoltys.seedvault.header.HeaderWriter
+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 io.mockk.CapturingSlot
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.fail
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD
-import java.io.*
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.EOFException
+import java.io.IOException
+import java.io.InputStream
 import javax.crypto.Cipher
 import kotlin.random.Random
 
diff --git a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt
index 6c06e0f3..ab83b885 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt
@@ -3,7 +3,9 @@ package com.stevesoltys.seedvault.header
 import com.stevesoltys.seedvault.Utf8
 import com.stevesoltys.seedvault.assertContains
 import com.stevesoltys.seedvault.getRandomString
-import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertThrows
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
diff --git a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderWriterReaderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderWriterReaderTest.kt
index b47dfc66..03b4cd26 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderWriterReaderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderWriterReaderTest.kt
@@ -2,7 +2,9 @@ package com.stevesoltys.seedvault.header
 
 import com.stevesoltys.seedvault.getRandomByteArray
 import com.stevesoltys.seedvault.getRandomString
-import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertThrows
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
index c1595818..694cc2bb 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
@@ -10,7 +10,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.stevesoltys.seedvault.Clock
 import com.stevesoltys.seedvault.getRandomByteArray
 import com.stevesoltys.seedvault.getRandomString
-import com.stevesoltys.seedvault.metadata.PackageState.*
+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 io.mockk.Runs
 import io.mockk.every
 import io.mockk.just
@@ -21,7 +25,11 @@ import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.koin.core.context.stopKoin
-import java.io.*
+import java.io.ByteArrayOutputStream
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
 import kotlin.random.Random
 
 @RunWith(AndroidJUnit4::class)
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 b12aa2d8..6dba62f6 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataReaderTest.kt
@@ -8,7 +8,11 @@ import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
 import io.mockk.mockk
 import org.json.JSONArray
 import org.json.JSONObject
-import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.fail
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
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 6778e8c9..adaa8e96 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
@@ -2,7 +2,10 @@ package com.stevesoltys.seedvault.metadata
 
 import com.stevesoltys.seedvault.crypto.Crypto
 import com.stevesoltys.seedvault.getRandomString
-import com.stevesoltys.seedvault.metadata.PackageState.*
+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 io.mockk.mockk
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Test
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 112ae974..577dbd76 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
@@ -18,10 +18,31 @@ import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH
 import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
 import com.stevesoltys.seedvault.metadata.PackageMetadata
 import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
-import com.stevesoltys.seedvault.transport.backup.*
-import com.stevesoltys.seedvault.transport.restore.*
-import io.mockk.*
-import org.junit.jupiter.api.Assertions.*
+import com.stevesoltys.seedvault.transport.backup.ApkBackup
+import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
+import com.stevesoltys.seedvault.transport.backup.BackupPlugin
+import com.stevesoltys.seedvault.transport.backup.DEFAULT_QUOTA_FULL_BACKUP
+import com.stevesoltys.seedvault.transport.backup.FullBackup
+import com.stevesoltys.seedvault.transport.backup.FullBackupPlugin
+import com.stevesoltys.seedvault.transport.backup.InputFactory
+import com.stevesoltys.seedvault.transport.backup.KVBackup
+import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
+import com.stevesoltys.seedvault.transport.backup.PackageService
+import com.stevesoltys.seedvault.transport.restore.FullRestore
+import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
+import com.stevesoltys.seedvault.transport.restore.KVRestore
+import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
+import com.stevesoltys.seedvault.transport.restore.OutputFactory
+import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
+import com.stevesoltys.seedvault.transport.restore.RestorePlugin
+import io.mockk.CapturingSlot
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.fail
 import org.junit.jupiter.api.Test
 import java.io.ByteArrayInputStream
 import java.io.ByteArrayOutputStream
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt
index dc99353a..7a4168de 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt
@@ -10,8 +10,16 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.getRandomString
 import com.stevesoltys.seedvault.metadata.PackageMetadata
 import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
-import io.mockk.*
-import org.junit.jupiter.api.Assertions.*
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.io.TempDir
 import java.io.ByteArrayOutputStream
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
index 96524896..2bb51378 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
@@ -1,6 +1,9 @@
 package com.stevesoltys.seedvault.transport.backup
 
-import android.app.backup.BackupTransport.*
+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 android.content.pm.PackageInfo
 import android.net.Uri
 import android.os.ParcelFileDescriptor
@@ -9,9 +12,16 @@ import com.stevesoltys.seedvault.BackupNotificationManager
 import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.getRandomString
 import com.stevesoltys.seedvault.metadata.PackageMetadata
-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.settings.Storage
-import io.mockk.*
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verify
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Assertions.assertThrows
 import org.junit.jupiter.api.Test
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt
index 80cf7f51..91f3d1aa 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt
@@ -1,11 +1,16 @@
 package com.stevesoltys.seedvault.transport.backup
 
-import android.app.backup.BackupTransport.*
+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 io.mockk.Runs
 import io.mockk.every
 import io.mockk.just
 import io.mockk.mockk
-import org.junit.jupiter.api.Assertions.*
+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 java.io.FileInputStream
 import java.io.IOException
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt
index 69775e67..66956d92 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt
@@ -1,7 +1,11 @@
 package com.stevesoltys.seedvault.transport.backup
 
 import android.app.backup.BackupDataInput
-import android.app.backup.BackupTransport.*
+import android.app.backup.BackupTransport.FLAG_INCREMENTAL
+import android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL
+import android.app.backup.BackupTransport.TRANSPORT_ERROR
+import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED
+import android.app.backup.BackupTransport.TRANSPORT_OK
 import com.stevesoltys.seedvault.Utf8
 import com.stevesoltys.seedvault.getRandomString
 import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE
@@ -10,7 +14,9 @@ import io.mockk.Runs
 import io.mockk.every
 import io.mockk.just
 import io.mockk.mockk
-import org.junit.jupiter.api.Assertions.*
+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 java.io.IOException
 import java.util.*
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/ApkRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/ApkRestoreTest.kt
index d5c3c81f..c8a823ab 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/ApkRestoreTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/ApkRestoreTest.kt
@@ -2,14 +2,19 @@ package com.stevesoltys.seedvault.transport.restore
 
 import android.content.Context
 import android.content.pm.ApplicationInfo
-import android.content.pm.ApplicationInfo.*
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.ApplicationInfo.FLAG_SYSTEM
+import android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
 import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
 import com.stevesoltys.seedvault.getRandomString
 import com.stevesoltys.seedvault.metadata.PackageMetadata
 import com.stevesoltys.seedvault.metadata.PackageMetadataMap
-import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.*
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.FAILED
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.IN_PROGRESS
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.QUEUED
+import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.SUCCEEDED
 import io.mockk.every
 import io.mockk.mockk
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -24,7 +29,6 @@ import org.junit.jupiter.api.io.TempDir
 import java.io.ByteArrayInputStream
 import java.io.File
 import java.nio.file.Path
-import java.util.logging.Logger.getLogger
 import kotlin.random.Random
 
 @ExperimentalCoroutinesApi
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 65f62727..46f5532e 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
@@ -1,6 +1,9 @@
 package com.stevesoltys.seedvault.transport.restore
 
-import android.app.backup.BackupTransport.*
+import android.app.backup.BackupTransport.NO_MORE_DATA
+import android.app.backup.BackupTransport.TRANSPORT_ERROR
+import android.app.backup.BackupTransport.TRANSPORT_OK
+import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
 import com.stevesoltys.seedvault.getRandomByteArray
 import com.stevesoltys.seedvault.header.UnsupportedVersionException
 import com.stevesoltys.seedvault.header.VERSION
@@ -9,7 +12,11 @@ import io.mockk.Runs
 import io.mockk.every
 import io.mockk.just
 import io.mockk.mockk
-import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import java.io.ByteArrayOutputStream
 import java.io.EOFException
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 304f471f..db0c29a3 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
@@ -8,7 +8,11 @@ import com.stevesoltys.seedvault.getRandomByteArray
 import com.stevesoltys.seedvault.header.UnsupportedVersionException
 import com.stevesoltys.seedvault.header.VERSION
 import com.stevesoltys.seedvault.header.VersionHeader
-import io.mockk.*
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verifyAll
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Assertions.assertThrows
 import org.junit.jupiter.api.Test
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 09594e46..fd8b5ca5 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
@@ -3,7 +3,9 @@ package com.stevesoltys.seedvault.transport.restore
 import android.app.backup.BackupTransport.TRANSPORT_ERROR
 import android.app.backup.BackupTransport.TRANSPORT_OK
 import android.app.backup.RestoreDescription
-import android.app.backup.RestoreDescription.*
+import android.app.backup.RestoreDescription.NO_MORE_PACKAGES
+import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
+import android.app.backup.RestoreDescription.TYPE_KEY_VALUE
 import android.content.pm.PackageInfo
 import android.os.ParcelFileDescriptor
 import androidx.documentfile.provider.DocumentFile
@@ -15,8 +17,16 @@ import com.stevesoltys.seedvault.metadata.MetadataReader
 import com.stevesoltys.seedvault.metadata.PackageMetadata
 import com.stevesoltys.seedvault.settings.Storage
 import com.stevesoltys.seedvault.transport.TransportTest
-import io.mockk.*
-import org.junit.jupiter.api.Assertions.*
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.fail
 import org.junit.jupiter.api.Test
 import java.io.IOException
 import java.io.InputStream
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 b240dbc8..417cb940 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,9 +2,9 @@ package com.stevesoltys.seedvault.transport.restore
 
 import android.os.ParcelFileDescriptor
 import com.stevesoltys.seedvault.getRandomByteArray
-import com.stevesoltys.seedvault.transport.TransportTest
 import com.stevesoltys.seedvault.header.HeaderReader
 import com.stevesoltys.seedvault.header.VERSION
+import com.stevesoltys.seedvault.transport.TransportTest
 import io.mockk.mockk
 import java.io.InputStream
 import kotlin.random.Random