Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow choosing which apps will get restored #670

Merged
merged 30 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1681339
Change app excludes from switches to checkboxes
grote May 20, 2024
4803288
Show other (launchable) system apps in backup status
grote May 20, 2024
9053407
Allow user to choose which apps should get restored
grote May 20, 2024
5a2f118
Store app icons in separate file
grote May 21, 2024
56d8d64
Store app name and whether it is a launchable system app in metadata
grote May 22, 2024
1667d44
Sort apps that failed to install by name
grote May 20, 2024
a0a3e87
Show actual app name in app selection for restore
grote May 22, 2024
1ac613f
Also store icons of launchable system apps
grote May 22, 2024
a447895
Cleaned up backup status list by separating system data and apps
grote May 23, 2024
af1b3de
Fix icon colors (again!)
grote May 23, 2024
573e48f
Sort app selection like backup status and show sections
grote May 22, 2024
787b934
Add meta item for restoring all (internal) system apps
grote May 23, 2024
eecfcdb
Improve icon display when selecting apps for restore
grote May 23, 2024
332387f
Encrypt zip file with icons
grote May 23, 2024
f5fb9ff
Factor out app restore selection code into new AppSelectionManager
grote May 27, 2024
9cc72bf
Show size for (all combined) system apps when restoring
grote May 28, 2024
6143ec0
Upgrade Roboeletric and bump minSdk to 34
grote May 28, 2024
05c39e9
Add tests for AppSelectionManager
grote May 28, 2024
e54d96d
Re-factor and improve ApkRestore
grote May 28, 2024
4b1c76d
don't record excluded apps in backup
grote May 28, 2024
f68fa0f
Fix AOSP build
grote May 30, 2024
88619b9
Fix red error color
grote May 31, 2024
f408381
Fix backup/restore instrumentation tests
grote May 31, 2024
ebf68cf
Don't try to install system apps without APK
grote May 31, 2024
66f3852
Delete cached icons after restore is done
grote May 31, 2024
b3f93ad
Factor out code into new
grote May 31, 2024
fa19261
Improve app data restore process
grote May 31, 2024
0e224b1
Filter out @end@ helper package
grote May 31, 2024
0872765
Skip magic package manager in assertions
stevesoltys Jun 6, 2024
c483332
Try to fix issue where our transport doesn't get registered in emulator
grote May 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions .github/scripts/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,12 @@
# SPDX-License-Identifier: Apache-2.0
#

adb root
sleep 5
adb remount
echo "Disable auto-restore"
adb shell bmgr autorestore false

echo "Installing Seedvault app..."
adb shell mkdir -p /system/priv-app/Seedvault
adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk

echo "Installing Seedvault permissions..."
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

echo "Setting Seedvault transport..."
sleep 10
adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport
./gradlew --stacktrace :app:installDebugAndroidTest
sleep 60

D2D_BACKUP_TEST=$1

Expand Down
1 change: 1 addition & 0 deletions .idea/dictionaries/user.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,15 @@ dependencies {
// anything less than 'implementation' fails tests run with gradlew
testImplementation(aospLibs)
testImplementation("androidx.test.ext:junit:1.1.5")
testImplementation("org.robolectric:robolectric:4.10.3")
testImplementation("org.robolectric:robolectric:4.12.2")
testImplementation("org.hamcrest:hamcrest:2.2")
testImplementation("org.junit.jupiter:junit-jupiter-api:${libs.versions.junit5.get()}")
testImplementation("org.junit.jupiter:junit-jupiter-params:${libs.versions.junit5.get()}")
testImplementation("io.mockk:mockk:${libs.versions.mockk.get()}")
testImplementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-test:${libs.versions.coroutines.get()}"
)
testImplementation("app.cash.turbine:turbine:1.0.0")
testImplementation("org.bitcoinj:bitcoinj-core:0.16.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${libs.versions.junit5.get()}")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:${libs.versions.junit5.get()}")
Expand Down
13 changes: 4 additions & 9 deletions app/development/scripts/install_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ if [ -z "$ANDROID_HOME" ]; then
fi

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
DEVELOPMENT_DIR=$SCRIPT_DIR/..
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..

