From 208dbd6a6026bcee456f67d740c47b9dacd68ae3 Mon Sep 17 00:00:00 2001
From: t-m-w <7275539+t-m-w@users.noreply.github.com>
Date: Mon, 28 Nov 2022 00:08:14 -0500
Subject: [PATCH 1/2] Support a lack of MANAGE_DOCUMENTS permission
When `android.permission.MANAGE_DOCUMENTS` is not granted, Seedvault
prompts to select a storage location. Do not insist on resolving the
storage's actual root, as it will likely (or perhaps always) fail.
Use a fake storage root as is done for USB and app-based options.
Known issues:
* Selected location is assumed not to be USB and not to require
network access in its storage options.
Change-Id: I357b4c68673d71c087be41e9c94c2841c1d6658e
---
.../seedvault/ui/storage/StorageActivity.kt | 4 +++-
.../seedvault/ui/storage/StorageRootResolver.kt | 16 ++++++++++++++++
app/src/main/res/values/strings.xml | 2 ++
3 files changed, 21 insertions(+), 1 deletion(-)
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 b2cd0326..198d0f50 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
@@ -35,7 +35,9 @@ class StorageActivity : BackupActivity() {
val authority = uri.authority ?: throw AssertionError("No authority in $uri")
val storageRoot = StorageRootResolver.getStorageRoots(this, authority).getOrNull(0)
if (storageRoot == null) {
- viewModel.onUriPermissionResultReceived(null)
+ viewModel.onSafOptionChosen(
+ StorageRootResolver.getFakeStorageRootForUri(this, uri))
+ viewModel.onUriPermissionResultReceived(uri)
} else {
viewModel.onSafOptionChosen(storageRoot)
viewModel.onUriPermissionResultReceived(uri)
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 4d615e6a..57cfdf6a 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
@@ -3,6 +3,7 @@ package com.stevesoltys.seedvault.ui.storage
import android.content.Context
import android.database.Cursor
import android.graphics.drawable.Drawable
+import android.net.Uri
import android.os.UserHandle
import android.provider.DocumentsContract
import android.provider.DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
@@ -57,6 +58,21 @@ internal object StorageRootResolver {
return roots
}
+ fun getFakeStorageRootForUri(context: Context, uri: Uri): SafOption {
+ return SafOption(
+ authority = AUTHORITY_STORAGE,
+ rootId = "fake",
+ documentId = "fake",
+ // TODO: Use something other than the USB icon?
+ icon = getIcon(context, AUTHORITY_STORAGE, "usb", 0),
+ title = context.getString(R.string.storage_user_selected_location_title),
+ summary = context.getString(R.string.storage_user_selected_location_summary),
+ availableBytes = null,
+ isUsb = false, // TODO: Check this if possible instead of forcing false
+ requiresNetwork = false, // TODO: Check this if possible instead of forcing false
+ )
+ }
+
private fun getStorageRoot(context: Context, authority: String, cursor: Cursor): SafOption? {
val flags = cursor.getInt(COLUMN_FLAGS)
val supportsCreate = flags and FLAG_SUPPORTS_CREATE != 0
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9b936b2d..fa1b5618 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -58,6 +58,8 @@
Where to find your backups?
People with access to your storage location can learn which apps you use, but do not get access to the apps\' data.
Existing backups in this location will be deleted.
+ User-chosen location
+ Chosen using the folder browser
USB flash drive
Needs to be plugged in
%1$s free
From 1e69831244058d15fde1acf5398c3d913449870d Mon Sep 17 00:00:00 2001
From: Torsten Grote
Date: Wed, 13 Sep 2023 12:06:57 +0200
Subject: [PATCH 2/2] Improving support for missing MANAGE_DOCUMENTS permission
---
.../seedvault/ui/storage/StorageActivity.kt | 15 +++++------
.../ui/storage/StorageOptionsFragment.kt | 4 +--
.../ui/storage/StorageRootResolver.kt | 25 +++++++++++++------
app/src/main/res/values/strings.xml | 1 -
4 files changed, 27 insertions(+), 18 deletions(-)
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 198d0f50..ca743c6f 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
@@ -31,12 +31,13 @@ class StorageActivity : BackupActivity() {
*/
private val openDocumentTree = registerForActivityResult(OpenPersistableDocumentTree()) { uri ->
if (uri != null) {
- Log.e(TAG, "OpenDocumentTree: $uri")
val authority = uri.authority ?: throw AssertionError("No authority in $uri")
+ // we are most likely not allowed to resolve storage roots,
+ // but being the optimists we are, we are still trying...
val storageRoot = StorageRootResolver.getStorageRoots(this, authority).getOrNull(0)
if (storageRoot == null) {
- viewModel.onSafOptionChosen(
- StorageRootResolver.getFakeStorageRootForUri(this, uri))
+ val fakeRoot = StorageRootResolver.getFakeStorageRootForUri(this, uri)
+ viewModel.onSafOptionChosen(fakeRoot)
viewModel.onUriPermissionResultReceived(uri)
} else {
viewModel.onSafOptionChosen(storageRoot)
@@ -58,11 +59,11 @@ class StorageActivity : BackupActivity() {
}
viewModel.isSetupWizard = isSetupWizard()
- viewModel.locationSet.observeEvent(this, {
+ viewModel.locationSet.observeEvent(this) {
showFragment(StorageCheckFragment.newInstance(getCheckFragmentTitle()), true)
- })
+ }
- viewModel.locationChecked.observeEvent(this, { result ->
+ viewModel.locationChecked.observeEvent(this) { result ->
val errorMsg = result.errorMsg
if (errorMsg == null) {
setResult(RESULT_OK)
@@ -70,7 +71,7 @@ class StorageActivity : BackupActivity() {
} else {
onInvalidLocation(errorMsg)
}
- })
+ }
if (savedInstanceState == null) {
if (canUseStorageRootsFragment()) {
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 62146332..fc84248c 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
@@ -95,9 +95,9 @@ internal class StorageOptionsFragment : Fragment(), StorageOptionClickedListener
listView.adapter = adapter
- viewModel.storageOptions.observe(viewLifecycleOwner, { roots ->
+ viewModel.storageOptions.observe(viewLifecycleOwner) { roots ->
onRootsLoaded(roots)
- })
+ }
}
override fun onStart() {
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 57cfdf6a..9db71b6f 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
@@ -1,5 +1,6 @@
package com.stevesoltys.seedvault.ui.storage
+import android.Manifest.permission.MANAGE_DOCUMENTS
import android.content.Context
import android.database.Cursor
import android.graphics.drawable.Drawable
@@ -58,18 +59,22 @@ internal object StorageRootResolver {
return roots
}
+ /**
+ * Used for getting a SafOption when we lack [MANAGE_DOCUMENTS],
+ * since we are not allowed to use [getStorageRoots] in this case.
+ */
fun getFakeStorageRootForUri(context: Context, uri: Uri): SafOption {
+ val authority = uri.authority ?: throw AssertionError("No authority in $uri")
return SafOption(
- authority = AUTHORITY_STORAGE,
- rootId = "fake",
- documentId = "fake",
- // TODO: Use something other than the USB icon?
- icon = getIcon(context, AUTHORITY_STORAGE, "usb", 0),
+ authority = authority,
+ rootId = ROOT_ID_DEVICE,
+ documentId = DocumentsContract.getTreeDocumentId(uri),
+ icon = getIcon(context, authority, ROOT_ID_DEVICE, 0),
title = context.getString(R.string.storage_user_selected_location_title),
- summary = context.getString(R.string.storage_user_selected_location_summary),
+ summary = "Please open a bug if you see this",
availableBytes = null,
- isUsb = false, // TODO: Check this if possible instead of forcing false
- requiresNetwork = false, // TODO: Check this if possible instead of forcing false
+ isUsb = false, // FIXME not supported without MANAGE_DOCUMENTS permission
+ requiresNetwork = authority != AUTHORITY_STORAGE && authority != AUTHORITY_DOWNLOADS,
)
}
@@ -123,15 +128,19 @@ internal object StorageRootResolver {
authority == AUTHORITY_STORAGE && rootId == ROOT_ID_DEVICE -> {
context.getDrawable(R.drawable.ic_phone_android)
}
+
authority == AUTHORITY_STORAGE && rootId != ROOT_ID_HOME -> {
context.getDrawable(R.drawable.ic_usb)
}
+
authority == AUTHORITY_NEXTCLOUD -> {
context.getDrawable(R.drawable.nextcloud)
}
+
authority == AUTHORITY_DAVX5 -> {
context.getDrawable(R.drawable.davx5)
}
+
else -> null
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fa1b5618..502ba39c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,7 +59,6 @@
People with access to your storage location can learn which apps you use, but do not get access to the apps\' data.
Existing backups in this location will be deleted.
User-chosen location
- Chosen using the folder browser
USB flash drive
Needs to be plugged in
%1$s free