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 android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BackupMonitor 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.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver 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.context.GlobalContext.get
import org.koin.core.inject
private val TAG = ConfigurableBackupTransportService::class.java.simpleName private val TAG = ConfigurableBackupTransportService::class.java.simpleName
@ -24,10 +26,12 @@ private val TAG = ConfigurableBackupTransportService::class.java.simpleName
* @author Steve Soltys * @author Steve Soltys
* @author Torsten Grote * @author Torsten Grote
*/ */
class ConfigurableBackupTransportService : Service() { class ConfigurableBackupTransportService : Service(), KoinComponent {
private var transport: ConfigurableBackupTransport? = null private var transport: ConfigurableBackupTransport? = null
private val notificationManager: BackupNotificationManager by inject()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
transport = ConfigurableBackupTransport(applicationContext) transport = ConfigurableBackupTransport(applicationContext)
@ -43,6 +47,7 @@ class ConfigurableBackupTransportService : Service() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
notificationManager.onBackupBackgroundFinished()
transport = null transport = null
Log.d(TAG, "Service destroyed.") Log.d(TAG, "Service destroyed.")
} }
@ -55,7 +60,7 @@ fun requestBackup(context: Context) {
val packages = packageService.eligiblePackages val packages = packageService.eligiblePackages
val appTotals = packageService.expectedAppTotals val appTotals = packageService.expectedAppTotals
val observer = NotificationBackupObserver(context, packages.size, appTotals, true) 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(), 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_OBSERVER = 1
private const val NOTIFICATION_ID_ERROR = 2 private const val NOTIFICATION_ID_ERROR = 2
private const val NOTIFICATION_ID_RESTORE_ERROR = 3 private const val NOTIFICATION_ID_RESTORE_ERROR = 3
private const val NOTIFICATION_ID_BACKGROUND = 4
private val TAG = BackupNotificationManager::class.java.simpleName private val TAG = BackupNotificationManager::class.java.simpleName
@ -71,14 +72,12 @@ internal class BackupNotificationManager(private val context: Context) {
*/ */
fun onBackupStarted( fun onBackupStarted(
expectedPackages: Int, expectedPackages: Int,
appTotals: ExpectedAppTotals, appTotals: ExpectedAppTotals
userInitiated: Boolean
) { ) {
updateBackupNotification( updateBackupNotification(
infoText = "", // This passes quickly, no need to show something here infoText = "", // This passes quickly, no need to show something here
transferred = 0, transferred = 0,
expected = expectedPackages, expected = expectedPackages
userInitiated = userInitiated
) )
expectedApps = expectedPackages expectedApps = expectedPackages
expectedOptOutApps = appTotals.appsOptOut expectedOptOutApps = appTotals.appsOptOut
@ -89,57 +88,55 @@ internal class BackupNotificationManager(private val context: Context) {
* This is expected to get called before [onOptOutAppBackup] and [onBackupUpdate]. * This is expected to get called before [onOptOutAppBackup] and [onBackupUpdate].
*/ */
fun onPmKvBackup(packageName: String, transferred: Int, expected: Int) { fun onPmKvBackup(packageName: String, transferred: Int, expected: Int) {
val text = "@pm@ record for $packageName"
if (expectedApps == null) { if (expectedApps == null) {
Log.d(TAG, "Expected number of apps unknown. Not showing @pm@ notification.") updateBackgroundBackupNotification(text)
return } else {
val addend = (expectedOptOutApps ?: 0) + (expectedApps ?: 0)
updateBackupNotification(
infoText = text,
transferred = transferred,
expected = expected + addend
)
expectedPmRecords = expected
} }
val addend = (expectedOptOutApps ?: 0) + (expectedApps ?: 0)
updateBackupNotification(
infoText = "@pm@ record for $packageName",
transferred = transferred,
expected = expected + addend,
userInitiated = false
)
expectedPmRecords = expected
} }
/** /**
* This should get called after [onPmKvBackup], but before [onBackupUpdate]. * This should get called after [onPmKvBackup], but before [onBackupUpdate].
*/ */
fun onOptOutAppBackup(packageName: String, transferred: Int, expected: Int) { fun onOptOutAppBackup(packageName: String, transferred: Int, expected: Int) {
val text = "Opt-out APK for $packageName"
if (expectedApps == null) { if (expectedApps == null) {
Log.d(TAG, "Expected number of apps unknown. Not showing APK notification.") updateBackgroundBackupNotification(text)
return } else {
updateBackupNotification(
infoText = text,
transferred = transferred + (expectedPmRecords ?: 0),
expected = expected + (expectedApps ?: 0) + (expectedPmRecords ?: 0)
)
expectedOptOutApps = expected
} }
updateBackupNotification(
infoText = "Opt-out APK for $packageName",
transferred = transferred + (expectedPmRecords ?: 0),
expected = expected + (expectedApps ?: 0) + (expectedPmRecords ?: 0),
userInitiated = false
)
expectedOptOutApps = expected
} }
/** /**
* In the series of notification updates, * In the series of notification updates,
* this type is is expected to get called after [onOptOutAppBackup] and [onPmKvBackup]. * 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 expected = expectedApps ?: error("expectedApps is null")
val addend = (expectedOptOutApps ?: 0) + (expectedPmRecords ?: 0) val addend = (expectedOptOutApps ?: 0) + (expectedPmRecords ?: 0)
updateBackupNotification( updateBackupNotification(
infoText = app, infoText = app,
transferred = transferred + addend, transferred = transferred + addend,
expected = expected + addend, expected = expected + addend
userInitiated = userInitiated
) )
} }
private fun updateBackupNotification( private fun updateBackupNotification(
infoText: CharSequence, infoText: CharSequence,
transferred: Int, transferred: Int,
expected: Int, expected: Int
userInitiated: Boolean
) { ) {
@Suppress("MagicNumber") @Suppress("MagicNumber")
val percentage = (transferred.toFloat() / expected) * 100 val percentage = (transferred.toFloat() / expected) * 100
@ -149,22 +146,33 @@ internal class BackupNotificationManager(private val context: Context) {
setSmallIcon(R.drawable.ic_cloud_upload) setSmallIcon(R.drawable.ic_cloud_upload)
setContentTitle(context.getString(R.string.notification_title)) setContentTitle(context.getString(R.string.notification_title))
setContentText(percentageStr) setContentText(percentageStr)
setTicker(infoText)
setOngoing(true) setOngoing(true)
setShowWhen(false) 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 = PRIORITY_DEFAULT
}.build() }.build()
nm.notify(NOTIFICATION_ID_OBSERVER, notification) nm.notify(NOTIFICATION_ID_OBSERVER, notification)
} }
fun onBackupFinished(success: Boolean, numBackedUp: Int?, userInitiated: Boolean) { private fun updateBackgroundBackupNotification(infoText: CharSequence) {
if (!userInitiated) { Log.i(TAG, "$infoText")
// don't show permanent finished notification if backup was not triggered by user val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
nm.cancel(NOTIFICATION_ID_OBSERVER) setSmallIcon(R.drawable.ic_cloud_upload)
return 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 = val titleRes =
if (success) R.string.notification_success_title else R.string.notification_failed_title if (success) R.string.notification_success_title else R.string.notification_failed_title
val total = expectedAppTotals?.appsTotal val total = expectedAppTotals?.appsTotal

View file

@ -19,8 +19,7 @@ private val TAG = NotificationBackupObserver::class.java.simpleName
internal class NotificationBackupObserver( internal class NotificationBackupObserver(
private val context: Context, private val context: Context,
private val expectedPackages: Int, private val expectedPackages: Int,
appTotals: ExpectedAppTotals, appTotals: ExpectedAppTotals
private val userInitiated: Boolean
) : IBackupObserver.Stub(), KoinComponent { ) : IBackupObserver.Stub(), KoinComponent {
private val nm: BackupNotificationManager by inject() private val nm: BackupNotificationManager by inject()
@ -31,7 +30,7 @@ internal class NotificationBackupObserver(
init { init {
// Inform the notification manager that a backup has started // Inform the notification manager that a backup has started
// and inform about the expected numbers, so it can compute a total. // 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 success = status == 0
val numBackedUp = if (success) metadataManager.getPackagesNumBackedUp() else null val numBackedUp = if (success) metadataManager.getPackagesNumBackedUp() else null
nm.onBackupFinished(success, numBackedUp, userInitiated) nm.onBackupFinished(success, numBackedUp)
} }
private fun showProgressNotification(packageName: String) { private fun showProgressNotification(packageName: String) {
@ -93,7 +92,7 @@ internal class NotificationBackupObserver(
currentPackage = packageName currentPackage = packageName
val app = getAppName(packageName) val app = getAppName(packageName)
numPackages += 1 numPackages += 1
nm.onBackupUpdate(app, numPackages, userInitiated) nm.onBackupUpdate(app, numPackages)
} }
private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId) private fun getAppName(packageId: String): CharSequence = getAppName(context, packageId)