Tolerate backup attempts when flash drive is not plugged in

Also remove hardcoding of PACKAGE_MANAGER_SENTINEL constant
This commit is contained in:
Torsten Grote 2019-09-20 10:04:37 -03:00
parent 08018fcc9b
commit 470b5a2ccf
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
8 changed files with 59 additions and 10 deletions

View file

@ -1,6 +1,7 @@
package com.stevesoltys.backup
import android.app.Application
import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL
import android.app.backup.IBackupManager
import android.content.Context.BACKUP_SERVICE
import android.os.Build
@ -33,4 +34,6 @@ class Backup : Application() {
}
const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
fun isDebugBuild() = Build.TYPE == "userdebug"

View file

@ -68,7 +68,7 @@ class NotificationBackupObserver(context: Context, private val userInitiated: Bo
}
fun getAppName(pm: PackageManager, packageId: String): CharSequence {
if (packageId == "@pm@") return packageId
if (packageId == MAGIC_PACKAGE_MANAGER) return packageId
val appInfo = pm.getApplicationInfo(packageId, 0)
return pm.getApplicationLabel(appInfo)
}

View file

@ -8,6 +8,7 @@ import android.os.UserHandle
import android.util.Log
import com.google.android.collect.Sets.newArraySet
import com.stevesoltys.backup.Backup
import com.stevesoltys.backup.MAGIC_PACKAGE_MANAGER
import java.util.*
private val TAG = PackageService::class.java.simpleName
@ -49,7 +50,7 @@ class PackageService {
// add magic @pm@ package (PACKAGE_MANAGER_SENTINEL) which holds package manager data
val packageArray = eligibleApps.toMutableList()
packageArray.add("@pm@")
packageArray.add(MAGIC_PACKAGE_MANAGER)
return packageArray.toTypedArray()
}

View file

@ -2,6 +2,8 @@ package com.stevesoltys.backup.settings
import android.content.ContentResolver
import android.provider.Settings
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.DAYS
private val SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS
private const val DELIMITER = ','
@ -11,13 +13,19 @@ private const val FULL_BACKUP_INTERVAL_MILLISECONDS = "full_backup_interval_mill
object BackupManagerSettings {
/**
* This clears the backup settings, so that default values will be used.
*/
fun enableAutomaticBackups(resolver: ContentResolver) {
// setting this to null will cause the BackupManagerConstants to use default values
setSettingValue(resolver, null)
}
/**
* This sets the backup intervals to a longer than default value. Currently 30 days
*/
fun disableAutomaticBackups(resolver: ContentResolver) {
val value = Long.MAX_VALUE
val value = DAYS.toMillis(30)
val kv = "$KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS=$value"
val full = "$FULL_BACKUP_INTERVAL_MILLISECONDS=$value"
setSettingValue(resolver, "$kv$DELIMITER$full")

View file

@ -1,16 +1,16 @@
package com.stevesoltys.backup.transport.backup
import android.app.backup.BackupTransport.TRANSPORT_ERROR
import android.app.backup.BackupTransport.TRANSPORT_OK
import android.app.backup.BackupTransport.*
import android.content.Context
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.backup.BackupNotificationManager
import com.stevesoltys.backup.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.backup.metadata.MetadataWriter
import com.stevesoltys.backup.settings.SettingsManager
import java.io.IOException
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.DAYS
private val TAG = BackupCoordinator::class.java.simpleName
@ -63,7 +63,8 @@ class BackupCoordinator(
TRANSPORT_OK
} catch (e: IOException) {
Log.e(TAG, "Error initializing device", e)
nm.onBackupError()
// Show error notification if we were ready for backups
if (getBackupBackoff() == 0L) nm.onBackupError()
TRANSPORT_ERROR
}
}
@ -101,6 +102,12 @@ class BackupCoordinator(
}
fun performIncrementalBackup(packageInfo: PackageInfo, data: ParcelFileDescriptor, flags: Int): Int {
// backups of package manager metadata do not respect backoff
// we need to reject them manually when now is not a good time for a backup
if (packageInfo.packageName == MAGIC_PACKAGE_MANAGER && getBackupBackoff() != 0L) {
return TRANSPORT_PACKAGE_REJECTED
}
val result = kv.performBackup(packageInfo, data, flags)
if (result == TRANSPORT_OK) settingsManager.saveNewBackupTime()
return result
@ -194,7 +201,7 @@ class BackupCoordinator(
private fun getBackupBackoff(): Long {
val noBackoff = 0L
val defaultBackoff = MINUTES.toMillis(10)
val defaultBackoff = DAYS.toMillis(30)
// back off if there's no storage set
val storage = settingsManager.getStorage() ?: return defaultBackoff

View file

@ -42,7 +42,7 @@ class DocumentsProviderBackupPlugin(
}
override val providerPackageName: String? by lazy {
val authority = storage.rootBackupDir?.uri?.authority ?: return@lazy null
val authority = storage.getAuthority() ?: return@lazy null
val providerInfo = packageManager.resolveContentProvider(authority, 0) ?: return@lazy null
providerInfo.packageName
}

View file

@ -72,6 +72,8 @@ class DocumentsStorage(private val context: Context, private val settingsManager
}
}
fun getAuthority(): String? = storage?.uri?.authority
fun getSetDir(token: Long = currentToken): DocumentFile? {
if (token == currentToken) return currentSetDir
return rootBackupDir?.findFile(token.toString())

View file

@ -2,8 +2,12 @@ package com.stevesoltys.backup.transport.backup
import android.app.backup.BackupTransport.TRANSPORT_ERROR
import android.app.backup.BackupTransport.TRANSPORT_OK
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.backup.BackupNotificationManager
import com.stevesoltys.backup.getRandomString
import com.stevesoltys.backup.metadata.MetadataWriter
import com.stevesoltys.backup.settings.Storage
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
@ -40,8 +44,11 @@ internal class BackupCoordinatorTest: BackupTest() {
}
@Test
fun `device initialization fails`() {
fun `error notification when device initialization fails`() {
val storage = Storage(Uri.EMPTY, getRandomString(), false)
every { plugin.initializeDevice() } throws IOException()
every { settingsManager.getStorage() } returns storage
every { notificationManager.onBackupError() } just Runs
assertEquals(TRANSPORT_ERROR, backup.initializeDevice())
@ -54,6 +61,27 @@ internal class BackupCoordinatorTest: BackupTest() {
}
}
@Test
fun `no error notification when device initialization fails on unplugged USB storage`() {
val storage = mockk<Storage>()
val documentFile = mockk<DocumentFile>()
every { plugin.initializeDevice() } throws IOException()
every { settingsManager.getStorage() } returns storage
every { storage.isUsb } returns true
every { storage.getDocumentFile(context) } returns documentFile
every { documentFile.isDirectory } returns false
assertEquals(TRANSPORT_ERROR, backup.initializeDevice())
// finish will only be called when TRANSPORT_OK is returned, so it should throw
every { kv.hasState() } returns false
every { full.hasState() } returns false
assertThrows(IllegalStateException::class.java) {
backup.finishBackup()
}
}
@Test
fun `getBackupQuota() delegates to right plugin`() {
val isFullBackup = Random.nextBoolean()