Skip to content

Commit

Permalink
Merge pull request #722 from grote/restore-foreground-service
Browse files Browse the repository at this point in the history
Start a foreground service during restore
  • Loading branch information
grote authored Sep 5, 2024
2 parents 0a7ce66 + d266c36 commit 710354d
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 5 deletions.
10 changes: 8 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,19 @@
android:exported="false"
android:label="BackupJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<!-- Does the actual backup work as a foreground service -->
<!-- Does app restore as a foreground service -->
<service
android:name=".restore.RestoreService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:label="RestoreService" />
<!-- Does the actual file backup work as a foreground service -->
<service
android:name=".storage.StorageBackupService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:label="BackupService" />
<!-- Does restore as a foreground service -->
<!-- Does file restore as a foreground service -->
<service
android:name=".storage.StorageRestoreService"
android:exported="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.app.backup.IRestoreObserver
import android.app.backup.IRestoreSession
import android.app.backup.RestoreSet
import android.content.Context
import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
Expand Down Expand Up @@ -60,6 +61,7 @@ internal class AppDataRestoreManager(

private var session: IRestoreSession? = null
private val monitor = BackupMonitor()
private val foregroundServiceIntent = Intent(context, RestoreService::class.java)

private val mRestoreProgress = MutableLiveData(
LinkedList<AppRestoreResult>().apply {
Expand Down Expand Up @@ -120,6 +122,9 @@ internal class AppDataRestoreManager(
mRestoreBackupResult.postValue(
RestoreBackupResult(context.getString(R.string.restore_set_error))
)
} else {
// don't use startForeground(), because we may stop it sooner than the system likes
context.startService(foregroundServiceIntent)
}
}

Expand Down Expand Up @@ -208,6 +213,8 @@ internal class AppDataRestoreManager(
mRestoreProgress.postValue(list)

mRestoreBackupResult.postValue(result)

context.stopService(foregroundServiceIntent)
}

fun closeSession() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.restore

import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
import android.os.IBinder
import android.util.Log
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_RESTORE
import org.koin.android.ext.android.inject

class RestoreService : Service() {

companion object {
private const val TAG = "RestoreService"
}

private val nm: BackupNotificationManager by inject()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand $intent $flags $startId")

startForeground(
NOTIFICATION_ID_RESTORE,
nm.getRestoreNotification(),
FOREGROUND_SERVICE_TYPE_MANIFEST,
)
return START_STICKY_COMPATIBILITY
}

override fun onBind(intent: Intent?): IBinder? {
return null
}

override fun onDestroy() {
Log.i(TAG, "onDestroy")
super.onDestroy()
nm.cancelRestoreNotification()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.stevesoltys.seedvault.restore.install

import android.app.backup.IBackupManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_SIGNATURES
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
Expand All @@ -20,6 +21,7 @@ import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.restore.RestorableBackup
import com.stevesoltys.seedvault.restore.RestoreService
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
Expand Down Expand Up @@ -85,14 +87,18 @@ internal class ApkRestore(
return
}
mInstallResult.value = InstallResult(packages)
val i = Intent(context, RestoreService::class.java)
val autoRestore = backupStateManager.isAutoRestoreEnabled
try {
// don't use startForeground(), because we may stop it sooner than the system likes
context.startService(i)
// disable auto-restore before installing apps, if it was enabled before
if (autoRestore) backupManager.setAutoRestore(false)
reInstallApps(backup, packages.asIterable().reversed())
} finally {
// re-enable auto-restore, if it was enabled before
if (autoRestore) backupManager.setAutoRestore(true)
context.stopService(i)
}
mInstallResult.update { it.copy(isFinished = true) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ import kotlin.math.min
private const val CHANNEL_ID_OBSERVER = "NotificationBackupObserver"
private const val CHANNEL_ID_SUCCESS = "NotificationBackupSuccess"
private const val CHANNEL_ID_ERROR = "NotificationError"
private const val CHANNEL_ID_RESTORE = "NotificationRestore"
private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
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_SPACE_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_RESTORE = 5
private const val NOTIFICATION_ID_RESTORE_ERROR = 6
private const val NOTIFICATION_ID_BACKGROUND = 7
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 8

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

Expand All @@ -55,6 +57,7 @@ internal class BackupNotificationManager(private val context: Context) {
createNotificationChannel(getObserverChannel())
createNotificationChannel(getSuccessChannel())
createNotificationChannel(getErrorChannel())
createNotificationChannel(getRestoreChannel())
createNotificationChannel(getRestoreErrorChannel())
}

Expand All @@ -77,6 +80,11 @@ internal class BackupNotificationManager(private val context: Context) {
return NotificationChannel(CHANNEL_ID_ERROR, title, IMPORTANCE_DEFAULT)
}

private fun getRestoreChannel(): NotificationChannel {
val title = context.getString(R.string.notification_restore_error_channel_title)
return NotificationChannel(CHANNEL_ID_RESTORE, title, IMPORTANCE_LOW)
}

private fun getRestoreErrorChannel(): NotificationChannel {
val title = context.getString(R.string.notification_restore_error_channel_title)
return NotificationChannel(CHANNEL_ID_RESTORE_ERROR, title, IMPORTANCE_HIGH)
Expand Down Expand Up @@ -235,6 +243,18 @@ internal class BackupNotificationManager(private val context: Context) {
nm.notify(NOTIFICATION_ID_SPACE_ERROR, notification)
}

fun getRestoreNotification() = Notification.Builder(context, CHANNEL_ID_RESTORE).apply {
setSmallIcon(R.drawable.ic_cloud_restore)
setContentTitle(context.getString(R.string.notification_restore_title))
setOngoing(true)
setShowWhen(false)
setWhen(System.currentTimeMillis())
}.build()

fun cancelRestoreNotification() {
nm.cancel(NOTIFICATION_ID_RESTORE)
}

@SuppressLint("RestrictedApi")
fun onRemovableStorageNotAvailableForRestore(packageName: String, storageName: String) {
val appName = try {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@
<string name="notification_space_error_title">Insufficient backup space</string>
<string name="notification_space_error_text">Your backup location is running out of space. Free up space, so backups can run.</string>

<string name="notification_restore_channel_title">Restore notification</string>
<string name="notification_restore_title">Restore running</string>

<string name="notification_restore_error_channel_title">Auto restore flash drive error</string>
<string name="notification_restore_error_title">Could not restore data for %1$s</string>
<string name="notification_restore_error_text">Plug in your %1$s before installing the app to restore its data from backup.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.stevesoltys.seedvault.restore.install

import android.app.backup.IBackupManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
Expand Down Expand Up @@ -127,6 +128,13 @@ internal class ApkBackupRestoreTest : TransportTest() {
writeBytes(splitBytes)
}.absolutePath)

// related to starting/stopping service
every { strictContext.packageName } returns "org.foo.bar"
every {
strictContext.startService(any())
} returns ComponentName(strictContext, "org.foo.bar.Class")
every { strictContext.stopService(any()) } returns true

every { settingsManager.isBackupEnabled(any()) } returns true
every { settingsManager.backupApks() } returns true
every { sigInfo.hasMultipleSigners() } returns false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.stevesoltys.seedvault.restore.install

import android.app.backup.IBackupManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
Expand Down Expand Up @@ -108,6 +109,13 @@ internal class ApkRestoreTest : TransportTest() {
packageInfo.signingInfo = mockk(relaxed = true)

every { storagePluginManager.appPlugin } returns storagePlugin

// related to starting/stopping service
every { strictContext.packageName } returns "org.foo.bar"
every {
strictContext.startService(any())
} returns ComponentName(strictContext, "org.foo.bar.Class")
every { strictContext.stopService(any()) } returns true
}

@Test
Expand Down

0 comments on commit 710354d

Please sign in to comment.