diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6bc9213ab..c3bd194cc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -156,6 +156,11 @@
+
+
{ IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) }
factory { AppListRetriever(this@App, get(), get(), get()) }
- viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get(), get()) }
+ viewModel {
+ SettingsViewModel(
+ app = this@App,
+ settingsManager = get(),
+ keyManager = get(),
+ notificationManager = get(),
+ metadataManager = get(),
+ appListRetriever = get(),
+ storageBackup = get(),
+ backupManager = get(),
+ workScheduler = get(),
+ )
+ }
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get(), get()) }
viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) }
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
@@ -95,6 +108,7 @@ open class App : Application() {
restoreModule,
installModule,
storageModule,
+ workerModule,
appModule
)
diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupWorker.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupWorker.kt
deleted file mode 100644
index 024461622..000000000
--- a/app/src/main/java/com/stevesoltys/seedvault/BackupWorker.kt
+++ /dev/null
@@ -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(
- 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()
- }
-}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
index e220462c3..53e134cd2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
@@ -25,7 +25,7 @@ import androidx.lifecycle.Transformations.switchMap
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.DiffUtil.calculateDiff
-import com.stevesoltys.seedvault.BackupWorker
+import com.stevesoltys.seedvault.worker.AppDataBackupWorker
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.metadata.MetadataManager
@@ -36,6 +36,7 @@ import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_ST
import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
+import com.stevesoltys.seedvault.worker.WorkScheduler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -57,6 +58,7 @@ internal class SettingsViewModel(
private val appListRetriever: AppListRetriever,
private val storageBackup: StorageBackup,
private val backupManager: IBackupManager,
+ private val workScheduler: WorkScheduler,
) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
private val contentResolver = app.contentResolver
@@ -266,9 +268,9 @@ internal class SettingsViewModel(
fun onD2dChanged(enabled: Boolean) {
backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), !enabled)
if (enabled) {
- BackupWorker.schedule(app)
+ AppDataBackupWorker.schedule(workScheduler)
} else {
- BackupWorker.unschedule(app)
+ AppDataBackupWorker.unschedule(workScheduler)
}
}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupWorker.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupWorker.kt
new file mode 100644
index 000000000..13e7270c6
--- /dev/null
+++ b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupWorker.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.CoroutineWorker
+import androidx.work.ForegroundInfo
+import androidx.work.WorkerParameters
+import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
+import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_APK
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+
+class ApkBackupWorker(
+ appContext: Context,
+ workerParams: WorkerParameters,
+) : CoroutineWorker(appContext, workerParams), KoinComponent {
+
+ companion object {
+ private const val UNIQUE_WORK_NAME = "APK_BACKUP"
+
+ fun schedule(workScheduler: WorkScheduler) {
+ workScheduler.schedule(UNIQUE_WORK_NAME)
+ }
+
+ fun scheduleNow(workScheduler: WorkScheduler) {
+ workScheduler.scheduleNow(UNIQUE_WORK_NAME)
+ }
+
+ fun unschedule(workScheduler: WorkScheduler) {
+ workScheduler.unschedule(UNIQUE_WORK_NAME)
+ }
+ }
+
+ private val apkBackupManager by inject()
+ private val nm by inject()
+
+ override suspend fun doWork(): Result {
+ try {
+ setForeground(createForegroundInfo())
+ } catch (e: Exception) {
+ Log.e("ApkBackupWorker", "Error while running setForeground: ", e)
+ }
+ return try {
+ apkBackupManager.backup()
+ Result.success()
+ } catch (e: Exception) {
+ Log.e(ApkBackupWorker::class.simpleName, "Error backing up APKs: ", e)
+ Result.retry()
+ }
+ }
+
+ private fun createForegroundInfo() = ForegroundInfo(
+ NOTIFICATION_ID_APK,
+ nm.getApkBackupNotification(null),
+ FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ )
+}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/AppDataBackupWorker.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/AppDataBackupWorker.kt
new file mode 100644
index 000000000..efe5dd80b
--- /dev/null
+++ b/app/src/main/java/com/stevesoltys/seedvault/worker/AppDataBackupWorker.kt
@@ -0,0 +1,36 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The Calyx Institute
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.stevesoltys.seedvault.worker
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.stevesoltys.seedvault.transport.requestBackup
+
+class AppDataBackupWorker(
+ appContext: Context,
+ workerParams: WorkerParameters,
+) : Worker(appContext, workerParams) {
+
+ companion object {
+ private const val UNIQUE_WORK_NAME = "APP_BACKUP"
+
+ fun schedule(workScheduler: WorkScheduler) {
+ workScheduler.schedule(UNIQUE_WORK_NAME)
+ }
+
+ fun unschedule(workScheduler: WorkScheduler) {
+ workScheduler.unschedule(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()
+ }
+}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/WorkScheduler.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkScheduler.kt
new file mode 100644
index 000000000..65d19f953
--- /dev/null
+++ b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkScheduler.kt
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The Calyx Institute
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.stevesoltys.seedvault.worker
+
+import android.content.Context
+import androidx.work.BackoffPolicy
+import androidx.work.Constraints
+import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
+import androidx.work.ExistingWorkPolicy.REPLACE
+import androidx.work.ListenableWorker
+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 java.util.concurrent.TimeUnit
+
+class WorkScheduler(appContext: Context) {
+
+ val workManager = WorkManager.getInstance(appContext)
+
+ inline fun schedule(
+ uniqueWorkName: String,
+ ) {
+ val constraints = Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.UNMETERED)
+ .setRequiresCharging(true)
+ .build()
+ val workRequest = PeriodicWorkRequestBuilder(
+ repeatInterval = 24,
+ repeatIntervalTimeUnit = TimeUnit.HOURS,
+ flexTimeInterval = 2,
+ flexTimeIntervalUnit = TimeUnit.HOURS,
+ ).setConstraints(constraints)
+ .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS)
+ .build()
+ workManager.enqueueUniquePeriodicWork(uniqueWorkName, UPDATE, workRequest)
+ }
+
+ inline fun scheduleNow(
+ uniqueWorkName: String,
+ ) {
+ val workRequest = OneTimeWorkRequestBuilder()
+ .setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST)
+ .build()
+ // TODO still replaces scheduled request
+ workManager.enqueueUniqueWork(uniqueWorkName, REPLACE, workRequest)
+ }
+
+ fun unschedule(uniqueWorkName: String) {
+ workManager.cancelUniqueWork(uniqueWorkName)
+ }
+
+}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt
new file mode 100644
index 000000000..d7a41a3f0
--- /dev/null
+++ b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt
@@ -0,0 +1,14 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The Calyx Institute
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.stevesoltys.seedvault.worker
+
+import org.koin.android.ext.koin.androidContext
+import org.koin.dsl.module
+
+val workerModule = module {
+ single { WorkScheduler(androidContext()) }
+ single { ApkBackupManager(androidContext(), get(), get(), get(), get(), get(), get()) }
+}