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 21, 2024
1 parent 0a29d01 commit 53e7fd5
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 152 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
4 changes: 3 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 @@ -51,7 +52,7 @@ open class App : Application() {

viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get(), get()) }
viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get(), get()) }
viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) }
viewModel { BackupStorageViewModel(this@App, get(), get(), get()) }
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) }
viewModel { FileSelectionViewModel(this@App, get()) }
Expand Down Expand Up @@ -95,6 +96,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 @@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.storage.StorageBackupService
import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
import com.stevesoltys.seedvault.transport.requestBackup
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

Expand Down Expand Up @@ -63,9 +63,7 @@ class UsbIntentReceiver : UsbMonitor() {
i.putExtra(EXTRA_START_APP_BACKUP, true)
startForegroundService(context, i)
} else {
Thread {
requestBackup(context)
}.start()
AppBackupWorker.scheduleNow(context)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import com.stevesoltys.seedvault.restore.install.isInstalled
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.storage.StorageRestoreService
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.backup.NUM_PACKAGES_PER_TRANSACTION
import com.stevesoltys.seedvault.worker.NUM_PACKAGES_PER_TRANSACTION
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
import com.stevesoltys.seedvault.ui.AppBackupState
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ 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 androidx.work.ExistingPeriodicWorkPolicy
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.permitDiskReads
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.transport.requestBackup
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.worker.AppBackupWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -174,7 +174,7 @@ internal class SettingsViewModel(
i.putExtra(EXTRA_START_APP_BACKUP, true)
startForegroundService(app, i)
} else {
requestBackup(app)
AppBackupWorker.scheduleNow(app)
}
}
}
Expand Down Expand Up @@ -266,9 +266,9 @@ internal class SettingsViewModel(
fun onD2dChanged(enabled: Boolean) {
backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), !enabled)
if (enabled) {
BackupWorker.schedule(app)
AppBackupWorker.schedule(app)
} else {
BackupWorker.unschedule(app)
AppBackupWorker.unschedule(app)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.stevesoltys.seedvault.storage

import android.content.Intent
import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.worker.AppBackupWorker
import org.calyxos.backup.storage.api.BackupObserver
import org.calyxos.backup.storage.api.RestoreObserver
import org.calyxos.backup.storage.api.StorageBackup
Expand Down Expand Up @@ -40,7 +40,7 @@ internal class StorageBackupService : BackupService() {

override fun onBackupFinished(intent: Intent, success: Boolean) {
if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) {
requestBackup(applicationContext)
AppBackupWorker.scheduleNow(applicationContext)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@ package com.stevesoltys.seedvault.transport

import android.app.Service
import android.app.backup.IBackupManager
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.transport.backup.BackupRequester
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.context.GlobalContext.get

private val TAG = ConfigurableBackupTransportService::class.java.simpleName

Expand Down Expand Up @@ -56,23 +51,3 @@ 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): Boolean {
val backupManager: IBackupManager = get().get()
return if (backupManager.isBackupEnabled) {
val packageService: PackageService = get().get()

Log.d(TAG, "Backup is enabled, request backup...")
val backupRequester = BackupRequester(context, backupManager, packageService)
return backupRequester.requestBackup()
} else {
Log.i(TAG, "Backup is not enabled")
true // this counts as success
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,22 @@ import com.stevesoltys.seedvault.settings.SettingsActivity
import kotlin.math.min

private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
private const val CHANNEL_ID_APK = "NotificationApkBackup"
private const val CHANNEL_ID_SUCCESS = "NotificationBackupSuccess"
private const val CHANNEL_ID_ERROR = "NotificationError"
private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
private const val NOTIFICATION_ID_OBSERVER = 1
internal const val NOTIFICATION_ID_APK = 2
private const val NOTIFICATION_ID_SUCCESS = 3
private const val NOTIFICATION_ID_ERROR = 4
private const val NOTIFICATION_ID_RESTORE_ERROR = 5
private const val NOTIFICATION_ID_BACKGROUND = 6
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 7
internal const val NOTIFICATION_ID_OBSERVER = 1
private const val NOTIFICATION_ID_SUCCESS = 2
private const val NOTIFICATION_ID_ERROR = 3
private const val NOTIFICATION_ID_RESTORE_ERROR = 4
private const val NOTIFICATION_ID_BACKGROUND = 5
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 6

private val TAG = BackupNotificationManager::class.java.simpleName

internal class BackupNotificationManager(private val context: Context) {

private val nm = context.getSystemService(NotificationManager::class.java)!!.apply {
createNotificationChannel(getObserverChannel())
createNotificationChannel(getApkChannel())
createNotificationChannel(getSuccessChannel())
createNotificationChannel(getErrorChannel())
createNotificationChannel(getRestoreErrorChannel())
Expand All @@ -61,13 +58,6 @@ internal class BackupNotificationManager(private val context: Context) {
}
}

private fun getApkChannel(): NotificationChannel {
val title = context.getString(R.string.notification_apk_channel_title)
return NotificationChannel(CHANNEL_ID_APK, title, IMPORTANCE_LOW).apply {
enableVibration(false)
}
}

private fun getSuccessChannel(): NotificationChannel {
val title = context.getString(R.string.notification_success_channel_title)
return NotificationChannel(CHANNEL_ID_SUCCESS, title, IMPORTANCE_LOW).apply {
Expand All @@ -91,49 +81,31 @@ internal class BackupNotificationManager(private val context: Context) {
fun onApkBackup(packageName: String, name: CharSequence, transferred: Int, expected: Int) {
Log.i(TAG, "$transferred/$expected - $name ($packageName)")
val text = context.getString(R.string.notification_apk_text, name)
val notification = getApkBackupNotification(text, transferred, expected)
nm.notify(NOTIFICATION_ID_APK, notification)
updateBackupNotification(text, transferred, expected)
}

/**
* This should get called for recording apps we don't back up.
*/
fun onAppsNotBackedUp() {
Log.i(TAG, "onAppsNotBackedUp")
val notification =
getApkBackupNotification(context.getString(R.string.notification_apk_not_backed_up))
nm.notify(NOTIFICATION_ID_APK, notification)
val text = context.getString(R.string.notification_apk_not_backed_up)
updateBackupNotification(text)
}

fun getApkBackupNotification(
text: String?,
expected: Int = 0,
transferred: Int = 0,
): Notification = Builder(context, CHANNEL_ID_APK).apply {
setSmallIcon(R.drawable.ic_cloud_upload)
setContentTitle(context.getString(R.string.notification_title))
setContentText(text)
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis())
setProgress(expected, transferred, false)
priority = PRIORITY_DEFAULT
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}.build()

/**
* Call after [onApkBackup] or [onAppsNotBackedUp] were called.
*/
fun onApkBackupDone() {
nm.cancel(NOTIFICATION_ID_APK)
nm.cancel(NOTIFICATION_ID_OBSERVER)
}

/**
* Call this right after starting a backup.
*/
fun onBackupStarted(expectedPackages: Int) {
updateBackupNotification(
appName = "", // This passes quickly, no need to show something here
text = "", // This passes quickly, no need to show something here
transferred = 0,
expected = expectedPackages
)
Expand All @@ -145,32 +117,33 @@ internal class BackupNotificationManager(private val context: Context) {
* this type is is expected to get called after [onApkBackup].
*/
fun onBackupUpdate(app: CharSequence, transferred: Int, total: Int) {
updateBackupNotification(
appName = app,
transferred = min(transferred, total),
expected = total
)
updateBackupNotification(app, min(transferred, total), total)
}

private fun updateBackupNotification(
appName: CharSequence,
transferred: Int,
expected: Int,
text: CharSequence,
transferred: Int = 0,
expected: Int = 0,
) {
val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
val notification = getBackupNotification(text, transferred, expected)
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
}

fun getBackupNotification(text: CharSequence, progress: Int = 0, total: Int = 0): Notification {
return Builder(context, CHANNEL_ID_OBSERVER).apply {
setSmallIcon(R.drawable.ic_cloud_upload)
setContentTitle(context.getString(R.string.notification_title))
setContentText(appName)
setContentText(text)
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis())
setProgress(expected, transferred, false)
setProgress(progress, total, false)
priority = PRIORITY_DEFAULT
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
}.build()
nm.notify(NOTIFICATION_ID_OBSERVER, notification)
}

// TODO where was this used?
private fun updateBackgroundBackupNotification(infoText: CharSequence) {
Log.i(TAG, "$infoText")
val notification = Builder(context, CHANNEL_ID_OBSERVER).apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import android.util.Log.isLoggable
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.transport.backup.BackupRequester
import com.stevesoltys.seedvault.worker.BackupRequester
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

Expand Down
Loading

0 comments on commit 53e7fd5

Please sign in to comment.