Show notification for backup running in the background

The system triggers backup jobs periodically or when a package is
announcing that its data has changed. So far we were not showing
notifications for those. This commit shows a notification with an
indeterminate progress bar as we don't have any information about how
many packages will get backed up.
This commit is contained in:
Torsten Grote 2020-09-04 14:51:20 -03:00 committed by Chirayu Desai
parent 72871d3d66
commit b9ffe2c03e
3 changed files with 56 additions and 44 deletions

View file

@ -13,10 +13,12 @@ import android.os.RemoteException
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.transport.backup.PackageService
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.KoinComponent
import org.koin.core.context.GlobalContext.get
import org.koin.core.inject
private val TAG = ConfigurableBackupTransportService::class.java.simpleName
@ -24,10 +26,12 @@ private val TAG = ConfigurableBackupTransportService::class.java.simpleName
* @author Steve Soltys
* @author Torsten Grote
*/
class ConfigurableBackupTransportService : Service() {
class ConfigurableBackupTransportService : Service(), KoinComponent {
private var transport: ConfigurableBackupTransport? = null
private val notificationManager: BackupNotificationManager by inject()
override fun onCreate() {
super.onCreate()
transport = ConfigurableBackupTransport(applicationContext)
@ -43,6 +47,7 @@ class ConfigurableBackupTransportService : Service() {
override fun onDestroy() {
super.onDestroy()
notificationManager.onBackupBackgroundFinished()
transport = null
Log.d(TAG, "Service destroyed.")
}
@ -55,7 +60,7 @@ fun requestBackup(context: Context) {
val packages = packageService.eligiblePackages
val appTotals = packageService.expectedAppTotals
val observer = NotificationBackupObserver(context, packages.size, appTotals, true)
val observer = NotificationBackupObserver(context, packages.size, appTotals)
val result = try {
val backupManager: IBackupManager = get().koin.get()
backupManager.requestBackup(packages, observer, BackupMonitor(), FLAG_USER_INITIATED)

View file

@ -31,6 +31,7 @@ private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
private const val NOTIFICATION_ID_OBSERVER = 1
private const val NOTIFICATION_ID_ERROR = 2
private const val NOTIFICATION_ID_RESTORE_ERROR = 3
private const val NOTIFICATION_ID_BACKGROUND = 4
private val TAG = BackupNotificationManager::class.java.simpleName
@ -71,14 +72,12 @@ internal class BackupNotificationManager(private val context: Context) {
*/
fun onBackupStarted(
expectedPackages: Int,
appTotals: ExpectedAppTotals,
userInitiated: Boolean
appTotals: ExpectedAppTotals
) {
updateBackupNotification(
infoText = "", // This passes quickly, no need to show something here
transferred = 0,
expected = expectedPackages,
userInitiated = userInitiated
expected = expectedPackages
)
expectedApps = expectedPackages
expectedOptOutApps = appTotals.appsOptOut
@ -89,57 +88,55 @@ internal class BackupNotificationManager(private val context: Context) {
* This is expected to get called before [onOptOutAppBackup] and [onBackupUpdate].
*/
fun onPmKvBackup(packageName: String, transferred: Int, expected: Int) {
val text = "@pm@ record for $packageName"
if (expectedApps == null) {
Log.d(TAG, "Expected number of apps unknown. Not showing @pm@ notification.")
return
}
updateBackgroundBackupNotification(text)
} else {
val addend = (expectedOptOutApps ?: 0) + (expectedApps ?: 0)
updateBackupNotification(
infoText = "@pm@ record for $packageName",
infoText = text,
transferred = transferred,
expected = expected + addend,
userInitiated = false
expected = expected + addend
)
expectedPmRecords = expected
}
}
/**
* This should get called after [onPmKvBackup], but before [onBackupUpdate].
*/
fun onOptOutAppBackup(packageName: String, transferred: Int, expected: Int) {
val text = "Opt-out APK for $packageName"
if (expectedApps == null) {
Log.d(TAG, "Expected number of apps unknown. Not showing APK notification.")
return
}
updateBackgroundBackupNotification(text)
} else {
updateBackupNotification(
infoText = "Opt-out APK for $packageName",
infoText = text,
transferred = transferred + (expectedPmRecords ?: 0),
expected = expected + (expectedApps ?: 0) + (expectedPmRecords ?: 0),
userInitiated = false
expected = expected + (expectedApps ?: 0) + (expectedPmRecords ?: 0)
)
expectedOptOutApps = expected
}
}
/**
* In the series of notification updates,
* this type is is expected to get called after [onOptOutAppBackup] and [onPmKvBackup].
*/
fun onBackupUpdate(app: CharSequence, transferred: Int, userInitiated: Boolean) {
fun onBackupUpdate(app: CharSequence, transferred: Int) {
val expected = expectedApps ?: error("expectedApps is null")
val addend = (expectedOptOutApps ?: 0) + (expectedPmRecords ?: 0)
updateBackupNotification(
infoText = app,
transferred = transferred + addend,
expected = expected + addend,
userInitiated = userInitiated
expected = expected + addend
)
}
private fun updateBackupNotification(
infoText: CharSequence,
transferred: Int,
expected: Int,
userInitiated: Boolean
expected: Int
) {
@Suppress("MagicNumber")
val percentage = (transferred.toFloat() / expected) * 100
@ -149,22 +146,33 @@ internal class BackupNotificationManager(private val context: Context) {
setSmallIcon(R.drawable.ic_cloud_upload)
setContentTitle(context.getString(R.string.notification_title))
setContentText(percentageStr)
setTicker(infoText)
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis())
setProgress(expected, transferred, false)
priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
priority = PRIORITY_DEFAULT
}.build()
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
}
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
private fun updateBackgroundBackupNotification(infoText: CharSequence) {
Log.i(TAG, "$infoText")
val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
setSmallIcon(R.drawable.ic_cloud_upload)
setContentTitle(context.getString(R.string.notification_title))
setShowWhen(false)
setWhen(System.currentTimeMillis())
setProgress(0, 0, true)
priority = PRIORITY_LOW
}.build()
nm.notify(NOTIFICATION_ID_BACKGROUND, notification)
}
fun onBackupBackgroundFinished() {
nm.cancel(NOTIFICATION_ID_BACKGROUND)
}
fun onBackupFinished(success: Boolean, numBackedUp: Int?) {
val titleRes =
if (success) R.string.notification_success_title else R.string.notification_failed_title
val total = expectedAppTotals?.appsTotal

View file

@ -19,8 +19,7 @@ private val TAG = NotificationBackupObserver::class.java.simpleName
internal class NotificationBackupObserver(
private val context: Context,
private val expectedPackages: Int,
appTotals: ExpectedAppTotals,
private val userInitiated: Boolean
appTotals: ExpectedAppTotals
) : IBackupObserver.Stub(), KoinComponent {
private val nm: BackupNotificationManager by inject()
@ -31,7 +30,7 @@ internal 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, appTotals, userInitiated)
nm.onBackupStarted(expectedPackages, appTotals)
}
/**
@ -79,7 +78,7 @@ internal class NotificationBackupObserver(
}
val success = status == 0
val numBackedUp = if (success) metadataManager.getPackagesNumBackedUp() else null
nm.onBackupFinished(success, numBackedUp, userInitiated)
nm.onBackupFinished(success, numBackedUp)
}
private fun showProgressNotification(packageName: String) {
@ -93,7 +92,7 @@ internal class NotificationBackupObserver(
currentPackage = packageName
val app = getAppName(packageName)
numPackages += 1
nm.onBackupUpdate(app, numPackages, userInitiated)
nm.onBackupUpdate(app, numPackages)
}
private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId)