Skip to content

Commit

Permalink
WIP: schedule our own APK backups
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Feb 20, 2024
1 parent 697719c commit 7d9cff3
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 61 deletions.
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 15 additions & 1 deletion app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,7 +50,19 @@ open class App : Application() {
factory<IBackupManager> { 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()) }
Expand Down Expand Up @@ -95,6 +108,7 @@ open class App : Application() {
restoreModule,
installModule,
storageModule,
workerModule,
appModule
)

Expand Down
57 changes: 0 additions & 57 deletions app/src/main/java/com/stevesoltys/seedvault/BackupWorker.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ApkBackupWorker>(UNIQUE_WORK_NAME)
}

fun scheduleNow(workScheduler: WorkScheduler) {
workScheduler.scheduleNow<ApkBackupWorker>(UNIQUE_WORK_NAME)
}

fun unschedule(workScheduler: WorkScheduler) {
workScheduler.unschedule(UNIQUE_WORK_NAME)
}
}

private val apkBackupManager by inject<ApkBackupManager>()
private val nm by inject<BackupNotificationManager>()

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,
)
}
Original file line number Diff line number Diff line change
@@ -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<AppDataBackupWorker>(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()
}
}
Original file line number Diff line number Diff line change
@@ -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 <reified W : ListenableWorker> schedule(
uniqueWorkName: String,
) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val workRequest = PeriodicWorkRequestBuilder<W>(
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 <reified W : ListenableWorker> scheduleNow(
uniqueWorkName: String,
) {
val workRequest = OneTimeWorkRequestBuilder<W>()
.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)
}

}
14 changes: 14 additions & 0 deletions app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt
Original file line number Diff line number Diff line change
@@ -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()) }
}

0 comments on commit 7d9cff3

Please sign in to comment.