Merge branch 'master' into nextcloud-selection

This commit is contained in:
Chirayu Desai 2020-09-25 05:56:49 +05:30 committed by GitHub
commit 48b8167e42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 138 additions and 46 deletions

View file

@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.metadata package com.stevesoltys.seedvault.metadata
import android.content.pm.ApplicationInfo.FLAG_STOPPED
import android.os.Build import android.os.Build
import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
@ -39,6 +40,10 @@ enum class PackageState {
* Package data could not get backed up, because the app reported no data to back up. * Package data could not get backed up, because the app reported no data to back up.
*/ */
NO_DATA, 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. * 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. * Most often, this is a manifest opt-out, but it could also be a disabled or system-user app.

View file

@ -13,6 +13,7 @@ import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.transport.backup.isSystemApp import com.stevesoltys.seedvault.transport.backup.isSystemApp
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
@ -84,12 +85,15 @@ class MetadataManager(
} }
val oldPackageMetadata = metadata.packageMetadataMap[packageName] val oldPackageMetadata = metadata.packageMetadataMap[packageName]
?: PackageMetadata() ?: PackageMetadata()
// only allow state change if backup of this package is not allowed // only allow state change if backup of this package is not allowed,
val newState = if (packageMetadata.state == NOT_ALLOWED) { // because we need to change from the default of UNKNOWN_ERROR here,
packageMetadata.state // but otherwise don't want to modify the state since set elsewhere.
} else { val newState =
oldPackageMetadata.state if (packageMetadata.state == NOT_ALLOWED || packageMetadata.state == WAS_STOPPED) {
} packageMetadata.state
} else {
oldPackageMetadata.state
}
modifyMetadata(metadataOutputStream) { modifyMetadata(metadataOutputStream) {
metadata.packageMetadataMap[packageName] = oldPackageMetadata.copy( metadata.packageMetadataMap[packageName] = oldPackageMetadata.copy(
state = newState, state = newState,

View file

@ -9,6 +9,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.io.IOException import java.io.IOException
@ -64,11 +65,12 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
for (packageName in json.keys()) { for (packageName in json.keys()) {
if (packageName == JSON_METADATA) continue if (packageName == JSON_METADATA) continue
val p = json.getJSONObject(packageName) val p = json.getJSONObject(packageName)
val pState = when(p.optString(JSON_PACKAGE_STATE)) { val pState = when (p.optString(JSON_PACKAGE_STATE)) {
"" -> APK_AND_DATA "" -> APK_AND_DATA
QUOTA_EXCEEDED.name -> QUOTA_EXCEEDED QUOTA_EXCEEDED.name -> QUOTA_EXCEEDED
NO_DATA.name -> NO_DATA NO_DATA.name -> NO_DATA
NOT_ALLOWED.name -> NOT_ALLOWED NOT_ALLOWED.name -> NOT_ALLOWED
WAS_STOPPED.name -> WAS_STOPPED
else -> UNKNOWN_ERROR else -> UNKNOWN_ERROR
} }
val pSystem = p.optBoolean(JSON_PACKAGE_SYSTEM, false) val pSystem = p.optBoolean(JSON_PACKAGE_SYSTEM, false)

View file

@ -71,9 +71,10 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
enum class AppRestoreStatus { enum class AppRestoreStatus {
IN_PROGRESS, IN_PROGRESS,
SUCCEEDED, SUCCEEDED,
NOT_ELIGIBLE, NOT_YET_BACKED_UP,
FAILED, FAILED,
FAILED_NO_DATA, FAILED_NO_DATA,
FAILED_WAS_STOPPED,
FAILED_NOT_ALLOWED, FAILED_NOT_ALLOWED,
FAILED_QUOTA_EXCEEDED, FAILED_QUOTA_EXCEEDED,
FAILED_NOT_INSTALLED, FAILED_NOT_INSTALLED,

View file

@ -25,12 +25,14 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED 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_NOT_INSTALLED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_YET_BACKED_UP
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
@ -218,6 +220,7 @@ internal class RestoreViewModel(
val metadata = restorableBackup.packageMetadataMap[packageName] ?: return FAILED val metadata = restorableBackup.packageMetadataMap[packageName] ?: return FAILED
return when (metadata.state) { return when (metadata.state) {
NO_DATA -> FAILED_NO_DATA NO_DATA -> FAILED_NO_DATA
WAS_STOPPED -> NOT_YET_BACKED_UP
NOT_ALLOWED -> FAILED_NOT_ALLOWED NOT_ALLOWED -> FAILED_NOT_ALLOWED
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
UNKNOWN_ERROR -> FAILED UNKNOWN_ERROR -> FAILED

View file

@ -1,12 +1,16 @@
package com.stevesoltys.seedvault.settings package com.stevesoltys.seedvault.settings
import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DiffUtil.DiffResult import androidx.recyclerview.widget.DiffUtil.DiffResult
import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.Adapter
@ -54,8 +58,8 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe
fun bind(item: AppStatus) { fun bind(item: AppStatus) {
appName.text = item.name appName.text = item.name
appIcon.setImageDrawable(item.icon) appIcon.setImageDrawable(item.icon)
v.background = clickableBackground
if (editMode) { if (editMode) {
v.background = clickableBackground
v.setOnClickListener { v.setOnClickListener {
switchView.toggle() switchView.toggle()
item.enabled = switchView.isChecked item.enabled = switchView.isChecked
@ -67,8 +71,14 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe
switchView.visibility = VISIBLE switchView.visibility = VISIBLE
switchView.isChecked = item.enabled switchView.isChecked = item.enabled
} else { } else {
v.background = null
v.setOnClickListener(null) v.setOnClickListener(null)
v.setOnLongClickListener {
val intent = Intent(ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", item.packageName, null)
}
startActivity(context, intent, null)
true
}
setStatus(item.status) setStatus(item.status)
if (item.status == SUCCEEDED) { if (item.status == SUCCEEDED) {
appInfo.text = item.time.toRelativeTime(context) appInfo.text = item.time.toRelativeTime(context)

View file

@ -23,11 +23,13 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_ELIGIBLE import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_WAS_STOPPED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_YET_BACKED_UP
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.requestBackup import com.stevesoltys.seedvault.transport.requestBackup
@ -97,9 +99,10 @@ internal class SettingsViewModel(
val status = when (metadata?.state) { val status = when (metadata?.state) {
null -> { null -> {
Log.w(TAG, "No metadata available for: ${it.packageName}") Log.w(TAG, "No metadata available for: ${it.packageName}")
NOT_ELIGIBLE NOT_YET_BACKED_UP
} }
NO_DATA -> FAILED_NO_DATA NO_DATA -> FAILED_NO_DATA
WAS_STOPPED -> FAILED_WAS_STOPPED
NOT_ALLOWED -> FAILED_NOT_ALLOWED NOT_ALLOWED -> FAILED_NOT_ALLOWED
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
UNKNOWN_ERROR -> FAILED UNKNOWN_ERROR -> FAILED

View file

@ -63,7 +63,7 @@ fun requestBackup(context: Context) {
val observer = NotificationBackupObserver(context, packages.size, appTotals) val observer = NotificationBackupObserver(context, packages.size, appTotals)
val result = try { val result = try {
val backupManager: IBackupManager = get().koin.get() val backupManager: IBackupManager = get().koin.get()
backupManager.requestBackup(packages, observer, BackupMonitor(), FLAG_USER_INITIATED) backupManager.requestBackup(packages, observer, BackupMonitor(), 0)
} catch (e: RemoteException) { } catch (e: RemoteException) {
Log.e(TAG, "Error during backup: ", e) Log.e(TAG, "Error during backup: ", e)
val nm: BackupNotificationManager = get().koin.get() val nm: BackupNotificationManager = get().koin.get()

View file

@ -10,6 +10,8 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.Clock import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
@ -19,6 +21,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import java.io.IOException import java.io.IOException
@ -299,34 +302,54 @@ internal class BackupCoordinator(
else -> throw IllegalStateException("Unexpected state in finishBackup()") else -> throw IllegalStateException("Unexpected state in finishBackup()")
} }
private suspend fun backUpNotAllowedPackages() { @VisibleForTesting(otherwise = PRIVATE)
internal suspend fun backUpNotAllowedPackages() {
Log.d(TAG, "Checking if APKs of opt-out apps need backup...") Log.d(TAG, "Checking if APKs of opt-out apps need backup...")
val notAllowedPackages = packageService.notAllowedPackages val notAllowedPackages = packageService.notAllowedPackages
notAllowedPackages.forEachIndexed { i, optOutPackageInfo -> notAllowedPackages.forEachIndexed { i, packageInfo ->
val packageName = packageInfo.packageName
try { try {
nm.onOptOutAppBackup(optOutPackageInfo.packageName, i + 1, notAllowedPackages.size) nm.onOptOutAppBackup(packageName, i + 1, notAllowedPackages.size)
backUpApk(optOutPackageInfo, NOT_ALLOWED) val packageState = if (packageInfo.isStopped()) WAS_STOPPED else NOT_ALLOWED
val wasBackedUp = backUpApk(packageInfo, packageState)
if (!wasBackedUp) {
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")
plugin.getMetadataOutputStream().use {
metadataManager.onPackageBackupError(packageInfo, packageState, it)
}
}
}
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error backing up opt-out APK of ${optOutPackageInfo.packageName}", e) Log.e(TAG, "Error backing up opt-out APK of $packageName", e)
} }
} }
} }
/**
* Backs up an APK for the given [PackageInfo].
*
* @return true if a backup was performed and false if no backup was needed or it failed.
*/
private suspend fun backUpApk( private suspend fun backUpApk(
packageInfo: PackageInfo, packageInfo: PackageInfo,
packageState: PackageState = UNKNOWN_ERROR packageState: PackageState = UNKNOWN_ERROR
) { ): Boolean {
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
try { return try {
apkBackup.backupApkIfNecessary(packageInfo, packageState) { apkBackup.backupApkIfNecessary(packageInfo, packageState) {
plugin.getApkOutputStream(packageInfo) plugin.getApkOutputStream(packageInfo)
}?.let { packageMetadata -> }?.let { packageMetadata ->
plugin.getMetadataOutputStream().use { plugin.getMetadataOutputStream().use {
metadataManager.onApkBackedUp(packageInfo, packageMetadata, it) metadataManager.onApkBackedUp(packageInfo, packageMetadata, it)
} }
} true
} ?: false
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error while writing APK or metadata for $packageName", e) Log.e(TAG, "Error while writing APK or metadata for $packageName", e)
false
} }
} }

View file

@ -154,6 +154,11 @@ internal fun PackageInfo.isNotUpdatedSystemApp(): Boolean {
internal fun PackageInfo.doesNotGetBackedUp(): Boolean { internal fun PackageInfo.doesNotGetBackedUp(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
return applicationInfo.flags and FLAG_ALLOW_BACKUP == 0 && // does not allow backup return applicationInfo.flags and FLAG_ALLOW_BACKUP == 0 || // does not allow backup
applicationInfo.flags and FLAG_STOPPED != 0 // is stopped applicationInfo.flags and FLAG_STOPPED != 0 // is stopped
} }
internal fun PackageInfo.isStopped(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return false
return applicationInfo.flags and FLAG_STOPPED != 0
}

View file

@ -137,7 +137,7 @@ internal class ApkRestore(
if (isOlder || !installedPackageInfo.isSystemApp()) throw NameNotFoundException() if (isOlder || !installedPackageInfo.isSystemApp()) throw NameNotFoundException()
} catch (e: NameNotFoundException) { } catch (e: NameNotFoundException) {
Log.w(TAG, "Not installing $packageName because older or not a system app here.") Log.w(TAG, "Not installing $packageName because older or not a system app here.")
fail(installResult, packageName) emit(fail(installResult, packageName))
return@flow return@flow
} }
} }

View file

@ -18,12 +18,12 @@ 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_NOT_INSTALLED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_WAS_STOPPED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_ELIGIBLE import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_YET_BACKED_UP
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
internal abstract class AppViewHolder(protected val v: View) : RecyclerView.ViewHolder(v) {
internal open class AppViewHolder(protected val v: View) : RecyclerView.ViewHolder(v) {
protected val context: Context = v.context protected val context: Context = v.context
protected val pm: PackageManager = context.packageManager protected val pm: PackageManager = context.packageManager
@ -42,7 +42,6 @@ internal open class AppViewHolder(protected val v: View) : RecyclerView.ViewHold
} }
protected fun setStatus(status: AppRestoreStatus) { protected fun setStatus(status: AppRestoreStatus) {
v.background = null
if (status == IN_PROGRESS) { if (status == IN_PROGRESS) {
appInfo.visibility = GONE appInfo.visibility = GONE
appStatus.visibility = INVISIBLE appStatus.visibility = INVISIBLE
@ -64,8 +63,9 @@ internal open class AppViewHolder(protected val v: View) : RecyclerView.ViewHold
} }
private fun AppRestoreStatus.getInfo(): String = when (this) { private fun AppRestoreStatus.getInfo(): String = when (this) {
NOT_ELIGIBLE -> context.getString(R.string.restore_app_not_eligible) NOT_YET_BACKED_UP -> context.getString(R.string.restore_app_not_yet_backed_up)
FAILED_NO_DATA -> context.getString(R.string.restore_app_no_data) FAILED_NO_DATA -> context.getString(R.string.restore_app_no_data)
FAILED_WAS_STOPPED -> context.getString(R.string.restore_app_was_stopped)
FAILED_NOT_ALLOWED -> context.getString(R.string.restore_app_not_allowed) FAILED_NOT_ALLOWED -> context.getString(R.string.restore_app_not_allowed)
FAILED_NOT_INSTALLED -> context.getString(R.string.restore_app_not_installed) FAILED_NOT_INSTALLED -> context.getString(R.string.restore_app_not_installed)
FAILED_QUOTA_EXCEEDED -> context.getString(R.string.restore_app_quota_exceeded) FAILED_QUOTA_EXCEEDED -> context.getString(R.string.restore_app_quota_exceeded)

View file

@ -107,7 +107,8 @@
<string name="restore_restoring">Restoring backup</string> <string name="restore_restoring">Restoring backup</string>
<string name="restore_magic_package">System package manager</string> <string name="restore_magic_package">System package manager</string>
<!-- This text gets shown for apps that the OS did not try to backup for whatever reason e.g. no backup was run yet --> <!-- This text gets shown for apps that the OS did not try to backup for whatever reason e.g. no backup was run yet -->
<string name="restore_app_not_eligible">Not yet backed up</string> <string name="restore_app_not_yet_backed_up">Not yet backed up</string>
<string name="restore_app_was_stopped">Not backed up as it wasn\'t used recently</string>
<string name="restore_app_no_data">App reported no data for backup</string> <string name="restore_app_no_data">App reported no data for backup</string>
<string name="restore_app_not_allowed">App doesn\'t allow backup</string> <string name="restore_app_not_allowed">App doesn\'t allow backup</string>
<string name="restore_app_not_installed">App not installed</string> <string name="restore_app_not_installed">App not installed</string>

View file

@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
@ -163,6 +164,11 @@ class MetadataManagerTest {
packageMetadata = packageMetadata.copy(version = ++version, state = NOT_ALLOWED) packageMetadata = packageMetadata.copy(version = ++version, state = NOT_ALLOWED)
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream) 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))
} }
@Test @Test

View file

@ -6,6 +6,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import io.mockk.mockk import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -32,6 +33,7 @@ internal class MetadataWriterDecoderTest {
val time = Random.nextLong() val time = Random.nextLong()
val packages = HashMap<String, PackageMetadata>().apply { val packages = HashMap<String, PackageMetadata>().apply {
put(getRandomString(), PackageMetadata(time, APK_AND_DATA)) put(getRandomString(), PackageMetadata(time, APK_AND_DATA))
put(getRandomString(), PackageMetadata(time, WAS_STOPPED))
} }
val metadata = getMetadata(packages) 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))

View file

@ -4,6 +4,8 @@ import android.app.backup.BackupTransport.TRANSPORT_ERROR
import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.BackupTransport.TRANSPORT_OK
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_STOPPED
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.net.Uri import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
@ -16,6 +18,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.settings.Storage
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.Runs import io.mockk.Runs
@ -318,7 +321,11 @@ internal class BackupCoordinatorTest : BackupTest() {
val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER } val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER }
val notAllowedPackages = listOf( val notAllowedPackages = listOf(
PackageInfo().apply { packageName = "org.example.1" }, PackageInfo().apply { packageName = "org.example.1" },
PackageInfo().apply { packageName = "org.example.2" } PackageInfo().apply {
packageName = "org.example.2"
// the second package does not get backed up, because it is stopped
applicationInfo = ApplicationInfo().apply { flags = FLAG_STOPPED }
}
) )
val packageMetadata: PackageMetadata = mockk() val packageMetadata: PackageMetadata = mockk()
@ -337,13 +344,13 @@ internal class BackupCoordinatorTest : BackupTest() {
} just Runs } just Runs
// no backup needed // no backup needed
coEvery { coEvery {
apkBackup.backupApkIfNecessary( apkBackup.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any())
notAllowedPackages[0],
NOT_ALLOWED,
any()
)
} returns null } returns null
// update notification // check old metadata for state changes, because we won't update it otherwise
every { metadataManager.getPackageMetadata(notAllowedPackages[0].packageName) } returns packageMetadata
every { packageMetadata.state } returns NOT_ALLOWED // no change
// update notification for second package
every { every {
notificationManager.onOptOutAppBackup( notificationManager.onOptOutAppBackup(
notAllowedPackages[1].packageName, notAllowedPackages[1].packageName,
@ -353,11 +360,7 @@ internal class BackupCoordinatorTest : BackupTest() {
} just Runs } just Runs
// was backed up, get new packageMetadata // was backed up, get new packageMetadata
coEvery { coEvery {
apkBackup.backupApkIfNecessary( apkBackup.backupApkIfNecessary(notAllowedPackages[1], WAS_STOPPED, any())
notAllowedPackages[1],
NOT_ALLOWED,
any()
)
} returns packageMetadata } returns packageMetadata
coEvery { plugin.getMetadataOutputStream() } returns metadataOutputStream coEvery { plugin.getMetadataOutputStream() } returns metadataOutputStream
every { every {
@ -369,14 +372,38 @@ internal class BackupCoordinatorTest : BackupTest() {
} just Runs } just Runs
every { metadataOutputStream.close() } just Runs every { metadataOutputStream.close() } just Runs
assertEquals( assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
TRANSPORT_OK,
backup.performIncrementalBackup(packageInfo, fileDescriptor, 0)
)
coVerify { coVerify {
apkBackup.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any()) apkBackup.backupApkIfNecessary(notAllowedPackages[0], NOT_ALLOWED, any())
apkBackup.backupApkIfNecessary(notAllowedPackages[1], NOT_ALLOWED, any()) apkBackup.backupApkIfNecessary(notAllowedPackages[1], WAS_STOPPED, any())
metadataOutputStream.close()
}
}
@Test
fun `APK backup of not allowed apps updates state even without new APK`() = runBlocking {
val oldPackageMetadata: PackageMetadata = mockk()
every { packageService.notAllowedPackages } returns listOf(packageInfo)
every { notificationManager.onOptOutAppBackup(packageInfo.packageName, 1, 1) } just Runs
coEvery { apkBackup.backupApkIfNecessary(packageInfo, NOT_ALLOWED, any()) } returns null
every { metadataManager.getPackageMetadata(packageInfo.packageName) } returns oldPackageMetadata
every { oldPackageMetadata.state } returns WAS_STOPPED // state differs now, was stopped before
coEvery { plugin.getMetadataOutputStream() } returns metadataOutputStream
every {
metadataManager.onPackageBackupError(
packageInfo,
NOT_ALLOWED,
metadataOutputStream
)
} just Runs
every { metadataOutputStream.close() } just Runs
backup.backUpNotAllowedPackages()
verify {
metadataManager.onPackageBackupError(packageInfo, NOT_ALLOWED, metadataOutputStream)
metadataOutputStream.close() metadataOutputStream.close()
} }
} }