Skip to content

Commit

Permalink
Add experimental support for forcing D2D transfer backups
Browse files Browse the repository at this point in the history
  • Loading branch information
stevesoltys committed Jan 12, 2024
1 parent 57adc57 commit daaf0e0
Show file tree
Hide file tree
Showing 34 changed files with 325 additions and 70 deletions.
4 changes: 3 additions & 1 deletion .github/scripts/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ echo "Setting Seedvault transport..."
sleep 10
adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport

D2D_BACKUP_TEST=$1

large_test_exit_code=0
./gradlew --stacktrace -Pinstrumented_test_size=large :app:connectedAndroidTest || large_test_exit_code=$?
./gradlew --stacktrace -Pinstrumented_test_size=large -Pd2d_backup_test="$D2D_BACKUP_TEST" :app:connectedAndroidTest || large_test_exit_code=$?

adb pull /sdcard/seedvault_test_results

Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
name: Build
on: [push, pull_request]
on: [ push, pull_request ]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read
actions: read
checks: write

jobs:
build:
name: Build
Expand Down Expand Up @@ -40,3 +45,10 @@ jobs:
app/build/outputs/apk/debug/app-debug.apk
contactsbackup/build/outputs/apk/debug/contactsbackup-debug.apk
storage/demo/build/outputs/apk/debug/demo-debug.apk
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure()
with:
report_paths: '**/build/test-results/**/TEST-*.xml'

3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
matrix:
android_target: [ 33, 34 ]
emulator_type: [ default ]
d2d_backup_test: [ true, false ]
steps:
- name: Checkout Code
uses: actions/checkout@v3
Expand Down Expand Up @@ -52,7 +53,7 @@ jobs:
disable-animations: true
script: |
./app/development/scripts/provision_emulator.sh "test" "system-images;android-${{ matrix.android_target }};${{ matrix.emulator_type }};x86_64"
./.github/scripts/run_tests.sh
./.github/scripts/run_tests.sh ${{ matrix.d2d_backup_test }}
- name: Upload test results
if: always()
Expand Down
7 changes: 5 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ android {
targetSdk = libs.versions.targetSdk.get().toInt()
versionNameSuffix = "-${gitDescribe()}"
testInstrumentationRunner = "com.stevesoltys.seedvault.KoinInstrumentationTestRunner"
testInstrumentationRunnerArguments(mapOf("disableAnalytics" to "true"))
testInstrumentationRunnerArguments["disableAnalytics"] = "true"

if (project.hasProperty("instrumented_test_size")) {
val testSize = project.property("instrumented_test_size").toString()
println("Instrumented test size: $testSize")

testInstrumentationRunnerArguments(mapOf("size" to testSize))
testInstrumentationRunnerArguments["size"] = testSize
}

val d2dBackupTest = project.findProperty("d2d_backup_test")?.toString() ?: "true"
testInstrumentationRunnerArguments["d2d_backup_test"] = d2dBackupTest
}

