Save more information about current storage location

Show storage name in settings
This commit is contained in:
Torsten Grote 2019-09-13 14:37:32 -03:00
parent c6f83647b2
commit 54ad762eb1
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
14 changed files with 80 additions and 57 deletions

View file

@ -3,8 +3,8 @@ package com.stevesoltys.backup
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import com.stevesoltys.backup.settings.getBackupFolderUri
import com.stevesoltys.backup.settings.getBackupToken import com.stevesoltys.backup.settings.getBackupToken
import com.stevesoltys.backup.settings.getStorage
import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage import com.stevesoltys.backup.transport.backup.plugins.DocumentsStorage
import com.stevesoltys.backup.transport.backup.plugins.createOrGetFile import com.stevesoltys.backup.transport.backup.plugins.createOrGetFile
import org.junit.After import org.junit.After
@ -22,7 +22,7 @@ class DocumentsStorageTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val token = getBackupToken(context) private val token = getBackupToken(context)
private val folderUri = getBackupFolderUri(context) private val folderUri = getStorage(context)
private val storage = DocumentsStorage(context, folderUri, token) private val storage = DocumentsStorage(context, folderUri, token)
private lateinit var file: DocumentFile private lateinit var file: DocumentFile

View file

@ -31,7 +31,4 @@ class Backup : Application() {
} }
// TODO fix
fun Uri.isOnExternalStorage() = authority == AUTHORITY_STORAGE
fun isDebugBuild() = Build.TYPE == "userdebug" fun isDebugBuild() = Build.TYPE == "userdebug"

View file

@ -18,9 +18,8 @@ import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION; import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_BACKUP_REQUEST_CODE; import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_BACKUP_REQUEST_CODE;
import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_REQUEST_CODE; import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_REQUEST_CODE;
import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupFolderUri;
import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupPassword; import static com.stevesoltys.backup.settings.SettingsManagerKt.getBackupPassword;
import static com.stevesoltys.backup.settings.SettingsManagerKt.setBackupFolderUri; import static com.stevesoltys.backup.settings.SettingsManagerKt.getStorage;
/** /**
* @author Steve Soltys * @author Steve Soltys
@ -31,7 +30,7 @@ public class MainActivityController {
public static final String DOCUMENT_MIME_TYPE = "application/octet-stream"; public static final String DOCUMENT_MIME_TYPE = "application/octet-stream";
void onBackupButtonClicked(Activity parent) { void onBackupButtonClicked(Activity parent) {
Uri folderUri = getBackupFolderUri(parent); Uri folderUri = null;
if (folderUri == null) { if (folderUri == null) {
showChooseFolderActivity(parent, true); showChooseFolderActivity(parent, true);
} else { } else {
@ -42,7 +41,7 @@ public class MainActivityController {
} }
boolean isChangeBackupLocationButtonVisible(Activity parent) { boolean isChangeBackupLocationButtonVisible(Activity parent) {
return getBackupFolderUri(parent) != null; return getStorage(parent) != null;
} }
private void showChooseFolderActivity(Activity parent, boolean continueToBackup) { private void showChooseFolderActivity(Activity parent, boolean continueToBackup) {
@ -75,7 +74,7 @@ public class MainActivityController {
} }
boolean onAutomaticBackupsButtonClicked(Activity parent) { boolean onAutomaticBackupsButtonClicked(Activity parent) {
if (getBackupFolderUri(parent) == null || getBackupPassword(parent) == null) { if (getStorage(parent) == null || getBackupPassword(parent) == null) {
Toast.makeText(parent, "Please make at least one manual backup first.", Toast.LENGTH_SHORT).show(); Toast.makeText(parent, "Please make at least one manual backup first.", Toast.LENGTH_SHORT).show();
return false; return false;
} }
@ -103,9 +102,6 @@ public class MainActivityController {
(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION); (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
parent.getContentResolver().takePersistableUriPermission(folderUri, takeFlags); parent.getContentResolver().takePersistableUriPermission(folderUri, takeFlags);
// store backup folder location in settings
setBackupFolderUri(parent, folderUri);
if (!continueToBackup) return; if (!continueToBackup) return;
showCreateBackupActivity(parent); showCreateBackupActivity(parent);

View file

@ -29,6 +29,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var backup: TwoStatePreference private lateinit var backup: TwoStatePreference
private lateinit var autoRestore: TwoStatePreference private lateinit var autoRestore: TwoStatePreference
private lateinit var backupLocation: Preference
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey) setPreferencesFromResource(R.xml.settings, rootKey)
@ -49,7 +50,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
} }
} }
val backupLocation = findPreference<Preference>("backup_location")!! backupLocation = findPreference<Preference>("backup_location")!!
backupLocation.setOnPreferenceClickListener { backupLocation.setOnPreferenceClickListener {
viewModel.chooseBackupLocation() viewModel.chooseBackupLocation()
true true
@ -85,6 +86,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
val resolver = requireContext().contentResolver val resolver = requireContext().contentResolver
autoRestore.isChecked = Settings.Secure.getInt(resolver, BACKUP_AUTO_RESTORE, 1) == 1 autoRestore.isChecked = Settings.Secure.getInt(resolver, BACKUP_AUTO_RESTORE, 1) == 1
// TODO add time of last backup here
val storageName = getStorage(requireContext())?.name
backupLocation.summary = storageName ?: getString(R.string.settings_backup_location_none )
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View file

@ -5,21 +5,34 @@ import android.net.Uri
import android.preference.PreferenceManager.getDefaultSharedPreferences import android.preference.PreferenceManager.getDefaultSharedPreferences
import java.util.* import java.util.*
private const val PREF_KEY_BACKUP_URI = "backupUri" private const val PREF_KEY_STORAGE_URI = "storageUri"
private const val PREF_KEY_STORAGE_NAME = "storageName"
private const val PREF_KEY_STORAGE_EJECTABLE = "storageEjectable"
private const val PREF_KEY_BACKUP_TOKEN = "backupToken" private const val PREF_KEY_BACKUP_TOKEN = "backupToken"
private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword" private const val PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"
fun setBackupFolderUri(context: Context, uri: Uri) { data class Storage(
val uri: Uri,
val name: String,
val ejectable: Boolean
)
fun setStorage(context: Context, storage: Storage) {
getDefaultSharedPreferences(context) getDefaultSharedPreferences(context)
.edit() .edit()
.putString(PREF_KEY_BACKUP_URI, uri.toString()) .putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
.putString(PREF_KEY_STORAGE_NAME, storage.name)
.putBoolean(PREF_KEY_STORAGE_EJECTABLE, storage.ejectable)
.apply() .apply()
} }
fun getBackupFolderUri(context: Context): Uri? { fun getStorage(context: Context): Storage? {
val uriStr = getDefaultSharedPreferences(context).getString(PREF_KEY_BACKUP_URI, null) val prefs = getDefaultSharedPreferences(context)
?: return null val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
return Uri.parse(uriStr) val uri = Uri.parse(uriStr)
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) ?: throw IllegalStateException()
val ejectable = prefs.getBoolean(PREF_KEY_STORAGE_EJECTABLE, false)
return Storage(uri, name, ejectable)
} }
/** /**

View file

@ -8,8 +8,8 @@ import com.stevesoltys.backup.header.HeaderReaderImpl
import com.stevesoltys.backup.header.HeaderWriterImpl import com.stevesoltys.backup.header.HeaderWriterImpl
import com.stevesoltys.backup.metadata.MetadataReaderImpl import com.stevesoltys.backup.metadata.MetadataReaderImpl
import com.stevesoltys.backup.metadata.MetadataWriterImpl import com.stevesoltys.backup.metadata.MetadataWriterImpl
import com.stevesoltys.backup.settings.getBackupFolderUri
import com.stevesoltys.backup.settings.getBackupToken import com.stevesoltys.backup.settings.getBackupToken
import com.stevesoltys.backup.settings.getStorage
import com.stevesoltys.backup.transport.backup.BackupCoordinator import com.stevesoltys.backup.transport.backup.BackupCoordinator
import com.stevesoltys.backup.transport.backup.FullBackup import com.stevesoltys.backup.transport.backup.FullBackup
import com.stevesoltys.backup.transport.backup.InputFactory import com.stevesoltys.backup.transport.backup.InputFactory
@ -26,7 +26,7 @@ class PluginManager(context: Context) {
// We can think about using an injection framework such as Dagger to simplify this. // We can think about using an injection framework such as Dagger to simplify this.
private val storage = DocumentsStorage(context, getBackupFolderUri(context), getBackupToken(context)) private val storage = DocumentsStorage(context, getStorage(context), getBackupToken(context))
private val headerWriter = HeaderWriterImpl() private val headerWriter = HeaderWriterImpl()
private val headerReader = HeaderReaderImpl() private val headerReader = HeaderReaderImpl()

View file

@ -2,9 +2,9 @@ package com.stevesoltys.backup.transport.backup.plugins
import android.content.Context import android.content.Context
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.net.Uri
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.backup.settings.Storage
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -19,10 +19,10 @@ private const val MIME_TYPE = "application/octet-stream"
private val TAG = DocumentsStorage::class.java.simpleName private val TAG = DocumentsStorage::class.java.simpleName
class DocumentsStorage(private val context: Context, parentFolder: Uri?, token: Long) { class DocumentsStorage(private val context: Context, storage: Storage?, token: Long) {
internal val rootBackupDir: DocumentFile? by lazy { internal val rootBackupDir: DocumentFile? by lazy {
val folderUri = parentFolder ?: return@lazy null val folderUri = storage?.uri ?: return@lazy null
// [fromTreeUri] should only return null when SDK_INT < 21 // [fromTreeUri] should only return null when SDK_INT < 21
val parent = DocumentFile.fromTreeUri(context, folderUri) ?: throw AssertionError() val parent = DocumentFile.fromTreeUri(context, folderUri) ?: throw AssertionError()
try { try {

View file

@ -3,15 +3,12 @@ package com.stevesoltys.backup.ui.storage
import android.app.Application import android.app.Application
import android.app.backup.BackupProgress import android.app.backup.BackupProgress
import android.app.backup.IBackupObserver import android.app.backup.IBackupObserver
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.backup.Backup import com.stevesoltys.backup.Backup
import com.stevesoltys.backup.R import com.stevesoltys.backup.R
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
import com.stevesoltys.backup.settings.setBackupFolderUri
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
import com.stevesoltys.backup.transport.TRANSPORT_ID import com.stevesoltys.backup.transport.TRANSPORT_ID
private val TAG = BackupStorageViewModel::class.java.simpleName private val TAG = BackupStorageViewModel::class.java.simpleName
@ -21,19 +18,11 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
override val isRestoreOperation = false override val isRestoreOperation = false
override fun onLocationSet(uri: Uri) { override fun onLocationSet(uri: Uri) {
// store backup folder location in settings saveStorage(uri)
setBackupFolderUri(app, uri)
// TODO also set the storage name
// stop backup service to be sure the old location will get updated
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
// use a new backup token // use a new backup token
getAndSaveNewBackupToken(app) getAndSaveNewBackupToken(app)
Log.d(TAG, "New storage location chosen: $uri")
// initialize the new location // initialize the new location
val observer = InitializationObserver() val observer = InitializationObserver()
Backup.backupManager.initializeTransports(arrayOf(TRANSPORT_ID), observer) Backup.backupManager.initializeTransports(arrayOf(TRANSPORT_ID), observer)

View file

@ -1,13 +1,10 @@
package com.stevesoltys.backup.ui.storage package com.stevesoltys.backup.ui.storage
import android.app.Application import android.app.Application
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.backup.R import com.stevesoltys.backup.R
import com.stevesoltys.backup.settings.setBackupFolderUri
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
import com.stevesoltys.backup.transport.backup.plugins.DIRECTORY_ROOT import com.stevesoltys.backup.transport.backup.plugins.DIRECTORY_ROOT
import com.stevesoltys.backup.transport.restore.plugins.DocumentsProviderRestorePlugin import com.stevesoltys.backup.transport.restore.plugins.DocumentsProviderRestorePlugin
@ -19,13 +16,7 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
override fun onLocationSet(uri: Uri) { override fun onLocationSet(uri: Uri) {
if (hasBackup(uri)) { if (hasBackup(uri)) {
// store backup folder location in settings saveStorage(uri)
setBackupFolderUri(app, uri)
// stop backup service to be sure the old location will get updated
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
Log.d(TAG, "New storage location chosen: $uri")
mLocationChecked.setEvent(LocationResult()) mLocationChecked.setEvent(LocationResult())
} else { } else {

View file

@ -36,6 +36,10 @@ data class StorageRoot(
internal val supportsEject: Boolean, internal val supportsEject: Boolean,
internal val enabled: Boolean = true) { internal val enabled: Boolean = true) {
internal val uri: Uri by lazy {
DocumentsContract.buildTreeDocumentUri(authority, documentId)
}
fun isInternal(): Boolean { fun isInternal(): Boolean {
return authority == AUTHORITY_STORAGE && !supportsEject return authority == AUTHORITY_STORAGE && !supportsEject
} }

View file

@ -3,7 +3,6 @@ package com.stevesoltys.backup.ui.storage
import android.content.Intent import android.content.Intent
import android.content.Intent.* import android.content.Intent.*
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract
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
@ -72,8 +71,9 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
} }
override fun onClick(root: StorageRoot) { override fun onClick(root: StorageRoot) {
viewModel.onStorageRootChosen(root)
val intent = Intent(requireContext(), PermissionGrantActivity::class.java) val intent = Intent(requireContext(), PermissionGrantActivity::class.java)
intent.data = DocumentsContract.buildTreeDocumentUri(root.authority, root.documentId) intent.data = root.uri
intent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION or intent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION) FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE) startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)

View file

@ -6,15 +6,21 @@ import android.content.Intent
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.stevesoltys.backup.isOnExternalStorage import com.stevesoltys.backup.R
import com.stevesoltys.backup.settings.getBackupFolderUri import com.stevesoltys.backup.settings.Storage
import com.stevesoltys.backup.settings.getStorage
import com.stevesoltys.backup.settings.setStorage
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
import com.stevesoltys.backup.ui.LiveEvent import com.stevesoltys.backup.ui.LiveEvent
import com.stevesoltys.backup.ui.MutableLiveEvent import com.stevesoltys.backup.ui.MutableLiveEvent
private val TAG = StorageViewModel::class.java.simpleName
internal abstract class StorageViewModel(private val app: Application) : AndroidViewModel(app), RemovableStorageListener { internal abstract class StorageViewModel(private val app: Application) : AndroidViewModel(app), RemovableStorageListener {
private val mStorageRoots = MutableLiveData<List<StorageRoot>>() private val mStorageRoots = MutableLiveData<List<StorageRoot>>()
@ -27,14 +33,15 @@ internal abstract class StorageViewModel(private val app: Application) : Android
internal val locationChecked: LiveEvent<LocationResult> get() = mLocationChecked internal val locationChecked: LiveEvent<LocationResult> get() = mLocationChecked
private val storageRootFetcher by lazy { StorageRootFetcher(app) } private val storageRootFetcher by lazy { StorageRootFetcher(app) }
private var storageRoot: StorageRoot? = null
abstract val isRestoreOperation: Boolean abstract val isRestoreOperation: Boolean
companion object { companion object {
internal fun validLocationIsSet(context: Context): Boolean { internal fun validLocationIsSet(context: Context): Boolean {
val uri = getBackupFolderUri(context) ?: return false val storage = getStorage(context) ?: return false
if (uri.isOnExternalStorage()) return true // TODO use ejectable instead if (storage.ejectable) return true
val file = DocumentFile.fromTreeUri(context, uri) ?: return false val file = DocumentFile.fromTreeUri(context, storage.uri) ?: return false
return file.isDirectory return file.isDirectory
} }
} }
@ -50,6 +57,9 @@ internal abstract class StorageViewModel(private val app: Application) : Android
override fun onStorageChanged() = loadStorageRoots() override fun onStorageChanged() = loadStorageRoots()
fun onStorageRootChosen(root: StorageRoot) {
storageRoot = root
}
internal fun onUriPermissionGranted(result: Intent?) { internal fun onUriPermissionGranted(result: Intent?) {
val uri = result?.data ?: return val uri = result?.data ?: return
@ -66,6 +76,23 @@ internal abstract class StorageViewModel(private val app: Application) : Android
abstract fun onLocationSet(uri: Uri) abstract fun onLocationSet(uri: Uri)
protected fun saveStorage(uri: Uri) {
// store backup storage location in settings
val root = storageRoot ?: throw IllegalStateException()
val name = if (root.isInternal()) {
"${root.title} (${app.getString(R.string.settings_backup_location_internal)})"
} else {
root.title
}
val storage = Storage(uri, name, root.supportsEject)
setStorage(app, storage)
// stop backup service to be sure the old location will get updated
app.stopService(Intent(app, ConfigurableBackupTransportService::class.java))
Log.d(TAG, "New storage location saved: $uri")
}
override fun onCleared() { override fun onCleared() {
storageRootFetcher.setRemovableStorageListener(null) storageRootFetcher.setRemovableStorageListener(null)
super.onCleared() super.onCleared()

View file

@ -29,7 +29,8 @@
<string name="settings_backup_location_picker">Choose backup location</string> <string name="settings_backup_location_picker">Choose backup location</string>
<string name="settings_backup_location_title">Backup Location</string> <string name="settings_backup_location_title">Backup Location</string>
<string name="settings_backup_location_invalid">The chosen location can not be used.</string> <string name="settings_backup_location_invalid">The chosen location can not be used.</string>
<string name="settings_backup_external_storage">External Storage</string> <string name="settings_backup_location_none">None</string>
<string name="settings_backup_location_internal">Internal 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_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> <string name="settings_auto_restore_title">Automatic restore</string>
<string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string> <string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string>

View file

@ -10,7 +10,7 @@
app:dependency="backup" app:dependency="backup"
app:icon="@drawable/ic_storage" app:icon="@drawable/ic_storage"
app:key="backup_location" app:key="backup_location"
app:summary="@string/settings_backup_external_storage" app:summary="@string/settings_backup_location_none"
app:title="@string/settings_backup_location" /> app:title="@string/settings_backup_location" />
<androidx.preference.SwitchPreferenceCompat <androidx.preference.SwitchPreferenceCompat