Show one single progress bar in the notification

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

Closes #59, #61
This commit is contained in:
Torsten Grote 2020-01-14 15:11:17 -03:00
parent 2bcf82d607
commit 6ed522bfb7
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
5 changed files with 67 additions and 18 deletions

View file

@ -63,6 +63,8 @@ class BackupNotificationManager(private val context: Context) {
val notification = observerBuilder.apply { val notification = observerBuilder.apply {
setContentTitle(context.getString(R.string.notification_title)) setContentTitle(context.getString(R.string.notification_title))
setContentText(app) setContentText(app)
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis()) setWhen(System.currentTimeMillis())
setProgress(expected, transferred, false) setProgress(expected, transferred, false)
priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
@ -79,14 +81,33 @@ class BackupNotificationManager(private val context: Context) {
val notification = observerBuilder.apply { val notification = observerBuilder.apply {
setContentTitle(title) setContentTitle(title)
setContentText(app) setContentText(app)
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis()) setWhen(System.currentTimeMillis())
priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
}.build() }.build()
nm.notify(NOTIFICATION_ID_OBSERVER, notification) nm.notify(NOTIFICATION_ID_OBSERVER, notification)
} }
fun onBackupFinished() { fun onBackupFinished(success: Boolean, notBackedUp: Int?, userInitiated: Boolean) {
if (!userInitiated) {
nm.cancel(NOTIFICATION_ID_OBSERVER) nm.cancel(NOTIFICATION_ID_OBSERVER)
return
}
val titleRes = if (success) R.string.notification_success_title else R.string.notification_failed_title
val contentText = if (notBackedUp == null) null else {
context.getString(R.string.notification_success_num_not_backed_up, notBackedUp)
}
val notification = observerBuilder.apply {
setContentTitle(context.getString(titleRes))
setContentText(contentText)
setOngoing(false)
setShowWhen(true)
setWhen(System.currentTimeMillis())
setProgress(0, 0, false)
priority = PRIORITY_LOW
}.build()
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
} }
fun onBackupError() { fun onBackupError() {

View file

@ -7,6 +7,7 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.util.Log import android.util.Log
import android.util.Log.INFO import android.util.Log.INFO
import android.util.Log.isLoggable import android.util.Log.isLoggable
import com.stevesoltys.seedvault.metadata.MetadataManager
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.inject import org.koin.core.inject
@ -14,9 +15,18 @@ private val TAG = NotificationBackupObserver::class.java.simpleName
class NotificationBackupObserver( class NotificationBackupObserver(
private val context: Context, private val context: Context,
private val expectedPackages: Int,
private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent { private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent {
private val nm: BackupNotificationManager by inject() private val nm: BackupNotificationManager by inject()
private val metadataManager: MetadataManager by inject()
private var currentPackage: String? = null
private var numPackages: Int = 0
init {
// we need to show this manually as [onUpdate] isn't called for first @pm@ package
nm.onBackupUpdate(getAppName(MAGIC_PACKAGE_MANAGER), 0, expectedPackages, userInitiated)
}
/** /**
* This method could be called several times for packages with full data backup. * This method could be called several times for packages with full data backup.
@ -26,10 +36,7 @@ class NotificationBackupObserver(
* @param backupProgress Current progress of backup for the package. * @param backupProgress Current progress of backup for the package.
*/ */
override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) { override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) {
val transferred = backupProgress.bytesTransferred.toInt() showProgressNotification(currentBackupPackage)
val expected = backupProgress.bytesExpected.toInt()
val app = getAppName(currentBackupPackage)
nm.onBackupUpdate(app, transferred, expected, userInitiated)
} }
/** /**
@ -46,7 +53,8 @@ class NotificationBackupObserver(
if (isLoggable(TAG, INFO)) { if (isLoggable(TAG, INFO)) {
Log.i(TAG, "Completed. Target: $target, status: $status") Log.i(TAG, "Completed. Target: $target, status: $status")
} }
nm.onBackupResult(getAppName(target), status, userInitiated) // often [onResult] gets called right away without any [onUpdate] call
showProgressNotification(target)
} }
/** /**
@ -58,9 +66,23 @@ class NotificationBackupObserver(
*/ */
override fun backupFinished(status: Int) { override fun backupFinished(status: Int) {
if (isLoggable(TAG, INFO)) { if (isLoggable(TAG, INFO)) {
Log.i(TAG, "Backup finished. Status: $status") Log.i(TAG, "Backup finished $numPackages/$expectedPackages. Status: $status")
} }
nm.onBackupFinished() val success = status == 0
val notBackedUp = if (success) metadataManager.getPackagesNumNotBackedUp() else null
nm.onBackupFinished(success, notBackedUp, userInitiated)
}
private fun showProgressNotification(packageName: String) {
if (currentPackage == packageName) return
if (isLoggable(TAG, INFO)) {
Log.i(TAG, "Showing progress notification for $currentPackage $numPackages/$expectedPackages")
}
currentPackage = packageName
val app = getAppName(packageName)
numPackages += 1
nm.onBackupUpdate(app, numPackages, expectedPackages, userInitiated)
} }
private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId) private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId)

View file

@ -178,6 +178,13 @@ class MetadataManager(
return metadata.packageMetadataMap[packageName]?.copy() return metadata.packageMetadataMap[packageName]?.copy()
} }
@Synchronized
fun getPackagesNumNotBackedUp(): Int {
return metadata.packageMetadataMap.filter { (_, packageMetadata) ->
!packageMetadata.system && packageMetadata.state != APK_AND_DATA
}.count()
}
@Synchronized @Synchronized
@VisibleForTesting @VisibleForTesting
private fun getMetadataFromCache(): BackupMetadata? { private fun getMetadataFromCache(): BackupMetadata? {

View file

@ -15,7 +15,6 @@ import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.BackupNotificationManager import com.stevesoltys.seedvault.BackupNotificationManager
import com.stevesoltys.seedvault.NotificationBackupObserver import com.stevesoltys.seedvault.NotificationBackupObserver
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
import org.koin.core.context.GlobalContext.get import org.koin.core.context.GlobalContext.get
@ -52,19 +51,16 @@ class ConfigurableBackupTransportService : Service() {
@WorkerThread @WorkerThread
fun requestBackup(context: Context) { fun requestBackup(context: Context) {
// show notification
val nm: BackupNotificationManager = get().koin.get()
nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true)
val packageService: PackageService = get().koin.get() val packageService: PackageService = get().koin.get()
val observer = NotificationBackupObserver(context, true)
val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED
val packages = packageService.eligiblePackages val packages = packageService.eligiblePackages
val observer = NotificationBackupObserver(context, packages.size, true)
val result = try { val result = try {
val backupManager: IBackupManager = get().koin.get() val backupManager: IBackupManager = get().koin.get()
backupManager.requestBackup(packages, observer, BackupMonitor(), flags) backupManager.requestBackup(packages, observer, BackupMonitor(), FLAG_USER_INITIATED)
} 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()
nm.onBackupError() nm.onBackupError()
} }
if (result == BackupManager.SUCCESS) { if (result == BackupManager.SUCCESS) {

View file

@ -70,11 +70,14 @@
<!-- Notification --> <!-- Notification -->
<string name="notification_channel_title">Backup Notification</string> <string name="notification_channel_title">Backup Notification</string>
<string name="notification_title">Backup running</string> <string name="notification_title">Backup running</string>
<string name="notification_backup_starting">Starting Backup…</string>
<string name="notification_backup_result_complete">Backup complete</string> <string name="notification_backup_result_complete">Backup complete</string>
<string name="notification_backup_result_rejected">Not backed up</string> <string name="notification_backup_result_rejected">Not backed up</string>
<string name="notification_backup_result_error">Backup failed</string> <string name="notification_backup_result_error">Backup failed</string>
<string name="notification_success_title">Backup finished</string>
<string name="notification_success_num_not_backed_up">%1$d apps could not get backed up</string>
<string name="notification_failed_title">Backup failed</string>
<string name="notification_error_channel_title">Error Notification</string> <string name="notification_error_channel_title">Error Notification</string>
<string name="notification_error_title">Backup Error</string> <string name="notification_error_title">Backup Error</string>
<string name="notification_error_text">A device backup failed to run.</string> <string name="notification_error_text">A device backup failed to run.</string>