From 3082a974a2c9f19512eb2ff31d7987e9133b3eb2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 22 Feb 2021 11:26:21 -0300 Subject: [PATCH] Add UI for restoring files after app restore --- app/src/main/AndroidManifest.xml | 6 ++ .../java/com/stevesoltys/seedvault/App.kt | 2 +- .../seedvault/restore/RestoreActivity.kt | 4 ++ .../seedvault/restore/RestoreFilesFragment.kt | 66 +++++++++++++++++++ .../restore/RestoreProgressFragment.kt | 6 +- .../seedvault/restore/RestoreViewModel.kt | 34 +++++++++- .../install/InstallProgressFragment.kt | 4 +- .../stevesoltys/seedvault/storage/Services.kt | 12 ++++ app/src/main/res/layout/footer_snapshots.xml | 18 +++++ .../layout/fragment_restore_files_started.xml | 56 ++++++++++++++++ app/src/main/res/layout/header_snapshots.xml | 33 ++++++++++ app/src/main/res/values/strings.xml | 7 ++ 12 files changed, 238 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt create mode 100644 app/src/main/res/layout/footer_snapshots.xml create mode 100644 app/src/main/res/layout/fragment_restore_files_started.xml create mode 100644 app/src/main/res/layout/header_snapshots.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 20bdb3c4..5eb0f1ef 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -125,6 +125,12 @@ android:exported="false" android:foregroundServiceType="dataSync" android:label="BackupService" /> + + diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index eafcf5c8..b2d3e0e5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -49,7 +49,7 @@ open class App : Application() { viewModel { RecoveryCodeViewModel(this@App, get(), get(), get()) } viewModel { BackupStorageViewModel(this@App, get(), get(), get()) } viewModel { RestoreStorageViewModel(this@App, get(), get()) } - viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get()) } + viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) } viewModel { FileSelectionViewModel(this@App, get()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt index 635d10d7..9b78d7d3 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt @@ -5,6 +5,8 @@ import androidx.annotation.CallSuper import com.stevesoltys.seedvault.R 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.install.InstallProgressFragment import com.stevesoltys.seedvault.ui.LiveEventHandler import com.stevesoltys.seedvault.ui.RequireProvisioningActivity @@ -28,6 +30,8 @@ class RestoreActivity : RequireProvisioningActivity() { when (fragment) { RESTORE_APPS -> showFragment(InstallProgressFragment()) RESTORE_BACKUP -> showFragment(RestoreProgressFragment()) + RESTORE_FILES -> showFragment(RestoreFilesFragment()) + RESTORE_FILES_STARTED -> showFragment(RestoreFilesStartedFragment()) else -> throw AssertionError() } }) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt new file mode 100644 index 00000000..235ce9ab --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt @@ -0,0 +1,66 @@ +package com.stevesoltys.seedvault.restore + +import android.app.Activity.RESULT_OK +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 android.widget.TextView +import androidx.fragment.app.Fragment +import com.stevesoltys.seedvault.R +import org.calyxos.backup.storage.api.SnapshotItem +import org.calyxos.backup.storage.ui.restore.SnapshotFragment +import org.koin.androidx.viewmodel.ext.android.sharedViewModel + +internal class RestoreFilesFragment : SnapshotFragment() { + override val viewModel: RestoreViewModel by sharedViewModel() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val v = super.onCreateView(inflater, container, savedInstanceState) + + val topStub: ViewStub = v.findViewById(R.id.topStub) + topStub.layoutResource = R.layout.header_snapshots + topStub.inflate() + + val bottomStub: ViewStub = v.findViewById(R.id.bottomStub) + bottomStub.layoutResource = R.layout.footer_snapshots + val footer = bottomStub.inflate() + val skipView: TextView = footer.findViewById(R.id.skipView) + skipView.setOnClickListener { + requireActivity().apply { + setResult(RESULT_OK) + finishAfterTransition() + } + } + return v + } + + override fun onSnapshotClicked(item: SnapshotItem) { + viewModel.startFilesRestore(item) + } +} + +internal class RestoreFilesStartedFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val v: View = inflater.inflate(R.layout.fragment_restore_files_started, container, false) + + val button: Button = v.findViewById(R.id.button) + button.setOnClickListener { + requireActivity().apply { + setResult(RESULT_OK) + finishAfterTransition() + } + } + return v + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt index e3b4ce77..adc8ced4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreProgressFragment.kt @@ -1,6 +1,5 @@ package com.stevesoltys.seedvault.restore -import android.app.Activity.RESULT_OK import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -38,7 +37,7 @@ class RestoreProgressFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { val v: View = inflater.inflate(R.layout.fragment_restore_progress, container, false) progressBar = v.findViewById(R.id.progressBar) @@ -61,8 +60,7 @@ class RestoreProgressFragment : Fragment() { button.setText(R.string.restore_finished_button) button.setOnClickListener { - requireActivity().setResult(RESULT_OK) - requireActivity().finishAfterTransition() + viewModel.onFinishClickedAfterRestoringAppData() } // decryption will fail when the device is locked, so keep the screen on to prevent locking diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index 199de755..6fb3dea8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -5,9 +5,11 @@ import android.app.backup.IBackupManager import android.app.backup.IRestoreObserver import android.app.backup.IRestoreSession import android.app.backup.RestoreSet +import android.content.Intent import android.os.RemoteException import android.os.UserHandle import android.util.Log +import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -27,11 +29,14 @@ import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED 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.install.ApkRestore import com.stevesoltys.seedvault.restore.install.InstallIntentCreator import com.stevesoltys.seedvault.restore.install.InstallResult import com.stevesoltys.seedvault.restore.install.isInstalled import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.storage.StorageRestoreService import com.stevesoltys.seedvault.transport.TRANSPORT_ID import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator import com.stevesoltys.seedvault.ui.AppBackupState @@ -54,6 +59,10 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +import org.calyxos.backup.storage.api.SnapshotItem +import org.calyxos.backup.storage.api.StorageBackup +import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTAMP_START +import org.calyxos.backup.storage.ui.restore.SnapshotViewModel import java.util.LinkedList import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -68,8 +77,10 @@ internal class RestoreViewModel( private val backupManager: IBackupManager, private val restoreCoordinator: RestoreCoordinator, private val apkRestore: ApkRestore, + storageBackup: StorageBackup, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO -) : RequireProvisioningViewModel(app, settingsManager, keyManager), RestorableBackupClickListener { +) : RequireProvisioningViewModel(app, settingsManager, keyManager), + RestorableBackupClickListener, SnapshotViewModel { override val isRestoreOperation = true @@ -110,6 +121,8 @@ internal class RestoreViewModel( private val mRestoreBackupResult = MutableLiveData() internal val restoreBackupResult: LiveData get() = mRestoreBackupResult + override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher) + @Throws(RemoteException::class) private fun getOrStartSession(): IRestoreSession { val session = this.session @@ -168,7 +181,7 @@ internal class RestoreViewModel( .asLiveData(ioDispatcher) } - internal fun onNextClicked() { + internal fun onNextClickedAfterInstallingApps() { mDisplayFragment.postEvent(RESTORE_BACKUP) val token = mChosenRestorableBackup.value?.token ?: throw AssertionError() viewModelScope.launch(ioDispatcher) { @@ -371,6 +384,19 @@ internal class RestoreViewModel( } + @UiThread + internal fun onFinishClickedAfterRestoringAppData() { + mDisplayFragment.setEvent(RESTORE_FILES) + } + + @UiThread + internal fun startFilesRestore(item: SnapshotItem) { + val i = Intent(app, StorageRestoreService::class.java) + i.putExtra(EXTRA_TIMESTAMP_START, item.time) + app.startForegroundService(i) + mDisplayFragment.setEvent(RESTORE_FILES_STARTED) + } + } internal class RestoreSetResult( @@ -389,4 +415,6 @@ internal class RestoreBackupResult(val errorMsg: String? = null) { internal fun hasError(): Boolean = errorMsg != null } -internal enum class DisplayFragment { RESTORE_APPS, RESTORE_BACKUP } +internal enum class DisplayFragment { + RESTORE_APPS, RESTORE_BACKUP, RESTORE_FILES, RESTORE_FILES_STARTED +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt index 9388c7e2..a4c2c813 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/InstallProgressFragment.kt @@ -59,7 +59,7 @@ class InstallProgressFragment : Fragment(), InstallItemListener { addItemDecoration(DividerItemDecoration(context, VERTICAL)) } button.setText(R.string.restore_next) - button.setOnClickListener { viewModel.onNextClicked() } + button.setOnClickListener { viewModel.onNextClickedAfterInstallingApps() } viewModel.chosenRestorableBackup.observe(viewLifecycleOwner, Observer { restorableBackup -> backupNameView.text = restorableBackup.name @@ -76,7 +76,7 @@ class InstallProgressFragment : Fragment(), InstallItemListener { private fun onInstallResult(installResult: InstallResult) { // skip this screen, if there are no apps to install - if (installResult.isEmpty) viewModel.onNextClicked() + if (installResult.isEmpty) viewModel.onNextClickedAfterInstallingApps() // if finished, treat all still queued apps as failed and resort/redisplay adapter items if (installResult.isFinished) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt index 30b82e28..5f55caec 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt @@ -1,10 +1,13 @@ package com.stevesoltys.seedvault.storage import org.calyxos.backup.storage.api.BackupObserver +import org.calyxos.backup.storage.api.RestoreObserver import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.backup.BackupJobService 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.koin.android.ext.android.inject /* @@ -28,3 +31,12 @@ internal class StorageBackupService : BackupService() { NotificationBackupObserver(applicationContext) } } + +internal class StorageRestoreService : RestoreService() { + override val storageBackup: StorageBackup by inject() + + // use lazy delegate because context isn't available during construction time + override val restoreObserver: RestoreObserver by lazy { + NotificationRestoreObserver(applicationContext) + } +} diff --git a/app/src/main/res/layout/footer_snapshots.xml b/app/src/main/res/layout/footer_snapshots.xml new file mode 100644 index 00000000..9b4e4d84 --- /dev/null +++ b/app/src/main/res/layout/footer_snapshots.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/fragment_restore_files_started.xml b/app/src/main/res/layout/fragment_restore_files_started.xml new file mode 100644 index 00000000..8746717b --- /dev/null +++ b/app/src/main/res/layout/fragment_restore_files_started.xml @@ -0,0 +1,56 @@ + + + + + + + + + +