Auto-format code style of all files to match official style

This also adds a note to the README
and the Android Studio coding style files.
This commit is contained in:
Torsten Grote 2020-09-24 15:45:59 -03:00 committed by Chirayu Desai
parent 55909ce305
commit 53937bda2f
72 changed files with 1776 additions and 1166 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@ out/
lib/
.idea/*
!.idea/runConfigurations*
!.idea/codeStyles*
*.ipr
*.iws
*.iml

View file

@ -0,0 +1,139 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View file

@ -34,5 +34,7 @@ It uses the same internal APIs as `adb backup` which is deprecated and thus need
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/stevesoltys/seedvault.
This project aims to adhere to the [official Kotlin coding style](https://developer.android.com/kotlin/style-guide).
## License
This application is available as open source under the terms of the [Apache-2.0 License](https://opensource.org/licenses/Apache-2.0).

View file

@ -1,7 +1,7 @@
package com.stevesoltys.seedvault
import java.nio.charset.Charset
import java.util.*
import java.util.Base64
val Utf8: Charset = Charset.forName("UTF-8")

View file

@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit.HOURS
private val TAG = UsbIntentReceiver::class.java.simpleName
private const val HOURS_AUTO_BACKUP: Long = 24
class UsbIntentReceiver : UsbMonitor() {
// using KoinComponent would crash robolectric tests :(
@ -37,7 +39,8 @@ class UsbIntentReceiver : UsbMonitor() {
val attachedFlashDrive = FlashDrive.from(device)
return if (savedFlashDrive == attachedFlashDrive) {
Log.d(TAG, "Matches stored device, checking backup time...")
if (System.currentTimeMillis() - metadataManager.getLastBackupTime() >= HOURS.toMillis(24)) {
val backupMillis = System.currentTimeMillis() - metadataManager.getLastBackupTime()
if (backupMillis >= HOURS.toMillis(HOURS_AUTO_BACKUP)) {
Log.d(TAG, "Last backup older than 24 hours, requesting a backup...")
true
} else {
@ -101,6 +104,7 @@ internal fun UsbDevice.isMassStorage(): Boolean {
}
private fun UsbInterface.isMassStorage(): Boolean {
@Suppress("MagicNumber")
return interfaceClass == 8 && interfaceProtocol == 80 && interfaceSubclass == 6
}

View file

@ -75,8 +75,12 @@ interface Crypto {
* @return The read [VersionHeader] present in the beginning of the given [InputStream].
*/
@Throws(IOException::class, SecurityException::class)
fun decryptHeader(inputStream: InputStream, expectedVersion: Byte, expectedPackageName: String,
expectedKey: String? = null): VersionHeader
fun decryptHeader(
inputStream: InputStream,
expectedVersion: Byte,
expectedPackageName: String,
expectedKey: String? = null
): VersionHeader
/**
* Reads and decrypts a segment from the given [InputStream].
@ -96,7 +100,8 @@ interface Crypto {
internal class CryptoImpl(
private val cipherFactory: CipherFactory,
private val headerWriter: HeaderWriter,
private val headerReader: HeaderReader) : Crypto {
private val headerReader: HeaderReader
) : Crypto {
@Throws(IOException::class)
override fun encryptHeader(outputStream: OutputStream, versionHeader: VersionHeader) {
@ -136,16 +141,26 @@ internal class CryptoImpl(
}
@Throws(IOException::class, SecurityException::class)
override fun decryptHeader(inputStream: InputStream, expectedVersion: Byte,
expectedPackageName: String, expectedKey: String?): VersionHeader {
override fun decryptHeader(
inputStream: InputStream,
expectedVersion: Byte,
expectedPackageName: String,
expectedKey: String?
): VersionHeader {
val decrypted = decryptSegment(inputStream, MAX_VERSION_HEADER_SIZE)
val header = headerReader.getVersionHeader(decrypted)
if (header.version != expectedVersion) {
throw SecurityException("Invalid version '${header.version.toInt()}' in header, expected '${expectedVersion.toInt()}'.")
throw SecurityException(
"Invalid version '${header.version.toInt()}' in header, " +
"expected '${expectedVersion.toInt()}'."
)
}
if (header.packageName != expectedPackageName) {
throw SecurityException("Invalid package name '${header.packageName}' in header, expected '$expectedPackageName'.")
throw SecurityException(
"Invalid package name '${header.packageName}' in header, " +
"expected '$expectedPackageName'."
)
}
if (header.key != expectedKey) {
throw SecurityException("Invalid key '${header.key}' in header, expected '$expectedKey'.")

View file

@ -5,7 +5,8 @@ import com.stevesoltys.seedvault.crypto.GCM_AUTHENTICATION_TAG_LENGTH
internal const val VERSION: Byte = 0
internal const val MAX_PACKAGE_LENGTH_SIZE = 255
internal const val MAX_KEY_LENGTH_SIZE = MAX_PACKAGE_LENGTH_SIZE
internal const val MAX_VERSION_HEADER_SIZE = 1 + Short.SIZE_BYTES * 2 + MAX_PACKAGE_LENGTH_SIZE + MAX_KEY_LENGTH_SIZE
internal const val MAX_VERSION_HEADER_SIZE =
1 + Short.SIZE_BYTES * 2 + MAX_PACKAGE_LENGTH_SIZE + MAX_KEY_LENGTH_SIZE
/**
* After the first version byte of each backup stream
@ -26,10 +27,10 @@ data class VersionHeader(
}
}
internal const val SEGMENT_LENGTH_SIZE: Int = Short.SIZE_BYTES
internal const val MAX_SEGMENT_LENGTH: Int = Short.MAX_VALUE.toInt()
internal const val MAX_SEGMENT_CLEARTEXT_LENGTH: Int = MAX_SEGMENT_LENGTH - GCM_AUTHENTICATION_TAG_LENGTH / 8
internal const val MAX_SEGMENT_CLEARTEXT_LENGTH: Int =
MAX_SEGMENT_LENGTH - GCM_AUTHENTICATION_TAG_LENGTH / 8
internal const val IV_SIZE: Int = 12
internal const val SEGMENT_HEADER_SIZE = SEGMENT_LENGTH_SIZE + IV_SIZE

View file

@ -32,23 +32,28 @@ enum class PackageState {
* This is the expected state of all user-installed packages.
*/
APK_AND_DATA,
/**
* Package data could not get backed up, because the app exceeded the allowed quota.
*/
QUOTA_EXCEEDED,
/**
* Package data could not get backed up, because the app reported no data to back up.
*/
NO_DATA,
/**
* Package data could not get backed up, because the app has [FLAG_STOPPED].
*/
WAS_STOPPED,
/**
* Package data could not get backed up, because it was not allowed.
* Most often, this is a manifest opt-out, but it could also be a disabled or system-user app.
*/
NOT_ALLOWED,
/**
* Package data could not get backed up, because an error occurred during backup.
*/
@ -86,9 +91,11 @@ internal class DecryptionFailedException(cause: Throwable) : Exception(cause)
class EncryptedBackupMetadata private constructor(
val token: Long,
val inputStream: InputStream?,
val error: Boolean) {
val error: Boolean
) {
constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false)
/**
* Indicates that there was an error retrieving the encrypted backup metadata.
*/

View file

