Show error notification when backup fails
The implementation is rudimentary for now. E.g. The notification is only shown when a device init fails which seems to be triggered after the first failure.
This commit is contained in:
parent
683268a15f
commit
c714a4e7e1
12 changed files with 156 additions and 54 deletions
|
@ -5,6 +5,7 @@ import android.app.Application
|
||||||
import android.app.backup.IBackupManager
|
import android.app.backup.IBackupManager
|
||||||
import android.content.Context.BACKUP_SERVICE
|
import android.content.Context.BACKUP_SERVICE
|
||||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ServiceManager.getService
|
import android.os.ServiceManager.getService
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -14,6 +15,8 @@ import com.stevesoltys.backup.settings.getDeviceName
|
||||||
import com.stevesoltys.backup.settings.setDeviceName
|
import com.stevesoltys.backup.settings.setDeviceName
|
||||||
import io.github.novacrypto.hashing.Sha256.sha256Twice
|
import io.github.novacrypto.hashing.Sha256.sha256Twice
|
||||||
|
|
||||||
|
private const val URI_AUTHORITY_EXTERNAL_STORAGE = "com.android.externalstorage.documents"
|
||||||
|
|
||||||
private val TAG = Backup::class.java.simpleName
|
private val TAG = Backup::class.java.simpleName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +34,10 @@ class Backup : Application() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val notificationManager by lazy {
|
||||||
|
BackupNotificationManager(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
storeDeviceName()
|
storeDeviceName()
|
||||||
|
@ -53,3 +60,5 @@ class Backup : Application() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Uri.isOnExternalStorage() = authority == URI_AUTHORITY_EXTERNAL_STORAGE
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.stevesoltys.backup
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
import android.app.NotificationManager.IMPORTANCE_LOW
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.NotificationCompat.*
|
||||||
|
import com.stevesoltys.backup.settings.SettingsActivity
|
||||||
|
|
||||||
|
private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
|
||||||
|
private const val CHANNEL_ID_ERROR = "NotificationError"
|
||||||
|
private const val NOTIFICATION_ID_OBSERVER = 1
|
||||||
|
private const val NOTIFICATION_ID_ERROR = 2
|
||||||
|
|
||||||
|
class BackupNotificationManager(private val context: Context) {
|
||||||
|
|
||||||
|
private val nm = context.getSystemService(NotificationManager::class.java)!!.apply {
|
||||||
|
createNotificationChannel(getObserverChannel())
|
||||||
|
createNotificationChannel(getErrorChannel())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getObserverChannel(): NotificationChannel {
|
||||||
|
val title = context.getString(R.string.notification_channel_title)
|
||||||
|
return NotificationChannel(CHANNEL_ID_OBSERVER, title, IMPORTANCE_LOW).apply {
|
||||||
|
enableVibration(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getErrorChannel(): NotificationChannel {
|
||||||
|
val title = context.getString(R.string.notification_error_channel_title)
|
||||||
|
return NotificationChannel(CHANNEL_ID_ERROR, title, IMPORTANCE_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val observerBuilder = Builder(context, CHANNEL_ID_OBSERVER).apply {
|
||||||
|
setSmallIcon(R.drawable.ic_cloud_upload)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val errorBuilder = Builder(context, CHANNEL_ID_ERROR).apply {
|
||||||
|
setSmallIcon(R.drawable.ic_cloud_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackupUpdate(app: CharSequence, transferred: Int, expected: Int, userInitiated: Boolean) {
|
||||||
|
val notification = observerBuilder.apply {
|
||||||
|
setContentTitle(context.getString(R.string.notification_title))
|
||||||
|
setContentText(app)
|
||||||
|
setProgress(expected, transferred, false)
|
||||||
|
priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
|
||||||
|
}.build()
|
||||||
|
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackupResult(app: CharSequence, status: Int, userInitiated: Boolean) {
|
||||||
|
val title = context.getString(when (status) {
|
||||||
|
0 -> R.string.notification_backup_result_complete
|
||||||
|
TRANSPORT_PACKAGE_REJECTED -> R.string.notification_backup_result_rejected
|
||||||
|
else -> R.string.notification_backup_result_error
|
||||||
|
})
|
||||||
|
val notification = observerBuilder.apply {
|
||||||
|
setContentTitle(title)
|
||||||
|
setContentText(app)
|
||||||
|
priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
|
||||||
|
}.build()
|
||||||
|
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackupFinished() {
|
||||||
|
nm.cancel(NOTIFICATION_ID_OBSERVER)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackupError() {
|
||||||
|
val intent = Intent(context, SettingsActivity::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
|
||||||
|
val actionText = context.getString(R.string.notification_error_action)
|
||||||
|
val action = Action(R.drawable.ic_storage, actionText, pendingIntent)
|
||||||
|
val notification = errorBuilder.apply {
|
||||||
|
setContentTitle(context.getString(R.string.notification_error_title))
|
||||||
|
setContentText(context.getString(R.string.notification_error_text))
|
||||||
|
addAction(action)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
setAutoCancel(true)
|
||||||
|
}.build()
|
||||||
|
nm.notify(NOTIFICATION_ID_ERROR, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackupErrorSeen() {
|
||||||
|
nm.cancel(NOTIFICATION_ID_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,40 +1,18 @@
|
||||||
package com.stevesoltys.backup
|
package com.stevesoltys.backup
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.NotificationManager.IMPORTANCE_LOW
|
|
||||||
import android.app.backup.BackupProgress
|
import android.app.backup.BackupProgress
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
|
|
||||||
import android.app.backup.IBackupObserver
|
import android.app.backup.IBackupObserver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
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 androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
|
|
||||||
import androidx.core.app.NotificationCompat.PRIORITY_LOW
|
|
||||||
|
|
||||||
private const val CHANNEL_ID = "NotificationBackupObserver"
|
|
||||||
private const val NOTIFICATION_ID = 1042
|
|
||||||
|
|
||||||
private val TAG = NotificationBackupObserver::class.java.simpleName
|
private val TAG = NotificationBackupObserver::class.java.simpleName
|
||||||
|
|
||||||
class NotificationBackupObserver(
|
class NotificationBackupObserver(context: Context, private val userInitiated: Boolean) : IBackupObserver.Stub() {
|
||||||
private val context: Context,
|
|
||||||
private val userInitiated: Boolean) : IBackupObserver.Stub() {
|
|
||||||
|
|
||||||
private val pm = context.packageManager
|
private val pm = context.packageManager
|
||||||
private val nm = context.getSystemService(NotificationManager::class.java).apply {
|
private val nm = (context.applicationContext as Backup).notificationManager
|
||||||
val title = context.getString(R.string.notification_channel_title)
|
|
||||||
val channel = NotificationChannel(CHANNEL_ID, title, IMPORTANCE_LOW).apply {
|
|
||||||
enableVibration(false)
|
|
||||||
}
|
|
||||||
createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
private val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID).apply {
|
|
||||||
setSmallIcon(R.drawable.ic_cloud_upload)
|
|
||||||
priority = if (userInitiated) PRIORITY_DEFAULT else PRIORITY_LOW
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -44,17 +22,13 @@ 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
|
val transferred = backupProgress.bytesTransferred.toInt()
|
||||||
val expected = backupProgress.bytesExpected
|
val expected = backupProgress.bytesExpected.toInt()
|
||||||
if (isLoggable(TAG, INFO)) {
|
if (isLoggable(TAG, INFO)) {
|
||||||
Log.i(TAG, "Update. Target: $currentBackupPackage, $transferred/$expected")
|
Log.i(TAG, "Update. Target: $currentBackupPackage, $transferred/$expected")
|
||||||
}
|
}
|
||||||
val notification = notificationBuilder.apply {
|
val app = getAppName(currentBackupPackage)
|
||||||
setContentTitle(context.getString(R.string.notification_title))
|
nm.onBackupUpdate(app, transferred, expected, userInitiated)
|
||||||
setContentText(getAppName(currentBackupPackage))
|
|
||||||
setProgress(expected.toInt(), transferred.toInt(), false)
|
|
||||||
}.build()
|
|
||||||
nm.notify(NOTIFICATION_ID, notification)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,16 +45,7 @@ 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")
|
||||||
}
|
}
|
||||||
val title = context.getString(when (status) {
|
nm.onBackupResult(getAppName(target), status, userInitiated)
|
||||||
0 -> R.string.notification_backup_result_complete
|
|
||||||
TRANSPORT_PACKAGE_REJECTED -> R.string.notification_backup_result_rejected
|
|
||||||
else -> R.string.notification_backup_result_error
|
|
||||||
})
|
|
||||||
val notification = notificationBuilder.apply {
|
|
||||||
setContentTitle(title)
|
|
||||||
setContentText(getAppName(target))
|
|
||||||
}.build()
|
|
||||||
nm.notify(NOTIFICATION_ID, notification)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,7 +59,7 @@ class NotificationBackupObserver(
|
||||||
if (isLoggable(TAG, INFO)) {
|
if (isLoggable(TAG, INFO)) {
|
||||||
Log.i(TAG, "Backup finished. Status: $status")
|
Log.i(TAG, "Backup finished. Status: $status")
|
||||||
}
|
}
|
||||||
nm.cancel(NOTIFICATION_ID)
|
nm.onBackupFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAppName(packageId: String): CharSequence {
|
private fun getAppName(packageId: String): CharSequence {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.LiveEventHandler
|
import com.stevesoltys.backup.LiveEventHandler
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
|
|
||||||
|
@ -25,8 +26,8 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
setContentView(R.layout.activity_settings)
|
setContentView(R.layout.activity_settings)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
|
viewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
|
||||||
viewModel.onLocationSet.observeEvent(this, LiveEventHandler { wasEmptyBefore ->
|
viewModel.onLocationSet.observeEvent(this, LiveEventHandler { initialSetUp ->
|
||||||
if (wasEmptyBefore) showFragment(SettingsFragment())
|
if (initialSetUp) showFragment(SettingsFragment())
|
||||||
else supportFragmentManager.popBackStack()
|
else supportFragmentManager.popBackStack()
|
||||||
})
|
})
|
||||||
viewModel.chooseBackupLocation.observeEvent(this, LiveEventHandler { show ->
|
viewModel.chooseBackupLocation.observeEvent(this, LiveEventHandler { show ->
|
||||||
|
@ -54,8 +55,10 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
// check that backup is provisioned
|
// check that backup is provisioned
|
||||||
if (!viewModel.recoveryCodeIsSet()) {
|
if (!viewModel.recoveryCodeIsSet()) {
|
||||||
showRecoveryCodeActivity()
|
showRecoveryCodeActivity()
|
||||||
} else if (!viewModel.locationIsSet()) {
|
} else if (!viewModel.validLocationIsSet()) {
|
||||||
showFragment(BackupLocationFragment())
|
showFragment(BackupLocationFragment())
|
||||||
|
// remove potential error notifications
|
||||||
|
(application as Backup).notificationManager.onBackupErrorSeen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,12 @@ import android.content.Intent
|
||||||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import com.stevesoltys.backup.Backup
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.LiveEvent
|
import com.stevesoltys.backup.LiveEvent
|
||||||
import com.stevesoltys.backup.MutableLiveEvent
|
import com.stevesoltys.backup.MutableLiveEvent
|
||||||
|
import com.stevesoltys.backup.isOnExternalStorage
|
||||||
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
|
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
|
||||||
import com.stevesoltys.backup.transport.requestBackup
|
import com.stevesoltys.backup.transport.requestBackup
|
||||||
|
|
||||||
|
@ -30,7 +32,13 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||||
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
||||||
|
|
||||||
fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey()
|
fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey()
|
||||||
fun locationIsSet() = getBackupFolderUri(getApplication()) != null
|
|
||||||
|
fun validLocationIsSet(): Boolean {
|
||||||
|
val uri = getBackupFolderUri(app) ?: return false
|
||||||
|
if (uri.isOnExternalStorage()) return true // might be a temporary failure
|
||||||
|
val file = DocumentFile.fromTreeUri(app, uri) ?: return false
|
||||||
|
return file.isDirectory
|
||||||
|
}
|
||||||
|
|
||||||
fun handleChooseFolderResult(result: Intent?) {
|
fun handleChooseFolderResult(result: Intent?) {
|
||||||
val folderUri = result?.data ?: return
|
val folderUri = result?.data ?: return
|
||||||
|
@ -40,13 +48,13 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||||
app.contentResolver.takePersistableUriPermission(folderUri, takeFlags)
|
app.contentResolver.takePersistableUriPermission(folderUri, takeFlags)
|
||||||
|
|
||||||
// check if this is initial set-up or a later change
|
// check if this is initial set-up or a later change
|
||||||
val wasEmptyBefore = getBackupFolderUri(app) == null
|
val initialSetUp = !validLocationIsSet()
|
||||||
|
|
||||||
// store backup folder location in settings
|
// store backup folder location in settings
|
||||||
setBackupFolderUri(app, folderUri)
|
setBackupFolderUri(app, folderUri)
|
||||||
|
|
||||||
// notify the UI that the location has been set
|
// notify the UI that the location has been set
|
||||||
locationWasSet.setEvent(wasEmptyBefore)
|
locationWasSet.setEvent(initialSetUp)
|
||||||
|
|
||||||
// stop backup service to be sure the old location will get updated
|
// stop backup service to be sure the old location will get updated
|
||||||
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
|
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
|
||||||
|
|
|
@ -36,8 +36,9 @@ class PluginManager(context: Context) {
|
||||||
private val inputFactory = InputFactory()
|
private val inputFactory = InputFactory()
|
||||||
private val kvBackup = KVBackup(backupPlugin.kvBackupPlugin, inputFactory, headerWriter, crypto)
|
private val kvBackup = KVBackup(backupPlugin.kvBackupPlugin, inputFactory, headerWriter, crypto)
|
||||||
private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto)
|
private val fullBackup = FullBackup(backupPlugin.fullBackupPlugin, inputFactory, headerWriter, crypto)
|
||||||
|
private val notificationManager = (context.applicationContext as Backup).notificationManager
|
||||||
|
|
||||||
internal val backupCoordinator = BackupCoordinator(backupPlugin, kvBackup, fullBackup)
|
internal val backupCoordinator = BackupCoordinator(backupPlugin, kvBackup, fullBackup, notificationManager)
|
||||||
|
|
||||||
|
|
||||||
private val restorePlugin = DocumentsProviderRestorePlugin(storage)
|
private val restorePlugin = DocumentsProviderRestorePlugin(storage)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.stevesoltys.backup.BackupNotificationManager
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
private val TAG = BackupCoordinator::class.java.simpleName
|
private val TAG = BackupCoordinator::class.java.simpleName
|
||||||
|
@ -16,7 +17,8 @@ private val TAG = BackupCoordinator::class.java.simpleName
|
||||||
class BackupCoordinator(
|
class BackupCoordinator(
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: BackupPlugin,
|
||||||
private val kv: KVBackup,
|
private val kv: KVBackup,
|
||||||
private val full: FullBackup) {
|
private val full: FullBackup,
|
||||||
|
private val nm: BackupNotificationManager) {
|
||||||
|
|
||||||
private var calledInitialize = false
|
private var calledInitialize = false
|
||||||
private var calledClearBackupData = false
|
private var calledClearBackupData = false
|
||||||
|
@ -53,6 +55,7 @@ class BackupCoordinator(
|
||||||
TRANSPORT_OK
|
TRANSPORT_OK
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "Error initializing device", e)
|
Log.e(TAG, "Error initializing device", e)
|
||||||
|
nm.onBackupError()
|
||||||
TRANSPORT_ERROR
|
TRANSPORT_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
app/src/main/res/drawable/ic_cloud_error.xml
Normal file
10
app/src/main/res/drawable/ic_cloud_error.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/textColorSecondary"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,20H6C2.71,20 0,17.29 0,14C0,10.9 2.34,8.36 5.35,8.03C6.6,5.64 9.11,4 12,4C15.64,4 18.67,6.59 19.35,10.03C21.95,10.22 24,12.36 24,15C24,17.74 21.74,20 19,20M11,15V17H13V15H11M11,13H13V8H11V13Z" />
|
||||||
|
</vector>
|
|
@ -65,4 +65,9 @@
|
||||||
<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_error_channel_title">Error Notification</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_action">Fix</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.app.backup.RestoreDescription
|
import android.app.backup.RestoreDescription
|
||||||
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
|
import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import com.stevesoltys.backup.BackupNotificationManager
|
||||||
import com.stevesoltys.backup.crypto.CipherFactoryImpl
|
import com.stevesoltys.backup.crypto.CipherFactoryImpl
|
||||||
import com.stevesoltys.backup.crypto.CryptoImpl
|
import com.stevesoltys.backup.crypto.CryptoImpl
|
||||||
import com.stevesoltys.backup.crypto.KeyManagerTestImpl
|
import com.stevesoltys.backup.crypto.KeyManagerTestImpl
|
||||||
|
@ -37,7 +38,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
private val kvBackup = KVBackup(kvBackupPlugin, inputFactory, headerWriter, cryptoImpl)
|
private val kvBackup = KVBackup(kvBackupPlugin, inputFactory, headerWriter, cryptoImpl)
|
||||||
private val fullBackupPlugin = mockk<FullBackupPlugin>()
|
private val fullBackupPlugin = mockk<FullBackupPlugin>()
|
||||||
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
|
private val fullBackup = FullBackup(fullBackupPlugin, inputFactory, headerWriter, cryptoImpl)
|
||||||
private val backup = BackupCoordinator(backupPlugin, kvBackup, fullBackup)
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
|
private val backup = BackupCoordinator(backupPlugin, kvBackup, fullBackup, notificationManager)
|
||||||
|
|
||||||
private val restorePlugin = mockk<RestorePlugin>()
|
private val restorePlugin = mockk<RestorePlugin>()
|
||||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.stevesoltys.backup.transport.backup
|
||||||
|
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_ERROR
|
import android.app.backup.BackupTransport.TRANSPORT_ERROR
|
||||||
import android.app.backup.BackupTransport.TRANSPORT_OK
|
import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
|
import com.stevesoltys.backup.BackupNotificationManager
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
|
@ -17,8 +18,9 @@ internal class BackupCoordinatorTest: BackupTest() {
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<BackupPlugin>()
|
||||||
private val kv = mockk<KVBackup>()
|
private val kv = mockk<KVBackup>()
|
||||||
private val full = mockk<FullBackup>()
|
private val full = mockk<FullBackup>()
|
||||||
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
|
|
||||||
private val backup = BackupCoordinator(plugin, kv, full)
|
private val backup = BackupCoordinator(plugin, kv, full, notificationManager)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `device initialization succeeds and delegates to plugin`() {
|
fun `device initialization succeeds and delegates to plugin`() {
|
||||||
|
@ -33,6 +35,7 @@ internal class BackupCoordinatorTest: BackupTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `device initialization fails`() {
|
fun `device initialization fails`() {
|
||||||
every { plugin.initializeDevice() } throws IOException()
|
every { plugin.initializeDevice() } throws IOException()
|
||||||
|
every { notificationManager.onBackupError() } just Runs
|
||||||
|
|
||||||
assertEquals(TRANSPORT_ERROR, backup.initializeDevice())
|
assertEquals(TRANSPORT_ERROR, backup.initializeDevice())
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
|
||||||
ext.kotlin_version = '1.3.41'
|
ext.kotlin_version = '1.3.50'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|
Loading…
Reference in a new issue