EMULATOR_DEVICE_NAME=$($ANDROID_HOME/platform-tools/adb devices | grep emulator | cut -f1)
EMULATOR_DEVICE_NAME=$("$ANDROID_HOME"/platform-tools/adb devices | grep emulator | cut -f1)

if [ -z "$EMULATOR_DEVICE_NAME" ]; then
echo "Emulator device name not found"
Expand All @@ -29,13 +28,9 @@ $ADB remount # remount /system as writable

echo "Installing Seedvault app..."
$ADB shell mkdir -p /system/priv-app/Seedvault
$ADB push $ROOT_PROJECT_DIR/app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk
$ADB push "$ROOT_PROJECT_DIR"/app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk

echo "Installing Seedvault permissions..."
$ADB push $ROOT_PROJECT_DIR/permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml
$ADB push $ROOT_PROJECT_DIR/allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml
$ADB shell am force-stop com.stevesoltys.seedvault
$ADB push "$ROOT_PROJECT_DIR"/permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml
$ADB push "$ROOT_PROJECT_DIR"/allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml
$ADB shell am broadcast -a android.intent.action.BOOT_COMPLETED

echo "Setting Seedvault transport..."
$ADB shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport
22 changes: 9 additions & 13 deletions app/development/scripts/provision_emulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,23 @@ EMULATOR_NAME=$1
SYSTEM_IMAGE=$2

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
DEVELOPMENT_DIR=$SCRIPT_DIR/..
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..

echo "Downloading system image..."
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "$SYSTEM_IMAGE"
yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --install "$SYSTEM_IMAGE"

# create AVD if it doesn't exist
if $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager list avd | grep -q "$EMULATOR_NAME"; then
if "$ANDROID_HOME"/cmdline-tools/latest/bin/avdmanager list avd | grep -q "$EMULATOR_NAME"; then
echo "AVD already exists. Skipping creation."
else
echo "Creating AVD..."
echo 'no' | $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n "$EMULATOR_NAME" -k "$SYSTEM_IMAGE"
echo 'no' | "$ANDROID_HOME"/cmdline-tools/latest/bin/avdmanager create avd -n "$EMULATOR_NAME" -k "$SYSTEM_IMAGE"
sleep 1
fi

EMULATOR_DEVICE_NAME=$($ANDROID_HOME/platform-tools/adb devices | grep emulator | cut -f1)
EMULATOR_DEVICE_NAME=$("$ANDROID_HOME"/platform-tools/adb devices | grep emulator | cut -f1)

if [ -z "$EMULATOR_DEVICE_NAME" ]; then
$SCRIPT_DIR/start_emulator.sh "$EMULATOR_NAME"
"$SCRIPT_DIR"/start_emulator.sh "$EMULATOR_NAME"
fi

# wait for emulator device to appear with 180 second timeout
Expand All @@ -47,7 +45,7 @@ echo "Waiting for emulator device..."
for i in {1..180}; do
if [ -z "$EMULATOR_DEVICE_NAME" ]; then
sleep 1
EMULATOR_DEVICE_NAME=$($ANDROID_HOME/platform-tools/adb devices | grep emulator | cut -f1)
EMULATOR_DEVICE_NAME=$("$ANDROID_HOME"/platform-tools/adb devices | grep emulator | cut -f1)
else
break
fi
Expand All @@ -73,16 +71,14 @@ $ADB reboot # need to reboot first time we remount
$ADB wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'

echo "Provisioning emulator for Seedvault..."
$SCRIPT_DIR/install_app.sh
"$SCRIPT_DIR"/install_app.sh

