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 3, 2024
1 parent 57adc57 commit 8ac75bb
Show file tree
Hide file tree
Showing 30 changed files with 244 additions and 66 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
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,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
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ data class RestorableBackup(val backupMetadata: BackupMetadata) {
val deviceName: String
get() = backupMetadata.deviceName

val d2dBackup: Boolean
get() = backupMetadata.d2dBackup

val packageMetadataMap: PackageMetadataMap
get() = backupMetadata.packageMetadataMap

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,16 @@ internal class AppListRetriever(

@WorkerThread
fun getAppList(): List<AppListItem> {
return listOf(AppSectionTitle(R.string.backup_section_system)) + getSpecialApps() +
listOf(AppSectionTitle(R.string.backup_section_user)) + getUserApps() +
listOf(AppSectionTitle(R.string.backup_section_not_allowed)) + getNotAllowedApps()

val appListSections = linkedMapOf(
AppSectionTitle(R.string.backup_section_system) to getSpecialApps(),
AppSectionTitle(R.string.backup_section_user) to getUserApps(),
AppSectionTitle(R.string.backup_section_not_allowed) to getNotAllowedApps()
).filter { it.value.isNotEmpty() }

return appListSections.flatMap { (sectionTitle, appList) ->
listOf(sectionTitle) + appList
}
}

private fun getSpecialApps(): List<AppListItem> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.transport.backup.PackageService
Expand All @@ -14,6 +15,7 @@ class ExpertSettingsFragment : PreferenceFragmentCompat() {

private val viewModel: SettingsViewModel by sharedViewModel()
private val packageService: PackageService by inject()

// TODO set mimeType when upgrading androidx lib
private val createFileLauncher = registerForActivityResult(CreateDocument()) { uri ->
viewModel.onLogcatUriReceived(uri)
Expand All @@ -23,13 +25,21 @@ class ExpertSettingsFragment : PreferenceFragmentCompat() {
permitDiskReads {
setPreferencesFromResource(R.xml.settings_expert, rootKey)
}

findPreference<Preference>("logcat")?.setOnPreferenceClickListener {
val versionName = packageService.getVersionName(requireContext().packageName) ?: "ver"
val timestamp = System.currentTimeMillis()
val name = "seedvault-$versionName-$timestamp.txt"
createFileLauncher.launch(name)
true
}

val d2dPreference = findPreference<SwitchPreferenceCompat>(PREF_KEY_D2D_BACKUPS)

d2dPreference?.setOnPreferenceChangeListener { _, newValue ->
d2dPreference.isChecked = newValue as Boolean
true
}
}

override fun onStart() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
true
}

autoRestore = findPreference("auto_restore")!!
autoRestore = findPreference(PREF_KEY_AUTO_RESTORE)!!
autoRestore.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
val enabled = newValue as Boolean
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import java.util.concurrent.ConcurrentSkipListSet

internal const val PREF_KEY_TOKEN = "token"
internal const val PREF_KEY_BACKUP_APK = "backup_apk"
internal const val PREF_KEY_AUTO_RESTORE = "auto_restore"

private const val PREF_KEY_STORAGE_URI = "storageUri"
private const val PREF_KEY_STORAGE_NAME = "storageName"
Expand All @@ -31,6 +32,7 @@ private const val PREF_KEY_BACKUP_APP_BLACKLIST = "backupAppBlacklist"

private const val PREF_KEY_BACKUP_STORAGE = "backup_storage"
private const val PREF_KEY_UNLIMITED_QUOTA = "unlimited_quota"
internal const val PREF_KEY_D2D_BACKUPS = "d2d_backups"

class SettingsManager(private val context: Context) {

Expand Down Expand Up @@ -151,6 +153,14 @@ class SettingsManager(private val context: Context) {
}

fun isQuotaUnlimited() = prefs.getBoolean(PREF_KEY_UNLIMITED_QUOTA, false)

fun d2dBackupsEnabled() = prefs.getBoolean(PREF_KEY_D2D_BACKUPS, false)

fun setD2dBackupsEnabled(enabled: Boolean) {
prefs.edit()
.putBoolean(PREF_KEY_D2D_BACKUPS, enabled)
.apply()
}
}

data class Storage(
Expand Down
Loading

0 comments on commit 8ac75bb

Please sign in to comment.