signingConfigs {
Expand Down
2 changes: 1 addition & 1 deletion app/development/scripts/provision_emulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ echo "Downloading and extracting test backup to '/sdcard/seedvault_baseline'..."

if [ ! -f backup.tar.gz ]; then
echo "Downloading test backup..."
wget --quiet https://github.com/seedvault-app/seedvault-test-data/releases/download/1/backup.tar.gz
wget --quiet https://github.com/seedvault-app/seedvault-test-data/releases/download/3/backup.tar.gz
fi

$ADB root
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.stevesoltys.seedvault

import com.stevesoltys.seedvault.restore.RestoreViewModel
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.FullBackup
import com.stevesoltys.seedvault.transport.backup.InputFactory
import com.stevesoltys.seedvault.transport.backup.KVBackup
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.restore.FullRestore
import com.stevesoltys.seedvault.transport.restore.KVRestore
import com.stevesoltys.seedvault.transport.restore.OutputFactory
Expand All @@ -25,6 +27,9 @@ class KoinInstrumentationTestApp : App() {
val testModule = module {
val context = this@KoinInstrumentationTestApp

single { spyk(PackageService(context, get(), get(), get())) }
single { spyk(SettingsManager(context)) }

single { spyk(BackupNotificationManager(context)) }
single { spyk(FullBackup(get(), get(), get(), get())) }
single { spyk(KVBackup(get(), get(), get(), get(), get())) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.stevesoltys.seedvault.e2e

import android.app.backup.IBackupManager
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import com.stevesoltys.seedvault.e2e.io.BackupDataInputIntercept
Expand All @@ -26,8 +25,6 @@ internal interface LargeBackupTestBase : LargeTestBase {
private const val BACKUP_TIMEOUT = 360 * 1000L
}

val backupManager: IBackupManager get() = get()

val spyBackupNotificationManager: BackupNotificationManager get() = get()

val spyFullBackup: FullBackup get() = get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ internal interface LargeRestoreTestBase : LargeTestBase {
coEvery {
spyFullRestore.initializeState(any(), any(), any(), any())
} answers {
packageName?.let {
restoreResult.full[it] = dataIntercept.toByteArray().sha256()
}

packageName = arg<PackageInfo>(3).packageName
dataIntercept = ByteArrayOutputStream()

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

import android.app.UiAutomation
import android.app.backup.IBackupManager
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager.PERMISSION_GRANTED
Expand Down Expand Up @@ -72,13 +73,16 @@ internal interface LargeTestBase : KoinComponent {

val spyMetadataManager: MetadataManager get() = get()

val backupManager: IBackupManager get() = get()

val spyRestoreViewModel: RestoreViewModel
get() = currentRestoreViewModel ?: error("currentRestoreViewModel is null")

val spyRestoreStorageViewModel: RestoreStorageViewModel
get() = currentRestoreStorageViewModel ?: error("currentRestoreStorageViewModel is null")

fun resetApplicationState() {
backupManager.setAutoRestore(false)
settingsManager.setNewToken(null)
documentsStorage.reset(null)

Expand All @@ -95,6 +99,7 @@ internal interface LargeTestBase : KoinComponent {
}

clearDocumentPickerAppData()
device.executeShellCommand("rm -R $externalStorageDir/.SeedVaultAndroidBackup")
}

fun waitUntilIdle() {
Expand Down Expand Up @@ -157,6 +162,7 @@ internal interface LargeTestBase : KoinComponent {

fun clearTestBackups() {
File(testStoragePath).deleteRecursively()
File(testVideoPath).deleteRecursively()
}

fun changeBackupLocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stevesoltys.seedvault.e2e

import android.content.pm.PackageManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
Expand Down Expand Up @@ -40,6 +41,17 @@ internal abstract class SeedvaultLargeTest :

startRecordingTest(keepRecordingScreen, name.methodName)
restoreBaselineBackup()

val arguments = InstrumentationRegistry.getArguments()

if (arguments.getString("d2d_backup_test") == "true") {
println("Enabling D2D backups for test")
settingsManager.setD2dBackupsEnabled(true)

} else {
println("Disabling D2D backups for test")
settingsManager.setD2dBackupsEnabled(false)
}
}

@After
Expand All @@ -63,10 +75,14 @@ internal abstract class SeedvaultLargeTest :
val extDir = externalStorageDir

device.executeShellCommand("rm -R $extDir/.SeedVaultAndroidBackup")
device.executeShellCommand("cp -R $extDir/$BASELINE_BACKUP_FOLDER/" +
".SeedVaultAndroidBackup $extDir")
device.executeShellCommand("cp -R $extDir/$BASELINE_BACKUP_FOLDER/" +
"recovery-code.txt $extDir")
device.executeShellCommand(
"cp -R $extDir/$BASELINE_BACKUP_FOLDER/" +
".SeedVaultAndroidBackup $extDir"
)
device.executeShellCommand(
"cp -R $extDir/$BASELINE_BACKUP_FOLDER/" +
"recovery-code.txt $extDir"
)
}

if (backupFile.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stevesoltys.seedvault.e2e

import android.content.pm.PackageInfo
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.restore.AppRestoreResult

/**
* Contains maps of (package name -> SHA-256 hashes) of application data.
Expand All @@ -12,8 +13,9 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata
* For full backups, the mapping is: Map<PackageName, SHA-256>
* For K/V backups, the mapping is: Map<PackageName, Map<Key, SHA-256>>
*/
data class SeedvaultLargeTestResult(
internal data class SeedvaultLargeTestResult(
val backupResults: Map<String, PackageMetadata?> = emptyMap(),
val restoreResults: Map<String, AppRestoreResult?> = emptyMap(),
val full: MutableMap<String, String>,
val kv: MutableMap<String, MutableMap<String, String>>,
val userApps: List<PackageInfo>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package com.stevesoltys.seedvault.transport.backup

import android.content.pm.PackageInfo
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.settings.AppStatus
import com.stevesoltys.seedvault.settings.SettingsManager
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.core.component.KoinComponent
Expand All @@ -14,10 +22,41 @@ class PackageServiceTest : KoinComponent {

private val packageService: PackageService by inject()

private val settingsManager: SettingsManager by inject()

private val storagePlugin: StoragePlugin by inject()

@Test
fun testNotAllowedPackages() {
val packages = packageService.notBackedUpPackages
Log.e("TEST", "Packages: $packages")
}

@Test
fun `shouldIncludeAppInBackup exempts plugin provider and blacklisted apps`() {
val packageInfo = PackageInfo().apply {
packageName = "com.example"
}

val disabledAppStatus = mockk<AppStatus>().apply {
every { packageName } returns packageInfo.packageName
every { enabled } returns false
}
settingsManager.onAppBackupStatusChanged(disabledAppStatus)

// Should not backup blacklisted apps
assertFalse(packageService.shouldIncludeAppInBackup(packageInfo.packageName))

val enabledAppStatus = mockk<AppStatus>().apply {
every { packageName } returns packageInfo.packageName
every { enabled } returns true
}
settingsManager.onAppBackupStatusChanged(enabledAppStatus)

// Should backup non-blacklisted apps
assertTrue(packageService.shouldIncludeAppInBackup(packageInfo.packageName))

// Should not backup storage provider
assertFalse(packageService.shouldIncludeAppInBackup(storagePlugin.providerPackageName!!))
}
}
4 changes: 0 additions & 4 deletions app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.ServiceManager.getService
import android.os.StrictMode
import android.os.SystemProperties
import android.os.UserManager
import com.stevesoltys.seedvault.crypto.cryptoModule
import com.stevesoltys.seedvault.header.headerModule
Expand Down Expand Up @@ -60,7 +59,6 @@ open class App : Application() {

override fun onCreate() {
super.onCreate()
SystemProperties.set(BACKUP_D2D_PROPERTY, "true")
startKoin()
if (isDebugBuild()) {
StrictMode.setThreadPolicy(
Expand Down Expand Up @@ -123,8 +121,6 @@ const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
const val GLOBAL_METADATA_KEY = "@meta@"

const val BACKUP_D2D_PROPERTY = "persist.backup.fake-d2d"

// TODO this doesn't work for LineageOS as they do public debug builds
fun isDebugBuild() = Build.TYPE == "userdebug"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ data class BackupMetadata(
internal val androidVersion: Int = Build.VERSION.SDK_INT,
internal val androidIncremental: String = Build.VERSION.INCREMENTAL,
internal val deviceName: String = "${Build.MANUFACTURER} ${Build.MODEL}",
internal var d2dBackup: Boolean = false,
internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap(),
)

Expand All @@ -29,6 +30,7 @@ internal const val JSON_METADATA_TIME = "time"
internal const val JSON_METADATA_SDK_INT = "sdk_int"
internal const val JSON_METADATA_INCREMENTAL = "incremental"
internal const val JSON_METADATA_NAME = "name"
internal const val JSON_METADATA_D2D_BACKUP = "d2d_backup"

enum class PackageState {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import java.io.FileNotFoundException
import java.io.IOException
Expand All @@ -35,6 +36,7 @@ internal class MetadataManager(
private val crypto: Crypto,
private val metadataWriter: MetadataWriter,
private val metadataReader: MetadataReader,
private val settingsManager: SettingsManager
) {

private val uninitializedMetadata = BackupMetadata(token = 0L, salt = "")
Expand Down Expand Up @@ -135,6 +137,8 @@ internal class MetadataManager(
modifyMetadata(metadataOutputStream) {
val now = clock.time()
metadata.time = now
metadata.d2dBackup = settingsManager.d2dBackupsEnabled()

if (metadata.packageMetadataMap.containsKey(packageName)) {
metadata.packageMetadataMap[packageName]!!.time = now
metadata.packageMetadataMap[packageName]!!.state = APK_AND_DATA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module

val metadataModule = module {
single { MetadataManager(androidContext(), get(), get(), get(), get()) }
single { MetadataManager(androidContext(), get(), get(), get(), get(), get()) }
single<MetadataWriter> { MetadataWriterImpl(get()) }
single<MetadataReader> { MetadataReaderImpl(get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
androidVersion = meta.getInt(JSON_METADATA_SDK_INT),
androidIncremental = meta.getString(JSON_METADATA_INCREMENTAL),
deviceName = meta.getString(JSON_METADATA_NAME),
packageMetadataMap = packageMetadataMap
d2dBackup = meta.optBoolean(JSON_METADATA_D2D_BACKUP, false),
packageMetadataMap = packageMetadataMap,
)
} catch (e: JSONException) {
throw SecurityException(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter {
put(JSON_METADATA_SDK_INT, metadata.androidVersion)
put(JSON_METADATA_INCREMENTAL, metadata.androidIncremental)
put(JSON_METADATA_NAME, metadata.deviceName)
put(JSON_METADATA_D2D_BACKUP, metadata.d2dBackup)
})
}
for ((packageName, packageMetadata) in metadata.packageMetadataMap) {
Expand Down
Loading

0 comments on commit daaf0e0

Please sign in to comment.