Make 'Backup now' action use AppBackupWorker
This commit is contained in:
parent
49066be31b
commit
8da73ad8d1
16 changed files with 172 additions and 168 deletions
|
@ -47,7 +47,7 @@ class KoinInstrumentationTestApp : App() {
|
|||
|
||||
viewModel {
|
||||
currentBackupStorageViewModel =
|
||||
spyk(BackupStorageViewModel(context, get(), get(), get(), get()))
|
||||
spyk(BackupStorageViewModel(context, get(), get(), get()))
|
||||
currentBackupStorageViewModel!!
|
||||
}
|
||||
|
||||
|
|
|
@ -156,6 +156,11 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Used by Workmanager to schedule our workers -->
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
tools:node="merge" />
|
||||
<!-- Used to start actual BackupService depending on scheduling criteria -->
|
||||
<service
|
||||
android:name=".storage.StorageBackupJobService"
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
|||
import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel
|
||||
import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel
|
||||
import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel
|
||||
import com.stevesoltys.seedvault.worker.workerModule
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
|
@ -51,7 +52,7 @@ open class App : Application() {
|
|||
|
||||
viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) }
|
||||
viewModel { BackupStorageViewModel(this@App, get(), get(), get()) }
|
||||
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
|
||||
viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { FileSelectionViewModel(this@App, get()) }
|
||||
|
@ -95,6 +96,7 @@ open class App : Application() {
|
|||
restoreModule,
|
||||
installModule,
|
||||
storageModule,
|
||||
workerModule,
|
||||
appModule
|
||||
)
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.stevesoltys.seedvault
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.stevesoltys.seedvault.transport.requestBackup
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BackupWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters,
|
||||
) : Worker(appContext, workerParams) {
|
||||
|
||||
companion object {
|
||||
private const val UNIQUE_WORK_NAME = "APP_BACKUP"
|
||||
|
||||
fun schedule(appContext: Context) {
|
||||
val backupConstraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresCharging(true)
|
||||
.build()
|
||||
val backupWorkRequest = PeriodicWorkRequestBuilder<BackupWorker>(
|
||||
repeatInterval = 24,
|
||||
repeatIntervalTimeUnit = TimeUnit.HOURS,
|
||||
flexTimeInterval = 2,
|
||||
flexTimeIntervalUnit = TimeUnit.HOURS,
|
||||
).setConstraints(backupConstraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS)
|
||||
.build()
|
||||
val workManager = WorkManager.getInstance(appContext)
|
||||
workManager.enqueueUniquePeriodicWork(UNIQUE_WORK_NAME, UPDATE, backupWorkRequest)
|
||||
}
|
||||
|
||||
fun unschedule(appContext: Context) {
|
||||
val workManager = WorkManager.getInstance(appContext)
|
||||
workManager.cancelUniqueWork(UNIQUE_WORK_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
// TODO once we make this the default, we should do storage backup here as well
|
||||
// or have two workers and ensure they never run at the same time
|
||||
return if (requestBackup(applicationContext)) Result.success()
|
||||
else Result.retry()
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.settings.FlashDrive
|
|||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import com.stevesoltys.seedvault.storage.StorageBackupService
|
||||
import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
|
||||
import com.stevesoltys.seedvault.transport.requestBackup
|
||||
import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||
import org.koin.core.context.GlobalContext.get
|
||||
import java.util.concurrent.TimeUnit.HOURS
|
||||
|
||||
|
@ -63,9 +63,7 @@ class UsbIntentReceiver : UsbMonitor() {
|
|||
i.putExtra(EXTRA_START_APP_BACKUP, true)
|
||||
startForegroundService(context, i)
|
||||
} else {
|
||||
Thread {
|
||||
requestBackup(context)
|
||||
}.start()
|
||||
AppBackupWorker.scheduleNow(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ import com.stevesoltys.seedvault.restore.install.isInstalled
|
|||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import com.stevesoltys.seedvault.storage.StorageRestoreService
|
||||
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
||||
import com.stevesoltys.seedvault.transport.backup.NUM_PACKAGES_PER_TRANSACTION
|
||||
import com.stevesoltys.seedvault.worker.NUM_PACKAGES_PER_TRANSACTION
|
||||
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||
import com.stevesoltys.seedvault.ui.AppBackupState
|
||||
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED
|
||||
|
|
|
@ -25,7 +25,6 @@ import androidx.lifecycle.liveData
|
|||
import androidx.lifecycle.switchMap
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.recyclerview.widget.DiffUtil.calculateDiff
|
||||
import com.stevesoltys.seedvault.BackupWorker
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
|
@ -33,9 +32,9 @@ import com.stevesoltys.seedvault.permitDiskReads
|
|||
import com.stevesoltys.seedvault.storage.StorageBackupJobService
|
||||
import com.stevesoltys.seedvault.storage.StorageBackupService
|
||||
import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
|
||||
import com.stevesoltys.seedvault.transport.requestBackup
|
||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -175,7 +174,7 @@ internal class SettingsViewModel(
|
|||
i.putExtra(EXTRA_START_APP_BACKUP, true)
|
||||
startForegroundService(app, i)
|
||||
} else {
|
||||
requestBackup(app)
|
||||
AppBackupWorker.scheduleNow(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,9 +266,9 @@ internal class SettingsViewModel(
|
|||
fun onD2dChanged(enabled: Boolean) {
|
||||
backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), !enabled)
|
||||
if (enabled) {
|
||||
BackupWorker.schedule(app)
|
||||
AppBackupWorker.schedule(app)
|
||||
} else {
|
||||
BackupWorker.unschedule(app)
|
||||
AppBackupWorker.unschedule(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.stevesoltys.seedvault.storage
|
||||
|
||||
import android.content.Intent
|
||||
import com.stevesoltys.seedvault.transport.requestBackup
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||
import org.calyxos.backup.storage.api.BackupObserver
|
||||
import org.calyxos.backup.storage.api.RestoreObserver
|
||||
import org.calyxos.backup.storage.api.StorageBackup
|
||||
|
@ -40,7 +40,7 @@ internal class StorageBackupService : BackupService() {
|
|||
|
||||
override fun onBackupFinished(intent: Intent, success: Boolean) {
|
||||
if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) {
|
||||
requestBackup(applicationContext)
|
||||
AppBackupWorker.scheduleNow(applicationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,13 @@ package com.stevesoltys.seedvault.transport
|
|||
|
||||
import android.app.Service
|
||||
import android.app.backup.IBackupManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||
import com.stevesoltys.seedvault.transport.backup.BackupRequester
|
||||
import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koin.core.context.GlobalContext.get
|
||||
|
||||
private val TAG = ConfigurableBackupTransportService::class.java.simpleName
|
||||
|
||||
|
@ -56,23 +51,3 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the system to initiate a backup.
|
||||
*
|
||||
* @return true iff backups was requested successfully (backup itself can still fail).
|
||||
*/
|
||||
@WorkerThread
|
||||
fun requestBackup(context: Context): Boolean {
|
||||
val backupManager: IBackupManager = get().get()
|
||||
return if (backupManager.isBackupEnabled) {
|
||||
val packageService: PackageService = get().get()
|
||||
|
||||
Log.d(TAG, "Backup is enabled, request backup...")
|
||||
val backupRequester = BackupRequester(context, backupManager, packageService)
|
||||
return backupRequester.requestBackup()
|
||||
} else {
|
||||
Log.i(TAG, "Backup is not enabled")
|
||||
true // this counts as success
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,17 +30,15 @@ import com.stevesoltys.seedvault.settings.SettingsActivity
|
|||
import kotlin.math.min
|
||||
|
||||
private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
|
||||
private const val CHANNEL_ID_APK = "NotificationApkBackup"
|
||||
private const val CHANNEL_ID_SUCCESS = "NotificationBackupSuccess"
|
||||
private const val CHANNEL_ID_ERROR = "NotificationError"
|
||||
private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
|
||||
private const val NOTIFICATION_ID_OBSERVER = 1
|
||||
internal const val NOTIFICATION_ID_APK = 2
|
||||
private const val NOTIFICATION_ID_SUCCESS = 3
|
||||
private const val NOTIFICATION_ID_ERROR = 4
|
||||
private const val NOTIFICATION_ID_RESTORE_ERROR = 5
|
||||
private const val NOTIFICATION_ID_BACKGROUND = 6
|
||||
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 7
|
||||
internal const val NOTIFICATION_ID_OBSERVER = 1
|
||||
private const val NOTIFICATION_ID_SUCCESS = 2
|
||||
private const val NOTIFICATION_ID_ERROR = 3
|
||||
private const val NOTIFICATION_ID_RESTORE_ERROR = 4
|
||||
private const val NOTIFICATION_ID_BACKGROUND = 5
|
||||
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 6
|
||||
|
||||
private val TAG = BackupNotificationManager::class.java.simpleName
|
||||
|
||||
|
@ -48,7 +46,6 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
|
||||
private val nm = context.getSystemService(NotificationManager::class.java)!!.apply {
|
||||
createNotificationChannel(getObserverChannel())
|
||||
createNotificationChannel(getApkChannel())
|
||||
createNotificationChannel(getSuccessChannel())
|
||||
createNotificationChannel(getErrorChannel())
|
||||
createNotificationChannel(getRestoreErrorChannel())
|
||||
|
@ -61,13 +58,6 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getApkChannel(): NotificationChannel {
|
||||
val title = context.getString(R.string.notification_apk_channel_title)
|
||||
return NotificationChannel(CHANNEL_ID_APK, title, IMPORTANCE_LOW).apply {
|
||||
enableVibration(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSuccessChannel(): NotificationChannel {
|
||||
val title = context.getString(R.string.notification_success_channel_title)
|
||||
return NotificationChannel(CHANNEL_ID_SUCCESS, title, IMPORTANCE_LOW).apply {
|
||||
|
@ -91,8 +81,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
fun onApkBackup(packageName: String, name: CharSequence, transferred: Int, expected: Int) {
|
||||
Log.i(TAG, "$transferred/$expected - $name ($packageName)")
|
||||
val text = context.getString(R.string.notification_apk_text, name)
|
||||
val notification = getApkBackupNotification(text, transferred, expected)
|
||||
nm.notify(NOTIFICATION_ID_APK, notification)
|
||||
updateBackupNotification(text, transferred, expected)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,32 +89,15 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
*/
|
||||
fun onAppsNotBackedUp() {
|
||||
Log.i(TAG, "onAppsNotBackedUp")
|
||||
val notification =
|
||||
getApkBackupNotification(context.getString(R.string.notification_apk_not_backed_up))
|
||||
nm.notify(NOTIFICATION_ID_APK, notification)
|
||||
val text = context.getString(R.string.notification_apk_not_backed_up)
|
||||
updateBackupNotification(text)
|
||||
}
|
||||
|
||||
fun getApkBackupNotification(
|
||||
text: String?,
|
||||
expected: Int = 0,
|
||||
transferred: Int = 0,
|
||||
): Notification = Builder(context, CHANNEL_ID_APK).apply {
|
||||
setSmallIcon(R.drawable.ic_cloud_upload)
|
||||
setContentTitle(context.getString(R.string.notification_title))
|
||||
setContentText(text)
|
||||
setOngoing(true)
|
||||
setShowWhen(false)
|
||||
setWhen(System.currentTimeMillis())
|
||||
setProgress(expected, transferred, false)
|
||||
priority = PRIORITY_DEFAULT
|
||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||
}.build()
|
||||
|
||||
/**
|
||||
* Call after [onApkBackup] or [onAppsNotBackedUp] were called.
|
||||
*/
|
||||
fun onApkBackupDone() {
|
||||
nm.cancel(NOTIFICATION_ID_APK)
|
||||
nm.cancel(NOTIFICATION_ID_OBSERVER)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +105,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
*/
|
||||
fun onBackupStarted(expectedPackages: Int) {
|
||||
updateBackupNotification(
|
||||
appName = "", // This passes quickly, no need to show something here
|
||||
text = "", // This passes quickly, no need to show something here
|
||||
transferred = 0,
|
||||
expected = expectedPackages
|
||||
)
|
||||
|
@ -145,44 +117,29 @@ internal class BackupNotificationManager(private val context: Context) {
|
|||
* this type is is expected to get called after [onApkBackup].
|
||||
*/
|
||||
fun onBackupUpdate(app: CharSequence, transferred: Int, total: Int) {
|
||||
updateBackupNotification(
|
||||
appName = app,
|
||||
transferred = min(transferred, total),
|
||||
expected = total
|
||||
)
|
||||
updateBackupNotification(app, min(transferred, total), total)
|
||||
}
|
||||
|
||||
private fun updateBackupNotification(
|
||||
appName: CharSequence,
|
||||
transferred: Int,
|
||||
expected: Int,
|
||||
text: CharSequence,
|
||||
transferred: Int = 0,
|
||||
expected: Int = 0,
|
||||
) {
|
||||
val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
|
||||
setSmallIcon(R.drawable.ic_cloud_upload)
|
||||
setContentTitle(context.getString(R.string.notification_title))
|
||||
setContentText(appName)
|
||||
setOngoing(true)
|
||||
setShowWhen(false)
|
||||
setWhen(System.currentTimeMillis())
|
||||
setProgress(expected, transferred, false)
|
||||
priority = PRIORITY_DEFAULT
|
||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||
}.build()
|
||||
val notification = getBackupNotification(text, transferred, expected)
|
||||
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
|
||||
}
|
||||
|
||||
private fun updateBackgroundBackupNotification(infoText: CharSequence) {
|
||||
Log.i(TAG, "$infoText")
|
||||
val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
|
||||
fun getBackupNotification(text: CharSequence, progress: Int = 0, total: Int = 0): Notification {
|
||||
return Builder(context, CHANNEL_ID_OBSERVER).apply {
|
||||
setSmallIcon(R.drawable.ic_cloud_upload)
|
||||
setContentTitle(context.getString(R.string.notification_title))
|
||||
setContentText(text)
|
||||
setOngoing(true)
|
||||
setShowWhen(false)
|
||||
setWhen(System.currentTimeMillis())
|
||||
setProgress(0, 0, true)
|
||||
priority = PRIORITY_LOW
|
||||
setProgress(total, progress, progress == 0 && total == 0)
|
||||
priority = PRIORITY_DEFAULT
|
||||
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||
}.build()
|
||||
nm.notify(NOTIFICATION_ID_BACKGROUND, notification)
|
||||
}
|
||||
|
||||
fun onServiceDestroyed() {
|
||||
|
|
|
@ -10,7 +10,7 @@ import android.util.Log.isLoggable
|
|||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
import com.stevesoltys.seedvault.transport.backup.BackupRequester
|
||||
import com.stevesoltys.seedvault.worker.BackupRequester
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
|
|
|
@ -12,8 +12,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
||||
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
|
||||
import com.stevesoltys.seedvault.transport.requestBackup
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.calyxos.backup.storage.api.StorageBackup
|
||||
|
@ -24,7 +23,6 @@ private val TAG = BackupStorageViewModel::class.java.simpleName
|
|||
internal class BackupStorageViewModel(
|
||||
private val app: Application,
|
||||
private val backupManager: IBackupManager,
|
||||
private val backupCoordinator: BackupCoordinator,
|
||||
private val storageBackup: StorageBackup,
|
||||
settingsManager: SettingsManager,
|
||||
) : StorageViewModel(app, settingsManager) {
|
||||
|
@ -73,7 +71,7 @@ internal class BackupStorageViewModel(
|
|||
// notify the UI that the location has been set
|
||||
mLocationChecked.postEvent(LocationResult())
|
||||
if (requestBackup) {
|
||||
requestBackup(app)
|
||||
AppBackupWorker.scheduleNow(app)
|
||||
}
|
||||
} else {
|
||||
// notify the UI that the location was invalid
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.stevesoltys.seedvault.worker
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.util.Log
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
||||
import androidx.work.ExistingWorkPolicy.REPLACE
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_OBSERVER
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AppBackupWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters,
|
||||
) : CoroutineWorker(appContext, workerParams), KoinComponent {
|
||||
|
||||
companion object {
|
||||
private val TAG = AppBackupWorker::class.simpleName
|
||||
private const val UNIQUE_WORK_NAME = "com.stevesoltys.seedvault.APP_BACKUP"
|
||||
private const val TAG_NOW = "com.stevesoltys.seedvault.TAG_NOW"
|
||||
|
||||
fun schedule(context: Context, existingWorkPolicy: ExistingPeriodicWorkPolicy = UPDATE) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresCharging(true)
|
||||
.build()
|
||||
val workRequest = PeriodicWorkRequestBuilder<AppBackupWorker>(
|
||||
repeatInterval = 24,
|
||||
repeatIntervalTimeUnit = TimeUnit.HOURS,
|
||||
flexTimeInterval = 2,
|
||||
flexTimeIntervalUnit = TimeUnit.HOURS,
|
||||
).setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||
.build()
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
Log.i(TAG, "Scheduling app backup: $workRequest")
|
||||
workManager.enqueueUniquePeriodicWork(UNIQUE_WORK_NAME, existingWorkPolicy, workRequest)
|
||||
}
|
||||
|
||||
fun scheduleNow(context: Context) {
|
||||
val workRequest = OneTimeWorkRequestBuilder<AppBackupWorker>()
|
||||
.setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.addTag(TAG_NOW)
|
||||
.build()
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
Log.i(TAG, "Asking to do app backup now...")
|
||||
workManager.enqueueUniqueWork(UNIQUE_WORK_NAME, REPLACE, workRequest)
|
||||
}
|
||||
|
||||
fun unschedule(context: Context) {
|
||||
Log.i(TAG, "Unscheduling app backup...")
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
workManager.cancelUniqueWork(UNIQUE_WORK_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
private val backupRequester: BackupRequester by inject()
|
||||
private val apkBackupManager: ApkBackupManager by inject()
|
||||
private val nm: BackupNotificationManager by inject()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
try {
|
||||
setForeground(createForegroundInfo())
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error while running setForeground: ", e)
|
||||
}
|
||||
var result: Result = Result.success()
|
||||
try {
|
||||
Log.i(TAG, "Starting APK backup...")
|
||||
apkBackupManager.backup()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error backing up APKs: ", e)
|
||||
result = Result.retry()
|
||||
} finally {
|
||||
Log.i(TAG, "Requesting app data backup...")
|
||||
val requestSuccess = try {
|
||||
if (backupRequester.isBackupEnabled) {
|
||||
Log.d(TAG, "Backup is enabled, request backup...")
|
||||
backupRequester.requestBackup()
|
||||
} else true
|
||||
} finally {
|
||||
// schedule next backup, because the old one gets lost
|
||||
// when scheduling a OneTimeWorkRequest with the same unique name via scheduleNow()
|
||||
if (tags.contains(TAG_NOW)) {
|
||||
// needs to use CANCEL_AND_REENQUEUE otherwise it doesn't get scheduled
|
||||
schedule(applicationContext, CANCEL_AND_REENQUEUE)
|
||||
}
|
||||
}
|
||||
if (!requestSuccess) result = Result.retry()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createForegroundInfo() = ForegroundInfo(
|
||||
NOTIFICATION_ID_OBSERVER,
|
||||
nm.getBackupNotification(""),
|
||||
FOREGROUND_SERVICE_TYPE_DATA_SYNC,
|
||||
)
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.stevesoltys.seedvault.transport.backup
|
||||
package com.stevesoltys.seedvault.worker
|
||||
|
||||
import android.app.backup.BackupManager
|
||||
import android.app.backup.IBackupManager
|
||||
|
@ -12,6 +12,7 @@ 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 org.koin.core.component.KoinComponent
|
||||
|
@ -34,6 +35,8 @@ internal class BackupRequester(
|
|||
val packageService: PackageService,
|
||||
) : KoinComponent {
|
||||
|
||||
val isBackupEnabled: Boolean get() = backupManager.isBackupEnabled
|
||||
|
||||
private val packages = packageService.eligiblePackages
|
||||
private val observer = NotificationBackupObserver(
|
||||
context = context,
|
|
@ -9,6 +9,13 @@ import org.koin.android.ext.koin.androidContext
|
|||
import org.koin.dsl.module
|
||||
|
||||
val workerModule = module {
|
||||
factory {
|
||||
BackupRequester(
|
||||
context = androidContext(),
|
||||
backupManager = get(),
|
||||
packageService = get(),
|
||||
)
|
||||
}
|
||||
single {
|
||||
ApkBackup(
|
||||
pm = androidContext().packageManager,
|
||||
|
|
|
@ -119,7 +119,6 @@
|
|||
|
||||
<!-- Notification -->
|
||||
<string name="notification_channel_title">Backup notification</string>
|
||||
<string name="notification_apk_channel_title">APK backup notification</string>
|
||||
<string name="notification_success_channel_title">Success notification</string>
|
||||
<string name="notification_title">Backup running</string>
|
||||
<string name="notification_apk_text">Backing up APK of %s</string>
|
||||
|
|
Loading…
Reference in a new issue