Repository: FossifyOrg/Commons
Pull request #90: More friendly access to data/obb folder on Android 14+ - Version 2
Author: login: papjul
Created: 2025-04-05T09:29:51Z | Merged: 2025-04-06T09:43:41Z

<!-- Hey there. Thank you so much for improving Fossify. Please consider filling out the details :)-->

#### What is it?
- [X] Bugfix
- [ ] Feature
- [ ] Codebase improvement

#### Description of the changes in your PR
<!-- Bullet points are preferred. The following is an example -->
- On Android 14+, there is no longer any workaround to access data and obb folder. This PR ensures users of Android 14+ are no longer prompted into using the workaround that no longer works.
- It also includes the ability to add a "Open in system app" option when relevant. By default, this option is off to avoid any side effect given how intensively it is used across all Fossify apps.

#### Before/After Screenshots/Screen Record
<!-- If your PR changes the app's UI in any way, consider including screenshots or a video showing exactly what changed, so that developers and users can pinpoint it easily. Delete this if it doesn't apply to your PR.-->
- Before:
<img alt="before" src="https://github.com/user-attachments/assets/411be3ee-7039-410e-8f4e-fd517eed6d43" width=303 />

- After:
On Android < 14:
<img alt="after on Android below 14" src="https://github.com/user-attachments/assets/411be3ee-7039-410e-8f4e-fd517eed6d43" width=303 />

On Android 14+:
<img alt="after on Android 14 and above" src="https://github.com/user-attachments/assets/699e3ffb-3516-4612-8489-40f7b6622a6e" width=198 />


#### Fixes the following issue(s)
<!-- Prefix issues with "Fixes FossifyOrg/AppRepo" so that GitHub closes them when this PR is merged (note that each "Fixes #" should be in its own item). For Commons, it should always be in a format like "Fixes FossifyOrg/AppRepo#123" -->
- First step of FossifyOrg/File-Manager#16. Built on top of existing UI dialog
- A different UI was suggested in FossifyOrg/File-Manager#16, and it will be implemented in a separate PR as discussed in #89 

#### Relies on the following changes
<!-- Delete this if it doesn't apply to your PR. -->
- FossifyOrg/File-Manager#152 depends on this PR

#### Acknowledgement
- [X] I read the [contribution guidelines](https://github.com/FossifyOrg/Commons/blob/master/CONTRIBUTING.md).



Diff:
diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt
index c1906678e6..f391eb812e 100644
--- a/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt
+++ b/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt
@@ -751,12 +751,12 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
         }
     }
 
-    fun handleAndroidSAFDialog(path: String, callback: (success: Boolean) -> Unit): Boolean {
+    fun handleAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false, callback: (success: Boolean) -> Unit): Boolean {
         hideKeyboard()
         return if (!packageName.startsWith("org.fossify")) {
             callback(true)
             false
-        } else if (isShowingAndroidSAFDialog(path)) {
+        } else if (isShowingAndroidSAFDialog(path, openInSystemAppAllowed)) {
             funAfterSAFPermission = callback
             true
         } else {
diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
index b9c3f0b094..fecb542989 100644
--- a/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
+++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
@@ -226,31 +226,29 @@ fun BaseSimpleActivity.isShowingSAFCreateDocumentDialogSdk30(path: String): Bool
     }
 }
 
-fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
+fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false): Boolean {
     return if (isRestrictedSAFOnlyRoot(path) && (getAndroidTreeUri(path).isEmpty() || !hasProperStoredAndroidTreeUri(path))) {
         runOnUiThread {
             if (!isDestroyed && !isFinishing) {
-                ConfirmationAdvancedDialog(this, "", R.string.confirm_storage_access_android_text, R.string.ok, R.string.cancel) { success ->
-                    if (success) {
-                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-                            putExtra(EXTRA_SHOW_ADVANCED, true)
-                            putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(path))
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                                return@apply
-                            } catch (e: Exception) {
-                                type = "*/*"
-                            }
-
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                            } catch (e: ActivityNotFoundException) {
-                                toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
-                            } catch (e: Exception) {
-                                toast(R.string.unknown_error_occurred)
-                            }
+                if (!openInSystemAppAllowed) {
+                    ConfirmationDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        positive = android.R.string.ok,
+                        negative = 0
+                    ) {}
+                } else {
+                    ConfirmationAdvancedDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        R.string.confirm_storage_access_restricted_text_open_system,
+                        R.string.cancel
+                    ) { success ->
+                        if (success) {
+                            val uri = createAndroidDataOrObbUri(path)
+                            launchSystemFileManager(uri)
                         }
                     }
                 }
@@ -262,6 +260,62 @@ fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
     }
 }
 
