Expose scheduling options in the UI
This commit is contained in:
parent
f593b66e00
commit
4eaa806636
16 changed files with 287 additions and 65 deletions
|
@ -13,6 +13,7 @@ import android.os.UserHandle
|
|||
import android.os.UserManager
|
||||
import android.provider.Settings
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
||||
import com.stevesoltys.seedvault.crypto.cryptoModule
|
||||
import com.stevesoltys.seedvault.header.headerModule
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
|
@ -56,7 +57,7 @@ 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(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get(), get()) }
|
||||
viewModel { BackupStorageViewModel(this@App, get(), get(), get()) }
|
||||
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
|
||||
|
@ -133,7 +134,9 @@ open class App : Application() {
|
|||
if (!isFrameworkSchedulingEnabled()) return // already on own scheduling
|
||||
|
||||
backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), false)
|
||||
if (backupManager.isBackupEnabled) AppBackupWorker.schedule(applicationContext)
|
||||
if (backupManager.isBackupEnabled) {
|
||||
AppBackupWorker.schedule(applicationContext, settingsManager, UPDATE)
|
||||
}
|
||||
// cancel old D2D worker
|
||||
WorkManager.getInstance(this).cancelUniqueWork("APP_BACKUP")
|
||||
}
|
||||
|
|
|
@ -23,12 +23,10 @@ import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_ST
|
|||
import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||
import org.koin.core.context.GlobalContext.get
|
||||
import java.util.concurrent.TimeUnit.HOURS
|
||||
import java.util.Date
|
||||
|
||||
private val TAG = UsbIntentReceiver::class.java.simpleName
|
||||
|
||||
private const val HOURS_AUTO_BACKUP: Long = 24
|
||||
|
||||
class UsbIntentReceiver : UsbMonitor() {
|
||||
|
||||
// using KoinComponent would crash robolectric tests :(
|
||||
|
@ -43,11 +41,13 @@ class UsbIntentReceiver : UsbMonitor() {
|
|||
return if (savedFlashDrive == attachedFlashDrive) {
|
||||
Log.d(TAG, "Matches stored device, checking backup time...")
|
||||
val backupMillis = System.currentTimeMillis() - metadataManager.getLastBackupTime()
|
||||
if (backupMillis >= HOURS.toMillis(HOURS_AUTO_BACKUP)) {
|
||||
Log.d(TAG, "Last backup older than 24 hours, requesting a backup...")
|
||||
if (backupMillis >= settingsManager.backupFrequencyInMillis) {
|
||||
Log.d(TAG, "Last backup older than it should be, requesting a backup...")
|
||||
Log.d(TAG, " ${Date(metadataManager.getLastBackupTime())}")
|
||||
true
|
||||
} else {
|
||||
Log.d(TAG, "We have a recent backup, not requesting a new one.")
|
||||
Log.d(TAG, " ${Date(metadataManager.getLastBackupTime())}")
|
||||
false
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -16,10 +16,10 @@ class ExpertSettingsFragment : PreferenceFragmentCompat() {
|
|||
private val viewModel: SettingsViewModel by sharedViewModel()
|
||||
private val packageService: PackageService by inject()
|
||||
|
||||
// TODO set mimeType when upgrading androidx lib
|
||||
private val createFileLauncher = registerForActivityResult(CreateDocument()) { uri ->
|
||||
viewModel.onLogcatUriReceived(uri)
|
||||
}
|
||||
private val createFileLauncher =
|
||||
registerForActivityResult(CreateDocument("text/plain")) { uri ->
|
||||
viewModel.onLogcatUriReceived(uri)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
permitDiskReads {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package com.stevesoltys.seedvault.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.permitDiskReads
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class SchedulingFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val viewModel: SettingsViewModel by sharedViewModel()
|
||||
private val settingsManager: SettingsManager by inject()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
permitDiskReads {
|
||||
setPreferencesFromResource(R.xml.settings_scheduling, rootKey)
|
||||
PreferenceManager.setDefaultValues(requireContext(), R.xml.settings_scheduling, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val storage = settingsManager.getStorage()
|
||||
if (storage?.isUsb == true) {
|
||||
findPreference<PreferenceCategory>("scheduling_category_conditions")?.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
activity?.setTitle(R.string.settings_backup_scheduling_title)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
settingsManager.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
settingsManager.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
// we can not use setOnPreferenceChangeListener() because that gets called
|
||||
// before prefs were saved
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
PREF_KEY_SCHED_FREQ -> viewModel.scheduleAppBackup(CANCEL_AND_REENQUEUE)
|
||||
PREF_KEY_SCHED_METERED -> viewModel.scheduleAppBackup(UPDATE)
|
||||
PREF_KEY_SCHED_CHARGING -> viewModel.scheduleAppBackup(UPDATE)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,12 +19,15 @@ import androidx.preference.Preference
|
|||
import androidx.preference.Preference.OnPreferenceChangeListener
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.TwoStatePreference
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
|
||||
import androidx.work.WorkInfo
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.permitDiskReads
|
||||
import com.stevesoltys.seedvault.restore.RestoreActivity
|
||||
import com.stevesoltys.seedvault.ui.toRelativeTime
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private val TAG = SettingsFragment::class.java.name
|
||||
|
||||
|
@ -39,6 +42,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
private lateinit var apkBackup: TwoStatePreference
|
||||
private lateinit var backupLocation: Preference
|
||||
private lateinit var backupStatus: Preference
|
||||
private lateinit var backupScheduling: Preference
|
||||
private lateinit var backupStorage: TwoStatePreference
|
||||
private lateinit var backupRecoveryCode: Preference
|
||||
|
||||
|
@ -121,6 +125,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
return@OnPreferenceChangeListener false
|
||||
}
|
||||
backupStatus = findPreference("backup_status")!!
|
||||
backupScheduling = findPreference("backup_scheduling")!!
|
||||
|
||||
backupStorage = findPreference("backup_storage")!!
|
||||
backupStorage.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||
|
@ -141,17 +146,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.lastBackupTime.observe(viewLifecycleOwner) { time ->
|
||||
setAppBackupStatusSummary(
|
||||
lastBackupInMillis = time,
|
||||
nextScheduleTimeMillis = viewModel.appBackupWorkInfo.value?.nextScheduleTimeMillis,
|
||||
)
|
||||
setAppBackupStatusSummary(time)
|
||||
}
|
||||
viewModel.appBackupWorkInfo.observe(viewLifecycleOwner) { workInfo ->
|
||||
viewModel.onWorkerStateChanged()
|
||||
setAppBackupStatusSummary(
|
||||
lastBackupInMillis = viewModel.lastBackupTime.value,
|
||||
nextScheduleTimeMillis = workInfo?.nextScheduleTimeMillis,
|
||||
)
|
||||
setAppBackupSchedulingSummary(workInfo)
|
||||
}
|
||||
|
||||
val backupFiles: Preference = findPreference("backup_files")!!
|
||||
|
@ -170,10 +169,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
setBackupEnabledState()
|
||||
setBackupLocationSummary()
|
||||
setAutoRestoreState()
|
||||
setAppBackupStatusSummary(
|
||||
lastBackupInMillis = viewModel.lastBackupTime.value,
|
||||
nextScheduleTimeMillis = viewModel.appBackupWorkInfo.value?.nextScheduleTimeMillis,
|
||||
)
|
||||
setAppBackupStatusSummary(viewModel.lastBackupTime.value)
|
||||
setAppBackupSchedulingSummary(viewModel.appBackupWorkInfo.value)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
|
@ -221,7 +218,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
return try {
|
||||
backupManager.isBackupEnabled = enabled
|
||||
if (enabled) {
|
||||
viewModel.scheduleAppBackup()
|
||||
viewModel.scheduleAppBackup(CANCEL_AND_REENQUEUE)
|
||||
viewModel.enableCallLogBackup()
|
||||
} else {
|
||||
viewModel.cancelAppBackup()
|
||||
|
@ -265,37 +262,41 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
backupLocation.summary = storage?.name ?: getString(R.string.settings_backup_location_none)
|
||||
}
|
||||
|
||||
private fun setAppBackupStatusSummary(
|
||||
lastBackupInMillis: Long?,
|
||||
nextScheduleTimeMillis: Long?,
|
||||
) {
|
||||
val sb = StringBuilder()
|
||||
private fun setAppBackupStatusSummary(lastBackupInMillis: Long?) {
|
||||
if (lastBackupInMillis != null) {
|
||||
// set time of last backup
|
||||
val lastBackup = lastBackupInMillis.toRelativeTime(requireContext())
|
||||
sb.append(getString(R.string.settings_backup_status_summary, lastBackup))
|
||||
backupStatus.summary = getString(R.string.settings_backup_status_summary, lastBackup)
|
||||
}
|
||||
if (nextScheduleTimeMillis != null) {
|
||||
// insert linebreak, if we have text before
|
||||
if (sb.isNotEmpty()) sb.append("\n")
|
||||
// set time of next backup
|
||||
when (nextScheduleTimeMillis) {
|
||||
Long.MAX_VALUE -> {
|
||||
val text = if (backupManager.isBackupEnabled && storage?.isUsb != true) {
|
||||
getString(R.string.notification_title)
|
||||
} else {
|
||||
getString(R.string.settings_backup_last_backup_never)
|
||||
}
|
||||
sb.append(getString(R.string.settings_backup_status_next_backup, text))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val text = nextScheduleTimeMillis.toRelativeTime(requireContext())
|
||||
sb.append(getString(R.string.settings_backup_status_next_backup_estimate, text))
|
||||
}
|
||||
private fun setAppBackupSchedulingSummary(workInfo: WorkInfo?) {
|
||||
if (storage?.isUsb == true) {
|
||||
backupScheduling.summary = getString(R.string.settings_backup_status_next_backup_usb)
|
||||
return
|
||||
}
|
||||
if (workInfo == null) return
|
||||
|
||||
val nextScheduleTimeMillis = workInfo.nextScheduleTimeMillis
|
||||
if (workInfo.state == WorkInfo.State.RUNNING) {
|
||||
val text = getString(R.string.notification_title)
|
||||
backupScheduling.summary = getString(R.string.settings_backup_status_next_backup, text)
|
||||
} else if (nextScheduleTimeMillis == Long.MAX_VALUE) {
|
||||
val text = getString(R.string.settings_backup_last_backup_never)
|
||||
backupScheduling.summary = getString(R.string.settings_backup_status_next_backup, text)
|
||||
} else {
|
||||
val diff = System.currentTimeMillis() - nextScheduleTimeMillis
|
||||
val isPast = diff > TimeUnit.MINUTES.toMillis(1)
|
||||
if (isPast) {
|
||||
val text = getString(R.string.settings_backup_status_next_backup_past)
|
||||
backupScheduling.summary =
|
||||
getString(R.string.settings_backup_status_next_backup, text)
|
||||
} else {
|
||||
val text = nextScheduleTimeMillis.toRelativeTime(requireContext())
|
||||
backupScheduling.summary =
|
||||
getString(R.string.settings_backup_status_next_backup_estimate, text)
|
||||
}
|
||||
}
|
||||
backupStatus.summary = sb.toString()
|
||||
}
|
||||
|
||||
private fun onEnablingStorageBackup() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.stevesoltys.seedvault.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
|
@ -17,6 +18,9 @@ import java.util.concurrent.ConcurrentSkipListSet
|
|||
internal const val PREF_KEY_TOKEN = "token"
|
||||
internal const val PREF_KEY_BACKUP_APK = "backup_apk"
|
||||
internal const val PREF_KEY_AUTO_RESTORE = "auto_restore"
|
||||
internal const val PREF_KEY_SCHED_FREQ = "scheduling_frequency"
|
||||
internal const val PREF_KEY_SCHED_METERED = "scheduling_metered"
|
||||
internal const val PREF_KEY_SCHED_CHARGING = "scheduling_charging"
|
||||
|
||||
private const val PREF_KEY_STORAGE_URI = "storageUri"
|
||||
private const val PREF_KEY_STORAGE_NAME = "storageName"
|
||||
|
@ -43,6 +47,14 @@ class SettingsManager(private val context: Context) {
|
|||
@Volatile
|
||||
private var token: Long? = null
|
||||
|
||||
fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
prefs.registerOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
fun unregisterOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets accessed by non-UI threads when saving with [PreferenceManager]
|
||||
* and when [isBackupEnabled] is called during a backup run.
|
||||
|
@ -141,6 +153,16 @@ class SettingsManager(private val context: Context) {
|
|||
return prefs.getBoolean(PREF_KEY_BACKUP_APK, true)
|
||||
}
|
||||
|
||||
val backupFrequencyInMillis: Long
|
||||
get() {
|
||||
return prefs.getString(PREF_KEY_SCHED_FREQ, "86400000")?.toLongOrNull()
|
||||
?: 86400000 // 24h
|
||||
}
|
||||
val useMeteredNetwork: Boolean
|
||||
get() = prefs.getBoolean(PREF_KEY_SCHED_METERED, false)
|
||||
val backupOnlyWhenCharging: Boolean
|
||||
get() = prefs.getBoolean(PREF_KEY_SCHED_CHARGING, true)
|
||||
|
||||
fun isBackupEnabled(packageName: String) = !blacklistedApps.contains(packageName)
|
||||
|
||||
fun isStorageBackupEnabled() = prefs.getBoolean(PREF_KEY_BACKUP_STORAGE, false)
|
||||
|
|
|
@ -26,6 +26,8 @@ import androidx.lifecycle.map
|
|||
import androidx.lifecycle.switchMap
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.recyclerview.widget.DiffUtil.calculateDiff
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.stevesoltys.seedvault.R
|
||||
|
@ -36,7 +38,6 @@ import com.stevesoltys.seedvault.storage.StorageBackupJobService
|
|||
import com.stevesoltys.seedvault.storage.StorageBackupService
|
||||
import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
|
||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||
import com.stevesoltys.seedvault.worker.AppBackupWorker.Companion.UNIQUE_WORK_NAME
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -55,7 +56,6 @@ internal class SettingsViewModel(
|
|||
app: Application,
|
||||
settingsManager: SettingsManager,
|
||||
keyManager: KeyManager,
|
||||
private val notificationManager: BackupNotificationManager,
|
||||
private val metadataManager: MetadataManager,
|
||||
private val appListRetriever: AppListRetriever,
|
||||
private val storageBackup: StorageBackup,
|
||||
|
@ -126,7 +126,7 @@ internal class SettingsViewModel(
|
|||
override fun onStorageLocationChanged() {
|
||||
val storage = settingsManager.getStorage() ?: return
|
||||
|
||||
Log.i(TAG, "onStorageLocationChanged")
|
||||
Log.i(TAG, "onStorageLocationChanged (isUsb: ${storage.isUsb}")
|
||||
if (storage.isUsb) {
|
||||
// disable storage backup if new storage is on USB
|
||||
cancelAppBackup()
|
||||
|
@ -134,7 +134,7 @@ internal class SettingsViewModel(
|
|||
} else {
|
||||
// enable it, just in case the previous storage was on USB,
|
||||
// also to update the network requirement of the new storage
|
||||
scheduleAppBackup()
|
||||
scheduleAppBackup(CANCEL_AND_REENQUEUE)
|
||||
scheduleFilesBackup()
|
||||
}
|
||||
onStoragePropertiesChanged()
|
||||
|
@ -248,9 +248,11 @@ internal class SettingsViewModel(
|
|||
return keyManager.hasMainKey()
|
||||
}
|
||||
|
||||
fun scheduleAppBackup() {
|
||||
fun scheduleAppBackup(existingWorkPolicy: ExistingPeriodicWorkPolicy) {
|
||||
val storage = settingsManager.getStorage() ?: error("no storage available")
|
||||
if (!storage.isUsb && backupManager.isBackupEnabled) AppBackupWorker.schedule(app)
|
||||
if (!storage.isUsb && backupManager.isBackupEnabled) {
|
||||
AppBackupWorker.schedule(app, settingsManager, existingWorkPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
fun scheduleFilesBackup() {
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.os.UserHandle
|
|||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import com.stevesoltys.seedvault.storage.StorageBackupJobService
|
||||
|
@ -69,7 +70,9 @@ internal class BackupStorageViewModel(
|
|||
private fun scheduleBackupWorkers() {
|
||||
val storage = settingsManager.getStorage() ?: error("no storage available")
|
||||
if (!storage.isUsb) {
|
||||
if (backupManager.isBackupEnabled) AppBackupWorker.schedule(app)
|
||||
if (backupManager.isBackupEnabled) {
|
||||
AppBackupWorker.schedule(app, settingsManager, CANCEL_AND_REENQUEUE)
|
||||
}
|
||||
if (settingsManager.isStorageBackupEnabled()) BackupJobService.scheduleJob(
|
||||
context = app,
|
||||
jobServiceClass = StorageBackupJobService::class.java,
|
||||
|
|
|
@ -7,13 +7,13 @@ package com.stevesoltys.seedvault.worker
|
|||
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.text.format.DateUtils.formatElapsedTime
|
||||
import android.util.Log
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
|
||||
import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
|
||||
import androidx.work.ExistingWorkPolicy.REPLACE
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
|
@ -22,6 +22,7 @@ import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
|
|||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_OBSERVER
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
@ -38,21 +39,43 @@ class AppBackupWorker(
|
|||
internal const val UNIQUE_WORK_NAME = "com.stevesoltys.seedvault.APP_BACKUP"
|
||||
private const val TAG_RESCHEDULE = "com.stevesoltys.seedvault.TAG_RESCHEDULE"
|
||||
|
||||
fun schedule(context: Context, existingWorkPolicy: ExistingPeriodicWorkPolicy = UPDATE) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresCharging(true)
|
||||
.build()
|
||||
/**
|
||||
* (Re-)schedules the [AppBackupWorker].
|
||||
*
|
||||
* @param existingWorkPolicy usually you want to use [ExistingPeriodicWorkPolicy.UPDATE]
|
||||
* only if you are sure that work is still scheduled
|
||||
* and you don't want to mess with the scheduling time.
|
||||
* In most other cases, you want to use [ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE],
|
||||
* because it ensures work gets schedules, even if it wasn't scheduled before.
|
||||
* It will however reset the scheduling time.
|
||||
*/
|
||||
fun schedule(
|
||||
context: Context,
|
||||
settingsManager: SettingsManager,
|
||||
existingWorkPolicy: ExistingPeriodicWorkPolicy,
|
||||
) {
|
||||
val logFrequency = formatElapsedTime(settingsManager.backupFrequencyInMillis / 1000)
|
||||
Log.i(TAG, "Scheduling in $logFrequency...")
|
||||
val constraints = Constraints.Builder().apply {
|
||||
if (!settingsManager.useMeteredNetwork) {
|
||||
Log.i(TAG, " only on unmetered networks")
|
||||
setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
}
|
||||
if (settingsManager.backupOnlyWhenCharging) {
|
||||
Log.i(TAG, " only when the device is charging")
|
||||
setRequiresCharging(true)
|
||||
}
|
||||
}.build()
|
||||
val workRequest = PeriodicWorkRequestBuilder<AppBackupWorker>(
|
||||
repeatInterval = 24,
|
||||
repeatIntervalTimeUnit = TimeUnit.HOURS,
|
||||
repeatInterval = settingsManager.backupFrequencyInMillis,
|
||||
repeatIntervalTimeUnit = TimeUnit.MILLISECONDS,
|
||||
flexTimeInterval = 2,
|
||||
flexTimeIntervalUnit = TimeUnit.HOURS,
|
||||
).setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||
.build()
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
Log.i(TAG, "Scheduling app backup: $workRequest")
|
||||
Log.i(TAG, " workRequest: ${workRequest.id}")
|
||||
workManager.enqueueUniquePeriodicWork(UNIQUE_WORK_NAME, existingWorkPolicy, workRequest)
|
||||
}
|
||||
|
||||
|
@ -74,6 +97,7 @@ class AppBackupWorker(
|
|||
}
|
||||
|
||||
private val backupRequester: BackupRequester by inject()
|
||||
private val settingsManager: SettingsManager by inject()
|
||||
private val apkBackupManager: ApkBackupManager by inject()
|
||||
private val nm: BackupNotificationManager by inject()
|
||||
|
||||
|
@ -95,7 +119,7 @@ class AppBackupWorker(
|
|||
// when scheduling a OneTimeWorkRequest with the same unique name via scheduleNow()
|
||||
if (tags.contains(TAG_RESCHEDULE) && backupRequester.isBackupEnabled) {
|
||||
// needs to use CANCEL_AND_REENQUEUE otherwise it doesn't get scheduled
|
||||
schedule(applicationContext, CANCEL_AND_REENQUEUE)
|
||||
schedule(applicationContext, settingsManager, CANCEL_AND_REENQUEUE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
app/src/main/res/drawable/ic_access_time.xml
Normal file
10
app/src/main/res/drawable/ic_access_time.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_battery_charging_full.xml
Normal file
10
app/src/main/res/drawable/ic_battery_charging_full.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM11,20v-5.5H9L13,7v5.5h2L11,20z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_network_warning.xml
Normal file
10
app/src/main/res/drawable/ic_network_warning.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19 17H21V11H19M19 21H21V19H19M1 21H17V9H21V1" />
|
||||
</vector>
|
20
app/src/main/res/values/arrays.xml
Normal file
20
app/src/main/res/values/arrays.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
<string-array name="settings_scheduling_frequency_labels">
|
||||
<item>@string/settings_scheduling_frequency_12_hours</item>
|
||||
<item>@string/settings_scheduling_frequency_daily</item>
|
||||
<item>@string/settings_scheduling_frequency_3_days</item>
|
||||
<item>@string/settings_scheduling_frequency_weekly</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="settings_scheduling_frequency_values">
|
||||
<item>43200000</item>
|
||||
<item>86400000</item>
|
||||
<item>259200000</item>
|
||||
<item>604800000</item>
|
||||
</string-array>
|
||||
</resources>
|
|
@ -32,6 +32,9 @@
|
|||
<string name="settings_backup_status_summary">Last backup: %1$s</string>
|
||||
<string name="settings_backup_status_next_backup">Next backup: %1$s</string>
|
||||
<string name="settings_backup_status_next_backup_estimate">Next backup (estimate): %1$s</string>
|
||||
<string name="settings_backup_status_next_backup_past">once conditions are fulfilled</string>
|
||||
<string name="settings_backup_status_next_backup_usb">Backups will happen automatically when you plug in your USB drive</string>
|
||||
<string name="settings_backup_scheduling_title">Backup scheduling</string>
|
||||
<string name="settings_backup_exclude_apps">Exclude apps</string>
|
||||
<string name="settings_backup_now">Backup now</string>
|
||||
<string name="settings_category_storage">Storage backup (beta)</string>
|
||||
|
@ -48,6 +51,15 @@
|
|||
<string name="settings_backup_new_code_dialog_message">To continue using app backups, you need to generate a new recovery code.\n\nWe are sorry for the inconvenience.</string>
|
||||
<string name="settings_backup_new_code_code_dialog_ok">New code</string>
|
||||
|
||||
<string name="settings_scheduling_frequency_title">Backup frequency</string>
|
||||
<string name="settings_scheduling_frequency_12_hours">Every 12 hours</string>
|
||||
<string name="settings_scheduling_frequency_daily">Daily</string>
|
||||
<string name="settings_scheduling_frequency_3_days">Every 3 days</string>
|
||||
<string name="settings_scheduling_frequency_weekly">Weekly</string>
|
||||
<string name="settings_scheduling_category_conditions_title">Conditions</string>
|
||||
<string name="settings_scheduling_metered_title">Back up when using mobile data</string>
|
||||
<string name="settings_scheduling_charging_title">Back up only when charging</string>
|
||||
|
||||
<string name="settings_expert_title">Expert settings</string>
|
||||
<string name="settings_expert_quota_title">Unlimited app quota</string>
|
||||
<string name="settings_expert_quota_summary">Do not impose a limitation on the size of app backups.\n\nWarning: This can fill up your storage location quickly. Not needed for most apps.</string>
|
||||
|
|
|
@ -46,6 +46,13 @@
|
|||
app:summary="@string/settings_backup_apk_summary"
|
||||
app:title="@string/settings_backup_apk_title" />
|
||||
|
||||
<androidx.preference.Preference
|
||||
app:fragment="com.stevesoltys.seedvault.settings.SchedulingFragment"
|
||||
app:icon="@drawable/ic_access_time"
|
||||
app:key="backup_scheduling"
|
||||
app:title="@string/settings_backup_scheduling_title"
|
||||
app:summary="Next backup: Never" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_category_storage">
|
||||
|
|
34
app/src/main/res/xml/settings_scheduling.xml
Normal file
34
app/src/main/res/xml/settings_scheduling.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<ListPreference
|
||||
android:defaultValue="86400000"
|
||||
android:entries="@array/settings_scheduling_frequency_labels"
|
||||
android:entryValues="@array/settings_scheduling_frequency_values"
|
||||
app:icon="@drawable/ic_access_time"
|
||||
app:key="scheduling_frequency"
|
||||
app:title="@string/settings_scheduling_frequency_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<PreferenceCategory
|
||||
app:key="scheduling_category_conditions"
|
||||
app:singleLineTitle="false"
|
||||
app:title="@string/settings_scheduling_category_conditions_title">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="scheduling_metered"
|
||||
android:title="@string/settings_scheduling_metered_title"
|
||||
app:icon="@drawable/ic_network_warning"
|
||||
app:singleLineTitle="false" />
|
||||
<SwitchPreferenceCompat
|
||||
android:id="@+id/d2d_backup_preference"
|
||||
android:defaultValue="true"
|
||||
android:key="scheduling_charging"
|
||||
android:title="@string/settings_scheduling_charging_title"
|
||||
app:icon="@drawable/ic_battery_charging_full"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in a new issue