Save more information about current storage location
Show storage name in settings
This commit is contained in:
parent
c6f83647b2
commit
54ad762eb1
14 changed files with 80 additions and 57 deletions
|
@ -3,8 +3,8 @@ package com.stevesoltys.backup
|
|||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.stevesoltys.backup.settings.getBackupFolderUri
|
||||
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.createOrGetFile
|
||||
import org.junit.After
|
||||
|
@ -22,7 +22,7 @@ class DocumentsStorageTest {
|
|||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
private val token = getBackupToken(context)
|
||||
private val folderUri = getBackupFolderUri(context)
|
||||
private val folderUri = getStorage(context)
|
||||
private val storage = DocumentsStorage(context, folderUri, token)
|
||||
|
||||
private lateinit var file: DocumentFile
|
||||
|
|
|
@ -31,7 +31,4 @@ class Backup : Application() {
|
|||
|
||||
}
|
||||
|
||||
// TODO fix
|
||||
fun Uri.isOnExternalStorage() = authority == AUTHORITY_STORAGE
|
||||
|
||||
fun isDebugBuild() = Build.TYPE == "userdebug"
|
||||
|
|
|
@ -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 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.settings.SettingsManagerKt.getBackupFolderUri;
|
||||
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
|
||||
|
@ -31,7 +30,7 @@ public class MainActivityController {
|
|||
public static final String DOCUMENT_MIME_TYPE = "application/octet-stream";
|
||||
|
||||
void onBackupButtonClicked(Activity parent) {
|
||||
Uri folderUri = getBackupFolderUri(parent);
|
||||
Uri folderUri = null;
|
||||
if (folderUri == null) {
|
||||
showChooseFolderActivity(parent, true);
|
||||
} else {
|
||||
|
@ -42,7 +41,7 @@ public class MainActivityController {
|
|||
}
|
||||
|
||||
boolean isChangeBackupLocationButtonVisible(Activity parent) {
|
||||
return getBackupFolderUri(parent) != null;
|
||||
return getStorage(parent) != null;
|
||||
}
|
||||
|
||||
private void showChooseFolderActivity(Activity parent, boolean continueToBackup) {
|
||||
|
@ -75,7 +74,7 @@ public class MainActivityController {
|
|||
}
|
||||
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
@ -103,9 +102,6 @@ public class MainActivityController {
|
|||
(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
parent.getContentResolver().takePersistableUriPermission(folderUri, takeFlags);
|
||||
|
||||
// store backup folder location in settings
|
||||
setBackupFolderUri(parent, folderUri);
|
||||
|
||||
if (!continueToBackup) return;
|
||||
|
||||
showCreateBackupActivity(parent);
|
||||
|
|
|
@ -29,6 +29,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
private lateinit var backup: TwoStatePreference
|
||||
private lateinit var autoRestore: TwoStatePreference
|
||||
private lateinit var backupLocation: Preference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
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 {
|
||||
viewModel.chooseBackupLocation()
|
||||
true
|
||||
|
@ -85,6 +86,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
val resolver = requireContext().contentResolver
|
||||
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) {
|
||||
|
|
|
@ -5,21 +5,34 @@ import android.net.Uri
|
|||
import android.preference.PreferenceManager.getDefaultSharedPreferences
|
||||
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_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)
|
||||
.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()
|
||||
}
|
||||
|
||||
fun getBackupFolderUri(context: Context): Uri? {
|
||||
val uriStr = getDefaultSharedPreferences(context).getString(PREF_KEY_BACKUP_URI, null)
|
||||
?: return null
|
||||
return Uri.parse(uriStr)
|
||||
fun getStorage(context: Context): Storage? {
|
||||
val prefs = getDefaultSharedPreferences(context)
|
||||
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,8 +8,8 @@ import com.stevesoltys.backup.header.HeaderReaderImpl
|
|||
import com.stevesoltys.backup.header.HeaderWriterImpl
|
||||
import com.stevesoltys.backup.metadata.MetadataReaderImpl
|
||||
import com.stevesoltys.backup.metadata.MetadataWriterImpl
|
||||
import com.stevesoltys.backup.settings.getBackupFolderUri
|
||||
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.FullBackup
|
||||
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.
|
||||
|
||||
private val storage = DocumentsStorage(context, getBackupFolderUri(context), getBackupToken(context))
|
||||
private val storage = DocumentsStorage(context, getStorage(context), getBackupToken(context))
|
||||
|
||||
private val headerWriter = HeaderWriterImpl()
|
||||
private val headerReader = HeaderReaderImpl()
|
||||
|
|
|
@ -2,9 +2,9 @@ package com.stevesoltys.backup.transport.backup.plugins
|
|||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.stevesoltys.backup.settings.Storage
|
||||
import com.stevesoltys.backup.settings.getAndSaveNewBackupToken
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
@ -19,10 +19,10 @@ private const val MIME_TYPE = "application/octet-stream"
|
|||
|
||||
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 {
|
||||
val folderUri = parentFolder ?: return@lazy null
|
||||
val folderUri = storage?.uri ?: return@lazy null
|
||||
// [fromTreeUri] should only return null when SDK_INT < 21
|
||||
val parent = DocumentFile.fromTreeUri(context, folderUri) ?: throw AssertionError()
|
||||
try {
|
||||
|
|
|
@ -3,15 +3,12 @@ package com.stevesoltys.backup.ui.storage
|
|||
import android.app.Application
|
||||
import android.app.backup.BackupProgress
|
||||
import android.app.backup.IBackupObserver
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.stevesoltys.backup.Backup
|
||||
import com.stevesoltys.backup.R
|
||||
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
|
||||
|
||||
private val TAG = BackupStorageViewModel::class.java.simpleName
|
||||
|
@ -21,19 +18,11 @@ internal class BackupStorageViewModel(private val app: Application) : StorageVie
|
|||
override val isRestoreOperation = false
|
||||
|
||||
override fun onLocationSet(uri: Uri) {
|
||||
// store backup folder location in settings
|
||||
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))
|
||||
saveStorage(uri)
|
||||
|
||||
// use a new backup token
|
||||
getAndSaveNewBackupToken(app)
|
||||
|
||||
Log.d(TAG, "New storage location chosen: $uri")
|
||||
|
||||
// initialize the new location
|
||||
val observer = InitializationObserver()
|
||||
Backup.backupManager.initializeTransports(arrayOf(TRANSPORT_ID), observer)
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package com.stevesoltys.backup.ui.storage
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
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.restore.plugins.DocumentsProviderRestorePlugin
|
||||
|
||||
|
@ -19,13 +16,7 @@ internal class RestoreStorageViewModel(private val app: Application) : StorageVi
|
|||
|
||||
override fun onLocationSet(uri: Uri) {
|
||||
if (hasBackup(uri)) {
|
||||
// store backup folder location in settings
|
||||
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")
|
||||
saveStorage(uri)
|
||||
|
||||
mLocationChecked.setEvent(LocationResult())
|
||||
} else {
|
||||
|
|
|
@ -36,6 +36,10 @@ data class StorageRoot(
|
|||
internal val supportsEject: Boolean,
|
||||
internal val enabled: Boolean = true) {
|
||||
|
||||
internal val uri: Uri by lazy {
|
||||
DocumentsContract.buildTreeDocumentUri(authority, documentId)
|
||||
}
|
||||
|
||||
fun isInternal(): Boolean {
|
||||
return authority == AUTHORITY_STORAGE && !supportsEject
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.stevesoltys.backup.ui.storage
|
|||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
import android.os.Bundle
|
||||
import android.provider.DocumentsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.INVISIBLE
|
||||
|
@ -72,8 +71,9 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener {
|
|||
}
|
||||
|
||||
override fun onClick(root: StorageRoot) {
|
||||
viewModel.onStorageRootChosen(root)
|
||||
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
|
||||
FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)
|
||||
|
|
|
@ -6,15 +6,21 @@ import android.content.Intent
|
|||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.stevesoltys.backup.isOnExternalStorage
|
||||
import com.stevesoltys.backup.settings.getBackupFolderUri
|
||||
import com.stevesoltys.backup.R
|
||||
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.MutableLiveEvent
|
||||
|
||||
private val TAG = StorageViewModel::class.java.simpleName
|
||||
|
||||
internal abstract class StorageViewModel(private val app: Application) : AndroidViewModel(app), RemovableStorageListener {
|
||||
|
||||
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
|
||||
|
||||
private val storageRootFetcher by lazy { StorageRootFetcher(app) }
|
||||
private var storageRoot: StorageRoot? = null
|
||||
|
||||
abstract val isRestoreOperation: Boolean
|
||||
|
||||
companion object {
|
||||
internal fun validLocationIsSet(context: Context): Boolean {
|
||||
val uri = getBackupFolderUri(context) ?: return false
|
||||
if (uri.isOnExternalStorage()) return true // TODO use ejectable instead
|
||||
val file = DocumentFile.fromTreeUri(context, uri) ?: return false
|
||||
val storage = getStorage(context) ?: return false
|
||||
if (storage.ejectable) return true
|
||||
val file = DocumentFile.fromTreeUri(context, storage.uri) ?: return false
|
||||
return file.isDirectory
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +57,9 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
|||
|
||||
override fun onStorageChanged() = loadStorageRoots()
|
||||
|
||||
fun onStorageRootChosen(root: StorageRoot) {
|
||||
storageRoot = root
|
||||
}
|
||||
|
||||
internal fun onUriPermissionGranted(result: Intent?) {
|
||||
val uri = result?.data ?: return
|
||||
|
@ -66,6 +76,23 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
|||
|
||||
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() {
|
||||
storageRootFetcher.setRemovableStorageListener(null)
|
||||
super.onCleared()
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
<string name="settings_backup_location_picker">Choose 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_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_auto_restore_title">Automatic restore</string>
|
||||
<string name="settings_auto_restore_summary">When reinstalling an app, restore backed up settings and data</string>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
app:dependency="backup"
|
||||
app:icon="@drawable/ic_storage"
|
||||
app:key="backup_location"
|
||||
app:summary="@string/settings_backup_external_storage"
|
||||
app:summary="@string/settings_backup_location_none"
|
||||
app:title="@string/settings_backup_location" />
|
||||
|
||||
<androidx.preference.SwitchPreferenceCompat
|
||||
|
|
Loading…
Reference in a new issue