diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index bfd33116..37eee54e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -1,6 +1,12 @@ package com.stevesoltys.seedvault.transport.backup +import android.app.backup.BackupTransport.FLAG_DATA_NOT_CHANGED +import android.app.backup.BackupTransport.FLAG_INCREMENTAL +import android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL +import android.app.backup.BackupTransport.FLAG_USER_INITIATED import android.app.backup.BackupTransport.TRANSPORT_ERROR +import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED +import android.app.backup.BackupTransport.TRANSPORT_NOT_INITIALIZED import android.app.backup.BackupTransport.TRANSPORT_OK import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED @@ -163,6 +169,42 @@ internal class BackupCoordinator( Log.i(TAG, "Request incremental backup time. Returned $this") } + /** + * Send one application's key/value data update to the backup destination. + * The transport may send the data immediately, or may buffer it. + * If this method returns [TRANSPORT_OK], [finishBackup] will then be called + * to ensure the data is sent and recorded successfully. + * + * If the backup data is a diff against the previous backup + * then the flag [FLAG_INCREMENTAL] will be set. + * Otherwise, if the data is a complete backup set, + * then [FLAG_NON_INCREMENTAL] will be set. + * Before P neither flag will be set regardless of whether the backup is incremental or not. + * + * If [FLAG_INCREMENTAL] is set and the transport does not have data + * for this package in its storage backend then it cannot apply the incremental diff. + * Thus it should return [TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED] + * to indicate that backup manager should delete its state + * and retry the package as a non-incremental backup. + * + * Note that if an app (e.g. com.whatsapp) has no data to backup, + * this method will NOT even be called for the app. + * + * @param packageInfo The identity of the application whose data is being backed up. + * This specifically includes the signature list for the package. + * @param data Descriptor of file with data that resulted from invoking the application's + * BackupService.doBackup() method. This may be a pipe rather than a file on + * persistent media, so it may not be seekable. + * @param flags a combination of [FLAG_USER_INITIATED], [FLAG_NON_INCREMENTAL], + * [FLAG_INCREMENTAL], [FLAG_DATA_NOT_CHANGED], or 0. + * @return one of [TRANSPORT_OK] (OK so far), + * [TRANSPORT_PACKAGE_REJECTED] (to suppress backup of this package, but let others proceed), + * [TRANSPORT_ERROR] (on network error or other failure), + * [TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED] (if the transport cannot accept + * an incremental backup for this package), or + * [TRANSPORT_NOT_INITIALIZED] (if the backend dataset has become lost due to + * inactivity purge or some other reason and needs re-initializing) + */ suspend fun performIncrementalBackup( packageInfo: PackageInfo, data: ParcelFileDescriptor, diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt index c8f42361..458af402 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt @@ -1,5 +1,6 @@ package com.stevesoltys.seedvault.transport.backup +import android.app.backup.BackupTransport.FLAG_DATA_NOT_CHANGED import android.app.backup.BackupTransport.FLAG_INCREMENTAL import android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL import android.app.backup.BackupTransport.TRANSPORT_ERROR @@ -8,12 +9,12 @@ import android.app.backup.BackupTransport.TRANSPORT_OK import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import android.util.Log -import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.crypto.Crypto import com.stevesoltys.seedvault.encodeBase64 import com.stevesoltys.seedvault.header.HeaderWriter import com.stevesoltys.seedvault.header.VersionHeader +import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import libcore.io.IoUtils.closeQuietly import java.io.IOException @@ -45,11 +46,15 @@ internal class KVBackup( data: ParcelFileDescriptor, flags: Int ): Int { + val dataNotChanged = flags and FLAG_DATA_NOT_CHANGED != 0 val isIncremental = flags and FLAG_INCREMENTAL != 0 val isNonIncremental = flags and FLAG_NON_INCREMENTAL != 0 val packageName = packageInfo.packageName when { + dataNotChanged -> { + Log.i(TAG, "No K/V backup data has changed for $packageName") + } isIncremental -> { Log.i(TAG, "Performing incremental K/V backup for $packageName") } @@ -65,6 +70,9 @@ internal class KVBackup( if (this.state != null) throw AssertionError() this.state = KVBackupState(packageInfo) + // no need for backup when no data has changed + if (dataNotChanged) return TRANSPORT_OK + // check if we have existing data for the given package val hasDataForPackage = try { plugin.hasDataForPackage(packageInfo) diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt index 5bf820e1..367484f2 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt @@ -1,17 +1,18 @@ package com.stevesoltys.seedvault.transport.backup import android.app.backup.BackupDataInput +import android.app.backup.BackupTransport.FLAG_DATA_NOT_CHANGED import android.app.backup.BackupTransport.FLAG_INCREMENTAL import android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL import android.app.backup.BackupTransport.TRANSPORT_ERROR import android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED import android.app.backup.BackupTransport.TRANSPORT_OK import android.content.pm.PackageInfo -import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.Utf8 import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE import com.stevesoltys.seedvault.header.VersionHeader +import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.Runs import io.mockk.coEvery import io.mockk.every @@ -150,6 +151,17 @@ internal class KVBackupTest : BackupTest() { assertFalse(backup.hasState()) } + @Test + fun `package with no new data comes back ok right away`() = runBlocking { + assertEquals(TRANSPORT_OK, backup.performBackup(packageInfo, data, FLAG_DATA_NOT_CHANGED)) + assertTrue(backup.hasState()) + + every { plugin.packageFinished(packageInfo) } just Runs + + assertEquals(TRANSPORT_OK, backup.finishBackup()) + assertFalse(backup.hasState()) + } + @Test fun `exception while reading next header`() = runBlocking { initPlugin(false)