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:
parent
72871d3d66
commit
b9ffe2c03e
3 changed files with 56 additions and 44 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue