diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/SafStorageOptions.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorageOptions.kt similarity index 86% rename from app/src/main/java/com/stevesoltys/seedvault/ui/storage/SafStorageOptions.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorageOptions.kt index 6d86e860..b20511c1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/SafStorageOptions.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorageOptions.kt @@ -1,4 +1,9 @@ -package com.stevesoltys.seedvault.ui.storage +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.plugins.saf import android.content.Context import android.content.Intent @@ -9,8 +14,13 @@ import android.provider.DocumentsContract import android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME import android.provider.DocumentsContract.Document.COLUMN_DOCUMENT_ID import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.plugins.saf.StorageRootResolver.getIcon +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_DAVX5 +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_NEXTCLOUD +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_ROUND_SYNC +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE +import com.stevesoltys.seedvault.ui.storage.StorageOption import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption -import com.stevesoltys.seedvault.ui.storage.StorageRootResolver.getIcon private const val DAVX5_PACKAGE = "at.bitfire.davdroid" private const val DAVX5_ACTIVITY = "at.bitfire.davdroid.ui.webdav.WebdavMountsActivity" @@ -29,15 +39,15 @@ internal class SafStorageOptions( private val packageManager = context.packageManager - internal fun checkOrAddExtraRoots(roots: ArrayList) { + internal fun checkOrAddExtraRoots(roots: ArrayList) { checkOrAddUsbRoot(roots) checkOrAddDavX5Root(roots) checkOrAddNextCloudRoot(roots) checkOrAddRoundSyncRoots(roots) } - private fun checkOrAddUsbRoot(roots: ArrayList) { - if (doNotInclude(AUTHORITY_STORAGE, roots) { it.isUsb }) return + private fun checkOrAddUsbRoot(roots: ArrayList) { + if (doNotInclude(AUTHORITY_STORAGE, roots) { it is SafOption && it.isUsb }) return val root = SafOption( authority = AUTHORITY_STORAGE, @@ -57,11 +67,11 @@ internal class SafStorageOptions( /** * Add a storage root for each child directory at the RoundSync root, if it exists. */ - private fun checkOrAddRoundSyncRoots(roots: ArrayList) { + private fun checkOrAddRoundSyncRoots(roots: ArrayList) { val roundSyncRoot = roots.firstOrNull { - it.authority == AUTHORITY_ROUND_SYNC - } ?: return + it is SafOption && it.authority == AUTHORITY_ROUND_SYNC + } as? SafOption ?: return roots.remove(roundSyncRoot) @@ -105,7 +115,7 @@ internal class SafStorageOptions( * * If it *is* installed and this is restore, the user can set up a new account by clicking. */ - private fun checkOrAddDavX5Root(roots: ArrayList) { + private fun checkOrAddDavX5Root(roots: ArrayList) { if (doNotInclude(AUTHORITY_DAVX5, roots)) return val intent = Intent().apply { @@ -155,7 +165,7 @@ internal class SafStorageOptions( * because we don't know if there's just no account or an activated passcode * (which hides existing accounts). */ - private fun checkOrAddNextCloudRoot(roots: ArrayList) { + private fun checkOrAddNextCloudRoot(roots: ArrayList) { if (doNotInclude(AUTHORITY_NEXTCLOUD, roots)) return val intent = Intent().apply { @@ -202,11 +212,12 @@ internal class SafStorageOptions( private fun doNotInclude( authority: String, - roots: ArrayList, - doNotIncludeIfTrue: ((SafOption) -> Boolean)? = null, + roots: ArrayList, + doNotIncludeIfTrue: ((StorageOption) -> Boolean)? = null, ): Boolean { if (!isAuthoritySupported(authority)) return true for (root in roots) { + if (root !is SafOption) continue if (root.authority == authority && doNotIncludeIfTrue?.invoke(root) != false) { return true } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/StorageRootResolver.kt similarity index 87% rename from app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt rename to app/src/main/java/com/stevesoltys/seedvault/plugins/saf/StorageRootResolver.kt index 9b9c95bb..d69e7de8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootResolver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/StorageRootResolver.kt @@ -1,4 +1,9 @@ -package com.stevesoltys.seedvault.ui.storage +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.plugins.saf import android.Manifest.permission.MANAGE_DOCUMENTS import android.content.Context @@ -19,8 +24,16 @@ import android.provider.DocumentsContract.Root.FLAG_REMOVABLE_USB import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_CREATE import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD import android.util.Log +import androidx.appcompat.content.res.AppCompatResources.getDrawable import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.getStorageContext +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_DAVX5 +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_DOWNLOADS +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_NEXTCLOUD +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_ROUND_SYNC +import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE +import com.stevesoltys.seedvault.ui.storage.ROOT_ID_DEVICE +import com.stevesoltys.seedvault.ui.storage.ROOT_ID_HOME import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption internal object StorageRootResolver { @@ -126,23 +139,23 @@ internal object StorageRootResolver { fun getIcon(context: Context, authority: String, rootId: String, icon: Int): Drawable? { return getPackageIcon(context, authority, icon) ?: when { authority == AUTHORITY_STORAGE && rootId == ROOT_ID_DEVICE -> { - context.getDrawable(R.drawable.ic_phone_android) + getDrawable(context, R.drawable.ic_phone_android) } authority == AUTHORITY_STORAGE && rootId != ROOT_ID_HOME -> { - context.getDrawable(R.drawable.ic_usb) + getDrawable(context, R.drawable.ic_usb) } authority == AUTHORITY_NEXTCLOUD -> { - context.getDrawable(R.drawable.nextcloud) + getDrawable(context, R.drawable.nextcloud) } authority == AUTHORITY_DAVX5 -> { - context.getDrawable(R.drawable.davx5) + getDrawable(context, R.drawable.davx5) } authority == AUTHORITY_ROUND_SYNC -> { - context.getDrawable(R.drawable.round_sync) + getDrawable(context, R.drawable.round_sync) } else -> null diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index b567f0c3..7ba5c695 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -31,7 +31,7 @@ internal class BackupStorageViewModel( override val isRestoreOperation = false - override fun onLocationSet(uri: Uri) { + override fun onSafUriSet(uri: Uri) { val isUsb = saveStorage(uri) if (isUsb) { // disable storage backup if new storage is on USB diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt index 916ca3e0..b1ea418e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt @@ -22,7 +22,7 @@ internal class RestoreStorageViewModel( override val isRestoreOperation = true - override fun onLocationSet(uri: Uri) { + override fun onSafUriSet(uri: Uri) { viewModelScope.launch(Dispatchers.IO) { val storage = createStorage(uri) val hasBackup = try { 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 ca743c6f..75658f8e 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 @@ -9,11 +9,11 @@ import android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION import android.content.pm.PackageManager.PERMISSION_GRANTED import android.net.Uri import android.os.Bundle -import android.util.Log import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree import androidx.annotation.CallSuper import androidx.appcompat.app.AlertDialog import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.plugins.saf.StorageRootResolver import com.stevesoltys.seedvault.ui.BackupActivity import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD @@ -82,14 +82,6 @@ class StorageActivity : BackupActivity() { } } - override fun onBackPressed() { - if (supportFragmentManager.backStackEntryCount > 0) { - Log.d(TAG, "Blocking back button.") - } else { - super.onBackPressed() - } - } - private fun onInvalidLocation(errorMsg: String) { if (viewModel.isRestoreOperation) { val dialog = AlertDialog.Builder(this) diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt index b1df24e0..b414ff7f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt @@ -1,6 +1,7 @@ package com.stevesoltys.seedvault.ui.storage import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.View.GONE @@ -10,6 +11,7 @@ import android.view.ViewGroup import android.widget.Button import android.widget.ProgressBar import android.widget.TextView +import androidx.activity.addCallback import androidx.fragment.app.Fragment import com.stevesoltys.seedvault.R @@ -34,6 +36,14 @@ class StorageCheckFragment : Fragment() { } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + requireActivity().onBackPressedDispatcher.addCallback(this) { + Log.i("StorageCheckFragment", "Not navigating back!") + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt similarity index 91% rename from app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt rename to app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt index 1357f299..afe35a00 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootFetcher.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt @@ -13,9 +13,11 @@ import android.provider.DocumentsContract.PROVIDER_INTERFACE import android.provider.DocumentsContract.buildRootsUri import android.util.Log import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.plugins.saf.SafStorageOptions +import com.stevesoltys.seedvault.plugins.saf.StorageRootResolver import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption -private val TAG = StorageRootFetcher::class.java.simpleName +private val TAG = StorageOptionFetcher::class.java.simpleName const val AUTHORITY_STORAGE = "com.android.externalstorage.documents" const val ROOT_ID_DEVICE = "primary" @@ -30,7 +32,7 @@ internal interface RemovableStorageListener { fun onStorageChanged() } -internal class StorageRootFetcher(private val context: Context, private val isRestore: Boolean) { +internal class StorageOptionFetcher(private val context: Context, private val isRestore: Boolean) { private val packageManager = context.packageManager private val contentResolver = context.contentResolver @@ -60,7 +62,9 @@ internal class StorageRootFetcher(private val context: Context, private val isRe internal fun getRemovableStorageListener() = listener internal fun getStorageOptions(): List { - val roots = ArrayList() + val roots = ArrayList().apply { + add(WebDavOption(context)) + } val intent = Intent(PROVIDER_INTERFACE) val providers = packageManager.queryIntentContentProviders(intent, 0) for (info in providers) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt index 12c65260..66f3b78b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionsFragment.kt @@ -55,7 +55,7 @@ internal class StorageOptionsFragment : Fragment(), StorageOptionClickedListener container: ViewGroup?, savedInstanceState: Bundle?, ): View { - val v: View = inflater.inflate(R.layout.fragment_storage_root, container, false) + val v: View = inflater.inflate(R.layout.fragment_storage_options, container, false) titleView = v.requireViewById(R.id.titleView) warningIcon = v.requireViewById(R.id.warningIcon) 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 0d0e5f11..9562e13e 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 @@ -1,5 +1,6 @@ package com.stevesoltys.seedvault.ui.storage +import android.annotation.UiThread import android.app.Application import android.content.Context import android.content.Context.USB_SERVICE @@ -11,6 +12,7 @@ import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.permitDiskReads @@ -21,6 +23,8 @@ 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 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch private val TAG = StorageViewModel::class.java.simpleName @@ -38,7 +42,7 @@ internal abstract class StorageViewModel( protected val mLocationChecked = MutableLiveEvent() internal val locationChecked: LiveEvent get() = mLocationChecked - private val storageRootFetcher by lazy { StorageRootFetcher(app, isRestoreOperation) } + private val storageOptionFetcher by lazy { StorageOptionFetcher(app, isRestoreOperation) } private var safOption: SafOption? = null internal var isSetupWizard: Boolean = false @@ -60,11 +64,11 @@ internal abstract class StorageViewModel( } internal fun loadStorageRoots() { - if (storageRootFetcher.getRemovableStorageListener() == null) { - storageRootFetcher.setRemovableStorageListener(this) + if (storageOptionFetcher.getRemovableStorageListener() == null) { + storageOptionFetcher.setRemovableStorageListener(this) } Thread { - mStorageOptions.postValue(storageRootFetcher.getStorageOptions()) + mStorageOptions.postValue(storageOptionFetcher.getStorageOptions()) }.start() } @@ -88,7 +92,7 @@ internal abstract class StorageViewModel( val takeFlags = FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION app.contentResolver.takePersistableUriPermission(uri, takeFlags) - onLocationSet(uri) + onSafUriSet(uri) } /** @@ -144,10 +148,10 @@ internal abstract class StorageViewModel( return false } - abstract fun onLocationSet(uri: Uri) + abstract fun onSafUriSet(uri: Uri) override fun onCleared() { - storageRootFetcher.setRemovableStorageListener(null) + storageOptionFetcher.setRemovableStorageListener(null) super.onCleared() } diff --git a/app/src/main/res/layout/fragment_storage_root.xml b/app/src/main/res/layout/fragment_storage_options.xml similarity index 100% rename from app/src/main/res/layout/fragment_storage_root.xml rename to app/src/main/res/layout/fragment_storage_options.xml