@ -182,7 +182,10 @@ class MetadataManager(
* If the token is 0L, it is not yet initialized and must not be used for anything.
*/
@Synchronized
@Deprecated("Responsibility for current token moved to SettingsManager", ReplaceWith("settingsManager.getToken()"))
@Deprecated(
"Responsibility for current token moved to SettingsManager",
ReplaceWith("settingsManager.getToken()")
)
fun getBackupToken(): Long = metadata.token
/**

View file

@ -18,17 +18,31 @@ import javax.crypto.AEADBadTagException
interface MetadataReader {
@Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class)
@Throws(
SecurityException::class,
DecryptionFailedException::class,
UnsupportedVersionException::class,
IOException::class
)
fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata
@Throws(SecurityException::class)
fun decode(bytes: ByteArray, expectedVersion: Byte? = null, expectedToken: Long? = null): BackupMetadata
fun decode(
bytes: ByteArray,
expectedVersion: Byte? = null,
expectedToken: Long? = null
): BackupMetadata
}
internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
@Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class)
@Throws(
SecurityException::class,
DecryptionFailedException::class,
UnsupportedVersionException::class,
IOException::class
)
override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata {
val version = inputStream.read().toByte()
if (version < 0) throw IOException()
@ -42,7 +56,11 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
}
@Throws(SecurityException::class)
override fun decode(bytes: ByteArray, expectedVersion: Byte?, expectedToken: Long?): BackupMetadata {
override fun decode(
bytes: ByteArray,
expectedVersion: Byte?,
expectedToken: Long?
): BackupMetadata {
// NOTE: We don't do extensive validation of the parsed input here,
// because it was encrypted with authentication, so we should be able to trust it.
//
@ -54,7 +72,10 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
val meta = json.getJSONObject(JSON_METADATA)
val version = meta.getInt(JSON_METADATA_VERSION).toByte()
if (expectedVersion != null && version != expectedVersion) {
throw SecurityException("Invalid version '${version.toInt()}' in metadata, expected '${expectedVersion.toInt()}'.")
throw SecurityException(
"Invalid version '${version.toInt()}' in metadata," +
"expected '${expectedVersion.toInt()}'."
)
}
val token = meta.getLong(JSON_METADATA_TOKEN)
if (expectedToken != null && token != expectedToken) {
@ -78,12 +99,13 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
val pInstaller = p.optString(JSON_PACKAGE_INSTALLER)
val pSha256 = p.optString(JSON_PACKAGE_SHA256)
val pSignatures = p.optJSONArray(JSON_PACKAGE_SIGNATURES)
val signatures = if (pSignatures == null) null else
val signatures = if (pSignatures == null) null else {
ArrayList<String>(pSignatures.length()).apply {
for (i in (0 until pSignatures.length())) {
add(pSignatures.getString(i))
}
}
}
packageMetadataMap[packageName] = PackageMetadata(
time = p.getLong(JSON_PACKAGE_TIME),
state = pState,

View file

@ -18,14 +18,22 @@ import com.stevesoltys.seedvault.ui.AppViewHolder
internal class InstallProgressAdapter : Adapter<AppInstallViewHolder>() {
private val items = SortedList<ApkRestoreResult>(ApkRestoreResult::class.java, object : SortedListAdapterCallback<ApkRestoreResult>(this) {
override fun areItemsTheSame(item1: ApkRestoreResult, item2: ApkRestoreResult) = item1.packageName == item2.packageName
override fun areContentsTheSame(oldItem: ApkRestoreResult, newItem: ApkRestoreResult) = oldItem == newItem
override fun compare(item1: ApkRestoreResult, item2: ApkRestoreResult) = item1.compareTo(item2)
private val items = SortedList<ApkRestoreResult>(
ApkRestoreResult::class.java,
object : SortedListAdapterCallback<ApkRestoreResult>(this) {
override fun areItemsTheSame(item1: ApkRestoreResult, item2: ApkRestoreResult) =
item1.packageName == item2.packageName
override fun areContentsTheSame(oldItem: ApkRestoreResult, newItem: ApkRestoreResult) =
oldItem == newItem
override fun compare(item1: ApkRestoreResult, item2: ApkRestoreResult) =
item1.compareTo(item2)
})
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppInstallViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false)
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item_app_status, parent, false)
return AppInstallViewHolder(v)
}

View file

@ -4,8 +4,10 @@ import android.app.backup.RestoreSet
import com.stevesoltys.seedvault.metadata.BackupMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
data class RestorableBackup(private val restoreSet: RestoreSet,
private val backupMetadata: BackupMetadata) {
data class RestorableBackup(
private val restoreSet: RestoreSet,
private val backupMetadata: BackupMetadata
) {
val name: String
get() = restoreSet.name

View file

@ -23,6 +23,7 @@ class RestoreErrorBroadcastReceiver : BroadcastReceiver() {
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()

View file

@ -17,7 +17,8 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
private val items = LinkedList<AppRestoreResult>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false)
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item_app_status, parent, false)
return PackageViewHolder(v)
}
@ -36,7 +37,8 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
private class Diff(
private val oldItems: LinkedList<AppRestoreResult>,
private val newItems: LinkedList<AppRestoreResult>) : DiffUtil.Callback() {
private val newItems: LinkedList<AppRestoreResult>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
@ -83,4 +85,5 @@ enum class AppRestoreStatus {
internal data class AppRestoreResult(
val packageName: String,
val name: CharSequence,
val status: AppRestoreStatus)
val status: AppRestoreStatus
)

View file

@ -32,8 +32,11 @@ class RestoreProgressFragment : Fragment() {
private lateinit var appList: RecyclerView
private lateinit var button: Button
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v: View = inflater.inflate(R.layout.fragment_restore_progress, container, false)
progressBar = v.findViewById(R.id.progressBar)

View file

@ -14,7 +14,8 @@ import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder
internal class RestoreSetAdapter(
private val listener: RestorableBackupClickListener,
private val items: List<RestorableBackup>) : Adapter<RestoreSetViewHolder>() {
private val items: List<RestorableBackup>
) : Adapter<RestoreSetViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RestoreSetViewHolder {
val v = LayoutInflater.from(parent.context)
@ -39,7 +40,8 @@ internal class RestoreSetAdapter(
val lastBackup = getRelativeTime(item.time)
val setup = getRelativeTime(item.token)
subtitleView.text = v.context.getString(R.string.restore_restore_set_times, lastBackup, setup)
subtitleView.text =
v.context.getString(R.string.restore_restore_set_times, lastBackup, setup)
}
private fun getRelativeTime(time: Long): CharSequence {

View file

@ -19,7 +19,7 @@ import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.ui.notification.getAppName
import com.stevesoltys.seedvault.metadata.BackupMetadata
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
@ -44,6 +44,7 @@ import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import com.stevesoltys.seedvault.ui.notification.getAppName
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -52,7 +53,7 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import java.util.*
import java.util.LinkedList
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -83,7 +84,8 @@ internal class RestoreViewModel(
private val mChosenRestorableBackup = MutableLiveData<RestorableBackup>()
internal val chosenRestorableBackup: LiveData<RestorableBackup> get() = mChosenRestorableBackup
internal val installResult: LiveData<InstallResult> = switchMap(mChosenRestorableBackup) { backup ->
internal val installResult: LiveData<InstallResult> =
switchMap(mChosenRestorableBackup) { backup ->
@Suppress("EXPERIMENTAL_API_USAGE")
getInstallResult(backup)
}
@ -93,7 +95,13 @@ internal class RestoreViewModel(
private val mRestoreProgress = MutableLiveData<LinkedList<AppRestoreResult>>().apply {
value = LinkedList<AppRestoreResult>().apply {
add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, getAppName(app, MAGIC_PACKAGE_MANAGER), IN_PROGRESS))
add(
AppRestoreResult(
packageName = MAGIC_PACKAGE_MANAGER,
name = getAppName(app, MAGIC_PACKAGE_MANAGER),
status = IN_PROGRESS
)
)
}
}
internal val restoreProgress: LiveData<LinkedList<AppRestoreResult>> get() = mRestoreProgress
@ -114,7 +122,8 @@ internal class RestoreViewModel(
mRestoreSetResults.value = getAvailableRestoreSets()
}
private suspend fun getAvailableRestoreSets() = suspendCoroutine<RestoreSetResult> { continuation ->
private suspend fun getAvailableRestoreSets() =
suspendCoroutine<RestoreSetResult> { continuation ->
val session = try {
getOrStartSession()
} catch (e: RemoteException) {
@ -216,7 +225,10 @@ internal class RestoreViewModel(
}
@WorkerThread
private fun getFailedStatus(packageName: String, restorableBackup: RestorableBackup = chosenRestorableBackup.value!!): AppRestoreStatus {
private fun getFailedStatus(
packageName: String,
restorableBackup: RestorableBackup = chosenRestorableBackup.value!!
): AppRestoreStatus {
val metadata = restorableBackup.packageMetadataMap[packageName] ?: return FAILED
return when (metadata.state) {
NO_DATA -> FAILED_NO_DATA
@ -267,7 +279,9 @@ internal class RestoreViewModel(
}
@WorkerThread
private inner class RestoreObserver(private val continuation: Continuation<RestoreSetResult>? = null) : IRestoreObserver.Stub() {
private inner class RestoreObserver(
private val continuation: Continuation<RestoreSetResult>? = null
) : IRestoreObserver.Stub() {
/**
* Supply a list of the restore datasets available from the current transport.
@ -290,10 +304,18 @@ internal class RestoreViewModel(
RestoreSetResult(app.getString(R.string.restore_set_error))
} else {
val restorableBackups = restoreSets.mapNotNull { set ->
val metadata = backupMetadata[set.token]
when {
getRestorableBackup(set, backupMetadata[set.token])
}
if (restorableBackups.isEmpty()) RestoreSetResult(app.getString(R.string.restore_set_empty_result))
else RestoreSetResult(restorableBackups)
}
}
continuation.resume(result)
}
private fun getRestorableBackup(set: RestoreSet, metadata: BackupMetadata?) = when {
metadata == null -> {
Log.e(TAG, "RestoreCoordinator#getAndClearBackupMetadata() has no metadata for token ${set.token}.")
Log.e(TAG, "No metadata for token ${set.token}.")
null
}
metadata.time == 0L -> {
@ -304,13 +326,6 @@ internal class RestoreViewModel(
RestorableBackup(set, metadata)
}
}
}
if (restorableBackups.isEmpty()) RestoreSetResult(app.getString(R.string.restore_set_empty_result))
else RestoreSetResult(restorableBackups)
}
}
continuation.resume(result)
}
/**
* The restore operation has begun.
@ -356,7 +371,8 @@ internal class RestoreViewModel(
internal class RestoreSetResult(
internal val restorableBackups: List<RestorableBackup>,
internal val errorMsg: String?) {
internal val errorMsg: String?
) {
internal constructor(restorableBackups: List<RestorableBackup>) : this(restorableBackups, null)

View file

@ -20,8 +20,11 @@ class AboutDialogFragment : DialogFragment() {
internal val TAG = AboutDialogFragment::class.java.simpleName
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v: View = inflater.inflate(R.layout.fragment_about, container, false)
licenseView = v.findViewById(R.id.licenseView)

View file

@ -22,13 +22,15 @@ import com.stevesoltys.seedvault.settings.AppStatusAdapter.AppStatusViewHolder
import com.stevesoltys.seedvault.ui.AppViewHolder
import com.stevesoltys.seedvault.ui.toRelativeTime
internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) : Adapter<AppStatusViewHolder>() {
internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) :
Adapter<AppStatusViewHolder>() {
private val items = ArrayList<AppStatus>()
private var editMode = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppStatusViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false)
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item_app_status, parent, false)
return AppStatusViewHolder(v)
}
@ -108,11 +110,13 @@ data class AppStatus(
val icon: Drawable,
val name: String,
val time: Long,
val status: AppRestoreStatus)
val status: AppRestoreStatus
)
internal class AppStatusDiff(
private val oldItems: List<AppStatus>,
private val newItems: List<AppStatus>) : DiffUtil.Callback() {
private val newItems: List<AppStatus>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size

View file

@ -32,8 +32,11 @@ class AppStatusFragment : Fragment(), AppStatusToggleListener {
private lateinit var list: RecyclerView
private lateinit var progressBar: ProgressBar
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
setHasOptionsMenu(true)
val v: View = inflater.inflate(R.layout.fragment_app_status, container, false)

View file

@ -53,7 +53,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
addAction(ACTION_USB_DEVICE_DETACHED)
}
private val usbReceiver = object : UsbMonitor() {
override fun shouldMonitorStatus(context: Context, action: String, device: UsbDevice): Boolean {
override fun shouldMonitorStatus(
context: Context,
action: String,
device: UsbDevice
): Boolean {
return device.isMassStorage()
}
@ -123,7 +127,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.lastBackupTime.observe(viewLifecycleOwner, Observer { time -> setAppBackupStatusSummary(time) })
viewModel.lastBackupTime.observe(viewLifecycleOwner, Observer { time ->
setAppBackupStatusSummary(time)
})
}
override fun onStart() {

View file

@ -3,7 +3,6 @@ package com.stevesoltys.seedvault.transport
import android.app.Service
import android.app.backup.BackupManager
import android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP
import android.app.backup.BackupTransport.FLAG_USER_INITIATED
import android.app.backup.IBackupManager
import android.content.Context
import android.content.Context.BACKUP_SERVICE

View file

@ -358,7 +358,10 @@ internal class BackupCoordinator(
val packageMetadata = metadataManager.getPackageMetadata(packageName)
val oldPackageState = packageMetadata?.state
if (oldPackageState != null && oldPackageState != packageState) {
Log.e(TAG, "Package $packageName was in $oldPackageState, update to $packageState")
Log.e(
TAG,
"Package $packageName was in $oldPackageState, update to $packageState"
)
plugin.getMetadataOutputStream().use {
metadataManager.onPackageBackupError(packageInfo, packageState, it)
}

View file

@ -5,9 +5,48 @@ import org.koin.dsl.module
val backupModule = module {
single { InputFactory() }
single { PackageService(androidContext(), get()) }
single { ApkBackup(androidContext().packageManager, get(), get()) }
single { KVBackup(get<BackupPlugin>().kvBackupPlugin, get(), get(), get(), get()) }
single { FullBackup(get<BackupPlugin>().fullBackupPlugin, get(), get(), get()) }
single { BackupCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
single {
PackageService(
context = androidContext(),
backupManager = get()
)
}
single {
ApkBackup(
pm = androidContext().packageManager,
settingsManager = get(),
metadataManager = get()
)
}
single {
KVBackup(
plugin = get<BackupPlugin>().kvBackupPlugin,
inputFactory = get(),
headerWriter = get(),
crypto = get(),
nm = get()
)
}
single {
FullBackup(
plugin = get<BackupPlugin>().fullBackupPlugin,
inputFactory = get(),
headerWriter = get(),
crypto = get()
)
}
single {
BackupCoordinator(
context = androidContext(),
plugin = get(),
kv = get(),
full = get(),
apkBackup = get(),
clock = get(),
packageService = get(),
metadataManager = get(),
settingsManager = get(),
nm = get()
)
}
}

View file

@ -18,14 +18,14 @@ import java.io.InputStream
import java.io.OutputStream
private class FullBackupState(
internal val packageInfo: PackageInfo,
internal val inputFileDescriptor: ParcelFileDescriptor,
internal val inputStream: InputStream,
internal var outputStreamInit: (suspend () -> OutputStream)?
val packageInfo: PackageInfo,
val inputFileDescriptor: ParcelFileDescriptor,
val inputStream: InputStream,
var outputStreamInit: (suspend () -> OutputStream)?
) {
internal var outputStream: OutputStream? = null
internal val packageName: String = packageInfo.packageName
internal var size: Long = 0
var outputStream: OutputStream? = null
val packageName: String = packageInfo.packageName
var size: Long = 0
}
const val DEFAULT_QUOTA_FULL_BACKUP = (2 * (25 * 1024 * 1024)).toLong()

View file

@ -36,7 +36,12 @@ internal class ApkInstaller(private val context: Context) {
@ExperimentalCoroutinesApi
@Throws(IOException::class, SecurityException::class)
internal fun install(cachedApk: File, packageName: String, installerPackageName: String?, installResult: MutableInstallResult) = callbackFlow {
internal fun install(
cachedApk: File,
packageName: String,
installerPackageName: String?,
installResult: MutableInstallResult
) = callbackFlow {
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, i: Intent) {
if (i.action != BROADCAST_ACTION) return
@ -76,11 +81,17 @@ internal class ApkInstaller(private val context: Context) {
flags = FLAG_RECEIVER_FOREGROUND
setPackage(context.packageName)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, broadcastIntent, FLAG_UPDATE_CURRENT)
val pendingIntent =
PendingIntent.getBroadcast(context, 0, broadcastIntent, FLAG_UPDATE_CURRENT)
return pendingIntent.intentSender
}
private fun onBroadcastReceived(i: Intent, expectedPackageName: String, cachedApk: File, installResult: MutableInstallResult): InstallResult {
private fun onBroadcastReceived(
i: Intent,
expectedPackageName: String,
cachedApk: File,
installResult: MutableInstallResult
): InstallResult {
val packageName = i.getStringExtra(EXTRA_PACKAGE_NAME)!!
val success = i.getIntExtra(EXTRA_STATUS, -1) == STATUS_SUCCESS
val statusMsg = i.getStringExtra(EXTRA_STATUS_MESSAGE)!!

View file

@ -23,13 +23,13 @@ import java.io.IOException
import java.security.MessageDigest
import java.util.concurrent.ConcurrentHashMap
private val TAG = ApkRestore::class.java.simpleName
internal class ApkRestore(
private val context: Context,
private val restorePlugin: RestorePlugin,
private val apkInstaller: ApkInstaller = ApkInstaller(context)) {
private val apkInstaller: ApkInstaller = ApkInstaller(context)
) {
private val pm = context.packageManager
@ -71,7 +71,12 @@ internal class ApkRestore(
@ExperimentalCoroutinesApi
@Suppress("BlockingMethodInNonBlockingContext") // flows on Dispatcher.IO
@Throws(IOException::class, SecurityException::class)
private fun restore(token: Long, packageName: String, metadata: PackageMetadata, installResult: MutableInstallResult) = flow {
private fun restore(
token: Long,
packageName: String,
metadata: PackageMetadata,
installResult: MutableInstallResult
) = flow {
// create a cache file to write the APK into
val cachedApk = File.createTempFile(packageName, ".apk", context.cacheDir)
// copy APK to cache file and calculate SHA-256 hash while we are at it
@ -106,7 +111,10 @@ internal class ApkRestore(
// check APK version code
if (metadata.version != packageInfo.longVersionCode) {
Log.w(TAG, "Package $packageName expects version code ${metadata.version}, but has ${packageInfo.longVersionCode}.")
Log.w(
TAG, "Package $packageName expects version code ${metadata.version}," +
"but has ${packageInfo.longVersionCode}."
)
// TODO should we let this one pass, maybe once we can revert PackageMetadata during backup?
}
@ -125,7 +133,13 @@ internal class ApkRestore(
val icon = appInfo.loadIcon(pm)
val name = pm.getApplicationLabel(appInfo)
installResult.update(packageName) { it.copy(status = IN_PROGRESS, name = name, icon = icon) }
installResult.update(packageName) { result ->
result.copy(
status = IN_PROGRESS,
name = name,
icon = icon
)
}
emit(installResult)
// ensure system apps are actually installed and newer system apps as well
@ -143,7 +157,8 @@ internal class ApkRestore(
}
// install APK and emit updates from it
apkInstaller.install(cachedApk, packageName, metadata.installer, installResult).collect { result ->
apkInstaller.install(cachedApk, packageName, metadata.installer, installResult)
.collect { result ->
emit(result)
}
}
@ -163,8 +178,12 @@ internal fun InstallResult.getInProgress(): ApkRestoreResult? {
return filtered.values.first()
}
internal class MutableInstallResult(initialCapacity: Int) : ConcurrentHashMap<String, ApkRestoreResult>(initialCapacity) {
fun update(packageName: String, updateFun: (ApkRestoreResult) -> ApkRestoreResult): MutableInstallResult {
internal class MutableInstallResult(initialCapacity: Int) :
ConcurrentHashMap<String, ApkRestoreResult>(initialCapacity) {
fun update(
packageName: String,
updateFun: (ApkRestoreResult) -> ApkRestoreResult
): MutableInstallResult {
val result = get(packageName)
check(result != null) { "ApkRestoreResult for $packageName does not exist." }
set(packageName, updateFun(result))

View file

@ -16,9 +16,10 @@ import java.io.IOException
import java.io.InputStream
private class FullRestoreState(
internal val token: Long,
internal val packageInfo: PackageInfo) {
internal var inputStream: InputStream? = null
val token: Long,
val packageInfo: PackageInfo
) {
var inputStream: InputStream? = null
}
private val TAG = FullRestore::class.java.simpleName
@ -28,7 +29,8 @@ internal class FullRestore(
private val plugin: FullRestorePlugin,
private val outputFactory: OutputFactory,
private val headerReader: HeaderReader,
private val crypto: Crypto) {
private val crypto: Crypto
) {
private var state: FullRestoreState? = null

View file

@ -19,12 +19,12 @@ import java.util.ArrayList
import javax.crypto.AEADBadTagException
private class KVRestoreState(
internal val token: Long,
internal val packageInfo: PackageInfo,
val token: Long,
val packageInfo: PackageInfo,
/**
* Optional [PackageInfo] for single package restore, optimizes restore of @pm@
*/
internal val pmPackageInfo: PackageInfo?
val pmPackageInfo: PackageInfo?
)
private val TAG = KVRestore::class.java.simpleName
@ -156,8 +156,8 @@ internal class KVRestore(
Unit
}
private class DecodedKey(internal val base64Key: String) : Comparable<DecodedKey> {
internal val key = base64Key.decodeBase64()
private class DecodedKey(val base64Key: String) : Comparable<DecodedKey> {
val key = base64Key.decodeBase64()
override fun compareTo(other: DecodedKey) = key.compareTo(other.key)
}

View file

@ -30,6 +30,10 @@ interface KVRestorePlugin {
* Note: Implementations might expect that you call [hasDataForPackage] before.
*/
@Throws(IOException::class)
suspend fun getInputStreamForRecord(token: Long, packageInfo: PackageInfo, key: String): InputStream
suspend fun getInputStreamForRecord(
token: Long,
packageInfo: PackageInfo,
key: String
): InputStream
}

View file

@ -23,7 +23,8 @@ open class LiveEvent<T> : LiveData<ConsumableEvent<T>>() {
}
}
internal class LiveEventObserver<T>(private val handler: LiveEventHandler<in T>) : Observer<ConsumableEvent<T>> {
internal class LiveEventObserver<T>(private val handler: LiveEventHandler<in T>) :
Observer<ConsumableEvent<T>> {
override fun onChanged(consumableEvent: ConsumableEvent<T>?) {
if (consumableEvent != null) {
val content = consumableEvent.contentIfNotConsumed

View file

@ -8,7 +8,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import com.stevesoltys.seedvault.R
class RecoveryCodeAdapter(private val items: List<CharSequence>) : Adapter<RecoveryCodeViewHolder>() {
class RecoveryCodeAdapter(private val items: List<CharSequence>) :
Adapter<RecoveryCodeViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecoveryCodeViewHolder {
val v = LayoutInflater.from(parent.context)

View file

@ -130,7 +130,8 @@ class RecoveryCodeInputFragment : Fragment() {
private fun showWrongWordError(input: List<CharSequence>, e: WordNotFoundException) {
val i = input.indexOf(e.word)
if (i == -1) throw AssertionError()
showError(i, getString(R.string.recovery_code_error_invalid_word, e.suggestion1, e.suggestion2))
val str = getString(R.string.recovery_code_error_invalid_word, e.suggestion1, e.suggestion2)
showError(i, str)
}
private fun showError(i: Int, errorMsg: CharSequence) {

View file

@ -17,7 +17,8 @@ private val TAG = RestoreStorageViewModel::class.java.simpleName
internal class RestoreStorageViewModel(
private val app: Application,
private val restorePlugin: RestorePlugin,
settingsManager: SettingsManager) : StorageViewModel(app, settingsManager) {
settingsManager: SettingsManager
) : StorageViewModel(app, settingsManager) {
override val isRestoreOperation = true
@ -37,7 +38,8 @@ internal class RestoreStorageViewModel(
Log.w(TAG, "Location was rejected: $uri")
// notify the UI that the location was invalid
val errorMsg = app.getString(R.string.restore_invalid_location_message, DIRECTORY_ROOT)
val errorMsg =
app.getString(R.string.restore_invalid_location_message, DIRECTORY_ROOT)
mLocationChecked.postEvent(LocationResult(errorMsg))
}
}

View file

@ -18,7 +18,8 @@ import com.stevesoltys.seedvault.ui.storage.StorageRootAdapter.StorageRootViewHo
internal class StorageRootAdapter(
private val isRestore: Boolean,
private val listener: StorageRootClickedListener) : Adapter<StorageRootViewHolder>() {
private val listener: StorageRootClickedListener
) : Adapter<StorageRootViewHolder>() {
private val items = ArrayList<StorageRoot>()

View file

@ -51,7 +51,8 @@ data class StorageRoot(
internal val availableBytes: Long?,
internal val isUsb: Boolean,
internal val enabled: Boolean = true,
internal val overrideClickListener: (() -> Unit)? = null) {
internal val overrideClickListener: (() -> Unit)? = null
) {
internal val uri: Uri by lazy {
DocumentsContract.buildTreeDocumentUri(authority, documentId)
@ -70,7 +71,8 @@ internal class StorageRootFetcher(private val context: Context, private val isRe
private val packageManager = context.packageManager
private val contentResolver = context.contentResolver
private val whitelistedAuthorities = context.resources.getStringArray(R.array.storage_authority_whitelist)
private val whitelistedAuthorities =
context.resources.getStringArray(R.array.storage_authority_whitelist)
private var listener: RemovableStorageListener? = null
private val handler = Handler(Looper.getMainLooper())

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
Nextcloud Android client application
Copyright (C) 2017 Andy Scherzinger
@ -19,6 +18,6 @@
License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/nextcloud_background"/>
<foreground android:drawable="@drawable/nextcloud_foreground"/>
<background android:drawable="@drawable/nextcloud_background" />
<foreground android:drawable="@drawable/nextcloud_foreground" />
</adaptive-icon>

File diff suppressed because it is too large Load diff

View file

@ -22,11 +22,12 @@
android:height="108dp"
android:viewportWidth="1636.9231"
android:viewportHeight="1636.9231">
<group android:translateX="286.46155"
<group
android:translateX="286.46155"
android:translateY="286.46155">
<path
android:pathData="M532.7,320C439.3,320 360.9,383.9 337,469.9 316.1,423.9 270.1,391.3 216.6,391.3 143.8,391.3 84,451.2 84,524c-0,72.8 59.8,132.6 132.6,132.7 53.5,-0 99.4,-32.6 120.4,-78.6 23.9,86 102.4,149.9 195.7,149.9 92.8,0 170.8,-63.2 195.3,-148.5 21.2,45.1 66.5,77.2 119.4,77.2 72.8,0 132.7,-59.8 132.6,-132.7 -0,-72.8 -59.9,-132.6 -132.6,-132.6 -52.8,0 -98.2,32 -119.4,77.2 -24.4,-85.3 -102.4,-148.5 -195.3,-148.5zM532.7,397.9c70.1,0 126.1,56 126.1,126.1 0,70.1 -56,126.1 -126.1,126.1 -70.1,-0 -126.1,-56 -126.1,-126.1 0,-70.1 56,-126.1 126.1,-126.1zM216.6,469.2c30.7,0 54.8,24.1 54.8,54.8 0,30.7 -24,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8zM847.4,469.2c30.7,-0 54.8,24.1 54.8,54.8 0,30.7 -24.1,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8z"
android:fillColor="#ffffff"
android:fillType="nonZero"
android:fillColor="#ffffff"/>
android:pathData="M532.7,320C439.3,320 360.9,383.9 337,469.9 316.1,423.9 270.1,391.3 216.6,391.3 143.8,391.3 84,451.2 84,524c-0,72.8 59.8,132.6 132.6,132.7 53.5,-0 99.4,-32.6 120.4,-78.6 23.9,86 102.4,149.9 195.7,149.9 92.8,0 170.8,-63.2 195.3,-148.5 21.2,45.1 66.5,77.2 119.4,77.2 72.8,0 132.7,-59.8 132.6,-132.7 -0,-72.8 -59.9,-132.6 -132.6,-132.6 -52.8,0 -98.2,32 -119.4,77.2 -24.4,-85.3 -102.4,-148.5 -195.3,-148.5zM532.7,397.9c70.1,0 126.1,56 126.1,126.1 0,70.1 -56,126.1 -126.1,126.1 -70.1,-0 -126.1,-56 -126.1,-126.1 0,-70.1 56,-126.1 126.1,-126.1zM216.6,469.2c30.7,0 54.8,24.1 54.8,54.8 0,30.7 -24,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8zM847.4,469.2c30.7,-0 54.8,24.1 54.8,54.8 0,30.7 -24.1,54.8 -54.8,54.8 -30.7,0 -54.8,-24.1 -54.8,-54.8 0,-30.7 24.1,-54.8 54.8,-54.8z" />
</group>
</vector>

View file

@ -81,9 +81,9 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/doneButton"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/wordList"
app:layout_constraintVertical_bias="1.0"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -62,8 +62,8 @@
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
tools:listitem="@layout/list_item_app_status" />
<Button
@ -76,8 +76,8 @@
android:layout_marginBottom="16dp"
android:enabled="false"
android:text="@string/restore_next"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent" />

View file

@ -55,8 +55,8 @@
<TextView
android:id="@+id/errorView"
android:layout_width="0dp"
android:layout_margin="16dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textColor="@android:color/holo_red_dark"
android:textSize="18sp"
android:visibility="invisible"

View file

@ -25,8 +25,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textSize="24sp"
android:autoSizeTextType="uniform"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/num"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -47,11 +47,16 @@ class CryptoTest {
private val iv = getRandomByteArray(IV_SIZE)
private val cleartext = getRandomByteArray(Random.nextInt(MAX_SEGMENT_LENGTH))
private val ciphertext = getRandomByteArray(Random.nextInt(MAX_SEGMENT_LENGTH))
private val versionHeader = VersionHeader(VERSION, getRandomString(MAX_PACKAGE_LENGTH_SIZE), getRandomString(MAX_KEY_LENGTH_SIZE))
private val versionHeader = VersionHeader(
VERSION,
getRandomString(MAX_PACKAGE_LENGTH_SIZE),
getRandomString(MAX_KEY_LENGTH_SIZE)
)
private val versionCiphertext = getRandomByteArray(MAX_VERSION_HEADER_SIZE)
private val versionSegmentHeader = SegmentHeader(versionCiphertext.size.toShort(), iv)
private val outputStream = ByteArrayOutputStream()
private val segmentHeader = SegmentHeader(ciphertext.size.toShort(), iv)
// the headerReader will not actually read the header, so only insert cipher text
private val inputStream = ByteArrayInputStream(ciphertext)
private val versionInputStream = ByteArrayInputStream(versionCiphertext)
@ -81,7 +86,10 @@ class CryptoTest {
assertEquals(ciphertext.size, segmentHeader.captured.segmentLength.toInt())
}
private fun encryptSegmentHeader(toEncrypt: ByteArray, segmentHeader: CapturingSlot<SegmentHeader>) {
private fun encryptSegmentHeader(
toEncrypt: ByteArray,
segmentHeader: CapturingSlot<SegmentHeader>
) {
every { cipherFactory.createEncryptionCipher() } returns cipher
every { cipher.getOutputSize(toEncrypt.size) } returns toEncrypt.size
every { cipher.iv } returns iv
@ -100,7 +108,12 @@ class CryptoTest {
assertEquals(
versionHeader,
crypto.decryptHeader(versionInputStream, versionHeader.version, versionHeader.packageName, versionHeader.key)
crypto.decryptHeader(
versionInputStream,
versionHeader.version,
versionHeader.packageName,
versionHeader.key
)
)
}
@ -114,7 +127,12 @@ class CryptoTest {
every { headerReader.readSegmentHeader(versionInputStream) } returns versionSegmentHeader
val e = assertThrows(SecurityException::class.java) {
crypto.decryptHeader(versionInputStream, versionHeader.version, versionHeader.packageName, versionHeader.key)
crypto.decryptHeader(
versionInputStream,
versionHeader.version,
versionHeader.packageName,
versionHeader.key
)
}
assertContains(e.message, size.toString())
}
@ -128,7 +146,12 @@ class CryptoTest {
val version = (VERSION + 1).toByte()
val e = assertThrows(SecurityException::class.java) {
crypto.decryptHeader(versionInputStream, version, versionHeader.packageName, versionHeader.key)
crypto.decryptHeader(
versionInputStream,
version,
versionHeader.packageName,
versionHeader.key
)
}
assertContains(e.message, version.toString())
}
@ -142,7 +165,12 @@ class CryptoTest {
val packageName = getRandomString(MAX_PACKAGE_LENGTH_SIZE)
val e = assertThrows(SecurityException::class.java) {
crypto.decryptHeader(versionInputStream, versionHeader.version, packageName, versionHeader.key)
crypto.decryptHeader(
versionInputStream,
versionHeader.version,
packageName,
versionHeader.key
)
}
assertContains(e.message, packageName)
}
@ -155,7 +183,12 @@ class CryptoTest {
every { headerReader.getVersionHeader(cleartext) } returns versionHeader
val e = assertThrows(SecurityException::class.java) {
crypto.decryptHeader(versionInputStream, versionHeader.version, versionHeader.packageName, null)
crypto.decryptHeader(
versionInputStream,
versionHeader.version,
versionHeader.packageName,
null
)
}
assertContains(e.message, "null")
assertContains(e.message, versionHeader.key ?: fail())

View file

@ -188,7 +188,10 @@ internal class HeaderReaderTest {
@Test
fun `too short SegmentHeader throws exception`() {
val input = byteArrayOf(0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
val input = byteArrayOf(
0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
val inputStream = ByteArrayInputStream(input)
assertThrows(IOException::class.javaObjectType) {
reader.readSegmentHeader(inputStream)
@ -197,7 +200,10 @@ internal class HeaderReaderTest {
@Test
fun `segment length of zero is rejected`() {
val input = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
val input = byteArrayOf(
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
val inputStream = ByteArrayInputStream(input)
assertThrows(IOException::class.javaObjectType) {
reader.readSegmentHeader(inputStream)
@ -206,7 +212,10 @@ internal class HeaderReaderTest {
@Test
fun `negative segment length is rejected`() {
val input = byteArrayOf(0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
val input = byteArrayOf(
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
val inputStream = ByteArrayInputStream(input)
assertThrows(IOException::class.javaObjectType) {
reader.readSegmentHeader(inputStream)
@ -215,7 +224,10 @@ internal class HeaderReaderTest {
@Test
fun `minimum negative segment length is rejected`() {
val input = byteArrayOf(0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
val input = byteArrayOf(
0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
val inputStream = ByteArrayInputStream(input)
assertThrows(IOException::class.javaObjectType) {
reader.readSegmentHeader(inputStream)
@ -224,14 +236,23 @@ internal class HeaderReaderTest {
@Test
fun `max segment length is accepted`() {
val input = byteArrayOf(0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
val input = byteArrayOf(
0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
val inputStream = ByteArrayInputStream(input)
assertEquals(MAX_SEGMENT_LENGTH, reader.readSegmentHeader(inputStream).segmentLength.toInt())
assertEquals(
MAX_SEGMENT_LENGTH,
reader.readSegmentHeader(inputStream).segmentLength.toInt()
)
}
@Test
fun `min segment length of 1 is accepted`() {
val input = byteArrayOf(0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
val input = byteArrayOf(
0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
)
val inputStream = ByteArrayInputStream(input)
assertEquals(1, reader.readSegmentHeader(inputStream).segmentLength.toInt())
}
@ -249,8 +270,14 @@ internal class HeaderReaderTest {
@Test
fun `nonce is read in big endian`() {
val nonce = byteArrayOf(0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01)
val input = byteArrayOf(0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01)
val nonce = byteArrayOf(
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01
)
val input = byteArrayOf(
0x00, 0x01, 0xff, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
)
val inputStream = ByteArrayInputStream(input)
assertArrayEquals(nonce, reader.readSegmentHeader(inputStream).nonce)
}

View file

@ -151,27 +151,42 @@ class MetadataManagerTest {
// state doesn't change for APK_AND_DATA
packageMetadata = packageMetadata.copy(version = ++version, state = APK_AND_DATA)
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
assertEquals(
packageMetadata.copy(state = oldState),
manager.getPackageMetadata(packageName)
)
// state doesn't change for QUOTA_EXCEEDED
packageMetadata = packageMetadata.copy(version = ++version, state = QUOTA_EXCEEDED)
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
assertEquals(
packageMetadata.copy(state = oldState),
manager.getPackageMetadata(packageName)
)
// state doesn't change for NO_DATA
packageMetadata = packageMetadata.copy(version = ++version, state = NO_DATA)
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
assertEquals(
packageMetadata.copy(state = oldState),
manager.getPackageMetadata(packageName)
)
// state DOES change for NOT_ALLOWED
packageMetadata = packageMetadata.copy(version = ++version, state = NOT_ALLOWED)
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
assertEquals(packageMetadata.copy(state = NOT_ALLOWED), manager.getPackageMetadata(packageName))
assertEquals(
packageMetadata.copy(state = NOT_ALLOWED),
manager.getPackageMetadata(packageName)
)
// state DOES change for WAS_STOPPED
packageMetadata = packageMetadata.copy(version = ++version, state = WAS_STOPPED)
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
assertEquals(packageMetadata.copy(state = WAS_STOPPED), manager.getPackageMetadata(packageName))
assertEquals(
packageMetadata.copy(state = WAS_STOPPED),
manager.getPackageMetadata(packageName)
)
}
@Test
@ -190,7 +205,10 @@ class MetadataManagerTest {
manager.onPackageBackedUp(packageInfo, storageOutputStream)
assertEquals(packageMetadata.copy(state = APK_AND_DATA, system = true), manager.getPackageMetadata(packageName))
assertEquals(
packageMetadata.copy(state = APK_AND_DATA, system = true),
manager.getPackageMetadata(packageName)
)
assertEquals(time, manager.getLastBackupTime())
}
@ -215,7 +233,10 @@ class MetadataManagerTest {
}
assertEquals(0L, manager.getLastBackupTime()) // time was reverted
assertEquals(initialMetadata.packageMetadataMap[packageName], manager.getPackageMetadata(packageName))
assertEquals(
initialMetadata.packageMetadataMap[packageName],
manager.getPackageMetadata(packageName)
)
}
@Test
@ -229,7 +250,8 @@ class MetadataManagerTest {
val updatedMetadata = cachedMetadata.copy(time = time)
updatedMetadata.packageMetadataMap[cachedPackageName] = PackageMetadata(time)
updatedMetadata.packageMetadataMap[packageName] = PackageMetadata(time, state = APK_AND_DATA)
updatedMetadata.packageMetadataMap[packageName] =
PackageMetadata(time, state = APK_AND_DATA)
expectReadFromCache()
every { clock.time() } returns time
@ -239,7 +261,10 @@ class MetadataManagerTest {
assertEquals(time, manager.getLastBackupTime())
assertEquals(PackageMetadata(time), manager.getPackageMetadata(cachedPackageName))
assertEquals(updatedMetadata.packageMetadataMap[packageName], manager.getPackageMetadata(packageName))
assertEquals(
updatedMetadata.packageMetadataMap[packageName],
manager.getPackageMetadata(packageName)
)
}
@Test
@ -269,7 +294,12 @@ class MetadataManagerTest {
private fun expectModifyMetadata(metadata: BackupMetadata) {
every { metadataWriter.write(metadata, storageOutputStream) } just Runs
every { metadataWriter.encode(metadata) } returns encodedMetadata
every { context.openFileOutput(METADATA_CACHE_FILE, MODE_PRIVATE) } returns cacheOutputStream
every {
context.openFileOutput(
METADATA_CACHE_FILE,
MODE_PRIVATE
)
} returns cacheOutputStream
every { cacheOutputStream.write(encodedMetadata) } just Runs
}

View file

@ -86,14 +86,16 @@ class MetadataReaderTest {
@Test
fun `package metadata gets read`() {
val packageMetadata = HashMap<String, PackageMetadata>().apply {
put("org.example", PackageMetadata(
put(
"org.example", PackageMetadata(
time = Random.nextLong(),
state = QUOTA_EXCEEDED,
version = Random.nextLong(),
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString(), getRandomString())
))
)
)
}
val metadata = getMetadata(packageMetadata)
val metadataByteArray = encoder.encode(metadata)

View file

@ -25,7 +25,10 @@ internal class MetadataWriterDecoderTest {
@Test
fun `encoded metadata matches decoded metadata (no packages)`() {
val metadata = getMetadata()
assertEquals(metadata, decoder.decode(encoder.encode(metadata), metadata.version, metadata.token))
assertEquals(
metadata,
decoder.decode(encoder.encode(metadata), metadata.version, metadata.token)
)
}
@Test
@ -36,28 +39,38 @@ internal class MetadataWriterDecoderTest {
put(getRandomString(), PackageMetadata(time, WAS_STOPPED))
}
val metadata = getMetadata(packages)
assertEquals(metadata, decoder.decode(encoder.encode(metadata), metadata.version, metadata.token))
assertEquals(
metadata,
decoder.decode(encoder.encode(metadata), metadata.version, metadata.token)
)
}
@Test
fun `encoded metadata matches decoded metadata (full package)`() {
val packages = HashMap<String, PackageMetadata>().apply {
put(getRandomString(), PackageMetadata(
put(
getRandomString(), PackageMetadata(
time = Random.nextLong(),
state = APK_AND_DATA,
version = Random.nextLong(),
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString(), getRandomString())))
signatures = listOf(getRandomString(), getRandomString())
)
)
}
val metadata = getMetadata(packages)
assertEquals(metadata, decoder.decode(encoder.encode(metadata), metadata.version, metadata.token))
assertEquals(
metadata,
decoder.decode(encoder.encode(metadata), metadata.version, metadata.token)
)
}
@Test
fun `encoded metadata matches decoded metadata (three full packages)`() {
val packages = HashMap<String, PackageMetadata>().apply {
put(getRandomString(), PackageMetadata(
put(
getRandomString(), PackageMetadata(
time = Random.nextLong(),
state = QUOTA_EXCEEDED,
system = Random.nextBoolean(),
@ -65,8 +78,10 @@ internal class MetadataWriterDecoderTest {
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString())
))
put(getRandomString(), PackageMetadata(
)
)
put(
getRandomString(), PackageMetadata(
time = Random.nextLong(),
state = NO_DATA,
system = Random.nextBoolean(),
@ -74,8 +89,10 @@ internal class MetadataWriterDecoderTest {
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString(), getRandomString())
))
put(getRandomString(), PackageMetadata(
)
)
put(
getRandomString(), PackageMetadata(
time = 0L,
state = NOT_ALLOWED,
system = Random.nextBoolean(),
@ -83,10 +100,14 @@ internal class MetadataWriterDecoderTest {
installer = getRandomString(),
sha256 = getRandomString(),
signatures = listOf(getRandomString(), getRandomString())
))
)
)
}
val metadata = getMetadata(packages)
assertEquals(metadata, decoder.decode(encoder.encode(metadata), metadata.version, metadata.token))
assertEquals(
metadata,
decoder.decode(encoder.encode(metadata), metadata.version, metadata.token)
)
}
private fun getMetadata(packageMetadata: HashMap<String, PackageMetadata> = HashMap()): BackupMetadata {

View file

@ -65,7 +65,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
private val backupPlugin = mockk<BackupPlugin>()
private val kvBackupPlugin = mockk<KVBackupPlugin>()
private val kvBackup = KVBackup(kvBackupPlugin, inputFactory, headerWriter, cryptoImpl, notificationManager)
private val kvBackup =
KVBackup(kvBackupPlugin, inputFactory, headerWriter, cryptoImpl, notificationManager)
private val fullBackupPlugin = mockk<FullBackupPlugin>()
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
private val apkBackup = mockk<ApkBackup>()

View file

@ -162,7 +162,8 @@ internal class FullBackupTest : BackupTest() {
}
@Test
fun `sendBackupData throws exception when writing encrypted data to OutputStream`() = runBlocking {
fun `sendBackupData throws exception when writing encrypted data to OutputStream`() =
runBlocking {
every { inputFactory.getInputStream(data) } returns inputStream
expectInitializeOutputStream()
every { plugin.getQuota() } returns quota

View file

@ -123,9 +123,21 @@ internal class ApkRestoreTest : RestoreTest() {
every { strictContext.cacheDir } returns File(tmpDir.toString())
coEvery { restorePlugin.getApkInputStream(token, packageName) } returns apkInputStream
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
every { pm.loadItemIcon(packageInfo.applicationInfo, packageInfo.applicationInfo) } returns icon
every {
pm.loadItemIcon(
packageInfo.applicationInfo,
packageInfo.applicationInfo
)
} returns icon
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
every { apkInstaller.install(any(), packageName, installerName, any()) } throws SecurityException()
every {
apkInstaller.install(
any(),
packageName,
installerName,
any()
)
} throws SecurityException()
apkRestore.restore(token, packageMetadataMap).collectIndexed { index, value ->
when (index) {
@ -153,15 +165,29 @@ internal class ApkRestoreTest : RestoreTest() {
@Test
fun `test successful run`(@TempDir tmpDir: Path) = runBlocking {
val installResult = MutableInstallResult(1).apply {
put(packageName, ApkRestoreResult(packageName, progress = 1, total = 1, status = SUCCEEDED))
put(
packageName, ApkRestoreResult(
packageName,
progress = 1,
total = 1,
status = SUCCEEDED
)
)
}
every { strictContext.cacheDir } returns File(tmpDir.toString())
coEvery { restorePlugin.getApkInputStream(token, packageName) } returns apkInputStream
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
every { pm.loadItemIcon(packageInfo.applicationInfo, packageInfo.applicationInfo) } returns icon
every {
pm.loadItemIcon(
packageInfo.applicationInfo,
packageInfo.applicationInfo
)
} returns icon
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
every { apkInstaller.install(any(), packageName, installerName, any()) } returns flowOf(installResult)
every { apkInstaller.install(any(), packageName, installerName, any()) } returns flowOf(
installResult
)
var i = 0
apkRestore.restore(token, packageMetadataMap).collect { value ->
@ -189,7 +215,8 @@ internal class ApkRestoreTest : RestoreTest() {
}
@Test
fun `test system apps only get reinstalled when older system apps exist`(@TempDir tmpDir: Path) = runBlocking {
fun `test system apps only get reinstalled when older system apps exist`(@TempDir tmpDir: Path) =
runBlocking {
val packageMetadata = this@ApkRestoreTest.packageMetadata.copy(system = true)
packageMetadataMap[packageName] = packageMetadata
packageInfo.applicationInfo = mockk()
@ -203,16 +230,31 @@ internal class ApkRestoreTest : RestoreTest() {
every { strictContext.cacheDir } returns File(tmpDir.toString())
coEvery { restorePlugin.getApkInputStream(token, packageName) } returns apkInputStream
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
every { pm.loadItemIcon(packageInfo.applicationInfo, packageInfo.applicationInfo) } returns icon
every {
pm.loadItemIcon(
packageInfo.applicationInfo,
packageInfo.applicationInfo
)
} returns icon
every { packageInfo.applicationInfo.loadIcon(pm) } returns icon
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
every { pm.getPackageInfo(packageName, 0) } returns installedPackageInfo
every { installedPackageInfo.longVersionCode } returns packageMetadata.version!! - 1
if (!willFail) {
val installResult = MutableInstallResult(1).apply {
put(packageName, ApkRestoreResult(packageName, progress = 1, total = 1, status = SUCCEEDED))
put(
packageName,
ApkRestoreResult(packageName, progress = 1, total = 1, status = SUCCEEDED)
)
}
every { apkInstaller.install(any(), packageName, installerName, any()) } returns flowOf(installResult)
every {
apkInstaller.install(
any(),
packageName,
installerName,
any()
)
} returns flowOf(installResult)
}
var i = 0

View file

@ -68,7 +68,10 @@ internal class FullRestoreTest : RestoreTest() {
coEvery { plugin.getInputStreamForPackage(token, packageInfo) } throws IOException()
assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor))
assertEquals(
TRANSPORT_PACKAGE_REJECTED,
restore.getNextFullRestoreDataChunk(fileDescriptor)
)
}
@Test
@ -78,7 +81,10 @@ internal class FullRestoreTest : RestoreTest() {
coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream
every { headerReader.readVersion(inputStream) } throws IOException()
assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor))
assertEquals(
TRANSPORT_PACKAGE_REJECTED,
restore.getNextFullRestoreDataChunk(fileDescriptor)
)
}
@Test
@ -86,9 +92,14 @@ internal class FullRestoreTest : RestoreTest() {
restore.initializeState(token, packageInfo)
coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream
every { headerReader.readVersion(inputStream) } throws UnsupportedVersionException(unsupportedVersion)
every {
headerReader.readVersion(inputStream)
} throws UnsupportedVersionException(unsupportedVersion)
assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor))
assertEquals(
TRANSPORT_PACKAGE_REJECTED,
restore.getNextFullRestoreDataChunk(fileDescriptor)
)
}
@Test
@ -97,18 +108,34 @@ internal class FullRestoreTest : RestoreTest() {
coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName) } throws IOException()
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName
)
} throws IOException()
assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor))
assertEquals(
TRANSPORT_PACKAGE_REJECTED,
restore.getNextFullRestoreDataChunk(fileDescriptor)
)
}
@Test
fun `decrypting version header when getting first chunk throws security exception`() = runBlocking {
fun `decrypting version header when getting first chunk throws security exception`() =
runBlocking {
restore.initializeState(token, packageInfo)
coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName) } throws SecurityException()
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName
)
} throws SecurityException()
assertEquals(TRANSPORT_ERROR, restore.getNextFullRestoreDataChunk(fileDescriptor))
}
@ -123,7 +150,10 @@ internal class FullRestoreTest : RestoreTest() {
every { inputStream.close() } just Runs
every { fileDescriptor.close() } just Runs
assertEquals(TRANSPORT_PACKAGE_REJECTED, restore.getNextFullRestoreDataChunk(fileDescriptor))
assertEquals(
TRANSPORT_PACKAGE_REJECTED,
restore.getNextFullRestoreDataChunk(fileDescriptor)
)
}
@Test
@ -171,7 +201,13 @@ internal class FullRestoreTest : RestoreTest() {
private fun initInputStream() {
coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName) } returns versionHeader
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName
)
} returns versionHeader
}
private fun readAndEncryptInputStream(encryptedBytes: ByteArray) {

View file

@ -67,7 +67,9 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } throws UnsupportedVersionException(unsupportedVersion)
every {
headerReader.readVersion(inputStream)
} throws UnsupportedVersionException(unsupportedVersion)
streamsGetClosed()
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
@ -94,7 +96,14 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} returns versionHeader
every { crypto.decryptMultipleSegments(inputStream) } throws IOException()
streamsGetClosed()
@ -109,7 +118,14 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } throws IOException()
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} throws IOException()
streamsGetClosed()
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
@ -123,7 +139,14 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } throws SecurityException()
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} throws SecurityException()
streamsGetClosed()
assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor))
@ -137,7 +160,14 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} returns versionHeader
every { crypto.decryptMultipleSegments(inputStream) } returns data
every { output.writeEntityHeader(key, data.size) } throws IOException()
streamsGetClosed()
@ -153,7 +183,14 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} returns versionHeader
every { crypto.decryptMultipleSegments(inputStream) } returns data
every { output.writeEntityHeader(key, data.size) } returns 42
every { output.writeEntityData(data, data.size) } throws IOException()
@ -170,7 +207,14 @@ internal class KVRestoreTest : RestoreTest() {
getRecordsAndOutput()
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} returns versionHeader
every { crypto.decryptMultipleSegments(inputStream) } returns data
every { output.writeEntityHeader(key, data.size) } returns 42
every { output.writeEntityData(data, data.size) } returns data.size
@ -190,14 +234,28 @@ internal class KVRestoreTest : RestoreTest() {
// first key/value
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream
every { headerReader.readVersion(inputStream) } returns VERSION
every { crypto.decryptHeader(inputStream, VERSION, packageInfo.packageName, key) } returns versionHeader
every {
crypto.decryptHeader(
inputStream,
VERSION,
packageInfo.packageName,
key
)
} returns versionHeader
every { crypto.decryptMultipleSegments(inputStream) } returns data
every { output.writeEntityHeader(key, data.size) } returns 42
every { output.writeEntityData(data, data.size) } returns data.size
// second key/value
coEvery { plugin.getInputStreamForRecord(token, packageInfo, key264) } returns inputStream2
every { headerReader.readVersion(inputStream2) } returns VERSION
every { crypto.decryptHeader(inputStream2, VERSION, packageInfo.packageName, key2) } returns versionHeader2
every {
crypto.decryptHeader(
inputStream2,
VERSION,
packageInfo.packageName,
key2
)
} returns versionHeader2
every { crypto.decryptMultipleSegments(inputStream2) } returns data2
every { output.writeEntityHeader(key2, data2.size) } returns 42
every { output.writeEntityData(data2, data2.size) } returns data2.size

View file

@ -76,9 +76,13 @@ internal class RestoreCoordinatorTest : TransportTest() {
token = token,
androidVersion = Random.nextInt(),
androidIncremental = getRandomString(),
deviceName = getRandomString())
deviceName = getRandomString()
)
coEvery { plugin.getAvailableBackups() } returns sequenceOf(encryptedMetadata, encryptedMetadata)
coEvery { plugin.getAvailableBackups() } returns sequenceOf(
encryptedMetadata,
encryptedMetadata
)
every { metadataReader.readMetadata(inputStream, token) } returns metadata
every { inputStream.close() } just Runs
@ -126,11 +130,21 @@ internal class RestoreCoordinatorTest : TransportTest() {
every { documentFile.isDirectory } returns false
every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L)
every { storage.name } returns storageName
every { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) } just Runs
every {
notificationManager.onRemovableStorageNotAvailableForRestore(
packageName,
storageName
)
} just Runs
assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))
verify(exactly = 1) { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) }
verify(exactly = 1) {
notificationManager.onRemovableStorageNotAvailableForRestore(
packageName,
storageName
)
}
}
@Test
@ -142,7 +156,12 @@ internal class RestoreCoordinatorTest : TransportTest() {
assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
verify(exactly = 0) { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) }
verify(exactly = 0) {
notificationManager.onRemovableStorageNotAvailableForRestore(
packageName,
storageName
)
}
}
@Test
@ -155,7 +174,12 @@ internal class RestoreCoordinatorTest : TransportTest() {
assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))
verify(exactly = 0) { notificationManager.onRemovableStorageNotAvailableForRestore(packageName, storageName) }
verify(exactly = 0) {
notificationManager.onRemovableStorageNotAvailableForRestore(
packageName,
storageName
)
}
}
@Test