diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/SafStorageOptions.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/SafStorageOptions.kt new file mode 100644 index 00000000..3ad63d8d --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/SafStorageOptions.kt @@ -0,0 +1,123 @@ +package com.stevesoltys.seedvault.ui.storage + +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_VIEW +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.net.Uri +import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption +import com.stevesoltys.seedvault.ui.storage.StorageRootResolver.getIcon + +private const val NEXTCLOUD_PACKAGE = "com.nextcloud.client" +private const val NEXTCLOUD_ACTIVITY = "com.owncloud.android.authentication.AuthenticatorActivity" + +/** + * A class for storage option placeholders that need to be shown under certain circumstances. + * E.g. a way to install an app when needed for restore. + */ +internal class SafStorageOptions( + private val context: Context, + private val isRestore: Boolean, + private val whitelistedAuthorities: Array, +) { + + private val packageManager = context.packageManager + + internal fun checkOrAddExtraRoots(roots: ArrayList) { + checkOrAddUsbRoot(roots) + checkOrAddNextCloudRoot(roots) + } + + private fun checkOrAddUsbRoot(roots: ArrayList) { + if (doNotInclude(AUTHORITY_STORAGE, roots) { it.isUsb }) return + + val root = SafOption( + authority = AUTHORITY_STORAGE, + rootId = "usb", + documentId = "fake", + icon = getIcon(context, AUTHORITY_STORAGE, "usb", 0), + title = context.getString(R.string.storage_fake_drive_title), + summary = context.getString(R.string.storage_fake_drive_summary), + availableBytes = null, + isUsb = true, + requiresNetwork = false, + enabled = false + ) + roots.add(root) + } + + /** + * This adds a fake Nextcloud entry if no real one was found. + * + * If Nextcloud is *not* installed, + * the user will always have the option to install it by clicking the entry. + * + * If it *is* installed and this is restore, the user can set up a new account by clicking. + * FIXME: If this isn't restore, the entry should be disabled, + * because we don't know if there's just no account or an activated passcode + * (which hides existing accounts). + */ + private fun checkOrAddNextCloudRoot(roots: ArrayList) { + if (doNotInclude(AUTHORITY_NEXTCLOUD, roots)) return + + val intent = Intent().apply { + addFlags(FLAG_ACTIVITY_NEW_TASK) + setClassName(NEXTCLOUD_PACKAGE, NEXTCLOUD_ACTIVITY) + // setting a nc:// Uri prevents FirstRunActivity to show + data = Uri.parse("nc://login/server:") + putExtra("onlyAdd", true) + } + val marketIntent = + Intent(ACTION_VIEW, Uri.parse("market://details?id=$NEXTCLOUD_PACKAGE")).apply { + addFlags(FLAG_ACTIVITY_NEW_TASK) + } + val isInstalled = packageManager.resolveActivity(intent, 0) != null + val canInstall = packageManager.resolveActivity(marketIntent, 0) != null + val summaryRes = if (isInstalled) { + if (isRestore) R.string.storage_fake_nextcloud_summary_installed + else R.string.storage_fake_nextcloud_summary_unavailable + } else { + if (canInstall) R.string.storage_fake_nextcloud_summary + else R.string.storage_fake_nextcloud_summary_unavailable_market + } + val root = SafOption( + authority = AUTHORITY_NEXTCLOUD, + rootId = "fake", + documentId = "fake", + icon = getIcon(context, AUTHORITY_NEXTCLOUD, "fake", 0), + title = context.getString(R.string.storage_fake_nextcloud_title), + summary = context.getString(summaryRes), + availableBytes = null, + isUsb = false, + requiresNetwork = true, + enabled = isInstalled || canInstall, + nonDefaultAction = { + if (isInstalled) context.startActivity(intent) + else if (canInstall) context.startActivity(marketIntent) + } + ) + roots.add(root) + } + + private fun doNotInclude( + authority: String, + roots: ArrayList, + doNotIncludeIfTrue: ((SafOption) -> Boolean)? = null, + ): Boolean { + if (!isAuthoritySupported(authority)) return true + for (root in roots) { + if (root.authority == authority && doNotIncludeIfTrue?.invoke(root) != false) { + return true + } + } + return false + } + + private fun isAuthoritySupported(authority: String): Boolean { + // just restrict where to store backups, + // restoring can be more free for forward compatibility + return isRestore || whitelistedAuthorities.contains(authority) + } + +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt index 7c49f1b6..b2cd0326 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt @@ -37,7 +37,7 @@ class StorageActivity : BackupActivity() { if (storageRoot == null) { viewModel.onUriPermissionResultReceived(null) } else { - viewModel.onStorageRootChosen(storageRoot) + viewModel.onSafOptionChosen(storageRoot) viewModel.onUriPermissionResultReceived(uri) } } @@ -72,7 +72,7 @@ class StorageActivity : BackupActivity() { if (savedInstanceState == null) { if (canUseStorageRootsFragment()) { - showFragment(StorageRootsFragment.newInstance(isRestore())) + showFragment(StorageOptionsFragment.newInstance(isRestore())) } else { openDocumentTree.launch(null) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOption.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOption.kt new file mode 100644 index 00000000..c7cc0bc1 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOption.kt @@ -0,0 +1,48 @@ +package com.stevesoltys.seedvault.ui.storage + +import android.graphics.drawable.Drawable +import android.net.Uri +import android.provider.DocumentsContract.buildTreeDocumentUri + +internal sealed class StorageOption { + abstract val id: String + abstract val icon: Drawable? + abstract val title: String + abstract val summary: String? + abstract val availableBytes: Long? + abstract val requiresNetwork: Boolean + abstract val enabled: Boolean + abstract val nonDefaultAction: (() -> Unit)? + + data class SafOption( + override val icon: Drawable?, + override val title: String, + override val summary: String?, + override val availableBytes: Long?, + val authority: String, + val rootId: String, + val documentId: String, + val isUsb: Boolean, + override val requiresNetwork: Boolean, + override val enabled: Boolean = true, + override val nonDefaultAction: (() -> Unit)? = null, + ) : StorageOption() { + override val id: String = "saf-$authority" + + val uri: Uri by lazy { + buildTreeDocumentUri(authority, documentId) + } + + fun isInternal(): Boolean { + return authority == AUTHORITY_STORAGE && !isUsb + } + } + + override fun equals(other: Any?): Boolean { + return other is StorageOption && other.id == id + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionAdapter.kt similarity index 73% rename from app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionAdapter.kt index 135630ad..ff4c4483 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionAdapter.kt @@ -1,5 +1,6 @@ package com.stevesoltys.seedvault.ui.storage +import android.annotation.SuppressLint import android.content.Context import android.text.format.Formatter import android.view.LayoutInflater @@ -13,40 +14,42 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.ui.storage.StorageRootAdapter.StorageRootViewHolder +import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption +import com.stevesoltys.seedvault.ui.storage.StorageOptionAdapter.StorageOptionViewHolder -internal class StorageRootAdapter( +internal class StorageOptionAdapter( private val isRestore: Boolean, - private val listener: StorageRootClickedListener, -) : Adapter() { + private val listener: StorageOptionClickedListener, +) : Adapter() { - private val items = ArrayList() + private val items = ArrayList() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StorageRootViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StorageOptionViewHolder { val v = LayoutInflater.from(parent.context) .inflate(R.layout.list_item_storage_root, parent, false) as View - return StorageRootViewHolder(v) + return StorageOptionViewHolder(v) } override fun getItemCount() = items.size - override fun onBindViewHolder(holder: StorageRootViewHolder, position: Int) { + override fun onBindViewHolder(holder: StorageOptionViewHolder, position: Int) { holder.bind(items[position]) } - internal fun setItems(items: List) { + @SuppressLint("NotifyDataSetChanged") + internal fun setItems(items: List) { this.items.clear() this.items.addAll(items) notifyDataSetChanged() } - internal inner class StorageRootViewHolder(private val v: View) : ViewHolder(v) { + internal inner class StorageOptionViewHolder(private val v: View) : ViewHolder(v) { private val iconView = v.findViewById(R.id.iconView) private val titleView = v.findViewById(R.id.titleView) private val summaryView = v.findViewById(R.id.summaryView) - internal fun bind(item: StorageRoot) { + internal fun bind(item: StorageOption) { if (item.enabled) { v.isEnabled = true v.alpha = 1f @@ -63,16 +66,16 @@ internal class StorageRootAdapter( summaryView.visibility = VISIBLE } item.availableBytes != null -> { - val str = Formatter.formatFileSize(v.context, item.availableBytes) + val str = Formatter.formatFileSize(v.context, item.availableBytes!!) summaryView.text = v.context.getString(R.string.storage_available_bytes, str) summaryView.visibility = VISIBLE } else -> summaryView.visibility = GONE } v.setOnClickListener { - if (item.overrideClickListener != null) { - item.overrideClickListener.invoke() - } else if (!isRestore && item.isInternal()) { + if (item.nonDefaultAction != null) { + item.nonDefaultAction?.invoke() + } else if (!isRestore && item is SafOption && item.isInternal()) { showWarningDialog(v.context, item) } else { listener.onClick(item) @@ -82,7 +85,7 @@ internal class StorageRootAdapter( } - private fun showWarningDialog(context: Context, item: StorageRoot) { + private fun showWarningDialog(context: Context, item: StorageOption) { AlertDialog.Builder(context) .setTitle(R.string.storage_internal_warning_title) .setMessage(R.string.storage_internal_warning_message) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt similarity index 83% rename from app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt index 6262c34a..a1e3eb69 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt @@ -1,8 +1,8 @@ package com.stevesoltys.seedvault.ui.storage import android.Manifest.permission.MANAGE_DOCUMENTS -import android.app.Activity.RESULT_FIRST_USER import android.annotation.SuppressLint +import android.app.Activity.RESULT_FIRST_USER import android.content.Context import android.content.Intent import android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION @@ -24,14 +24,15 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE +import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import org.koin.androidx.viewmodel.ext.android.getSharedViewModel -internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { +internal class StorageOptionsFragment : Fragment(), StorageOptionClickedListener { companion object { @RequiresPermission(MANAGE_DOCUMENTS) - fun newInstance(isRestore: Boolean): StorageRootsFragment { - val f = StorageRootsFragment() + fun newInstance(isRestore: Boolean): StorageOptionsFragment { + val f = StorageOptionsFragment() f.arguments = Bundle().apply { putBoolean(INTENT_EXTRA_IS_RESTORE, isRestore) } @@ -48,7 +49,7 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { private lateinit var progressBar: ProgressBar private lateinit var skipView: TextView - private val adapter by lazy { StorageRootAdapter(viewModel.isRestoreOperation, this) } + private val adapter by lazy { StorageOptionAdapter(viewModel.isRestoreOperation, this) } override fun onCreateView( inflater: LayoutInflater, @@ -97,7 +98,7 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { listView.adapter = adapter - viewModel.storageRoots.observe(viewLifecycleOwner, { roots -> + viewModel.storageOptions.observe(viewLifecycleOwner, { roots -> onRootsLoaded(roots) }) } @@ -107,7 +108,7 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { viewModel.loadStorageRoots() } - private fun onRootsLoaded(roots: List) { + private fun onRootsLoaded(roots: List) { progressBar.visibility = INVISIBLE adapter.setItems(roots) } @@ -116,15 +117,19 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { viewModel.onUriPermissionResultReceived(uri) } - override fun onClick(root: StorageRoot) { - viewModel.onStorageRootChosen(root) - openDocumentTree.launch(root.uri) + override fun onClick(storageOption: StorageOption) { + if (storageOption is SafOption) { + viewModel.onSafOptionChosen(storageOption) + openDocumentTree.launch(storageOption.uri) + } else { + throw IllegalArgumentException("Non-SAF storage not yet supported") + } } } -internal interface StorageRootClickedListener { - fun onClick(root: StorageRoot) +internal interface StorageOptionClickedListener { + fun onClick(storageOption: StorageOption) } private class OpenSeedvaultTree : OpenDocumentTree() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt index d11756c6..3f586f17 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt @@ -3,20 +3,17 @@ package com.stevesoltys.seedvault.ui.storage import android.Manifest.permission.MANAGE_DOCUMENTS import android.content.Context import android.content.Intent -import android.content.Intent.ACTION_VIEW -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.PackageManager.GET_META_DATA import android.content.pm.ProviderInfo import android.database.ContentObserver -import android.graphics.drawable.Drawable import android.net.Uri import android.os.Handler import android.os.Looper -import android.provider.DocumentsContract import android.provider.DocumentsContract.PROVIDER_INTERFACE +import android.provider.DocumentsContract.buildRootsUri import android.util.Log import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.ui.storage.StorageRootResolver.getIcon +import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption private val TAG = StorageRootFetcher::class.java.simpleName @@ -27,32 +24,6 @@ const val ROOT_ID_HOME = "home" const val AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents" const val AUTHORITY_NEXTCLOUD = "org.nextcloud.documents" -private const val NEXTCLOUD_PACKAGE = "com.nextcloud.client" -private const val NEXTCLOUD_ACTIVITY = "com.owncloud.android.authentication.AuthenticatorActivity" - -data class StorageRoot( - internal val authority: String, - internal val rootId: String, - internal val documentId: String, - internal val icon: Drawable?, - internal val title: String, - internal val summary: String?, - internal val availableBytes: Long?, - internal val isUsb: Boolean, - internal val requiresNetwork: Boolean, - internal val enabled: Boolean = true, - internal val overrideClickListener: (() -> Unit)? = null, -) { - - internal val uri: Uri by lazy { - DocumentsContract.buildTreeDocumentUri(authority, documentId) - } - - fun isInternal(): Boolean { - return authority == AUTHORITY_STORAGE && !isUsb - } -} - internal interface RemovableStorageListener { fun onStorageChanged() } @@ -63,6 +34,7 @@ internal class StorageRootFetcher(private val context: Context, private val isRe private val contentResolver = context.contentResolver private val whitelistedAuthorities = context.resources.getStringArray(R.array.storage_authority_whitelist) + private val safStorageOptions = SafStorageOptions(context, isRestore, whitelistedAuthorities) private var listener: RemovableStorageListener? = null private val handler = Handler(Looper.getMainLooper()) @@ -76,7 +48,7 @@ internal class StorageRootFetcher(private val context: Context, private val isRe internal fun setRemovableStorageListener(listener: RemovableStorageListener?) { this.listener = listener if (listener != null) { - val rootsUri = DocumentsContract.buildRootsUri(AUTHORITY_STORAGE) + val rootsUri = buildRootsUri(AUTHORITY_STORAGE) contentResolver.registerContentObserver(rootsUri, true, observer) } else { contentResolver.unregisterContentObserver(observer) @@ -85,8 +57,8 @@ internal class StorageRootFetcher(private val context: Context, private val isRe internal fun getRemovableStorageListener() = listener - internal fun getStorageRoots(): List { - val roots = ArrayList() + internal fun getStorageOptions(): List { + val roots = ArrayList() val intent = Intent(PROVIDER_INTERFACE) val providers = packageManager.queryIntentContentProviders(intent, 0) for (info in providers) { @@ -96,12 +68,12 @@ internal class StorageRootFetcher(private val context: Context, private val isRe roots.addAll(getRoots(providerInfo)) } } - checkOrAddUsbRoot(roots) - checkOrAddNextCloudRoot(roots) + // there's a couple of options, we still want to show, even if no roots are found for them + safStorageOptions.checkOrAddExtraRoots(roots) return roots } - private fun getRoots(providerInfo: ProviderInfo): List { + private fun getRoots(providerInfo: ProviderInfo): List { val authority = providerInfo.authority val provider = packageManager.resolveContentProvider(authority, GET_META_DATA) return if (provider == null || !provider.isSupported()) { @@ -112,82 +84,6 @@ internal class StorageRootFetcher(private val context: Context, private val isRe } } - private fun checkOrAddUsbRoot(roots: ArrayList) { - if (!isAuthoritySupported(AUTHORITY_STORAGE)) return - - for (root in roots) { - // return if we already have a USB storage root - if (root.authority == AUTHORITY_STORAGE && root.isUsb) return - } - val root = StorageRoot( - authority = AUTHORITY_STORAGE, - rootId = "usb", - documentId = "fake", - icon = getIcon(context, AUTHORITY_STORAGE, "usb", 0), - title = context.getString(R.string.storage_fake_drive_title), - summary = context.getString(R.string.storage_fake_drive_summary), - availableBytes = null, - isUsb = true, - requiresNetwork = false, - enabled = false - ) - roots.add(root) - } - - /** - * This adds a fake Nextcloud entry if no real one was found. - * - * If Nextcloud is *not* installed, - * the user will always have the option to install it by clicking the entry. - * - * If it *is* installed and this is restore, the user can set up a new account by clicking. - * If this isn't restore, the entry will be disabled, - * because we don't know if there's no account or an activated passcode. - */ - private fun checkOrAddNextCloudRoot(roots: ArrayList) { - for (root in roots) { - // return if we already have a NextCloud storage root - if (root.authority == AUTHORITY_NEXTCLOUD) return - } - val intent = Intent().apply { - addFlags(FLAG_ACTIVITY_NEW_TASK) - setClassName(NEXTCLOUD_PACKAGE, NEXTCLOUD_ACTIVITY) - // setting a nc:// Uri prevents FirstRunActivity to show - data = Uri.parse("nc://login/server:") - putExtra("onlyAdd", true) - } - val marketIntent = - Intent(ACTION_VIEW, Uri.parse("market://details?id=$NEXTCLOUD_PACKAGE")).apply { - addFlags(FLAG_ACTIVITY_NEW_TASK) - } - val isInstalled = packageManager.resolveActivity(intent, 0) != null - val canInstall = packageManager.resolveActivity(marketIntent, 0) != null - val summaryRes = if (isInstalled) { - if (isRestore) R.string.storage_fake_nextcloud_summary_installed - else R.string.storage_fake_nextcloud_summary_unavailable - } else { - if (canInstall) R.string.storage_fake_nextcloud_summary - else R.string.storage_fake_nextcloud_summary_unavailable_market - } - val root = StorageRoot( - authority = AUTHORITY_NEXTCLOUD, - rootId = "fake", - documentId = "fake", - icon = getIcon(context, AUTHORITY_NEXTCLOUD, "fake", 0), - title = context.getString(R.string.storage_fake_nextcloud_title), - summary = context.getString(summaryRes), - availableBytes = null, - isUsb = false, - requiresNetwork = true, - enabled = isInstalled || canInstall, - overrideClickListener = { - if (isInstalled) context.startActivity(intent) - else if (canInstall) context.startActivity(marketIntent) - } - ) - roots.add(root) - } - private fun ProviderInfo.isSupported(): Boolean { return if (!exported) { Log.w(TAG, "Provider is not exported") diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt index 07318440..37e60969 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt @@ -17,13 +17,14 @@ import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_CREATE import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD import android.util.Log import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption -object StorageRootResolver { +internal object StorageRootResolver { private val TAG = StorageRootResolver::class.java.simpleName - fun getStorageRoots(context: Context, authority: String): List { - val roots = ArrayList() + fun getStorageRoots(context: Context, authority: String): List { + val roots = ArrayList() val rootsUri = DocumentsContract.buildRootsUri(authority) try { @@ -39,7 +40,7 @@ object StorageRootResolver { return roots } - private fun getStorageRoot(context: Context, authority: String, cursor: Cursor): StorageRoot? { + private fun getStorageRoot(context: Context, authority: String, cursor: Cursor): SafOption? { val flags = cursor.getInt(COLUMN_FLAGS) val supportsCreate = flags and FLAG_SUPPORTS_CREATE != 0 val supportsIsChild = flags and FLAG_SUPPORTS_IS_CHILD != 0 @@ -47,7 +48,7 @@ object StorageRootResolver { val rootId = cursor.getString(COLUMN_ROOT_ID)!! if (authority == AUTHORITY_STORAGE && rootId == ROOT_ID_HOME) return null val documentId = cursor.getString(COLUMN_DOCUMENT_ID) ?: return null - return StorageRoot( + return SafOption( authority = authority, rootId = rootId, documentId = documentId, diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt index a9b59230..b94757b8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt @@ -20,6 +20,7 @@ import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent +import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption private val TAG = StorageViewModel::class.java.simpleName @@ -28,8 +29,8 @@ internal abstract class StorageViewModel( protected val settingsManager: SettingsManager, ) : AndroidViewModel(app), RemovableStorageListener { - private val mStorageRoots = MutableLiveData>() - internal val storageRoots: LiveData> get() = mStorageRoots + private val mStorageOptions = MutableLiveData>() + internal val storageOptions: LiveData> get() = mStorageOptions private val mLocationSet = MutableLiveEvent() internal val locationSet: LiveEvent get() = mLocationSet @@ -38,7 +39,7 @@ internal abstract class StorageViewModel( internal val locationChecked: LiveEvent get() = mLocationChecked private val storageRootFetcher by lazy { StorageRootFetcher(app, isRestoreOperation) } - private var storageRoot: StorageRoot? = null + private var safOption: SafOption? = null internal var isSetupWizard: Boolean = false internal val hasStorageSet: Boolean @@ -63,14 +64,14 @@ internal abstract class StorageViewModel( storageRootFetcher.setRemovableStorageListener(this) } Thread { - mStorageRoots.postValue(storageRootFetcher.getStorageRoots()) + mStorageOptions.postValue(storageRootFetcher.getStorageOptions()) }.start() } override fun onStorageChanged() = loadStorageRoots() - fun onStorageRootChosen(root: StorageRoot) { - storageRoot = root + fun onSafOptionChosen(option: SafOption) { + safOption = option } internal fun onUriPermissionResultReceived(uri: Uri?) { @@ -91,13 +92,13 @@ internal abstract class StorageViewModel( } /** - * Saves the storage behind the given [Uri] (and saved [storageRoot]). + * Saves the storage behind the given [Uri] (and saved [safOption]). * * @return true if the storage is a USB flash drive, false otherwise. */ protected fun saveStorage(uri: Uri): Boolean { // store backup storage location in settings - val root = storageRoot ?: throw IllegalStateException("no storage root") + val root = safOption ?: throw IllegalStateException("no storage root") val name = if (root.isInternal()) { "${root.title} (${app.getString(R.string.settings_backup_location_internal)})" } else {