From 4aeb05813dc7d2028cd6a719ba42ddb35447d53f Mon Sep 17 00:00:00 2001 From: Steve Soltys Date: Wed, 25 Sep 2024 01:29:47 +0000 Subject: [PATCH 01/16] Run tests in Cirrus CI again --- .cirrus.yml | 72 +++++++++++++++---- .../seedvault/e2e/LargeTestBase.kt | 11 +-- .../seedvault/e2e/SeedvaultLargeTest.kt | 3 +- .../seedvault/e2e/screen/impl/BackupScreen.kt | 2 +- .../e2e/screen/impl/RecoveryCodeScreen.kt | 2 + 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index d150203a..8ae8a268 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,13 +1,61 @@ -task: - name: Build with AOSP - only_if: $CIRRUS_PR_LABELS =~ ".*aosp-build.*" - timeout_in: 70m - container: - image: ubuntu:23.04 - cpu: 8 - memory: 32G - build_script: - - ./.github/scripts/build_aosp.sh aosp_arm64 ap1a userdebug android-14.0.0_r29 +container: + image: ghcr.io/cirruslabs/android-sdk:34 + kvm: true + cpu: 8 + memory: 16G + +instrumentation_tests_task: + name: "Cirrus CI Instrumentation Tests" + start_avd_background_script: + sdkmanager --install "system-images;android-34;default;x86_64" "emulator"; + echo no | avdmanager create avd -n seedvault -k "system-images;android-34;default;x86_64"; + $ANDROID_HOME/emulator/emulator + -avd seedvault + -no-audio + -no-boot-anim + -gpu swiftshader_indirect + -no-snapshot + -no-window + -writable-system; + provision_avd_background_script: + wget https://github.com/seedvault-app/seedvault-test-data/releases/download/3/backup.tar.gz; + + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; + adb root; + sleep 5; + adb remount; + adb reboot; + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; + adb root; + sleep 5; + adb remount; + sleep 5; + assemble_script: + ./gradlew :app:assembleRelease :app:assembleAndroidTest + install_app_script: + timeout 180s bash -c 'while [[ -z $(adb shell mount | grep "/system " | grep "(rw,") ]]; do sleep 1; done;'; + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; + + adb shell mkdir -p /sdcard/seedvault_baseline; + adb push backup.tar.gz /sdcard/seedvault_baseline/backup.tar.gz; + adb shell tar xzf /sdcard/seedvault_baseline/backup.tar.gz --directory=/sdcard/seedvault_baseline; + + adb shell mkdir -p /system/priv-app/Seedvault; + adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk; + adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml; + adb push allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml; + adb shell bmgr enable true; + adb reboot; + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; + adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport; + adb reboot; + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; + run_large_tests_script: ./gradlew -Pinstrumented_test_size=large :app:connectedAndroidTest + run_medium_tests_script: ./gradlew -Pinstrumented_test_size=medium :app:connectedAndroidTest always: - seedvault_artifacts: - path: Seedvault.apk + pull_screenshots_script: + adb pull /sdcard/seedvault_test_results + screenshots_artifacts: + path: "seedvault_test_results/**/*.mp4" + logcat_artifacts: + path: "seedvault_test_results/**/*.log" diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt index 467b5a28..822d2e9e 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt @@ -49,14 +49,14 @@ internal interface LargeTestBase : KoinComponent { companion object { private const val TEST_STORAGE_FOLDER = "seedvault_test" - private const val TEST_VIDEO_FOLDER = "seedvault_test_results" + private const val TEST_RESULT_FOLDER = "seedvault_test_results" } val externalStorageDir: String get() = Environment.getExternalStorageDirectory().absolutePath val testStoragePath get() = "$externalStorageDir/$TEST_STORAGE_FOLDER" - val testVideoPath get() = "$externalStorageDir/$TEST_VIDEO_FOLDER" + val testResultPath get() = "$externalStorageDir/$TEST_RESULT_FOLDER" val targetContext: Context get() = InstrumentationRegistry.getInstrumentation().targetContext @@ -123,7 +123,7 @@ internal interface LargeTestBase : KoinComponent { keepRecordingScreen: AtomicBoolean, testName: String, ) { - val folder = testVideoPath + val folder = testResultPath runCommand("mkdir -p $folder") val fileName = testResultFilename(testName) @@ -149,7 +149,7 @@ internal interface LargeTestBase : KoinComponent { // write logcat to file val fileName = testResultFilename(testName) - runCommand("logcat -d -f $testVideoPath/$fileName.log") + runCommand("logcat -d -f $testResultPath/$fileName.log") } fun uninstallPackages(packages: Collection) { @@ -162,7 +162,7 @@ internal interface LargeTestBase : KoinComponent { fun clearTestBackups() { File(testStoragePath).deleteRecursively() - File(testVideoPath).deleteRecursively() + File(testResultPath).deleteRecursively() } fun changeBackupLocation( @@ -225,6 +225,7 @@ internal interface LargeTestBase : KoinComponent { fun confirmCode() { RecoveryCodeScreen { + startNewBackupButton.click() confirmCodeButton.click() verifyCodeButton.scrollTo().click() diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt index 0c83cfa1..8e5c79f7 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt @@ -45,9 +45,8 @@ internal abstract class SeedvaultLargeTest : clearTestBackups() runCommand("bmgr enable true") - sleep(60_000) runCommand("bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport") - sleep(60_000) + sleep(5000) startRecordingTest(keepRecordingScreen, name.methodName) restoreBaselineBackup() diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/BackupScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/BackupScreen.kt index 17cdfc64..65e9683b 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/BackupScreen.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/BackupScreen.kt @@ -24,7 +24,7 @@ object BackupScreen : UiDeviceScreen() { val internalStorageButton = findObject { textContains(Build.MODEL) } - val useAnywayButton = findObject { text("USE ANYWAY") } + val useAnywayButton = findObject { text("Use anyway") } val initializingText: BySelector = By.textContains("Initializing backup location") } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt index 244ca24e..391283c1 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt @@ -9,6 +9,8 @@ import com.stevesoltys.seedvault.e2e.screen.UiDeviceScreen object RecoveryCodeScreen : UiDeviceScreen() { + val startNewBackupButton = findObject { text("Start new") } + val confirmCodeButton = findObject { text("Confirm code") } val verifyCodeButton = findObject { text("Verify") } From 42d7747641df0f43478c992728370256f0d29098 Mon Sep 17 00:00:00 2001 From: Steve Soltys Date: Wed, 25 Sep 2024 01:57:22 +0000 Subject: [PATCH 02/16] Add logging for unknown exceptions --- .../com/stevesoltys/seedvault/transport/restore/KVRestore.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt index 5b645e11..97a323ee 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt @@ -161,6 +161,9 @@ internal class KVRestore( } catch (e: AEADBadTagException) { Log.e(TAG, "Decryption failed", e) TRANSPORT_ERROR + } catch (e: Exception) { + Log.e(TAG, "Unknown error", e) + TRANSPORT_ERROR } finally { dbManager.deleteDb(state.packageInfo.packageName, true) this.state = null From 315e0736f5970b0177087183496319cfa411140c Mon Sep 17 00:00:00 2001 From: Steve Soltys Date: Wed, 25 Sep 2024 02:38:12 +0000 Subject: [PATCH 03/16] Fix KVRestore spies to use new functions --- .../seedvault/e2e/LargeRestoreTestBase.kt | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt index a2384191..d104d3ed 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt @@ -14,6 +14,8 @@ import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen import com.stevesoltys.seedvault.transport.restore.FullRestore import com.stevesoltys.seedvault.transport.restore.KVRestore import com.stevesoltys.seedvault.transport.restore.OutputFactory +import io.mockk.Call +import io.mockk.MockKAnswerScope import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.every @@ -161,14 +163,26 @@ internal interface LargeRestoreTestBase : LargeTestBase { clearMocks(spyKVRestore) - coEvery { - spyKVRestore.initializeState(any(), any(), any(), any()) - } answers { - packageName = arg(3).packageName + fun initializeStateBlock( + packageInfoIndex: Int + ): MockKAnswerScope.(Call) -> Unit = { + packageName = arg(packageInfoIndex).packageName restoreResult.kv[packageName!!] = mutableMapOf() callOriginal() } + coEvery { + spyKVRestore.initializeState(any(), any(), any(), any()) + } answers initializeStateBlock(1) + + coEvery { + spyKVRestore.initializeStateV1(any(), any(), any(), any()) + } answers initializeStateBlock(2) + + coEvery { + spyKVRestore.initializeStateV0(any(), any()) + } answers initializeStateBlock(1) + every { spyOutputFactory.getBackupDataOutput(any()) } answers { @@ -186,19 +200,31 @@ internal interface LargeRestoreTestBase : LargeTestBase { clearMocks(spyFullRestore) - coEvery { - spyFullRestore.initializeState(any(), any(), any()) - } answers { + fun initializeStateBlock( + packageInfoIndex: Int + ): MockKAnswerScope.(Call) -> Unit = { packageName?.let { restoreResult.full[it] = dataIntercept.toByteArray().sha256() } - packageName = arg(3).packageName + packageName = arg(packageInfoIndex).packageName dataIntercept = ByteArrayOutputStream() callOriginal() } + coEvery { + spyFullRestore.initializeState(any(), any(), any()) + } answers initializeStateBlock(1) + + coEvery { + spyFullRestore.initializeStateV1(any(), any(), any()) + } answers initializeStateBlock(2) + + coEvery { + spyFullRestore.initializeStateV0(any(), any()) + } answers initializeStateBlock(1) + every { spyOutputFactory.getOutputStream(any()) } answers { From 8234cd81964f7116c7777bfb1077f19a37f95bae Mon Sep 17 00:00:00 2001 From: Steve Soltys Date: Wed, 25 Sep 2024 21:38:50 +0000 Subject: [PATCH 04/16] Add contactsbackup permissions to test emulator --- .cirrus.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.cirrus.yml b/.cirrus.yml index 8ae8a268..6115b778 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -44,6 +44,7 @@ instrumentation_tests_task: adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk; adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml; adb push allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml; + adb push contactsbackup/default-permissions_org.calyxos.backup.contacts.xml /system/etc/default-permissions/default-permissions_org.calyxos.backup.contacts.xml; adb shell bmgr enable true; adb reboot; adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; From f38bed9616aa1cb31797e5c5db44fd7306ed6875 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Sep 2024 14:53:59 -0300 Subject: [PATCH 05/16] Run instrumentation tests from other gradle modules and those not annotated with @MediumTest --- .cirrus.yml | 4 ++-- app/build.gradle.kts | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 6115b778..42537515 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -51,8 +51,8 @@ instrumentation_tests_task: adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport; adb reboot; adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; - run_large_tests_script: ./gradlew -Pinstrumented_test_size=large :app:connectedAndroidTest - run_medium_tests_script: ./gradlew -Pinstrumented_test_size=medium :app:connectedAndroidTest + run_large_tests_script: ./gradlew -Pandroid.testInstrumentationRunnerArguments.size=large :app:connectedAndroidTest + run_other_tests_script: ./gradlew -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest connectedAndroidTest always: pull_screenshots_script: adb pull /sdcard/seedvault_test_results diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6a577a10..c1bb3a49 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,13 +32,6 @@ android { versionNameSuffix = "-${gitDescribe()}" testInstrumentationRunner = "com.stevesoltys.seedvault.KoinInstrumentationTestRunner" testInstrumentationRunnerArguments["disableAnalytics"] = "true" - - if (project.hasProperty("instrumented_test_size")) { - val testSize = project.property("instrumented_test_size").toString() - println("Instrumented test size: $testSize") - - testInstrumentationRunnerArguments["size"] = testSize - } } signingConfigs { From 910c68aa525c9339287225f155f15e701069b85b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Sep 2024 14:40:50 -0300 Subject: [PATCH 06/16] Launch stopped apps before test to ensure they get backed up --- .../seedvault/e2e/impl/BackupRestoreTest.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt index 3017a83d..e69bd118 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt @@ -5,11 +5,15 @@ package com.stevesoltys.seedvault.e2e.impl +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.util.Log import androidx.test.filters.LargeTest import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.e2e.SeedvaultLargeTest import com.stevesoltys.seedvault.e2e.SeedvaultLargeTestResult import com.stevesoltys.seedvault.metadata.PackageState +import com.stevesoltys.seedvault.settings.SettingsActivity import org.junit.Test @LargeTest @@ -17,6 +21,7 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { @Test fun `backup and restore applications`() { + launchStoppedApps() launchBackupActivity() if (!keyManager.hasBackupKey()) { @@ -58,6 +63,26 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { } } + private fun launchStoppedApps() { + val packageManager = targetContext.packageManager + packageService.notBackedUpPackages.forEach { packageInfo -> + val i = packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.apply { + addFlags(FLAG_ACTIVITY_NEW_TASK) + } + Log.i("TEST", "Launching $i") + try { + targetContext.startActivity(i) + } catch (e: Exception) { + Log.e("TEST", "Could not launch activity for ${packageInfo.packageName}", e) + } + } + // at the end launch us again, so we are back to foreground + val i = Intent(targetContext, SettingsActivity::class.java).apply { + flags = FLAG_ACTIVITY_NEW_TASK + } + targetContext.startActivity(i) + } + private fun assertValidResults( backup: SeedvaultLargeTestResult, restore: SeedvaultLargeTestResult, From 9e5f22a8a04a5ac43bab046723ff89231c8b4e2c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 27 Sep 2024 13:42:38 -0300 Subject: [PATCH 07/16] Install contactsbackup before restart, so default permissions get applied Also add testkey for signed builds --- .cirrus.yml | 6 +++++- contactsbackup/build.gradle.kts | 32 +++++++++++++------------------- contactsbackup/testkey.jks | Bin 0 -> 2897 bytes 3 files changed, 18 insertions(+), 20 deletions(-) create mode 100644 contactsbackup/testkey.jks diff --git a/.cirrus.yml b/.cirrus.yml index 42537515..380e5ddc 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -31,7 +31,7 @@ instrumentation_tests_task: adb remount; sleep 5; assemble_script: - ./gradlew :app:assembleRelease :app:assembleAndroidTest + ./gradlew :app:assembleRelease :contacts:assembleRelease assembleAndroidTest install_app_script: timeout 180s bash -c 'while [[ -z $(adb shell mount | grep "/system " | grep "(rw,") ]]; do sleep 1; done;'; adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; @@ -44,7 +44,11 @@ instrumentation_tests_task: adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk; adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml; adb push allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml; + + adb shell mkdir -p /system/priv-app/ContactsBackup; + adb push contactsbackup/build/outputs/apk/release/contactsbackup-release.apk /system/priv-app/ContactsBackup/contactsbackup.apk; adb push contactsbackup/default-permissions_org.calyxos.backup.contacts.xml /system/etc/default-permissions/default-permissions_org.calyxos.backup.contacts.xml; + adb shell bmgr enable true; adb reboot; adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'; diff --git a/contactsbackup/build.gradle.kts b/contactsbackup/build.gradle.kts index adbb4712..a59e8c0f 100644 --- a/contactsbackup/build.gradle.kts +++ b/contactsbackup/build.gradle.kts @@ -2,8 +2,6 @@ * SPDX-FileCopyrightText: 2020 The Calyx Institute * SPDX-License-Identifier: Apache-2.0 */ -import java.io.FileInputStream -import java.util.Properties plugins { alias(libs.plugins.android.application) @@ -42,23 +40,18 @@ android { isReturnDefaultValues = true } - // optional signingConfigs - // On userdebug builds, you can use the testkey here to update the system app - val keystorePropertiesFile = project.file("keystore.properties") - if (keystorePropertiesFile.exists()) { - val keystoreProperties = Properties() - keystoreProperties.load(FileInputStream(keystorePropertiesFile)) - - signingConfigs { - create("release") { - keyAlias = keystoreProperties["keyAlias"] as String - keyPassword = keystoreProperties["keyPassword"] as String - storeFile = file(keystoreProperties["storeFile"] as String) - storePassword = keystoreProperties["storePassword"] as String - } + signingConfigs { + create("aosp") { + keyAlias = "android" + keyPassword = "android" + storePassword = "android" + storeFile = file("testkey.jks") } - buildTypes.getByName("release").signingConfig = signingConfigs.getByName("release") - buildTypes.getByName("debug").signingConfig = signingConfigs.getByName("release") + } + + buildTypes { + getByName("release").signingConfig = signingConfigs.getByName("aosp") + getByName("debug").signingConfig = signingConfigs.getByName("aosp") } } @@ -74,6 +67,7 @@ dependencies { androidTestImplementation(libs.kotlin.stdlib.jdk8) androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation( - "androidx.test.espresso:espresso-core:${libs.versions.espresso.get()}") + "androidx.test.espresso:espresso-core:${libs.versions.espresso.get()}" + ) androidTestImplementation("io.mockk:mockk-android:${libs.versions.mockk.get()}") } diff --git a/contactsbackup/testkey.jks b/contactsbackup/testkey.jks new file mode 100644 index 0000000000000000000000000000000000000000..6de45e18efb93aba0df301c159254b9994d3bcfe GIT binary patch literal 2897 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 literal 0 HcmV?d00001 From 623eb331da9c71d0d2c64f8a0ab040b44582bf42 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 27 Sep 2024 15:05:05 -0300 Subject: [PATCH 08/16] Ignore contactsbackup BackupRestoreTest if we don't have the correct permissions --- .../java/org/calyxos/backup/contacts/BackupRestoreTest.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contactsbackup/src/androidTest/java/org/calyxos/backup/contacts/BackupRestoreTest.kt b/contactsbackup/src/androidTest/java/org/calyxos/backup/contacts/BackupRestoreTest.kt index bd6f5237..c1e13529 100644 --- a/contactsbackup/src/androidTest/java/org/calyxos/backup/contacts/BackupRestoreTest.kt +++ b/contactsbackup/src/androidTest/java/org/calyxos/backup/contacts/BackupRestoreTest.kt @@ -5,9 +5,12 @@ package org.calyxos.backup.contacts +import android.Manifest.permission.READ_CONTACTS +import android.Manifest.permission.WRITE_CONTACTS import android.app.backup.BackupAgent import android.app.backup.BackupAgent.TYPE_FILE import android.app.backup.FullBackupDataOutput +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor.MODE_READ_ONLY import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +22,7 @@ import org.calyxos.backup.contacts.ContactsBackupAgent.BACKUP_FILE import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue +import org.junit.Assume.assumeTrue import org.junit.Test import org.junit.runner.RunWith import java.io.File @@ -42,6 +46,9 @@ class BackupRestoreTest { @Test fun testBackupAndRestore() { + val hasReadPermission = context.checkSelfPermission(READ_CONTACTS) == PERMISSION_GRANTED + val hasWritePermission = context.checkSelfPermission(WRITE_CONTACTS) == PERMISSION_GRANTED + assumeTrue(hasReadPermission && hasWritePermission) assertEquals( "Test will remove *all* contacts and thus requires empty address book", 0, From eb9dad798ced09b64865a27b38a27a073ebd4513 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 11 Oct 2024 09:23:51 -0300 Subject: [PATCH 09/16] onBackupSuccess now doesn't need a boolean anymore --- .../java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt index ab4e1fdf..95d05d83 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt @@ -192,9 +192,6 @@ internal interface LargeBackupTestBase : LargeTestBase { every { spyBackupNotificationManager.onBackupSuccess(any(), any(), any()) } answers { - val success = firstArg() - assert(success) { "Backup failed." } - callOriginal() completed.set(true) } From 3d9eca0d4049c423d1cc310b6977587147f3b471 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 10 Oct 2024 16:29:16 -0300 Subject: [PATCH 10/16] adapt CI to new UI for restore set items --- .../stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt index e17e6d1e..59686d63 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt @@ -9,7 +9,9 @@ import com.stevesoltys.seedvault.e2e.screen.UiDeviceScreen object RestoreScreen : UiDeviceScreen() { - val backupListItem = findObject { textContains("Last backup") } + val backupListItem = findObject { + textContains("Android SDK") // device name of test backups + } val appsSelectedButton = findObject { text("Restore backup") } From 29576784652b0249838a49d5c59ad3c3ba67a138 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 11 Oct 2024 10:36:27 -0300 Subject: [PATCH 11/16] Fix potential thread-safety issue when changing backends --- .../seedvault/backend/BackendManager.kt | 5 +++ .../seedvault/backend/saf/SafHandler.kt | 1 + .../seedvault/backend/webdav/WebDavHandler.kt | 1 + .../stevesoltys/seedvault/repo/BlobCache.kt | 2 + .../ui/storage/BackupStorageViewModel.kt | 39 +++++++++++++------ 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt index a4f2d01f..ebc6d57e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt @@ -25,7 +25,10 @@ class BackendManager( backendFactory: BackendFactory, ) { + @Volatile private var mBackend: Backend? + + @Volatile private var mBackendProperties: BackendProperties<*>? val backend: Backend @@ -81,6 +84,8 @@ class BackendManager( * IMPORTANT: Do no call this while current plugins are being used, * e.g. while backup/restore operation is still running. */ + @WorkerThread + @Synchronized fun changePlugins( backend: Backend, storageProperties: BackendProperties, diff --git a/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt index b296bc57..d5610a4c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt @@ -90,6 +90,7 @@ internal class SafHandler( return false } + @WorkerThread fun setPlugin(safProperties: SafProperties) { backendManager.changePlugins( backend = backendFactory.createSafBackend(safProperties), diff --git a/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt index 02c43942..18577430 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt @@ -87,6 +87,7 @@ internal class WebDavHandler( settingsManager.saveWebDavConfig(properties.config) } + @WorkerThread fun setPlugin(properties: WebDavProperties, backend: Backend) { backendManager.changePlugins( backend = backend, diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/BlobCache.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/BlobCache.kt index 66204289..16085512 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/BlobCache.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/BlobCache.kt @@ -7,6 +7,7 @@ package com.stevesoltys.seedvault.repo import android.content.Context import android.content.Context.MODE_APPEND +import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.MemoryLogger import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.proto.Snapshot.Blob @@ -90,6 +91,7 @@ class BlobCache( * * changing to a different backup to prevent usage of blobs that don't exist there * * uploading a new snapshot to prevent the persistent cache from growing indefinitely */ + @WorkerThread fun clearLocalCache() { log.info { "Clearing local cache..." } context.deleteFile(CACHE_FILE_NAME) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index c57c7250..fd39b6b2 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -10,6 +10,7 @@ import android.app.backup.IBackupManager import android.app.job.JobInfo import android.os.UserHandle import android.util.Log +import androidx.annotation.UiThread import androidx.lifecycle.viewModelScope import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE import com.stevesoltys.seedvault.R @@ -23,6 +24,7 @@ import com.stevesoltys.seedvault.worker.AppBackupWorker import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.backup.BackupJobService import org.calyxos.seedvault.core.backends.Backend @@ -46,25 +48,38 @@ internal class BackupStorageViewModel( override val isRestoreOperation = false + @UiThread override fun onSafUriSet(safProperties: SafProperties) { safHandler.save(safProperties) - safHandler.setPlugin(safProperties) - if (safProperties.isUsb) { - // disable storage backup if new storage is on USB - cancelBackupWorkers() - } else { - // enable it, just in case the previous storage was on USB, - // also to update the network requirement of the new storage - scheduleBackupWorkers() + viewModelScope.launch { + withContext(Dispatchers.IO) { + safHandler.setPlugin(safProperties) + } + withContext(Dispatchers.Main) { // UiThread + if (safProperties.isUsb) { + // disable storage backup if new storage is on USB + cancelBackupWorkers() + } else { + // enable it, just in case the previous storage was on USB, + // also to update the network requirement of the new storage + scheduleBackupWorkers() + } + onStorageLocationSet(safProperties.isUsb) + } } - onStorageLocationSet(safProperties.isUsb) } override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) { webdavHandler.save(properties) - webdavHandler.setPlugin(properties, backend) - scheduleBackupWorkers() - onStorageLocationSet(isUsb = false) + viewModelScope.launch { + withContext(Dispatchers.IO) { + webdavHandler.setPlugin(properties, backend) + } + withContext(Dispatchers.Main) { + scheduleBackupWorkers() + onStorageLocationSet(isUsb = false) + } + } } private fun onStorageLocationSet(isUsb: Boolean) { From 32d7f49d4c8f62bd7f3742c53a080f5b7ebb65e2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 11 Oct 2024 11:16:25 -0300 Subject: [PATCH 12/16] show error notification when AppBackupWorker isn't successful otherwise there's no UI feedback when running backup manually --- .../java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt index dd07d77e..5ba281f6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt @@ -124,6 +124,8 @@ class AppBackupWorker( Result.retry() } else { val result = doBackup() + // show error notification if backup wasn't successful (maybe only when no retry?) + if (result != Result.success()) nm.onBackupError() // only allow retrying if rescheduling is allowed if (tags.contains(TAG_RESCHEDULE)) return result else Result.success() From b8c87a69280362a9e2c9a824eeea8e2f1682603e Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 11 Oct 2024 11:00:49 -0300 Subject: [PATCH 13/16] Always log BackupMonitor and start stopped apps later in CI test --- .../com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt | 4 +++- .../main/java/com/stevesoltys/seedvault/BackupMonitor.kt | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt index e69bd118..fee9ef2b 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt @@ -21,7 +21,6 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { @Test fun `backup and restore applications`() { - launchStoppedApps() launchBackupActivity() if (!keyManager.hasBackupKey()) { @@ -34,6 +33,9 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { changeBackupLocation() } + launchStoppedApps() + launchBackupActivity() + val backupResult = performBackup() assertValidBackupMetadata(backupResult) diff --git a/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt b/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt index b9c6f70e..79df7f18 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/BackupMonitor.kt @@ -13,7 +13,6 @@ import android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT import android.app.backup.IBackupManagerMonitor import android.os.Bundle import android.util.Log -import android.util.Log.DEBUG private val TAG = BackupMonitor::class.java.name @@ -29,14 +28,11 @@ open class BackupMonitor : IBackupManagerMonitor.Stub() { } open fun onEvent(id: Int, category: Int, packageName: String?, bundle: Bundle) { + Log.d(TAG, "${packageName?.padEnd(64, ' ')} cat: $category id: $id") if (id == LOG_EVENT_ID_ERROR_PREFLIGHT) { val preflightResult = bundle.getLong(EXTRA_LOG_PREFLIGHT_ERROR, -1) Log.w(TAG, "Pre-flight error from $packageName: $preflightResult") } - if (!Log.isLoggable(TAG, DEBUG)) return - Log.d(TAG, "ID: $id") - Log.d(TAG, "CATEGORY: $category") - Log.d(TAG, "PACKAGE: $packageName") } } From 1d17adedff60c8ec9039fb0ad9cfcacefc5cbe60 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 11 Oct 2024 12:46:16 -0300 Subject: [PATCH 14/16] log stopped packages in CI test --- .../seedvault/e2e/impl/BackupRestoreTest.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt index fee9ef2b..12fc3fb7 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt @@ -5,7 +5,6 @@ package com.stevesoltys.seedvault.e2e.impl -import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.util.Log import androidx.test.filters.LargeTest @@ -13,7 +12,7 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.e2e.SeedvaultLargeTest import com.stevesoltys.seedvault.e2e.SeedvaultLargeTestResult import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.settings.SettingsActivity +import com.stevesoltys.seedvault.transport.backup.isStopped import org.junit.Test @LargeTest @@ -67,7 +66,8 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { private fun launchStoppedApps() { val packageManager = targetContext.packageManager - packageService.notBackedUpPackages.forEach { packageInfo -> + val notBackedUp = packageService.notBackedUpPackages + notBackedUp.forEach { packageInfo -> val i = packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.apply { addFlags(FLAG_ACTIVITY_NEW_TASK) } @@ -77,12 +77,13 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { } catch (e: Exception) { Log.e("TEST", "Could not launch activity for ${packageInfo.packageName}", e) } + waitUntilIdle() } - // at the end launch us again, so we are back to foreground - val i = Intent(targetContext, SettingsActivity::class.java).apply { - flags = FLAG_ACTIVITY_NEW_TASK + waitUntilIdle() + notBackedUp.forEach { packageInfo -> + val pi = packageManager.getPackageInfo(packageInfo.packageName, 0) + Log.e("TEST", "${packageInfo.packageName} isStopped: ${pi.isStopped()}") } - targetContext.startActivity(i) } private fun assertValidResults( From a538a64395f9ed81413d14d68d2d3f48ef2468e5 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 11 Oct 2024 13:55:33 -0300 Subject: [PATCH 15/16] Use DigestInputStream to spy on backup data this is to save memory to prevent OOM errors we saw on CI --- .../seedvault/e2e/LargeBackupTestBase.kt | 19 ++++++------ .../seedvault/e2e/LargeRestoreTestBase.kt | 28 ++++++++++------- .../seedvault/e2e/io/InputStreamIntercept.kt | 31 ------------------- .../seedvault/e2e/io/OutputStreamIntercept.kt | 25 --------------- 4 files changed, 26 insertions(+), 77 deletions(-) delete mode 100644 app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/InputStreamIntercept.kt delete mode 100644 app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/OutputStreamIntercept.kt diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt index 95d05d83..45eb730a 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt @@ -9,7 +9,6 @@ import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import androidx.test.uiautomator.Until import com.stevesoltys.seedvault.e2e.io.BackupDataInputIntercept -import com.stevesoltys.seedvault.e2e.io.InputStreamIntercept import com.stevesoltys.seedvault.e2e.screen.impl.BackupScreen import com.stevesoltys.seedvault.transport.backup.FullBackup import com.stevesoltys.seedvault.transport.backup.InputFactory @@ -21,8 +20,10 @@ import io.mockk.every import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout +import org.calyxos.seedvault.core.toHexString import org.koin.core.component.get -import java.io.ByteArrayOutputStream +import java.security.DigestInputStream +import java.security.MessageDigest import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.fail @@ -154,7 +155,8 @@ internal interface LargeBackupTestBase : LargeTestBase { private fun spyOnFullBackupData(backupResult: SeedvaultLargeTestResult) { var packageName: String? = null - var dataIntercept = ByteArrayOutputStream() + val messageDigest = MessageDigest.getInstance("SHA-256") + var digestInputStream: DigestInputStream? = null coEvery { spyFullBackup.performFullBackup(any(), any(), any()) @@ -166,20 +168,19 @@ internal interface LargeBackupTestBase : LargeTestBase { every { spyInputFactory.getInputStream(any()) } answers { - InputStreamIntercept( - inputStream = callOriginal(), - intercept = dataIntercept - ) + digestInputStream = DigestInputStream(callOriginal(), messageDigest) + digestInputStream!! } coEvery { spyFullBackup.finishBackup() } answers { val result = callOriginal() - backupResult.full[packageName!!] = dataIntercept.toByteArray().sha256() + val digest = digestInputStream?.messageDigest ?: fail("No digestInputStream") + backupResult.full[packageName!!] = digest.digest().toHexString() packageName = null - dataIntercept = ByteArrayOutputStream() + digest.reset() result } } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt index d104d3ed..133a27d2 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt @@ -8,7 +8,6 @@ package com.stevesoltys.seedvault.e2e import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import com.stevesoltys.seedvault.e2e.io.BackupDataOutputIntercept -import com.stevesoltys.seedvault.e2e.io.OutputStreamIntercept import com.stevesoltys.seedvault.e2e.screen.impl.RecoveryCodeScreen import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen import com.stevesoltys.seedvault.transport.restore.FullRestore @@ -24,8 +23,11 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout +import org.calyxos.seedvault.core.toHexString import org.koin.core.component.get -import java.io.ByteArrayOutputStream +import java.security.DigestOutputStream +import java.security.MessageDigest +import kotlin.test.fail internal interface LargeRestoreTestBase : LargeTestBase { @@ -196,7 +198,8 @@ internal interface LargeRestoreTestBase : LargeTestBase { private fun spyOnFullRestoreData(restoreResult: SeedvaultLargeTestResult) { var packageName: String? = null - var dataIntercept = ByteArrayOutputStream() + val messageDigest = MessageDigest.getInstance("SHA-256") + var digestOutputStream: DigestOutputStream? = null clearMocks(spyFullRestore) @@ -204,11 +207,13 @@ internal interface LargeRestoreTestBase : LargeTestBase { packageInfoIndex: Int ): MockKAnswerScope.(Call) -> Unit = { packageName?.let { - restoreResult.full[it] = dataIntercept.toByteArray().sha256() + // sometimes finishRestore() doesn't get called, so get data from last package here + digestOutputStream?.messageDigest?.let { digest -> + restoreResult.full[packageName!!] = digest.digest().toHexString() + } } packageName = arg(packageInfoIndex).packageName - dataIntercept = ByteArrayOutputStream() callOriginal() } @@ -228,27 +233,26 @@ internal interface LargeRestoreTestBase : LargeTestBase { every { spyOutputFactory.getOutputStream(any()) } answers { - OutputStreamIntercept( - outputStream = callOriginal(), - intercept = dataIntercept - ) + digestOutputStream = DigestOutputStream(callOriginal(), messageDigest) + digestOutputStream!! } every { spyFullRestore.abortFullRestore() } answers { packageName = null - dataIntercept = ByteArrayOutputStream() + digestOutputStream?.messageDigest?.reset() callOriginal() } every { spyFullRestore.finishRestore() } answers { - restoreResult.full[packageName!!] = dataIntercept.toByteArray().sha256() + val digest = digestOutputStream?.messageDigest ?: fail("No digestOutputStream") + restoreResult.full[packageName!!] = digest.digest().toHexString() packageName = null - dataIntercept = ByteArrayOutputStream() + digest.reset() callOriginal() } } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/InputStreamIntercept.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/InputStreamIntercept.kt deleted file mode 100644 index 30c4fdf5..00000000 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/InputStreamIntercept.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.e2e.io - -import java.io.ByteArrayOutputStream -import java.io.InputStream - -class InputStreamIntercept( - private val inputStream: InputStream, - private val intercept: ByteArrayOutputStream -) : InputStream() { - - override fun read(): Int { - val byte = inputStream.read() - if (byte != -1) { - intercept.write(byte) - } - return byte - } - - override fun read(buffer: ByteArray, offset: Int, length: Int): Int { - val bytesRead = inputStream.read(buffer, offset, length) - if (bytesRead != -1) { - intercept.write(buffer, offset, bytesRead) - } - return bytesRead - } -} diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/OutputStreamIntercept.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/OutputStreamIntercept.kt deleted file mode 100644 index d82f0b06..00000000 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/io/OutputStreamIntercept.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.e2e.io - -import java.io.ByteArrayOutputStream -import java.io.OutputStream - -class OutputStreamIntercept( - private val outputStream: OutputStream, - private val intercept: ByteArrayOutputStream -) : OutputStream() { - - override fun write(byte: Int) { - intercept.write(byte) - outputStream.write(byte) - } - - override fun write(buffer: ByteArray, offset: Int, length: Int) { - intercept.write(buffer, offset, length) - outputStream.write(buffer, offset, length) - } -} From 0ed6d5c2c08e4fcc75a44b88a9ecd8cba9c32113 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 17 Oct 2024 15:19:33 -0300 Subject: [PATCH 16/16] Don't show toolbar for recovery code when restoring This restores original behavior and should make CI pass which otherwise can't see the last two words. --- .../ui/recoverycode/RecoveryCodeInputFragment.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt index bfa8db60..77ec609d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeInputFragment.kt @@ -107,8 +107,12 @@ class RecoveryCodeInputFragment : Fragment() { super.onViewCreated(view, savedInstanceState) view.requireViewById(R.id.toolbar).apply { - setNavigationOnClickListener { - requireActivity().onBackPressedDispatcher.onBackPressed() + if (viewModel.isRestore) { + visibility = GONE + } else { + setNavigationOnClickListener { + requireActivity().onBackPressedDispatcher.onBackPressed() + } } }