diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt index d48b196e..561f44cb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/BackupNotificationManager.kt @@ -63,6 +63,8 @@ class BackupNotificationManager(private val context: Context) { val notification = observerBuilder.apply { setContentTitle(context.getString(R.string.notification_title)) setContentText(app) + setOngoing(true) + setShowWhen(false) setWhen(System.currentTimeMillis()) setProgress(expected, transferred, false) priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW @@ -79,14 +81,33 @@ class BackupNotificationManager(private val context: Context) { val notification = observerBuilder.apply { setContentTitle(title) setContentText(app) + setOngoing(true) + setShowWhen(false) setWhen(System.currentTimeMillis()) priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW }.build() nm.notify(NOTIFICATION_ID_OBSERVER, notification) } - fun onBackupFinished() { - nm.cancel(NOTIFICATION_ID_OBSERVER) + fun onBackupFinished(success: Boolean, notBackedUp: Int?, userInitiated: Boolean) { + if (!userInitiated) { + nm.cancel(NOTIFICATION_ID_OBSERVER) + return + } + val titleRes = if (success) R.string.notification_success_title else R.string.notification_failed_title + val contentText = if (notBackedUp == null) null else { + context.getString(R.string.notification_success_num_not_backed_up, notBackedUp) + } + val notification = observerBuilder.apply { + setContentTitle(context.getString(titleRes)) + setContentText(contentText) + setOngoing(false) + setShowWhen(true) + setWhen(System.currentTimeMillis()) + setProgress(0, 0, false) + priority = PRIORITY_LOW + }.build() + nm.notify(NOTIFICATION_ID_OBSERVER, notification) } fun onBackupError() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt index c3394065..b579d4f7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/NotificationBackupObserver.kt @@ -7,6 +7,7 @@ import android.content.pm.PackageManager.NameNotFoundException import android.util.Log import android.util.Log.INFO import android.util.Log.isLoggable +import com.stevesoltys.seedvault.metadata.MetadataManager import org.koin.core.KoinComponent import org.koin.core.inject @@ -14,9 +15,18 @@ private val TAG = NotificationBackupObserver::class.java.simpleName class NotificationBackupObserver( private val context: Context, + private val expectedPackages: Int, private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent { private val nm: BackupNotificationManager by inject() + private val metadataManager: MetadataManager by inject() + private var currentPackage: String? = null + private var numPackages: Int = 0 + + init { + // we need to show this manually as [onUpdate] isn't called for first @pm@ package + nm.onBackupUpdate(getAppName(MAGIC_PACKAGE_MANAGER), 0, expectedPackages, userInitiated) + } /** * This method could be called several times for packages with full data backup. @@ -26,10 +36,7 @@ class NotificationBackupObserver( * @param backupProgress Current progress of backup for the package. */ override fun onUpdate(currentBackupPackage: String, backupProgress: BackupProgress) { - val transferred = backupProgress.bytesTransferred.toInt() - val expected = backupProgress.bytesExpected.toInt() - val app = getAppName(currentBackupPackage) - nm.onBackupUpdate(app, transferred, expected, userInitiated) + showProgressNotification(currentBackupPackage) } /** @@ -46,7 +53,8 @@ class NotificationBackupObserver( if (isLoggable(TAG, INFO)) { Log.i(TAG, "Completed. Target: $target, status: $status") } - nm.onBackupResult(getAppName(target), status, userInitiated) + // often [onResult] gets called right away without any [onUpdate] call + showProgressNotification(target) } /** @@ -58,9 +66,23 @@ class NotificationBackupObserver( */ override fun backupFinished(status: Int) { if (isLoggable(TAG, INFO)) { - Log.i(TAG, "Backup finished. Status: $status") + Log.i(TAG, "Backup finished $numPackages/$expectedPackages. Status: $status") } - nm.onBackupFinished() + val success = status == 0 + val notBackedUp = if (success) metadataManager.getPackagesNumNotBackedUp() else null + nm.onBackupFinished(success, notBackedUp, userInitiated) + } + + private fun showProgressNotification(packageName: String) { + if (currentPackage == packageName) return + + if (isLoggable(TAG, INFO)) { + Log.i(TAG, "Showing progress notification for $currentPackage $numPackages/$expectedPackages") + } + currentPackage = packageName + val app = getAppName(packageName) + numPackages += 1 + nm.onBackupUpdate(app, numPackages, expectedPackages, userInitiated) } private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId) diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt index 799b13cd..2ef14ef5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt @@ -178,6 +178,13 @@ class MetadataManager( return metadata.packageMetadataMap[packageName]?.copy() } + @Synchronized + fun getPackagesNumNotBackedUp(): Int { + return metadata.packageMetadataMap.filter { (_, packageMetadata) -> + !packageMetadata.system && packageMetadata.state != APK_AND_DATA + }.count() + } + @Synchronized @VisibleForTesting private fun getMetadataFromCache(): BackupMetadata? { diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt index f4392134..688f1026 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt @@ -15,7 +15,6 @@ import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.BackupNotificationManager import com.stevesoltys.seedvault.NotificationBackupObserver -import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.transport.backup.PackageService import org.koin.core.context.GlobalContext.get @@ -52,19 +51,16 @@ class ConfigurableBackupTransportService : Service() { @WorkerThread fun requestBackup(context: Context) { - // show notification - val nm: BackupNotificationManager = get().koin.get() - nm.onBackupUpdate(context.getString(R.string.notification_backup_starting), 0, 1, true) - val packageService: PackageService = get().koin.get() - val observer = NotificationBackupObserver(context, true) - val flags = FLAG_NON_INCREMENTAL_BACKUP or FLAG_USER_INITIATED val packages = packageService.eligiblePackages + + val observer = NotificationBackupObserver(context, packages.size, true) val result = try { val backupManager: IBackupManager = get().koin.get() - backupManager.requestBackup(packages, observer, BackupMonitor(), flags) + backupManager.requestBackup(packages, observer, BackupMonitor(), FLAG_USER_INITIATED) } catch (e: RemoteException) { Log.e(TAG, "Error during backup: ", e) + val nm: BackupNotificationManager = get().koin.get() nm.onBackupError() } if (result == BackupManager.SUCCESS) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cde0d8c1..bc3132ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -70,11 +70,14 @@ Backup Notification Backup running - Starting Backup… Backup complete Not backed up Backup failed + Backup finished + %1$d apps could not get backed up + Backup failed + Error Notification Backup Error A device backup failed to run.