+/**
+ * Launch system file manager by testing different possible intents depending on the device
+ * Each intent is tested in a OR condition which allows to stop at the first successful one
+ */
+fun BaseSimpleActivity.launchSystemFileManager(uri: Uri) {
+    if (
+        startIntentForUriAction(
+            uri,
+            "android.intent.action.VIEW",
+            ComponentName("com.google.android.documentsui", "com.android.documentsui.files.FilesActivity")
+        ) ||
+        startIntentForUriAction(
+            uri,
+            "android.intent.action.VIEW",
+            ComponentName("com.android.documentsui", "com.android.documentsui.files.FilesActivity")
+        ) ||
+        startIntentForUriAction(
+            uri,
+            "android.intent.action.VIEW",
+            ComponentName("com.android.documentsui", "com.android.documentsui.FilesActivity")
+        ) ||
+        startIntentForUriAction(uri, "android.intent.action.VIEW", null) ||
+        startIntentForUriAction(uri, "android.provider.action.BROWSE", null) ||
+        startIntentForUriAction(uri, "android.provider.action.BROWSE_DOCUMENT_ROOT", null)
+    ) {
+        return
+    } else {
+        toast(R.string.confirm_storage_access_restricted_text_not_found)
+    }
+}
+
+/**
+ * Start the intent
+ * @param uri URI to pass to the intent
+ * @param action Action of the intent
+ * @param componentName Optional ComponentName
+ * @return true if the intent was successful
+ */
+fun BaseSimpleActivity.startIntentForUriAction(
+    uri: Uri,
+    action: String,
+    componentName: ComponentName?
+): Boolean {
+    val intent = Intent(action, uri)
+    if (componentName != null) {
+        intent.setComponent(componentName)
+    }
+    return try {
+        startActivity(intent)
+        true
+    } catch (e: java.lang.Exception) {
+        e.printStackTrace()
+        false
+    }
+}
+
 fun BaseSimpleActivity.isShowingOTGDialog(path: String): Boolean {
     return if (!isRPlus() && isPathOnOTG(path) && (baseConfig.OTGTreeUri.isEmpty() || !hasProperStoredTreeUri(true))) {
         showOTGPermissionDialog(path)
diff --git a/commons/src/main/res/values/strings.xml b/commons/src/main/res/values/strings.xml
index f49d70800a..2a892e816a 100644
--- a/commons/src/main/res/values/strings.xml
+++ b/commons/src/main/res/values/strings.xml
@@ -290,6 +290,9 @@
     <string name="confirm_storage_access_text_sd">If you don\'t see the SD card, try this</string>
     <string name="confirm_storage_access_android_text">Please allow the app accessing the selected storage on the next screen by pressing \'Use this folder\' at the bottom.</string>
     <string name="confirm_storage_access_android_text_specific">Please allow access to \'&lt;b&gt;%s&lt;/b&gt;\' on the next screen by pressing \'&lt;b&gt;Use this folder&lt;/b&gt;\' at the bottom.</string>
+    <string name="confirm_storage_access_restricted_text">Due to system restrictions, this folder is not available.</string>
+    <string name="confirm_storage_access_restricted_text_open_system">Open in system app</string>
+    <string name="confirm_storage_access_restricted_text_not_found">A system file manager app could not be found.</string>
     <string name="confirm_create_doc_for_new_folder_text">Please press \'&lt;b&gt;Save&lt;/b&gt;\' at the bottom of the next screen to create the new folder.</string>
     <string name="confirm_selection">Confirm selection</string>
     <string name="loading">Loading…</string>


Reviews:
user: login: naveensingh
body: Hey, I did some quick testing, left some comments below. I'll review once again later.

Thanks for contributing!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-05T10:40:27Z
user: login: papjul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T11:18:40Z
user: login: naveensingh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T19:25:19Z
user: login: naveensingh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T20:45:50Z
user: login: naveensingh
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-06T09:40:54Z

Review comments:
diff_hunk: @@ -226,30 +226,82 @@ fun BaseSimpleActivity.isShowingSAFCreateDocumentDialogSdk30(path: String): Bool
     }
 }
 
-fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
+fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false): Boolean {
     return if (isRestrictedSAFOnlyRoot(path) && (getAndroidTreeUri(path).isEmpty() || !hasProperStoredAndroidTreeUri(path))) {
         runOnUiThread {
             if (!isDestroyed && !isFinishing) {
-                ConfirmationAdvancedDialog(this, "", R.string.confirm_storage_access_android_text, R.string.ok, R.string.cancel) { success ->
-                    if (success) {
-                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-                            putExtra(EXTRA_SHOW_ADVANCED, true)
-                            putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(path))
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                                return@apply
-                            } catch (e: Exception) {
-                                type = "*/*"
-                            }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !openInSystemAppAllowed) {
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: naveensingh
body: Use `isUpsideDownCakePlus()`:

```suggestion
                if (isUpsideDownCakePlus() && !openInSystemAppAllowed) {
```
created_at: 2025-04-05T10:02:19Z
author_association: MEMBER
diff_hunk: @@ -226,30 +226,82 @@ fun BaseSimpleActivity.isShowingSAFCreateDocumentDialogSdk30(path: String): Bool
     }
 }
 
-fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
+fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false): Boolean {
     return if (isRestrictedSAFOnlyRoot(path) && (getAndroidTreeUri(path).isEmpty() || !hasProperStoredAndroidTreeUri(path))) {
         runOnUiThread {
             if (!isDestroyed && !isFinishing) {
-                ConfirmationAdvancedDialog(this, "", R.string.confirm_storage_access_android_text, R.string.ok, R.string.cancel) { success ->
-                    if (success) {
-                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-                            putExtra(EXTRA_SHOW_ADVANCED, true)
-                            putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(path))
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                                return@apply
-                            } catch (e: Exception) {
-                                type = "*/*"
-                            }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !openInSystemAppAllowed) {
+                    ConfirmationDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        positive = android.R.string.ok,
+                        negative = 0
+                    ) {}
+                } else {
+                    ConfirmationAdvancedDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: naveensingh
body: ```suggestion
                        if (isUpsideDownCakePlus()) {
```
created_at: 2025-04-05T10:03:00Z
author_association: MEMBER
diff_hunk: @@ -226,30 +226,82 @@ fun BaseSimpleActivity.isShowingSAFCreateDocumentDialogSdk30(path: String): Bool
     }
 }
 
-fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
+fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false): Boolean {
     return if (isRestrictedSAFOnlyRoot(path) && (getAndroidTreeUri(path).isEmpty() || !hasProperStoredAndroidTreeUri(path))) {
         runOnUiThread {
             if (!isDestroyed && !isFinishing) {
-                ConfirmationAdvancedDialog(this, "", R.string.confirm_storage_access_android_text, R.string.ok, R.string.cancel) { success ->
-                    if (success) {
-                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-                            putExtra(EXTRA_SHOW_ADVANCED, true)
-                            putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(path))
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                                return@apply
-                            } catch (e: Exception) {
-                                type = "*/*"
-                            }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !openInSystemAppAllowed) {
+                    ConfirmationDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        positive = android.R.string.ok,
+                        negative = 0
+                    ) {}
+                } else {
+                    ConfirmationAdvancedDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                            R.string.confirm_storage_access_restricted_text_open_system
+                        } else {
+                            R.string.confirm_storage_access_restricted_text_request_access
+                        },
+                        R.string.cancel
+                    ) { success ->
+                        if (success) {
+                            val uri = createAndroidDataOrObbUri(path)
+
+                            // On Android 14+, there is no longer any workaround to access the data/ and obb/ folders
+                            // Open the system app instead
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: naveensingh
body: ```suggestion
                            if (isUpsideDownCakePlus()) {
```
created_at: 2025-04-05T10:03:15Z
author_association: MEMBER
diff_hunk: @@ -226,30 +226,82 @@ fun BaseSimpleActivity.isShowingSAFCreateDocumentDialogSdk30(path: String): Bool
     }
 }
 
-fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
+fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false): Boolean {
     return if (isRestrictedSAFOnlyRoot(path) && (getAndroidTreeUri(path).isEmpty() || !hasProperStoredAndroidTreeUri(path))) {
         runOnUiThread {
             if (!isDestroyed && !isFinishing) {
-                ConfirmationAdvancedDialog(this, "", R.string.confirm_storage_access_android_text, R.string.ok, R.string.cancel) { success ->
-                    if (success) {
-                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-                            putExtra(EXTRA_SHOW_ADVANCED, true)
-                            putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(path))
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                                return@apply
-                            } catch (e: Exception) {
-                                type = "*/*"
-                            }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !openInSystemAppAllowed) {
+                    ConfirmationDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        positive = android.R.string.ok,
+                        negative = 0
+                    ) {}
+                } else {
+                    ConfirmationAdvancedDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                            R.string.confirm_storage_access_restricted_text_open_system
+                        } else {
+                            R.string.confirm_storage_access_restricted_text_request_access
+                        },
+                        R.string.cancel
+                    ) { success ->
+                        if (success) {
+                            val uri = createAndroidDataOrObbUri(path)
+
+                            // On Android 14+, there is no longer any workaround to access the data/ and obb/ folders
+                            // Open the system app instead
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                                // Using a if with OR conditions allows to test them one by one, and stops at the first successful one
+                                if (
+                                    startIntentForUriAction(
+                                        uri,
+                                        "android.intent.action.VIEW",
+                                        ComponentName("com.google.android.documentsui", "com.android.documentsui.files.FilesActivity")
+                                    ) ||
+                                    startIntentForUriAction(
+                                        uri,
+                                        "android.intent.action.VIEW",
+                                        ComponentName("com.android.documentsui", "com.android.documentsui.files.FilesActivity")
+                                    ) ||
+                                    startIntentForUriAction(
+                                        uri,
+                                        "android.intent.action.VIEW",
+                                        ComponentName("com.android.documentsui", "com.android.documentsui.FilesActivity")
+                                    ) ||
+                                    startIntentForUriAction(uri, "android.intent.action.VIEW", null) ||
+                                    startIntentForUriAction(uri, "android.provider.action.BROWSE", null) ||
+                                    startIntentForUriAction(uri, "android.provider.action.BROWSE_DOCUMENT_ROOT", null)
+                                ) {
+                                    return@ConfirmationAdvancedDialog
+                                } else {
+                                    toast(R.string.confirm_storage_access_restricted_text_not_found)
+                                }
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: naveensingh
body: If we must handle all these cases, it would be cleaner to extract this to an extension function. 

Maybe `launchSystemFileManager()`?
created_at: 2025-04-05T10:11:45Z
author_association: MEMBER
diff_hunk: @@ -262,6 +314,32 @@ fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
     }
 }
 
+/**
+ * Start the intent
+ * @param uri URI to pass to the intent
+ * @param action Action of the intent
+ * @param componentName Optional ComponentName
+ * @return true if the intent was successful
+ */
+fun BaseSimpleActivity.startIntentForUriAction(
+    uri: Uri,
+    action: String,
+    componentName: ComponentName?
+): Boolean {
+    val intent = Intent(action, uri)
+    if (componentName != null) {
+        intent.setComponent(componentName)
+    }
+    return try {
+        startActivity(intent)
+        finish()
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: naveensingh
body: Why `finish()`? This breaks the usual navigation flow.

Users will expect to be back in Fossify File Manager on back press, current behavior will lead them to their home screen.
created_at: 2025-04-05T10:25:25Z
author_association: MEMBER
diff_hunk: @@ -262,6 +314,32 @@ fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
     }
 }
 
+/**
+ * Start the intent
+ * @param uri URI to pass to the intent
+ * @param action Action of the intent
+ * @param componentName Optional ComponentName
+ * @return true if the intent was successful
+ */
+fun BaseSimpleActivity.startIntentForUriAction(
+    uri: Uri,
+    action: String,
+    componentName: ComponentName?
+): Boolean {
+    val intent = Intent(action, uri)
+    if (componentName != null) {
+        intent.setComponent(componentName)
+    }
+    return try {
+        startActivity(intent)
+        finish()
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: papjul
body: On back press, the user won't be back in Fossify, but go up one folder in the system app (which is a good thing if they want to move things around). They will be back to Fossify if they press back until reaching the root folder. I removed the `finish()` line if you prefer; I don’t particularly mind keeping it ;)
created_at: 2025-04-05T11:18:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -226,31 +226,32 @@ fun BaseSimpleActivity.isShowingSAFCreateDocumentDialogSdk30(path: String): Bool
     }
 }
 
-fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String): Boolean {
+fun BaseSimpleActivity.isShowingAndroidSAFDialog(path: String, openInSystemAppAllowed: Boolean = false): Boolean {
     return if (isRestrictedSAFOnlyRoot(path) && (getAndroidTreeUri(path).isEmpty() || !hasProperStoredAndroidTreeUri(path))) {
         runOnUiThread {
             if (!isDestroyed && !isFinishing) {
-                ConfirmationAdvancedDialog(this, "", R.string.confirm_storage_access_android_text, R.string.ok, R.string.cancel) { success ->
-                    if (success) {
-                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-                            putExtra(EXTRA_SHOW_ADVANCED, true)
-                            putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(path))
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                                return@apply
-                            } catch (e: Exception) {
-                                type = "*/*"
-                            }
+                if (!openInSystemAppAllowed) {
+                    ConfirmationDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        positive = android.R.string.ok,
+                        negative = 0
+                    ) {}
+                } else {
+                    ConfirmationAdvancedDialog(
+                        this,
+                        "",
+                        R.string.confirm_storage_access_restricted_text,
+                        R.string.confirm_storage_access_restricted_text_open_system,
+                        R.string.cancel
+                    ) { success ->
+                        if (success) {
+                            val uri = createAndroidDataOrObbUri(path)
 
-                            try {
-                                startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB)
-                                checkedDocumentPath = path
-                            } catch (e: ActivityNotFoundException) {
-                                toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
-                            } catch (e: Exception) {
-                                toast(R.string.unknown_error_occurred)
-                            }
+                            // On Android 14+, there is no longer any workaround to access the data/ and obb/ folders
+                            // Open the system app instead
path: commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt
user: login: naveensingh
body: Outdated comment
created_at: 2025-04-05T19:25:18Z
author_association: MEMBER
diff_hunk: @@ -290,6 +290,10 @@
     <string name="confirm_storage_access_text_sd">If you don\'t see the SD card, try this</string>
     <string name="confirm_storage_access_android_text">Please allow the app accessing the selected storage on the next screen by pressing \'Use this folder\' at the bottom.</string>
     <string name="confirm_storage_access_android_text_specific">Please allow access to \'&lt;b&gt;%s&lt;/b&gt;\' on the next screen by pressing \'&lt;b&gt;Use this folder&lt;/b&gt;\' at the bottom.</string>
+    <string name="confirm_storage_access_restricted_text">Due to system restrictions, this folder is not available.</string>
+    <string name="confirm_storage_access_restricted_text_request_access">Request access</string>
path: commons/src/main/res/values/strings.xml
user: login: naveensingh
body: I guess this "Request access" string is not needed anymore, at least not for now.
created_at: 2025-04-05T20:45:49Z
author_association: MEMBER

PR comments:
user: login: naveensingh
created_at: 2025-04-05T10:42:40Z
body: Unrelated to your PR but it doesn't seem right to fire "Storage permission is required" toast when pressing back on the system SAF page.
author_association: MEMBER
user: login: papjul
created_at: 2025-04-05T11:21:45Z
body: > Unrelated to your PR but it doesn't seem right to fire "Storage permission is required" toast when pressing back on the system SAF page.

If you're talking about the `no_storage_permissions` string, I don’t see it used in the `Commons` project, and I’m not sure to understand how you trigger this
author_association: CONTRIBUTOR
user: login: naveensingh
created_at: 2025-04-05T15:57:19Z
body: Let's apply this new behavior to Android 11 and above. More context: https://github.com/FossifyOrg/File-Manager/issues/16#issuecomment-2780869235



author_association: MEMBER
user: login: papjul
created_at: 2025-04-05T16:23:12Z
body: Done. The `isRestrictedSAFOnlyRoot(path)` already checks if running Android 11+.
I made a separate commit, so you can easily revert once you find out which DocumentUI versions are compatible on Android 11-13.
author_association: CONTRIBUTOR
user: login: naveensingh
created_at: 2025-04-05T20:43:02Z
body: This change looks safe for the most part, even though the SAF callback is never executed now. These functions really are used extensively. 

I hope you have tested this and it is regression-free ;)
author_association: MEMBER
user: login: papjul
created_at: 2025-04-06T07:53:22Z
body: > This change looks safe for the most part, even though the SAF callback is never executed now. These functions really are used extensively.
> 
> I hope you have tested this and it is regression-free ;)

Well, I did my share of tests, and I hardly see how it could be worse than the current broken situation given the limited scope of the changes (that's also why I wanted to avoid any major UI changes).

Note that on my side, the file browsing feature is completely broken in apps other than the file manager (I know there is an ongoing issue about this), but the data/ and obb/ are still available (and the only available folders actually), so I could test the dialog on the Voice recorder app.

Then, you're the code reviewer, and you know your apps better than me, so your own tests are essential ;) And I noticed you already spotted a few existing bugs from before this PR ;)
author_association: CONTRIBUTOR
user: login: naveensingh
created_at: 2025-04-06T09:43:35Z
body: Looks good now, thanks!

A Jitpack build should be up soon for https://github.com/FossifyOrg/File-Manager/pull/152.
author_association: MEMBER