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.activity_activity-ktx",
"androidx.preference_preference", "androidx.preference_preference",
"androidx.documentfile_documentfile", "androidx.documentfile_documentfile",
"androidx.work_work-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel-ktx", "androidx.lifecycle_lifecycle-viewmodel-ktx",
"androidx.lifecycle_lifecycle-livedata-ktx", "androidx.lifecycle_lifecycle-livedata-ktx",
"androidx-constraintlayout_constraintlayout", "androidx-constraintlayout_constraintlayout",

View file

@ -135,6 +135,7 @@ dependencies {
implementation(libs.androidx.lifecycle.livedata.ktx) implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.documentfile) implementation(libs.androidx.documentfile)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.google.material) implementation(libs.google.material)
implementation(libs.google.tink.android) 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) val d2dPreference = findPreference<SwitchPreferenceCompat>(PREF_KEY_D2D_BACKUPS)
d2dPreference?.setOnPreferenceChangeListener { _, newValue -> d2dPreference?.setOnPreferenceChangeListener { _, newValue ->
d2dPreference.isChecked = newValue as Boolean viewModel.onD2dChanged(newValue as Boolean)
d2dPreference.isChecked = newValue
// automatically enable unlimited quota when enabling D2D backups // automatically enable unlimited quota when enabling D2D backups
if (d2dPreference.isChecked) { if (d2dPreference.isChecked) {

View file

@ -12,6 +12,7 @@ import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
import android.net.Uri import android.net.Uri
import android.os.Process.myUid import android.os.Process.myUid
import android.os.UserHandle
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
@ -24,6 +25,7 @@ import androidx.lifecycle.Transformations.switchMap
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.DiffUtil.calculateDiff import androidx.recyclerview.widget.DiffUtil.calculateDiff
import com.stevesoltys.seedvault.BackupWorker
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.MetadataManager
@ -261,4 +263,13 @@ internal class SettingsViewModel(
Toast.makeText(app, str, LENGTH_LONG).show() 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 @WorkerThread
fun requestBackup(context: Context) { fun requestBackup(context: Context): Boolean {
val backupManager: IBackupManager = get().get() val backupManager: IBackupManager = get().get()
if (backupManager.isBackupEnabled) { return if (backupManager.isBackupEnabled) {
val packageService: PackageService = get().get() val packageService: PackageService = get().get()
val packages = packageService.eligiblePackages val packages = packageService.eligiblePackages
val appTotals = packageService.expectedAppTotals val appTotals = packageService.expectedAppTotals
@ -78,11 +83,14 @@ fun requestBackup(context: Context) {
nm.onBackupError() nm.onBackupError()
} }
if (result == BackupManager.SUCCESS) { if (result == BackupManager.SUCCESS) {
Log.i(TAG, "Backup succeeded ") Log.i(TAG, "Backup request succeeded ")
true
} else { } else {
Log.e(TAG, "Backup failed: $result") Log.e(TAG, "Backup request failed: $result")
false
} }
} else { } else {
Log.i(TAG, "Backup is not enabled") 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 # AndroidX versions
# https://android.googlesource.com/platform/prebuilts/sdk/+/refs/tags/android-14.0.0_r1/current/androidx/Android.bp # 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-core = { strictly = "1.9.0-alpha05" }
androidx-fragment = { strictly = "1.5.0-alpha03" } androidx-fragment = { strictly = "1.5.0-alpha03" }
androidx-activity = { 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-lifecycle-livedata-ktx = { strictly = "2.5.0-alpha03" }
androidx-constraintlayout = { strictly = "2.2.0-alpha05" } androidx-constraintlayout = { strictly = "2.2.0-alpha05" }
androidx-documentfile = { strictly = "1.1.0-alpha01" } androidx-documentfile = { strictly = "1.1.0-alpha01" }
androidx-work-runtime = { strictly = "2.9.0-alpha01" }
[libraries] [libraries]
# Kotlin standard dependencies # 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-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-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "androidx-documentfile" } 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" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
[bundles] [bundles]