Use our own scheduling when doing d2d backups (experimental)

This commit is contained in:
Torsten Grote 2024-02-01 14:12:47 -03:00 committed by Chirayu Desai
parent d1f0fef718
commit de51ad2cc9
7 changed files with 99 additions and 6 deletions

View file

@ -30,6 +30,7 @@ android_app {
"androidx.activity_activity-ktx",
"androidx.preference_preference",
"androidx.documentfile_documentfile",
"androidx.work_work-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel-ktx",
"androidx.lifecycle_lifecycle-livedata-ktx",
"androidx-constraintlayout_constraintlayout",

View file

@ -135,6 +135,7 @@ dependencies {
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.documentfile)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.google.material)
implementation(libs.google.tink.android)

View file

@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault
import android.content.Context
import android.util.Log
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.Date
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)
}
fun logWorkInfo(appContext: Context) {
val workManager = WorkManager.getInstance(appContext)
workManager.getWorkInfosForUniqueWork(UNIQUE_WORK_NAME).get().forEach {
Log.e(
"BackupWorker", " ${it.state.name} - ${Date(it.nextScheduleTimeMillis)} - " +
"runAttempts: ${it.runAttemptCount}"
)
}
}
}
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()
}
}

View file

@ -44,7 +44,8 @@ class ExpertSettingsFragment : PreferenceFragmentCompat() {
val d2dPreference = findPreference<SwitchPreferenceCompat>(PREF_KEY_D2D_BACKUPS)
d2dPreference?.setOnPreferenceChangeListener { _, newValue ->
d2dPreference.isChecked = newValue as Boolean
viewModel.onD2dChanged(newValue as Boolean)
d2dPreference.isChecked = newValue
// automatically enable unlimited quota when enabling D2D backups
if (d2dPreference.isChecked) {

View file

@ -12,6 +12,7 @@ import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.Uri
import android.os.Process.myUid
import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
@ -24,6 +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.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.metadata.MetadataManager
@ -261,4 +263,13 @@ internal class SettingsViewModel(
Toast.makeText(app, str, LENGTH_LONG).show()
}
fun onD2dChanged(enabled: Boolean) {
backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), !enabled)
if (enabled) {
BackupWorker.schedule(app)
} else {
BackupWorker.unschedule(app)
}
}
}

View file

@ -60,10 +60,15 @@ 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) {
fun requestBackup(context: Context): Boolean {
val backupManager: IBackupManager = get().get()
if (backupManager.isBackupEnabled) {
return if (backupManager.isBackupEnabled) {
val packageService: PackageService = get().get()
val packages = packageService.eligiblePackages
val appTotals = packageService.expectedAppTotals
@ -78,11 +83,14 @@ fun requestBackup(context: Context) {
nm.onBackupError()
}
if (result == BackupManager.SUCCESS) {
Log.i(TAG, "Backup succeeded ")
Log.i(TAG, "Backup request succeeded ")
true
} else {
Log.e(TAG, "Backup failed: $result")
Log.e(TAG, "Backup request failed: $result")
false
}
} else {
Log.i(TAG, "Backup is not enabled")
true // this counts as success
}
}

View file

@ -38,7 +38,7 @@ coroutines = { strictly = "1.6.4" }
# AndroidX versions
# https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-14.0.0_r1/current/androidx/Android.bp
room = { strictly = "2.4.0-alpha05" }
room = { strictly = "2.5.0" }
androidx-core = { strictly = "1.9.0-alpha05" }
androidx-fragment = { strictly = "1.5.0-alpha03" }
androidx-activity = { strictly = "1.5.0-alpha03" }
@ -47,6 +47,7 @@ androidx-lifecycle-viewmodel-ktx = { strictly = "2.5.0-alpha03" }
androidx-lifecycle-livedata-ktx = { strictly = "2.5.0-alpha03" }
androidx-constraintlayout = { strictly = "2.2.0-alpha05" }
androidx-documentfile = { strictly = "1.1.0-alpha01" }
androidx-work-runtime = { strictly = "2.9.0-alpha01" }
[libraries]
# Kotlin standard dependencies
@ -76,6 +77,7 @@ androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-view
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle-livedata-ktx" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "androidx-documentfile" }
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work-runtime" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
[bundles]