Skip to content

Commit

Permalink
Merge pull request #705 from grote/565-choose-files-restore
Browse files Browse the repository at this point in the history
 Allow choosing what files/folders will get restored
  • Loading branch information
grote committed Aug 15, 2024
2 parents adbc412 + 0a10db3 commit b571da7
Show file tree
Hide file tree
Showing 41 changed files with 991 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class KoinInstrumentationTestApp : App() {
iconManager = get(),
storageBackup = get(),
pluginManager = get(),
fileSelectionManager = get(),
)
)
currentRestoreViewModel!!
Expand Down
18 changes: 2 additions & 16 deletions app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,15 @@ import com.stevesoltys.seedvault.metadata.metadataModule
import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.saf.storagePluginModuleSaf
import com.stevesoltys.seedvault.plugins.webdav.storagePluginModuleWebDav
import com.stevesoltys.seedvault.restore.RestoreViewModel
import com.stevesoltys.seedvault.restore.install.installModule
import com.stevesoltys.seedvault.restore.restoreUiModule
import com.stevesoltys.seedvault.settings.AppListRetriever
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.SettingsViewModel
import com.stevesoltys.seedvault.storage.storageModule
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.backup.backupModule
import com.stevesoltys.seedvault.transport.restore.restoreModule
import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel
import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel
Expand Down Expand Up @@ -97,20 +96,6 @@ open class App : Application() {
)
}
viewModel { RestoreStorageViewModel(this@App, 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()) }
}

