Implement restoring of backup and show progress in UI
Note that the progress view is not exact as the progress reporting of AOSP seems to be buggy.
This commit is contained in:
parent
491789e8e0
commit
1a7fdfa59a
14 changed files with 268 additions and 47 deletions
|
@ -62,3 +62,5 @@ class Backup : Application() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Uri.isOnExternalStorage() = authority == URI_AUTHORITY_EXTERNAL_STORAGE
|
fun Uri.isOnExternalStorage() = authority == URI_AUTHORITY_EXTERNAL_STORAGE
|
||||||
|
|
||||||
|
fun isDebugBuild() = Build.TYPE == "userdebug"
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.stevesoltys.backup
|
||||||
import android.app.backup.BackupProgress
|
import android.app.backup.BackupProgress
|
||||||
import android.app.backup.IBackupObserver
|
import android.app.backup.IBackupObserver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
import android.util.Log.isLoggable
|
import android.util.Log.isLoggable
|
||||||
|
@ -62,10 +63,12 @@ class NotificationBackupObserver(context: Context, private val userInitiated: Bo
|
||||||
nm.onBackupFinished()
|
nm.onBackupFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAppName(packageId: String): CharSequence {
|
private fun getAppName(packageId: String): CharSequence = getAppName(pm, packageId)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppName(pm: PackageManager, packageId: String): CharSequence {
|
||||||
if (packageId == "@pm@") return packageId
|
if (packageId == "@pm@") return packageId
|
||||||
val appInfo = pm.getApplicationInfo(packageId, 0)
|
val appInfo = pm.getApplicationInfo(packageId, 0)
|
||||||
return pm.getApplicationLabel(appInfo)
|
return pm.getApplicationLabel(appInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.stevesoltys.backup.restore
|
package com.stevesoltys.backup.restore
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.ui.BackupActivity
|
import com.stevesoltys.backup.ui.BackupActivity
|
||||||
|
@ -20,7 +21,13 @@ class RestoreActivity : BackupActivity() {
|
||||||
|
|
||||||
setContentView(R.layout.activity_fragment_container)
|
setContentView(R.layout.activity_fragment_container)
|
||||||
|
|
||||||
if (savedInstanceState == null) showFragment(getInitialFragment())
|
viewModel.chosenRestoreSet.observe(this, Observer { set ->
|
||||||
|
if (set != null) showFragment(RestoreProgressFragment())
|
||||||
|
})
|
||||||
|
|
||||||
|
if (savedInstanceState == null && viewModel.validLocationIsSet()) {
|
||||||
|
showFragment(getInitialFragment())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInvalidLocation() {
|
override fun onInvalidLocation() {
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.stevesoltys.backup.restore
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.INVISIBLE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.stevesoltys.backup.R
|
||||||
|
import com.stevesoltys.backup.getAppName
|
||||||
|
import com.stevesoltys.backup.isDebugBuild
|
||||||
|
import kotlinx.android.synthetic.main.fragment_restore_progress.*
|
||||||
|
|
||||||
|
class RestoreProgressFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var viewModel: RestoreViewModel
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_restore_progress, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
|
// decryption will fail when the device is locked, so keep the screen on to prevent locking
|
||||||
|
requireActivity().window.addFlags(FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
|
viewModel = ViewModelProviders.of(requireActivity()).get(RestoreViewModel::class.java)
|
||||||
|
|
||||||
|
viewModel.numPackages.observe(this, Observer { numPackages ->
|
||||||
|
progressBar.min = 0
|
||||||
|
progressBar.max = numPackages
|
||||||
|
})
|
||||||
|
|
||||||
|
viewModel.chosenRestoreSet.observe(this, Observer { set ->
|
||||||
|
backupNameView.text = set.device
|
||||||
|
})
|
||||||
|
|
||||||
|
viewModel.restoreProgress.observe(this, Observer { progress ->
|
||||||
|
progressBar.progress = progress.nowBeingRestored
|
||||||
|
val appName = getAppName(requireActivity().packageManager, progress.currentPackage)
|
||||||
|
val displayName = if (isDebugBuild()) "$appName (${progress.currentPackage})" else appName
|
||||||
|
currentPackageView.text = getString(R.string.restore_current_package, displayName)
|
||||||
|
})
|
||||||
|
|
||||||
|
viewModel.restoreFinished.observe(this, Observer { finished ->
|
||||||
|
progressBarIndefinite.visibility = INVISIBLE
|
||||||
|
progressBar.progress = viewModel.numPackages.value ?: progressBar.max
|
||||||
|
button.visibility = VISIBLE
|
||||||
|
if (finished == 0) {
|
||||||
|
// success
|
||||||
|
currentPackageView.text = getString(R.string.restore_finished_success)
|
||||||
|
warningView.visibility = VISIBLE
|
||||||
|
} else {
|
||||||
|
// error
|
||||||
|
currentPackageView.text = getString(R.string.restore_finished_error)
|
||||||
|
currentPackageView.setTextColor(warningView.textColors)
|
||||||
|
}
|
||||||
|
activity?.window?.clearFlags(FLAG_KEEP_SCREEN_ON)
|
||||||
|
})
|
||||||
|
|
||||||
|
button.setOnClickListener { requireActivity().finishAfterTransition() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ internal class RestoreSetAdapter(
|
||||||
internal fun bind(item: RestoreSet) {
|
internal fun bind(item: RestoreSet) {
|
||||||
v.setOnClickListener { listener.onRestoreSetClicked(item) }
|
v.setOnClickListener { listener.onRestoreSetClicked(item) }
|
||||||
titleView.text = item.name
|
titleView.text = item.name
|
||||||
subtitleView.text = item.device
|
subtitleView.text = "Android Backup" // TODO change to backup date when available
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.stevesoltys.backup.restore
|
||||||
|
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
|
@ -14,7 +13,7 @@ import androidx.lifecycle.ViewModelProviders
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import kotlinx.android.synthetic.main.fragment_restore_set.*
|
import kotlinx.android.synthetic.main.fragment_restore_set.*
|
||||||
|
|
||||||
class RestoreSetFragment : Fragment(), RestoreSetClickListener {
|
class RestoreSetFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var viewModel: RestoreViewModel
|
private lateinit var viewModel: RestoreViewModel
|
||||||
|
|
||||||
|
@ -34,8 +33,10 @@ class RestoreSetFragment : Fragment(), RestoreSetClickListener {
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
if (viewModel.recoveryCodeIsSet() && viewModel.validLocationIsSet()) {
|
||||||
viewModel.loadRestoreSets()
|
viewModel.loadRestoreSets()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onRestoreSetsLoaded(result: RestoreSetResult) {
|
private fun onRestoreSetsLoaded(result: RestoreSetResult) {
|
||||||
if (result.hasError()) {
|
if (result.hasError()) {
|
||||||
|
@ -49,14 +50,10 @@ class RestoreSetFragment : Fragment(), RestoreSetClickListener {
|
||||||
listView.visibility = VISIBLE
|
listView.visibility = VISIBLE
|
||||||
progressBar.visibility = INVISIBLE
|
progressBar.visibility = INVISIBLE
|
||||||
|
|
||||||
listView.adapter = RestoreSetAdapter(this, result.sets)
|
listView.adapter = RestoreSetAdapter(viewModel, result.sets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreSetClicked(set: RestoreSet) {
|
|
||||||
Log.e("TEST", "RESTORE SET CLICKED: ${set.name} ${set.device} ${set.token}")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface RestoreSetClickListener {
|
internal interface RestoreSetClickListener {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.app.backup.IRestoreSession
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.stevesoltys.backup.Backup
|
import com.stevesoltys.backup.Backup
|
||||||
|
@ -16,7 +17,7 @@ import com.stevesoltys.backup.ui.BackupViewModel
|
||||||
|
|
||||||
private val TAG = RestoreViewModel::class.java.simpleName
|
private val TAG = RestoreViewModel::class.java.simpleName
|
||||||
|
|
||||||
class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
class RestoreViewModel(app: Application) : BackupViewModel(app), RestoreSetClickListener {
|
||||||
|
|
||||||
private val backupManager = Backup.backupManager
|
private val backupManager = Backup.backupManager
|
||||||
|
|
||||||
|
@ -27,8 +28,21 @@ class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
||||||
private val mRestoreSets = MutableLiveData<RestoreSetResult>()
|
private val mRestoreSets = MutableLiveData<RestoreSetResult>()
|
||||||
internal val restoreSets: LiveData<RestoreSetResult> get() = mRestoreSets
|
internal val restoreSets: LiveData<RestoreSetResult> get() = mRestoreSets
|
||||||
|
|
||||||
|
private val mChosenRestoreSet = MutableLiveData<RestoreSet>()
|
||||||
|
internal val chosenRestoreSet: LiveData<RestoreSet> get() = mChosenRestoreSet
|
||||||
|
|
||||||
|
private var mNumPackages = MutableLiveData<Int>()
|
||||||
|
internal val numPackages: LiveData<Int> get() = mNumPackages
|
||||||
|
|
||||||
|
private val mRestoreProgress = MutableLiveData<RestoreProgress>()
|
||||||
|
internal val restoreProgress: LiveData<RestoreProgress> get() = mRestoreProgress
|
||||||
|
|
||||||
|
private val mRestoreFinished = MutableLiveData<Int>()
|
||||||
|
// Zero on success; a nonzero error code if the restore operation as a whole failed.
|
||||||
|
internal val restoreFinished: LiveData<Int> get() = mRestoreFinished
|
||||||
|
|
||||||
override fun acceptBackupLocation(folderUri: Uri): Boolean {
|
override fun acceptBackupLocation(folderUri: Uri): Boolean {
|
||||||
// TODO
|
// TODO search if there's really a backup available in this location and see if we can decrypt it
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +66,14 @@ class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRestoreSetClicked(set: RestoreSet) {
|
||||||
|
val session = this.session
|
||||||
|
check(session != null)
|
||||||
|
session.restoreAll(set.token, observer, monitor)
|
||||||
|
|
||||||
|
mChosenRestoreSet.value = set
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
endSession()
|
endSession()
|
||||||
|
@ -63,8 +85,11 @@ class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
||||||
observer = null
|
observer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private inner class RestoreObserver : IRestoreObserver.Stub() {
|
private inner class RestoreObserver : IRestoreObserver.Stub() {
|
||||||
|
|
||||||
|
private var correctedNow: Int = -1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supply a list of the restore datasets available from the current transport.
|
* Supply a list of the restore datasets available from the current transport.
|
||||||
* This method is invoked as a callback following the application's use of the
|
* This method is invoked as a callback following the application's use of the
|
||||||
|
@ -76,7 +101,7 @@ class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
||||||
*/
|
*/
|
||||||
override fun restoreSetsAvailable(restoreSets: Array<out RestoreSet>?) {
|
override fun restoreSetsAvailable(restoreSets: Array<out RestoreSet>?) {
|
||||||
if (restoreSets == null || restoreSets.isEmpty()) {
|
if (restoreSets == null || restoreSets.isEmpty()) {
|
||||||
mRestoreSets.value = RestoreSetResult(app.getString(R.string.restore_set_empty_result))
|
mRestoreSets.postValue(RestoreSetResult(app.getString(R.string.restore_set_empty_result)))
|
||||||
} else {
|
} else {
|
||||||
mRestoreSets.postValue(RestoreSetResult(restoreSets))
|
mRestoreSets.postValue(RestoreSetResult(restoreSets))
|
||||||
}
|
}
|
||||||
|
@ -88,7 +113,7 @@ class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
||||||
* @param numPackages The total number of packages being processed in this restore operation.
|
* @param numPackages The total number of packages being processed in this restore operation.
|
||||||
*/
|
*/
|
||||||
override fun restoreStarting(numPackages: Int) {
|
override fun restoreStarting(numPackages: Int) {
|
||||||
Log.e(TAG, "RESTORE STARTING $numPackages")
|
mNumPackages.postValue(numPackages)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,17 +126,22 @@ class RestoreViewModel(app: Application) : BackupViewModel(app) {
|
||||||
* @param currentPackage The name of the package now being restored.
|
* @param currentPackage The name of the package now being restored.
|
||||||
*/
|
*/
|
||||||
override fun onUpdate(nowBeingRestored: Int, currentPackage: String) {
|
override fun onUpdate(nowBeingRestored: Int, currentPackage: String) {
|
||||||
Log.e(TAG, "RESTORE UPDATE $nowBeingRestored $currentPackage")
|
if (nowBeingRestored <= correctedNow) {
|
||||||
|
correctedNow += 1
|
||||||
|
} else {
|
||||||
|
correctedNow = nowBeingRestored
|
||||||
|
}
|
||||||
|
mRestoreProgress.postValue(RestoreProgress(correctedNow, currentPackage))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The restore operation has completed.
|
* The restore operation has completed.
|
||||||
*
|
*
|
||||||
* @param error Zero on success; a nonzero error code if the restore operation
|
* @param result Zero on success; a nonzero error code if the restore operation
|
||||||
* as a whole failed.
|
* as a whole failed.
|
||||||
*/
|
*/
|
||||||
override fun restoreFinished(error: Int) {
|
override fun restoreFinished(result: Int) {
|
||||||
Log.e(TAG, "RESTORE FINISHED $error")
|
mRestoreFinished.postValue(result)
|
||||||
endSession()
|
endSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,3 +159,7 @@ internal class RestoreSetResult(
|
||||||
|
|
||||||
internal fun hasError(): Boolean = errorMsg != null
|
internal fun hasError(): Boolean = errorMsg != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class RestoreProgress(
|
||||||
|
internal val nowBeingRestored: Int,
|
||||||
|
internal val currentPackage: String)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.stevesoltys.backup.header.UnsupportedVersionException
|
||||||
import libcore.io.IoUtils.closeQuietly
|
import libcore.io.IoUtils.closeQuietly
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import javax.crypto.AEADBadTagException
|
||||||
|
|
||||||
private class KVRestoreState(
|
private class KVRestoreState(
|
||||||
internal val token: Long,
|
internal val token: Long,
|
||||||
|
@ -86,6 +87,9 @@ internal class KVRestore(
|
||||||
} catch (e: UnsupportedVersionException) {
|
} catch (e: UnsupportedVersionException) {
|
||||||
Log.e(TAG, "Unsupported version in backup: ${e.version}", e)
|
Log.e(TAG, "Unsupported version in backup: ${e.version}", e)
|
||||||
TRANSPORT_ERROR
|
TRANSPORT_ERROR
|
||||||
|
} catch (e: AEADBadTagException) {
|
||||||
|
Log.e(TAG, "Decryption failed", e)
|
||||||
|
TRANSPORT_ERROR
|
||||||
} finally {
|
} finally {
|
||||||
this.state = null
|
this.state = null
|
||||||
closeQuietly(data)
|
closeQuietly(data)
|
||||||
|
|
|
@ -59,7 +59,9 @@ abstract class BackupActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
|
||||||
if (resultCode != RESULT_OK) {
|
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE) {
|
||||||
|
getViewModel().handleChooseFolderResult(result)
|
||||||
|
} else if (resultCode != RESULT_OK) {
|
||||||
Log.w(TAG, "Error in activity result: $requestCode")
|
Log.w(TAG, "Error in activity result: $requestCode")
|
||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.stevesoltys.backup.ui
|
package com.stevesoltys.backup.ui
|
||||||
|
|
||||||
import android.app.Activity.RESULT_OK
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.*
|
import android.content.Intent.*
|
||||||
|
@ -8,23 +7,17 @@ import android.os.Bundle
|
||||||
import android.provider.DocumentsContract.EXTRA_PROMPT
|
import android.provider.DocumentsContract.EXTRA_PROMPT
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.settings.SettingsViewModel
|
|
||||||
|
|
||||||
class BackupLocationFragment : PreferenceFragmentCompat() {
|
class BackupLocationFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
private lateinit var viewModel: SettingsViewModel
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.backup_location, rootKey)
|
setPreferencesFromResource(R.xml.backup_location, rootKey)
|
||||||
|
|
||||||
requireActivity().setTitle(R.string.settings_backup_location_title)
|
requireActivity().setTitle(R.string.settings_backup_location_title)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
|
|
||||||
|
|
||||||
val externalStorage = Preference(requireContext()).apply {
|
val externalStorage = Preference(requireContext()).apply {
|
||||||
setIcon(R.drawable.ic_storage)
|
setIcon(R.drawable.ic_storage)
|
||||||
setTitle(R.string.settings_backup_external_storage)
|
setTitle(R.string.settings_backup_external_storage)
|
||||||
|
@ -43,18 +36,11 @@ class BackupLocationFragment : PreferenceFragmentCompat() {
|
||||||
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
try {
|
try {
|
||||||
val documentChooser = createChooser(openTreeIntent, null)
|
val documentChooser = createChooser(openTreeIntent, null)
|
||||||
startActivityForResult(documentChooser, REQUEST_CODE_OPEN_DOCUMENT_TREE)
|
// start from the activity context, so we can receive and handle the result there
|
||||||
|
requireActivity().startActivityForResult(documentChooser, REQUEST_CODE_OPEN_DOCUMENT_TREE)
|
||||||
} catch (ex: ActivityNotFoundException) {
|
} catch (ex: ActivityNotFoundException) {
|
||||||
Toast.makeText(requireContext(), "Please install a file manager.", LENGTH_LONG).show()
|
Toast.makeText(requireContext(), "Please install a file manager.", LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
|
|
||||||
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE) {
|
|
||||||
viewModel.handleChooseFolderResult(result)
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@ abstract class BackupViewModel(protected val app: Application) : AndroidViewMode
|
||||||
* Will be set to true if this is the initial location.
|
* Will be set to true if this is the initial location.
|
||||||
* It will be false if an existing location was changed.
|
* It will be false if an existing location was changed.
|
||||||
*/
|
*/
|
||||||
internal val onLocationSet: LiveEvent<LocationResult> = locationWasSet
|
internal val onLocationSet: LiveEvent<LocationResult> get() = locationWasSet
|
||||||
|
|
||||||
private val mChooseBackupLocation = MutableLiveEvent<Boolean>()
|
private val mChooseBackupLocation = MutableLiveEvent<Boolean>()
|
||||||
internal val chooseBackupLocation: LiveEvent<Boolean> = mChooseBackupLocation
|
internal val chooseBackupLocation: LiveEvent<Boolean> get() = mChooseBackupLocation
|
||||||
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
||||||
|
|
||||||
internal fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey()
|
internal fun recoveryCodeIsSet() = Backup.keyManager.hasBackupKey()
|
||||||
|
@ -52,12 +52,12 @@ abstract class BackupViewModel(protected val app: Application) : AndroidViewMode
|
||||||
// store backup folder location in settings
|
// store backup folder location in settings
|
||||||
setBackupFolderUri(app, folderUri)
|
setBackupFolderUri(app, folderUri)
|
||||||
|
|
||||||
// notify the UI that the location has been set
|
|
||||||
locationWasSet.setEvent(LocationResult(true, initialSetUp))
|
|
||||||
|
|
||||||
// stop backup service to be sure the old location will get updated
|
// stop backup service to be sure the old location will get updated
|
||||||
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
|
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
|
||||||
|
|
||||||
|
// notify the UI that the location has been set
|
||||||
|
locationWasSet.setEvent(LocationResult(true, initialSetUp))
|
||||||
|
|
||||||
Log.d(TAG, "New storage location chosen: $folderUri")
|
Log.d(TAG, "New storage location chosen: $folderUri")
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Location was rejected: $folderUri")
|
Log.w(TAG, "Location was rejected: $folderUri")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.stevesoltys.backup.ui
|
package com.stevesoltys.backup.ui
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -11,6 +10,7 @@ import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
|
import com.stevesoltys.backup.isDebugBuild
|
||||||
import io.github.novacrypto.bip39.Validation.InvalidChecksumException
|
import io.github.novacrypto.bip39.Validation.InvalidChecksumException
|
||||||
import io.github.novacrypto.bip39.Validation.WordNotFoundException
|
import io.github.novacrypto.bip39.Validation.WordNotFoundException
|
||||||
import kotlinx.android.synthetic.main.fragment_recovery_code_input.*
|
import kotlinx.android.synthetic.main.fragment_recovery_code_input.*
|
||||||
|
@ -37,7 +37,7 @@ class RecoveryCodeInputFragment : Fragment() {
|
||||||
}
|
}
|
||||||
doneButton.setOnClickListener { done() }
|
doneButton.setOnClickListener { done() }
|
||||||
|
|
||||||
if (Build.TYPE == "userdebug") debugPreFill()
|
if (isDebugBuild()) debugPreFill()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInput(): List<CharSequence> = ArrayList<String>(WORD_NUM).apply {
|
private fun getInput(): List<CharSequence> = ArrayList<String>(WORD_NUM).apply {
|
||||||
|
|
110
app/src/main/res/layout/fragment_restore_progress.xml
Normal file
110
app/src/main/res/layout/fragment_restore_progress.xml
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<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="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:tint="?android:colorAccent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_cloud_download"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:text="@string/restore_restoring"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="24sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/backupNameView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:textColor="?android:textColorTertiary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/titleView"
|
||||||
|
tools:text="Pixel 2 XL" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/currentPackageView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
|
||||||
|
tools:text="@string/restore_current_package" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBarIndefinite"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/currentPackageView" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/progressBarIndefinite"
|
||||||
|
tools:progress="50" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/warningView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="@string/restore_finished_warning_only_installed"
|
||||||
|
android:textColor="@android:color/holo_red_dark"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/progressBar"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@string/restore_finished_button"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/warningView"
|
||||||
|
app:layout_constraintVertical_bias="1.0"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -77,5 +77,11 @@
|
||||||
<string name="restore_back">Don\'t restore</string>
|
<string name="restore_back">Don\'t restore</string>
|
||||||
<string name="restore_set_error">An error occurred loading the backups.</string>
|
<string name="restore_set_error">An error occurred loading the backups.</string>
|
||||||
<string name="restore_set_empty_result">No backups found at given location.</string>
|
<string name="restore_set_empty_result">No backups found at given location.</string>
|
||||||
|
<string name="restore_restoring">Restoring Backup</string>
|
||||||
|
<string name="restore_current_package">Restoring %s…</string>
|
||||||
|
<string name="restore_finished_success">Restore complete.</string>
|
||||||
|
<string name="restore_finished_error">An error occurred while restoring the backup.</string>
|
||||||
|
<string name="restore_finished_warning_only_installed">Note that we could only restore data for apps that are already installed.\n\nWhen you install more apps, we will try to restore their data and settings from this backup. So please do not delete it as long as it might still be needed.</string>
|
||||||
|
<string name="restore_finished_button">Finish</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue