Show Backup Location screen before letting user choose backup folder
This screen can also be reached by tapping the previously inactive backup location setting.
This commit is contained in:
parent
3e64c3686f
commit
4c79d41963
9 changed files with 163 additions and 51 deletions
|
@ -0,0 +1,59 @@
|
|||
package com.stevesoltys.backup.settings
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.stevesoltys.backup.R
|
||||
|
||||
private val TAG = BackupLocationFragment::class.java.name
|
||||
|
||||
class BackupLocationFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private lateinit var viewModel: SettingsViewModel
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.backup_location, rootKey)
|
||||
|
||||
requireActivity().setTitle(R.string.settings_backup_location_title)
|
||||
|
||||
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
|
||||
|
||||
val externalStorage = Preference(requireContext()).apply {
|
||||
setIcon(R.drawable.ic_storage)
|
||||
setTitle(R.string.settings_backup_external_storage)
|
||||
setOnPreferenceClickListener {
|
||||
showChooseFolderActivity()
|
||||
true
|
||||
}
|
||||
}
|
||||
preferenceScreen.addPreference(externalStorage)
|
||||
}
|
||||
|
||||
private fun showChooseFolderActivity() {
|
||||
val openTreeIntent = Intent(ACTION_OPEN_DOCUMENT_TREE)
|
||||
openTreeIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
try {
|
||||
val documentChooser = createChooser(openTreeIntent, null)
|
||||
startActivityForResult(documentChooser, REQUEST_CODE_OPEN_DOCUMENT_TREE)
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.stevesoltys.backup.settings
|
||||
|
||||
import android.app.Application
|
||||
import android.util.ByteStringUtils.toHexString
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.stevesoltys.backup.LiveEvent
|
||||
import com.stevesoltys.backup.MutableLiveEvent
|
||||
|
@ -33,7 +34,8 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica
|
|||
internal val confirmButtonClicked: LiveEvent<Boolean> = mConfirmButtonClicked
|
||||
internal fun onConfirmButtonClicked() = mConfirmButtonClicked.setEvent(true)
|
||||
|
||||
internal val recoveryCodeSaved = MutableLiveEvent<Boolean>()
|
||||
private val mRecoveryCodeSaved = MutableLiveEvent<Boolean>()
|
||||
internal val recoveryCodeSaved: LiveEvent<Boolean> = mRecoveryCodeSaved
|
||||
|
||||
@Throws(WordNotFoundException::class, InvalidChecksumException::class)
|
||||
fun validateAndContinue(input: List<CharSequence>) {
|
||||
|
@ -47,7 +49,11 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica
|
|||
val mnemonic = input.joinToString(" ")
|
||||
val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "")
|
||||
KeyManager.storeBackupKey(seed)
|
||||
recoveryCodeSaved.setEvent(true)
|
||||
|
||||
// TODO remove once encryption/decryption uses key from KeyStore
|
||||
setBackupPassword(getApplication(), toHexString(seed))
|
||||
|
||||
mRecoveryCodeSaved.setEvent(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
package com.stevesoltys.backup.settings
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import android.widget.Toast.LENGTH_SHORT
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.stevesoltys.backup.LiveEventHandler
|
||||
import com.stevesoltys.backup.R
|
||||
|
||||
private val TAG = SettingsActivity::class.java.name
|
||||
|
@ -19,7 +15,6 @@ private val TAG = SettingsActivity::class.java.name
|
|||
const val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1
|
||||
const val REQUEST_CODE_RECOVERY_CODE = 2
|
||||
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var viewModel: SettingsViewModel
|
||||
|
@ -30,18 +25,25 @@ class SettingsActivity : AppCompatActivity() {
|
|||
setContentView(R.layout.activity_settings)
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
|
||||
viewModel.onLocationSet.observeEvent(this, LiveEventHandler { wasEmptyBefore ->
|
||||
if (wasEmptyBefore) showFragment(SettingsFragment())
|
||||
else supportFragmentManager.popBackStack()
|
||||
})
|
||||
viewModel.chooseBackupLocation.observeEvent(this, LiveEventHandler { show ->
|
||||
if (show) showFragment(BackupLocationFragment(), true)
|
||||
})
|
||||
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (savedInstanceState == null) showFragment(SettingsFragment())
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
|
||||
if (resultCode != RESULT_OK) {
|
||||
Log.w(TAG, "Error in activity result: $requestCode")
|
||||
finishAfterTransition()
|
||||
}
|
||||
|
||||
if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE) {
|
||||
viewModel.handleChooseFolderResult(result)
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, result)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,52 +55,28 @@ class SettingsActivity : AppCompatActivity() {
|
|||
if (!viewModel.recoveryCodeIsSet()) {
|
||||
showRecoveryCodeActivity()
|
||||
} else if (!viewModel.locationIsSet()) {
|
||||
showChooseFolderActivity()
|
||||
showFragment(BackupLocationFragment())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.settings_menu, menu)
|
||||
if (resources.getBoolean(R.bool.show_restore_in_settings)) {
|
||||
menu.findItem(R.id.action_restore).isVisible = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when {
|
||||
item.itemId == android.R.id.home -> {
|
||||
onBackPressed()
|
||||
true
|
||||
}
|
||||
item.itemId == R.id.action_backup -> {
|
||||
Toast.makeText(this, "Not yet implemented", LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
item.itemId == R.id.action_restore -> {
|
||||
Toast.makeText(this, "Not yet implemented", LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRecoveryCodeActivity() {
|
||||
val intent = Intent(this, RecoveryCodeActivity::class.java)
|
||||
startActivityForResult(intent, REQUEST_CODE_RECOVERY_CODE)
|
||||
}
|
||||
|
||||
private fun showChooseFolderActivity() {
|
||||
val openTreeIntent = Intent(ACTION_OPEN_DOCUMENT_TREE)
|
||||
openTreeIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
// TODO StringRes
|
||||
try {
|
||||
val documentChooser = createChooser(openTreeIntent, "Select the backup location")
|
||||
startActivityForResult(documentChooser, REQUEST_CODE_OPEN_DOCUMENT_TREE)
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
Toast.makeText(this, "Please install a file manager.", LENGTH_LONG).show()
|
||||
}
|
||||
private fun showFragment(f: Fragment, addToBackStack: Boolean = false) {
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragment, f)
|
||||
if (addToBackStack) fragmentTransaction.addToBackStack(null)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@ import com.stevesoltys.backup.R
|
|||
import android.content.Context.BACKUP_SERVICE
|
||||
import android.os.ServiceManager.getService
|
||||
import android.provider.Settings.Secure.BACKUP_AUTO_RESTORE
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.Preference.OnPreferenceChangeListener
|
||||
|
||||
|
@ -24,14 +29,19 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
private lateinit var backupManager: IBackupManager
|
||||
|
||||
private lateinit var viewModel: SettingsViewModel
|
||||
|
||||
private lateinit var backup: TwoStatePreference
|
||||
private lateinit var autoRestore: TwoStatePreference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
backupManager = IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE))
|
||||
|
||||
viewModel = ViewModelProviders.of(requireActivity()).get(SettingsViewModel::class.java)
|
||||
|
||||
backup = findPreference("backup") as TwoStatePreference
|
||||
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||
val enabled = newValue as Boolean
|
||||
|
@ -45,6 +55,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
val backupLocation = findPreference("backup_location")
|
||||
backupLocation.setOnPreferenceClickListener {
|
||||
viewModel.chooseBackupLocation()
|
||||
true
|
||||
}
|
||||
|
||||
autoRestore = findPreference("auto_restore") as TwoStatePreference
|
||||
autoRestore.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
|
||||
val enabled = newValue as Boolean
|
||||
|
@ -62,6 +78,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
// we need to re-set the title when returning to this fragment
|
||||
requireActivity().setTitle(R.string.app_name)
|
||||
|
||||
try {
|
||||
backup.isChecked = backupManager.isBackupEnabled
|
||||
backup.isEnabled = true
|
||||
|
@ -74,4 +93,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
autoRestore.isChecked = Settings.Secure.getInt(resolver, BACKUP_AUTO_RESTORE, 1) == 1
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.settings_menu, menu)
|
||||
if (resources.getBoolean(R.bool.show_restore_in_settings)) {
|
||||
menu.findItem(R.id.action_restore).isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when {
|
||||
item.itemId == R.id.action_backup -> {
|
||||
Toast.makeText(requireContext(), "Not yet implemented", Toast.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
item.itemId == R.id.action_restore -> {
|
||||
Toast.makeText(requireContext(), "Not yet implemented", Toast.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,12 +5,25 @@ import android.content.Intent
|
|||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.stevesoltys.backup.LiveEvent
|
||||
import com.stevesoltys.backup.MutableLiveEvent
|
||||
import com.stevesoltys.backup.security.KeyManager
|
||||
|
||||
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val app = application
|
||||
|
||||
private val mLocationWasSet = MutableLiveEvent<Boolean>()
|
||||
/**
|
||||
* Will be set to true if this is the initial location.
|
||||
* It will be false if an existing location was changed.
|
||||
*/
|
||||
internal val onLocationSet: LiveEvent<Boolean> = locationWasSet
|
||||
|
||||
private val mChooseBackupLocation = MutableLiveEvent<Boolean>()
|
||||
internal val chooseBackupLocation: LiveEvent<Boolean> = mChooseBackupLocation
|
||||
internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true)
|
||||
|
||||
fun recoveryCodeIsSet() = KeyManager.hasBackupKey()
|
||||
fun locationIsSet() = getBackupFolderUri(getApplication()) != null
|
||||
|
||||
|
@ -21,8 +34,14 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||
val takeFlags = result.flags and (FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
app.contentResolver.takePersistableUriPermission(folderUri, takeFlags)
|
||||
|
||||
// check if this is initial set-up or a later change
|
||||
val wasEmptyBefore = getBackupFolderUri(app) == null
|
||||
|
||||
// store backup folder location in settings
|
||||
setBackupFolderUri(app, folderUri)
|
||||
|
||||
// notify the UI that the location has been set
|
||||
mLocationWasSet.setEvent(wasEmptyBefore)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment"
|
||||
android:name="com.stevesoltys.backup.settings.SettingsFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
|
@ -26,6 +26,8 @@
|
|||
<!-- Settings -->
|
||||
<string name="settings_backup">Backup my data</string>
|
||||
<string name="settings_backup_location">Backup location</string>
|
||||
<string name="settings_backup_location_title">Backup Location</string>
|
||||
<string name="settings_backup_location_info">Choose where to store your backups. More options might get added in the future.</string>
|
||||
<string name="settings_backup_external_storage">External Storage</string>
|
||||
<string name="settings_info">All backups are encrypted on your phone. To restore from backup you will need your 12-word recovery code.</string>
|
||||
<string name="settings_auto_restore_title">Automatic restore</string>
|
||||
|
|
11
app/src/main/res/xml/backup_location.xml
Normal file
11
app/src/main/res/xml/backup_location.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<androidx.preference.PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.preference.Preference
|
||||
app:allowDividerAbove="true"
|
||||
app:allowDividerBelow="false"
|
||||
app:icon="@drawable/ic_info_outline"
|
||||
app:selectable="false"
|
||||
app:order="1337"
|
||||
app:summary="@string/settings_backup_location_info" />
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
|
@ -10,7 +10,6 @@
|
|||
app:dependency="backup"
|
||||
app:icon="@drawable/ic_storage"
|
||||
app:key="backup_location"
|
||||
app:selectable="false"
|
||||
app:summary="@string/settings_backup_external_storage"
|
||||
app:title="@string/settings_backup_location" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue