Show percentages in progress notification and x of n status at the end

Fine-grained progress reporting causes apps to show up twice which is
confusing. Also @pm@ metadata and opt-out APKs are too much detail for
normal users. So we decided to only show a percentage in the progress
notification.

When the backup finished, the app now shows "x of n apps backed up"
which is more positive when the previous negative message of how many
apps were not backed up.

Some further minor tweets were done to app counting to report proper
totals.
This commit is contained in:
Torsten Grote 2020-08-28 15:22:17 -03:00 committed by Chirayu Desai
parent 983f917391
commit 80187c8c70
20 changed files with 155 additions and 91 deletions

View file

@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.SettingsViewModel
import com.stevesoltys.seedvault.transport.backup.backupModule
import com.stevesoltys.seedvault.transport.restore.restoreModule
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel
import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel
import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel
@ -36,7 +37,7 @@ class App : Application() {
single { Clock() }
factory<IBackupManager> { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) }
viewModel { SettingsViewModel(this@App, get(), get(), get()) }
viewModel { SettingsViewModel(this@App, get(), get(), get(), get()) }
viewModel { RecoveryCodeViewModel(this@App, get()) }
viewModel { BackupStorageViewModel(this@App, get(), get()) }
viewModel { RestoreStorageViewModel(this@App, get(), get()) }

View file

@ -12,6 +12,7 @@ import androidx.lifecycle.distinctUntilChanged
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import java.io.FileNotFoundException
import java.io.IOException
@ -196,9 +197,12 @@ class MetadataManager(
}
@Synchronized
fun getPackagesNumNotBackedUp(): Int {
fun getPackagesNumBackedUp(): Int {
return metadata.packageMetadataMap.filter { (_, packageMetadata) ->
!packageMetadata.system && packageMetadata.state != APK_AND_DATA
!packageMetadata.system && ( // ignore system apps
packageMetadata.state == APK_AND_DATA || // either full success
packageMetadata.state == NO_DATA // or apps that simply had no data
)
}.count()
}

View file

@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import androidx.core.net.toUri
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import org.koin.core.context.GlobalContext.get
internal const val ACTION_RESTORE_ERROR_UNINSTALL = "com.stevesoltys.seedvault.action.UNINSTALL"

View file

@ -10,7 +10,7 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder
import com.stevesoltys.seedvault.ui.AppViewHolder
import java.util.*
import java.util.LinkedList
internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
@ -50,7 +50,7 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
}
}
inner class PackageViewHolder(v: View) : AppViewHolder(v) {
class PackageViewHolder(v: View) : AppViewHolder(v) {
fun bind(item: AppRestoreResult) {
appName.text = item.name
if (item.packageName == MAGIC_PACKAGE_MANAGER) {
@ -71,6 +71,7 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
enum class AppRestoreStatus {
IN_PROGRESS,
SUCCEEDED,
NOT_ELIGIBLE,
FAILED,
FAILED_NO_DATA,
FAILED_NOT_ALLOWED,

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.getAppName
import com.stevesoltys.seedvault.ui.notification.getAppName
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

View file

@ -5,7 +5,7 @@ import androidx.annotation.CallSuper
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel

View file

@ -14,7 +14,6 @@ import androidx.recyclerview.widget.DiffUtil.calculateDiff
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.getAppName
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
@ -25,21 +24,24 @@ import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED
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_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_ELIGIBLE
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import com.stevesoltys.seedvault.ui.notification.getAppName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale
private val TAG = SettingsViewModel::class.java.simpleName
class SettingsViewModel(
internal class SettingsViewModel(
app: Application,
settingsManager: SettingsManager,
keyManager: KeyManager,
private val metadataManager: MetadataManager
private val metadataManager: MetadataManager,
private val packageService: PackageService
) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
override val isRestoreOperation = false
@ -69,43 +71,41 @@ class SettingsViewModel(
private fun getAppStatusResult(): LiveData<AppStatusResult> = liveData {
val pm = app.packageManager
val locale = Locale.getDefault()
val list = pm.getInstalledPackages(0)
.filter { !it.isSystemApp() }
.map {
val icon = if (it.packageName == MAGIC_PACKAGE_MANAGER) {
val list = packageService.userApps.map {
val icon = if (it.packageName == MAGIC_PACKAGE_MANAGER) {
getDrawable(app, R.drawable.ic_launcher_default)!!
} else {
try {
pm.getApplicationIcon(it.packageName)
} catch (e: NameNotFoundException) {
getDrawable(app, R.drawable.ic_launcher_default)!!
} else {
try {
pm.getApplicationIcon(it.packageName)
} catch (e: NameNotFoundException) {
getDrawable(app, R.drawable.ic_launcher_default)!!
}
}
val metadata = metadataManager.getPackageMetadata(it.packageName)
val time = metadata?.time ?: 0
val status = when (metadata?.state) {
null -> {
Log.w(TAG, "No metadata available for: ${it.packageName}")
FAILED
}
NO_DATA -> FAILED_NO_DATA
NOT_ALLOWED -> FAILED_NOT_ALLOWED
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
UNKNOWN_ERROR -> FAILED
APK_AND_DATA -> SUCCEEDED
}
val metadata = metadataManager.getPackageMetadata(it.packageName)
val time = metadata?.time ?: 0
val status = when (metadata?.state) {
null -> {
Log.w(TAG, "No metadata available for: ${it.packageName}")
NOT_ELIGIBLE
}
if (metadata?.hasApk() == false) {
Log.w(TAG, "No APK stored for: ${it.packageName}")
}
AppStatus(
packageName = it.packageName,
enabled = settingsManager.isBackupEnabled(it.packageName),
icon = icon,
name = getAppName(app, it.packageName).toString(),
time = time,
status = status
)
}.sortedBy { it.name.toLowerCase(locale) }
NO_DATA -> FAILED_NO_DATA
NOT_ALLOWED -> FAILED_NOT_ALLOWED
QUOTA_EXCEEDED -> FAILED_QUOTA_EXCEEDED
UNKNOWN_ERROR -> FAILED
APK_AND_DATA -> SUCCEEDED
}
if (metadata?.hasApk() == false) {
Log.w(TAG, "No APK stored for: ${it.packageName}")
}
AppStatus(
packageName = it.packageName,
enabled = settingsManager.isBackupEnabled(it.packageName),
icon = icon,
name = getAppName(app, it.packageName).toString(),
time = time,
status = status
)
}.sortedBy { it.name.toLowerCase(locale) }
val oldList = mAppStatusList.value?.appStatusList ?: emptyList()
val diff = calculateDiff(AppStatusDiff(oldList, list))
emit(AppStatusResult(list, diff))

View file

@ -13,8 +13,8 @@ import android.os.RemoteException
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.NotificationBackupObserver
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver
import com.stevesoltys.seedvault.transport.backup.PackageService
import org.koin.core.context.GlobalContext.get
@ -53,9 +53,9 @@ class ConfigurableBackupTransportService : Service() {
fun requestBackup(context: Context) {
val packageService: PackageService = get().koin.get()
val packages = packageService.eligiblePackages
val optOutPackages = packageService.notAllowedPackages
val appTotals = packageService.expectedAppTotals
val observer = NotificationBackupObserver(context, packages.size, optOutPackages.size, true)
val observer = NotificationBackupObserver(context, packages.size, appTotals, true)
val result = try {
val backupManager: IBackupManager = get().koin.get()
backupManager.requestBackup(packages, observer, BackupMonitor(), FLAG_USER_INITIATED)

View file

@ -10,7 +10,7 @@ import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.metadata.MetadataManager

View file

@ -8,7 +8,7 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.encodeBase64

View file

@ -2,16 +2,19 @@ package com.stevesoltys.seedvault.transport.backup
import android.app.backup.IBackupManager
import android.content.pm.ApplicationInfo.FLAG_ALLOW_BACKUP
import android.content.pm.ApplicationInfo.FLAG_STOPPED
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_INSTRUMENTATION
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import android.util.Log.INFO
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BuildConfig.APPLICATION_ID
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
private val TAG = PackageService::class.java.simpleName
@ -68,8 +71,9 @@ internal class PackageService(
// because the package info is used by [ApkBackup] which needs signing info.
return packageManager.getInstalledPackages(GET_SIGNING_CERTIFICATES)
.filter { packageInfo ->
!packageInfo.isBackupAllowed() && // only apps that do not allow backup
!packageInfo.isNotUpdatedSystemApp() // and are not vanilla system apps
packageInfo.doesNotGetBackedUp() && // only apps that do not allow backup
!packageInfo.isNotUpdatedSystemApp() && // and are not vanilla system apps
packageInfo.packageName != APPLICATION_ID // not this app
}.sortedBy { packageInfo ->
packageInfo.packageName
}.also { notAllowed ->
@ -81,6 +85,32 @@ internal class PackageService(
}
}
/**
* A list of non-system apps (without instrumentation test apps).
*/
val userApps: List<PackageInfo>
@WorkerThread
get() {
return packageManager.getInstalledPackages(GET_INSTRUMENTATION)
.filter { it.isUserVisible() }
}
val expectedAppTotals: ExpectedAppTotals
@WorkerThread
get() {
var appsTotal = 0
var appsOptOut = 0
packageManager.getInstalledPackages(GET_INSTRUMENTATION).forEach { packageInfo ->
if (packageInfo.isUserVisible()) {
appsTotal++
if (packageInfo.doesNotGetBackedUp()) {
appsOptOut++
}
}
}
return ExpectedAppTotals(appsTotal, appsOptOut)
}
private fun logPackages(packages: List<String>) {
packages.chunked(LOG_MAX_PACKAGES).forEach {
Log.i(TAG, it.toString())
@ -89,6 +119,22 @@ internal class PackageService(
}
internal data class ExpectedAppTotals(
/**
* The total number of non-system apps eligible for backup.
*/
val appsTotal: Int,
/**
* The number of non-system apps that has opted-out of backup.
*/
val appsOptOut: Int
)
internal fun PackageInfo.isUserVisible(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return false
return !isNotUpdatedSystemApp() && instrumentation == null && packageName != APPLICATION_ID
}
internal fun PackageInfo.isSystemApp(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
return applicationInfo.flags and FLAG_SYSTEM != 0
@ -105,7 +151,8 @@ internal fun PackageInfo.isNotUpdatedSystemApp(): Boolean {
return isSystemApp && !isUpdatedSystemApp
}
internal fun PackageInfo.isBackupAllowed(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return false
return applicationInfo.flags and FLAG_ALLOW_BACKUP != 0
internal fun PackageInfo.doesNotGetBackedUp(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
return applicationInfo.flags and FLAG_ALLOW_BACKUP == 0 && // does not allow backup
applicationInfo.flags and FLAG_STOPPED != 0 // is stopped
}

View file

@ -13,7 +13,7 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.collection.LongSparseArray
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.header.UnsupportedVersionException

View file

@ -19,6 +19,7 @@ import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_INSTALLED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
import com.stevesoltys.seedvault.restore.AppRestoreStatus.NOT_ELIGIBLE
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
@ -63,6 +64,7 @@ internal open class AppViewHolder(protected val v: View) : RecyclerView.ViewHold
}
private fun AppRestoreStatus.getInfo(): String = when (this) {
NOT_ELIGIBLE -> context.getString(R.string.restore_app_not_eligible)
FAILED_NO_DATA -> context.getString(R.string.restore_app_no_data)
FAILED_NOT_ALLOWED -> context.getString(R.string.restore_app_not_allowed)
FAILED_NOT_INSTALLED -> context.getString(R.string.restore_app_not_installed)

View file

@ -1,4 +1,4 @@
package com.stevesoltys.seedvault
package com.stevesoltys.seedvault.ui.notification
import android.app.NotificationChannel
import android.app.NotificationManager
@ -16,11 +16,14 @@ import androidx.core.app.NotificationCompat.Builder
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationCompat.PRIORITY_HIGH
import androidx.core.app.NotificationCompat.PRIORITY_LOW
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.restore.ACTION_RESTORE_ERROR_UNINSTALL
import com.stevesoltys.seedvault.restore.EXTRA_PACKAGE_NAME
import com.stevesoltys.seedvault.restore.REQUEST_CODE_UNINSTALL
import com.stevesoltys.seedvault.settings.ACTION_APP_STATUS_LIST
import com.stevesoltys.seedvault.settings.SettingsActivity
import com.stevesoltys.seedvault.transport.backup.ExpectedAppTotals
private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
private const val CHANNEL_ID_ERROR = "NotificationError"
@ -31,7 +34,7 @@ private const val NOTIFICATION_ID_RESTORE_ERROR = 3
private val TAG = BackupNotificationManager::class.java.simpleName
class BackupNotificationManager(private val context: Context) {
internal class BackupNotificationManager(private val context: Context) {
private val nm = context.getSystemService(NotificationManager::class.java)!!.apply {
createNotificationChannel(getObserverChannel())
@ -41,6 +44,7 @@ class BackupNotificationManager(private val context: Context) {
private var expectedApps: Int? = null
private var expectedOptOutApps: Int? = null
private var expectedPmRecords: Int? = null
private var expectedAppTotals: ExpectedAppTotals? = null
private fun getObserverChannel(): NotificationChannel {
val title = context.getString(R.string.notification_channel_title)
@ -67,17 +71,18 @@ class BackupNotificationManager(private val context: Context) {
*/
fun onBackupStarted(
expectedPackages: Int,
expectedOptOutPackages: Int,
appTotals: ExpectedAppTotals,
userInitiated: Boolean
) {
updateBackupNotification(
contentText = "", // This passes quickly, no need to show something here
infoText = "", // This passes quickly, no need to show something here
transferred = 0,
expected = expectedPackages,
userInitiated = userInitiated
)
expectedApps = expectedPackages
expectedOptOutApps = expectedOptOutPackages
expectedOptOutApps = appTotals.appsOptOut
expectedAppTotals = appTotals
}
/**
@ -88,11 +93,9 @@ class BackupNotificationManager(private val context: Context) {
Log.d(TAG, "Expected number of apps unknown. Not showing @pm@ notification.")
return
}
val appName = getAppName(context, packageName)
val contentText = context.getString(R.string.notification_content_package_manager, appName)
val addend = (expectedOptOutApps ?: 0) + (expectedApps ?: 0)
updateBackupNotification(
contentText = contentText,
infoText = "@pm@ record for $packageName",
transferred = transferred,
expected = expected + addend,
userInitiated = false
@ -108,10 +111,8 @@ class BackupNotificationManager(private val context: Context) {
Log.d(TAG, "Expected number of apps unknown. Not showing APK notification.")
return
}
val appName = getAppName(context, packageName)
val contentText = context.getString(R.string.notification_content_opt_out_app, appName)
updateBackupNotification(
contentText = contentText,
infoText = "Opt-out APK for $packageName",
transferred = transferred + (expectedPmRecords ?: 0),
expected = expected + (expectedApps ?: 0) + (expectedPmRecords ?: 0),
userInitiated = false
@ -127,7 +128,7 @@ class BackupNotificationManager(private val context: Context) {
val expected = expectedApps ?: error("expectedApps is null")
val addend = (expectedOptOutApps ?: 0) + (expectedPmRecords ?: 0)
updateBackupNotification(
contentText = app,
infoText = app,
transferred = transferred + addend,
expected = expected + addend,
userInitiated = userInitiated
@ -135,16 +136,20 @@ class BackupNotificationManager(private val context: Context) {
}
private fun updateBackupNotification(
contentText: CharSequence,
infoText: CharSequence,
transferred: Int,
expected: Int,
userInitiated: Boolean
) {
Log.i(TAG, "$transferred/$expected $contentText")
@Suppress("MagicNumber")
val percentage = (transferred.toFloat() / expected) * 100
val percentageStr = "%.0f%%".format(percentage)
Log.i(TAG, "$transferred/$expected - $percentageStr - $infoText")
val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
setSmallIcon(R.drawable.ic_cloud_upload)
setContentTitle(context.getString(R.string.notification_title))
setContentText(contentText)
setContentText(percentageStr)
setTicker(infoText)
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis())
@ -154,15 +159,17 @@ class BackupNotificationManager(private val context: Context) {
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
}
fun onBackupFinished(success: Boolean, notBackedUp: Int?, userInitiated: Boolean) {
fun onBackupFinished(success: Boolean, numBackedUp: Int?, userInitiated: Boolean) {
if (!userInitiated) {
// don't show permanent finished notification if backup was not triggered by user
nm.cancel(NOTIFICATION_ID_OBSERVER)
return
}
val titleRes =
if (success) R.string.notification_success_title else R.string.notification_failed_title
val contentText = if (notBackedUp == null) null else {
context.getString(R.string.notification_success_num_not_backed_up, notBackedUp)
val total = expectedAppTotals?.appsTotal
val contentText = if (numBackedUp == null || total == null) null else {
context.getString(R.string.notification_success_text, numBackedUp, total)
}
val iconRes = if (success) R.drawable.ic_cloud_done else R.drawable.ic_cloud_error
val intent = Intent(context, SettingsActivity::class.java).apply {
@ -186,6 +193,7 @@ class BackupNotificationManager(private val context: Context) {
expectedOptOutApps = null
expectedPmRecords = null
expectedApps = null
expectedAppTotals = null
}
fun onBackupError() {

View file

@ -1,4 +1,4 @@
package com.stevesoltys.seedvault
package com.stevesoltys.seedvault.ui.notification
import android.app.backup.BackupProgress
import android.app.backup.IBackupObserver
@ -7,16 +7,19 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.util.Log
import android.util.Log.INFO
import android.util.Log.isLoggable
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.transport.backup.ExpectedAppTotals
import org.koin.core.KoinComponent
import org.koin.core.inject
private val TAG = NotificationBackupObserver::class.java.simpleName
class NotificationBackupObserver(
internal class NotificationBackupObserver(
private val context: Context,
private val expectedPackages: Int,
expectedOptOutPackages: Int,
appTotals: ExpectedAppTotals,
private val userInitiated: Boolean
) : IBackupObserver.Stub(), KoinComponent {
@ -28,7 +31,7 @@ class NotificationBackupObserver(
init {
// Inform the notification manager that a backup has started
// and inform about the expected numbers, so it can compute a total.
nm.onBackupStarted(expectedPackages, expectedOptOutPackages, userInitiated)
nm.onBackupStarted(expectedPackages, appTotals, userInitiated)
}
/**
@ -75,8 +78,8 @@ class NotificationBackupObserver(
Log.i(TAG, "Backup finished $numPackages/$expectedPackages. Status: $status")
}
val success = status == 0
val notBackedUp = if (success) metadataManager.getPackagesNumNotBackedUp() else null
nm.onBackupFinished(success, notBackedUp, userInitiated)
val numBackedUp = if (success) metadataManager.getPackagesNumBackedUp() else null
nm.onBackupFinished(success, numBackedUp, userInitiated)
}
private fun showProgressNotification(packageName: String) {

View file

@ -73,16 +73,12 @@
<!-- Notification -->
<string name="notification_channel_title">Backup notification</string>
<string name="notification_title">Backup running</string>
<!-- This is shown in a backup notification when metadata for an app is being backed up -->
<string name="notification_content_package_manager">Metadata for %s</string>
<!-- This is shown in a backup notification when *only* the APK of an app that opts out of backup gets backed up -->
<string name="notification_content_opt_out_app">Only app %s</string>
<string name="notification_backup_result_complete">Backup complete</string>
<string name="notification_backup_result_rejected">Not backed up</string>
<string name="notification_backup_result_error">Backup failed</string>
<string name="notification_success_title">Backup finished</string>
<string name="notification_success_num_not_backed_up">%1$d apps could not get backed up</string>
<string name="notification_success_text">%1$d of %2$d apps backed up. Tap to learn more.</string>
<string name="notification_failed_title">Backup failed</string>
<string name="notification_error_channel_title">Error notification</string>
@ -108,6 +104,8 @@
<string name="restore_next">Next</string>
<string name="restore_restoring">Restoring backup</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 -->
<string name="restore_app_not_eligible">Not yet backed up</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_installed">App not installed</string>

View file

@ -7,7 +7,7 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
import android.app.backup.RestoreDescription
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
import android.os.ParcelFileDescriptor
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.crypto.CipherFactoryImpl
import com.stevesoltys.seedvault.crypto.CryptoImpl
import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl

View file

@ -8,7 +8,7 @@ import android.content.pm.PackageInfo
import android.net.Uri
import android.os.ParcelFileDescriptor
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.coAssertThrows
import com.stevesoltys.seedvault.getRandomString

View file

@ -7,7 +7,7 @@ import android.app.backup.BackupTransport.TRANSPORT_ERROR
import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED
import android.app.backup.BackupTransport.TRANSPORT_OK
import android.content.pm.PackageInfo
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.Utf8
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE

View file

@ -9,7 +9,7 @@ import android.app.backup.RestoreDescription.TYPE_KEY_VALUE
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.coAssertThrows
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.metadata.BackupMetadata