override fun onCreate() {
Expand Down Expand Up @@ -155,6 +140,7 @@ open class App : Application() {
installModule,
storageModule,
workerModule,
restoreUiModule,
appModule
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.restore

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import com.stevesoltys.seedvault.R
import org.calyxos.backup.storage.ui.restore.FileSelectionFragment
import org.calyxos.backup.storage.ui.restore.FilesItem
import org.koin.androidx.viewmodel.ext.android.sharedViewModel

internal class FilesSelectionFragment : FileSelectionFragment() {

override val viewModel: RestoreViewModel by sharedViewModel()
private lateinit var button: Button

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
val v = super.onCreateView(inflater, container, savedInstanceState)
val topStub: ViewStub = v.requireViewById(R.id.topStub)
topStub.layoutResource = R.layout.header_files_selection
topStub.inflate()
val bottomStub: ViewStub = v.requireViewById(R.id.bottomStub)
bottomStub.layoutResource = R.layout.footer_files_selection
button = bottomStub.inflate() as Button
button.setOnClickListener {
viewModel.startFilesRestore()
}
return v
}

override fun onFileItemsChanged(filesItems: List<FilesItem>) {
slideUpInRootView(button)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES_STARTED
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_SELECT_FILES
import com.stevesoltys.seedvault.restore.DisplayFragment.SELECT_APPS
import com.stevesoltys.seedvault.restore.install.InstallProgressFragment
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
Expand All @@ -35,7 +36,12 @@ class RestoreActivity : RequireProvisioningActivity() {
RESTORE_APPS -> showFragment(InstallProgressFragment())
RESTORE_BACKUP -> showFragment(RestoreProgressFragment())
RESTORE_FILES -> showFragment(RestoreFilesFragment())
RESTORE_FILES_STARTED -> showFragment(RestoreFilesStartedFragment())
RESTORE_SELECT_FILES -> showFragment(FilesSelectionFragment(), true)
RESTORE_FILES_STARTED -> {
// pop back stack, so back navigation doesn't bring us to RESTORE_SELECT_FILES
supportFragmentManager.popBackStackImmediate()
showFragment(RestoreFilesStartedFragment())
}
else -> throw AssertionError()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal class RestoreFilesFragment : SnapshotFragment() {
}

override fun onSnapshotClicked(item: SnapshotItem) {
viewModel.startFilesRestore(item)
viewModel.selectFilesForRestore(item)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2020 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.restore

import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import org.koin.android.ext.koin.androidApplication
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

val restoreUiModule = module {
single { FileSelectionManager() }
viewModel {
RestoreViewModel(
app = androidApplication(),
settingsManager = get(),
keyManager = get(),
backupManager = get(),
restoreCoordinator = get(),
apkRestore = get(),
iconManager = get(),
storageBackup = get(),
pluginManager = get(),
fileSelectionManager = get(),
)
}
viewModel { FileSelectionViewModel(androidApplication(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES_STARTED
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_SELECT_FILES
import com.stevesoltys.seedvault.restore.DisplayFragment.SELECT_APPS
import com.stevesoltys.seedvault.restore.install.ApkRestore
import com.stevesoltys.seedvault.restore.install.InstallIntentCreator
Expand All @@ -44,8 +45,10 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.calyxos.backup.storage.api.SnapshotItem
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.api.StoredSnapshot
import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTAMP_START
import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_USER_ID
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
import java.util.LinkedList

Expand All @@ -63,6 +66,7 @@ internal class RestoreViewModel(
private val iconManager: IconManager,
storageBackup: StorageBackup,
pluginManager: StoragePluginManager,
override val fileSelectionManager: FileSelectionManager,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : RequireProvisioningViewModel(app, settingsManager, keyManager, pluginManager),
RestorableBackupClickListener, SnapshotViewModel {
Expand Down Expand Up @@ -98,6 +102,7 @@ internal class RestoreViewModel(
get() = appDataRestoreManager.restoreBackupResult

override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher)
private var storedSnapshot: StoredSnapshot? = null

internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->
Expand Down Expand Up @@ -179,12 +184,22 @@ internal class RestoreViewModel(
}

@UiThread
internal fun startFilesRestore(item: SnapshotItem) {
internal fun selectFilesForRestore(item: SnapshotItem) {
val snapshot = item.snapshot ?: error("${item.storedSnapshot} had null snapshot")
fileSelectionManager.onSnapshotChosen(snapshot)
storedSnapshot = item.storedSnapshot
mDisplayFragment.setEvent(RESTORE_SELECT_FILES)
}

@UiThread
internal fun startFilesRestore() {
val storedSnapshot = this.storedSnapshot ?: error("No snapshot stored")
val i = Intent(app, StorageRestoreService::class.java)
i.putExtra(EXTRA_USER_ID, item.storedSnapshot.userId)
i.putExtra(EXTRA_TIMESTAMP_START, item.time)
i.putExtra(EXTRA_USER_ID, storedSnapshot.userId)
i.putExtra(EXTRA_TIMESTAMP_START, storedSnapshot.timestamp)
app.startForegroundService(i)
mDisplayFragment.setEvent(RESTORE_FILES_STARTED)
this.storedSnapshot = null
}

}
Expand All @@ -206,5 +221,10 @@ internal class RestoreBackupResult(val errorMsg: String? = null) {
}

internal enum class DisplayFragment {
SELECT_APPS, RESTORE_APPS, RESTORE_BACKUP, RESTORE_FILES, RESTORE_FILES_STARTED
SELECT_APPS,
RESTORE_APPS,
RESTORE_BACKUP,
RESTORE_FILES,
RESTORE_SELECT_FILES,
RESTORE_FILES_STARTED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,13 @@ internal class SettingsViewModel(

internal fun backupNow() {
viewModelScope.launch(Dispatchers.IO) {
val isAppBackupEnabled = backupManager.isBackupEnabled
if (settingsManager.isStorageBackupEnabled()) {
val i = Intent(app, StorageBackupService::class.java)
// this starts an app backup afterwards
i.putExtra(EXTRA_START_APP_BACKUP, true)
// this starts an app backup afterwards (if enabled)
i.putExtra(EXTRA_START_APP_BACKUP, isAppBackupEnabled)
startForegroundService(app, i)
} else {
} else if (isAppBackupEnabled) {
AppBackupWorker.scheduleNow(app, reschedule = !pluginManager.isOnRemovableDrive)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.calyxos.backup.storage.backup.BackupService
import org.calyxos.backup.storage.backup.NotificationBackupObserver
import org.calyxos.backup.storage.restore.NotificationRestoreObserver
import org.calyxos.backup.storage.restore.RestoreService
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import org.koin.android.ext.android.inject

/*
Expand Down Expand Up @@ -70,6 +71,7 @@ internal class StorageBackupService : BackupService() {

internal class StorageRestoreService : RestoreService() {
override val storageBackup: StorageBackup by inject()
override val fileSelectionManager: FileSelectionManager by inject()

// use lazy delegate because context isn't available during construction time
override val restoreObserver: RestoreObserver by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ abstract class BackupActivity : AppCompatActivity() {

protected fun showFragment(f: Fragment, addToBackStack: Boolean = false, tag: String? = null) {
supportFragmentManager.beginTransaction().apply {
if (tag == null) replace(R.id.fragment, f)
else replace(R.id.fragment, f, tag)
replace(R.id.fragment, f, tag)
if (addToBackStack) addToBackStack(null)
commit()
}
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/res/layout/footer_files_selection.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!--
SPDX-FileCopyrightText: 2024 The Calyx Institute
SPDX-License-Identifier: Apache-2.0
-->
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/button"
style="@style/SudPrimaryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:drawableTint="?android:textColorPrimaryInverse"
app:icon="@drawable/ic_cloud_restore"
android:text="@string/select_files_button_restore"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
9 changes: 1 addition & 8 deletions app/src/main/res/layout/fragment_restore_app_selection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
tools:listitem="@layout/list_item_app_status" />

<Button
Expand All @@ -122,9 +118,6 @@
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:text="@string/restore_backup_button"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appList" />
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
45 changes: 45 additions & 0 deletions app/src/main/res/layout/header_files_selection.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?><!--
SPDX-FileCopyrightText: 2020 The Calyx Institute
SPDX-License-Identifier: Apache-2.0
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background"
android:paddingBottom="16dp">

<ImageView
android:id="@+id/imageView"
style="@style/SudHeaderIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_cloud_download"
app:tint="?android:colorAccent"
tools:ignore="ContentDescription" />

<TextView
android:id="@+id/titleView"
style="@style/SudHeaderTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/select_files_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />

<TextView
android:id="@+id/backupNameView"
style="@style/SudDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/restore_storage_selection_description"
android:textColor="?android:textColorSecondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" />

</androidx.constraintlayout.widget.ConstraintLayout>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@

<string name="restore_storage_skip">Skip restoring files</string>
<string name="restore_storage_choose_snapshot">Choose a storage backup to restore (beta)</string>
<string name="restore_storage_selection_description">Selected files will be restored. Tap folders to see files in them.</string>
<string name="restore_storage_in_progress_title">Files are being restored…</string>
<string name="restore_storage_in_progress_info">Your files are being restored in the background. You can start using your phone while this is running.\n\nSome apps (e.g. Signal or WhatsApp) might require files to be fully restored to import a backup. Try to avoid starting those apps before file restore is complete.</string>
<string name="restore_storage_got_it">Got it</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.util.Log
import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin
import de.grobox.storagebackuptester.settings.SettingsManager
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.ui.restore.FileSelectionManager

class App : Application() {

Expand All @@ -20,6 +21,7 @@ class App : Application() {
val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() }
StorageBackup(this, { plugin })
}
val fileSelectionManager: FileSelectionManager get() = FileSelectionManager()

override fun onCreate() {
super.onCreate()
Expand Down
Loading

0 comments on commit b571da7

Please sign in to comment.