Use our own scheduling when doing d2d backups (experimental)
This commit is contained in:
parent
d1f0fef718
commit
de51ad2cc9
7 changed files with 99 additions and 6 deletions
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
69
app/src/main/java/com/stevesoltys/seedvault/BackupWorker.kt
Normal file
69
app/src/main/java/com/stevesoltys/seedvault/BackupWorker.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in a new issue