echo "Rebooting emulator..."
$ADB reboot
$ADB wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'

echo "Setting backup transport to Seedvault..."
$ADB shell bmgr enable true
sleep 5
$ADB shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport
echo "Disabling backup..."
$ADB shell bmgr enable false

echo "Downloading and extracting test backup to '/sdcard/seedvault_baseline'..."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ class KoinInstrumentationTestApp : App() {

viewModel {
currentRestoreViewModel =
spyk(RestoreViewModel(context, get(), get(), get(), get(), get(), get(), get()))
spyk(
RestoreViewModel(
app = context,
settingsManager = get(),
keyManager = get(),
backupManager = get(),
restoreCoordinator = get(),
apkRestore = get(),
iconManager = get(),
storageBackup = get(),
pluginManager = get(),
)
)
currentRestoreViewModel!!
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ internal interface LargeRestoreTestBase : LargeTestBase {
backupListItem.clickAndWaitForNewWindow()
waitUntilIdle()

waitForAppSelectionLoaded()
// just tap next in app selection
appsSelectedButton.clickAndWaitForNewWindow()

waitForInstallResult()

if (someAppsNotInstalledText.exists()) {
Expand Down Expand Up @@ -104,13 +108,22 @@ internal interface LargeRestoreTestBase : LargeTestBase {
spyOnKVRestoreData(result)
}

private fun waitForAppSelectionLoaded() = runBlocking {
withContext(Dispatchers.Main) {
withTimeout(RESTORE_TIMEOUT) {
while (spyRestoreViewModel.selectedApps.value?.apps?.isNotEmpty() != true) {
delay(100)
}
}
}
waitUntilIdle()
}

private fun waitForInstallResult() = runBlocking {

withContext(Dispatchers.Main) {
withTimeout(RESTORE_TIMEOUT) {
while (spyRestoreViewModel.installResult.value == null ||
spyRestoreViewModel.nextButtonEnabled.value == false
) {
while (spyRestoreViewModel.installResult.value?.isFinished != true) {
delay(100)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ internal interface LargeTestBase : KoinComponent {
}

fun testResultFilename(testName: String): String {
val arguments = InstrumentationRegistry.getArguments()
val d2d = if (arguments.getString("d2d_backup_test") == "true") "d2d" else ""
val simpleDateFormat = SimpleDateFormat("yyyyMMdd_hhmmss")
val timeStamp = simpleDateFormat.format(Calendar.getInstance().time)
return "${timeStamp}_${testName.replace(" ", "_")}"
return "${timeStamp}_${d2d}_${testName.replace(" ", "_")}"
}

@OptIn(DelicateCoroutinesApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.junit.rules.TestName
import org.junit.runner.RunWith
import org.koin.core.component.KoinComponent
import java.io.File
import java.lang.Thread.sleep
import java.util.concurrent.atomic.AtomicBoolean

@RunWith(AndroidJUnit4::class)
Expand Down Expand Up @@ -44,6 +45,11 @@ internal abstract class SeedvaultLargeTest :
resetApplicationState()
clearTestBackups()

runCommand("bmgr enable true")
sleep(60_000)
grote marked this conversation as resolved.
Show resolved Hide resolved
runCommand("bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport")
sleep(60_000)

startRecordingTest(keepRecordingScreen, name.methodName)
restoreBaselineBackup()

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

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
Expand Down Expand Up @@ -127,17 +128,19 @@ internal class BackupRestoreTest : SeedvaultLargeTest() {
) {
// Assert all "key/value" restored data matches the backup data.
restore.kv.forEach { (pkg, kvData) ->
assert(backup.kv.containsKey(pkg)) {
"KV data for $pkg missing from backup."
}

kvData.forEach { (key, value) ->
assert(backup.kv[pkg]!!.containsKey(key)) {
"KV data for $pkg/$key exists in restore but is missing from backup."
if (pkg != MAGIC_PACKAGE_MANAGER) {
assert(backup.kv.containsKey(pkg)) {
"KV data for $pkg missing from backup."
}

assert(value.contentEquals(backup.kv[pkg]!![key]!!)) {
"KV data for $pkg/$key does not match."
kvData.forEach { (key, value) ->
assert(backup.kv[pkg]!!.containsKey(key)) {
"KV data for $pkg/$key exists in restore but is missing from backup."
}

assert(value.contentEquals(backup.kv[pkg]!![key]!!)) {
"KV data for $pkg/$key does not match."
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ object RestoreScreen : UiDeviceScreen<RestoreScreen>() {

val backupListItem = findObject { textContains("Last backup") }

val appsSelectedButton = findObject { text("Restore backup") }

val nextButton = findObject { text("Next") }

val finishButton = findObject { text("Finish") }
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,19 @@ open class App : Application() {
)
}
viewModel { RestoreStorageViewModel(this@App, get(), get(), get(), get()) }
viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get(), get()) }
viewModel {
RestoreViewModel(
app = this@App,
settingsManager = get(),
keyManager = get(),
backupManager = get(),
restoreCoordinator = get(),
apkRestore = get(),
iconManager = get(),
storageBackup = get(),
pluginManager = get(),
)
}
viewModel { FileSelectionViewModel(this@App, get()) }
}

Expand Down Expand Up @@ -189,6 +201,7 @@ open class App : Application() {

const val MAGIC_PACKAGE_MANAGER: String = PACKAGE_MANAGER_SENTINEL
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
const val NO_DATA_END_SENTINEL = "@end@"
const val GLOBAL_METADATA_KEY = "@meta@"
const val ERROR_BACKUP_CANCELLED: Int = BackupManager.ERROR_BACKUP_CANCELLED

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ internal interface Crypto {
internal const val TYPE_METADATA: Byte = 0x00
internal const val TYPE_BACKUP_KV: Byte = 0x01
internal const val TYPE_BACKUP_FULL: Byte = 0x02
internal const val TYPE_ICONS: Byte = 0x03

internal class CryptoImpl(
private val keyManager: KeyManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ data class BackupMetadata(
internal var d2dBackup: Boolean = false,
internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap(),
) {
val size: Long?
val size: Long
get() = packageMetadataMap.values.sumOf { m ->
(m.size ?: 0L) + (m.splits?.sumOf { it.size ?: 0L } ?: 0L)
}
Expand Down Expand Up @@ -85,13 +85,16 @@ data class PackageMetadata(
internal var state: PackageState = UNKNOWN_ERROR,
internal var backupType: BackupType? = null,
internal var size: Long? = null,
internal var name: CharSequence? = null,
internal val system: Boolean = false,
internal val isLaunchableSystemApp: Boolean = false,
internal val version: Long? = null,
internal val installer: String? = null,
internal val splits: List<ApkSplit>? = null,
internal val sha256: String? = null,
internal val signatures: List<String>? = null,
) {
val isInternalSystem: Boolean = system && !isLaunchableSystemApp
fun hasApk(): Boolean {
return version != null && sha256 != null && signatures != null
}
Expand All @@ -110,7 +113,9 @@ internal const val JSON_PACKAGE_TIME = "time"
internal const val JSON_PACKAGE_BACKUP_TYPE = "backupType"
internal const val JSON_PACKAGE_STATE = "state"
internal const val JSON_PACKAGE_SIZE = "size"
internal const val JSON_PACKAGE_APP_NAME = "name"
internal const val JSON_PACKAGE_SYSTEM = "system"
internal const val JSON_PACKAGE_SYSTEM_LAUNCHER = "systemLauncher"
internal const val JSON_PACKAGE_VERSION = "version"
internal const val JSON_PACKAGE_INSTALLER = "installer"
internal const val JSON_PACKAGE_SPLITS = "splits"
Expand Down
Loading
Loading