Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start a foreground service during restore #722

Merged
merged 2 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -107,6 +108,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
Loading