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