diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4735783704..7ec487a539 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -111,6 +111,14 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt
new file mode 100644
index 0000000000..e5037c74f2
--- /dev/null
+++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra ,
+ * Emmanuel Messulam, Raymond Lai and Contributors.
+ *
+ * This file is part of Amaze File Manager.
+ *
+ * Amaze File Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.amaze.filemanager.filesystem.files
+
+import android.content.ContentUris
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.text.TextUtils
+import java.io.File
+
+/**
+ * Tries to find the path of the file that is identified with [uri].
+ * If the path cannot be found, returns null.
+ *
+ * Adapted from: https://github.com/saparkhid/AndroidFileNamePicker/blob/main/javautil/FileUtils.java
+ */
+fun fromUri(
+ uri: Uri,
+ context: Context,
+): String? {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ val docId = DocumentsContract.getDocumentId(uri)
+ val split = docId.split(":")
+ val fullPath = getPathFromExtSD(split)
+ return if (fullPath !== "") {
+ fullPath
+ } else {
+ null
+ }
+ }
+
+ // DownloadsProvider
+ if (isDownloadsDocument(uri)) {
+ return getPathFromDownloads(uri, context)
+ }
+
+ // MediaProvider
+ if (isMediaDocument(uri)) {
+ val docId = DocumentsContract.getDocumentId(uri)
+ val split = docId.split(":")
+ val contentUri =
+ when (split[0]) {
+ "image" -> {
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ }
+ "video" -> {
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ }
+ "audio" -> {
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+ }
+ "document" -> {
+ MediaStore.Files.getContentUri("external")
+ }
+ else -> return getDataColumn(context, uri, null, null)
+ }
+ val selection = "_id=?"
+ val selectionArgs =
+ arrayOf(
+ split[1],
+ )
+ return getDataColumn(context, contentUri, selection, selectionArgs)
+ }
+ if ("content".equals(uri.scheme, ignoreCase = true)) {
+ if (isGooglePhotosUri(uri)) {
+ return uri.lastPathSegment
+ }
+ val path = getDataColumn(context, uri, null, null)
+ if (path != null) {
+ return path
+ } else if (fileExists(uri.path)) {
+ // Check if the full path is the uri path
+ return uri.path
+ } else {
+ // Check if the full path is contained in the uri path
+ return getPathInUri(uri)
+ }
+ }
+ if ("file".equals(uri.scheme, ignoreCase = true)) {
+ return uri.path
+ }
+ return null
+}
+
+private fun fileExists(filePath: String?): Boolean {
+ if (filePath == null) return false
+
+ val file = File(filePath)
+ return file.exists()
+}
+
+private fun getPathFromExtSD(pathData: List): String? {
+ val type = pathData[0]
+ val relativePath = File.separator + pathData[1]
+ var fullPath: String? = null
+ // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string
+ // something like "71F8-2C0A", some kind of unique id per storage
+ // don't know any API that can get the root path of that storage based on its id.
+ //
+ // so no "primary" type, but let the check here for other devices
+ if ("primary".equals(type, ignoreCase = true)) {
+ fullPath = Environment.getExternalStorageDirectory().toString() + relativePath
+ if (fileExists(fullPath)) {
+ return fullPath
+ }
+ }
+ if ("home".equals(type, ignoreCase = true)) {
+ fullPath = "/storage/emulated/0/Documents$relativePath"
+ if (fileExists(fullPath)) {
+ return fullPath
+ }
+ }
+
+ // Adapted from: https://stackoverflow.com/questions/42110882/get-real-path-from-uri-of-file-in-sdcard-marshmallow
+ fullPath = "/storage/$type$relativePath"
+ return if (fileExists(fullPath)) {
+ fullPath
+ } else {
+ null
+ }
+}
+
+private fun getPathFromDownloads(
+ uri: Uri,
+ context: Context,
+): String? {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // Try to use ContentResolver to get the file name
+ context.contentResolver.query(
+ uri,
+ arrayOf(MediaStore.MediaColumns.DISPLAY_NAME),
+ null,
+ null,
+ null,
+ ).use { cursor ->
+ if (cursor != null && cursor.moveToFirst()) {
+ val fileName =
+ cursor.getString(
+ cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME),
+ )
+ val path =
+ Environment.getExternalStorageDirectory()
+ .toString() + "/Download/" + fileName
+ if (!TextUtils.isEmpty(path)) {
+ return path
+ }
+ }
+ }
+ val id = DocumentsContract.getDocumentId(uri)
+ if (!TextUtils.isEmpty(id)) {
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "")
+ }
+ val contentUriPrefixesToTry =
+ arrayOf(
+ "content://downloads/public_downloads",
+ "content://downloads/my_downloads",
+ )
+ // Try to guess full path with frequently used download paths
+ for (contentUriPrefix in contentUriPrefixesToTry) {
+ return try {
+ val contentUri =
+ ContentUris.withAppendedId(
+ Uri.parse(contentUriPrefix),
+ java.lang.Long.valueOf(id),
+ )
+ getDataColumn(context, contentUri, null, null)
+ } catch (e: NumberFormatException) {
+ // In Android 8 and Android P the id is not a number
+ uri.path!!.replaceFirst("^/document/raw:", "")
+ .replaceFirst("^raw:", "")
+ }
+ }
+ }
+ } else {
+ val id = DocumentsContract.getDocumentId(uri)
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "")
+ }
+ return try {
+ val contentUri =
+ ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"),
+ java.lang.Long.valueOf(id),
+ )
+ getDataColumn(context, contentUri, null, null)
+ } catch (e: NumberFormatException) {
+ null
+ }
+ }
+ return null
+}
+
+private fun getDataColumn(
+ context: Context,
+ uri: Uri,
+ selection: String?,
+ selectionArgs: Array?,
+): String? {
+ val column = MediaStore.Files.FileColumns.DATA
+ val projection = arrayOf(column)
+
+ context.contentResolver.query(
+ uri,
+ projection,
+ selection,
+ selectionArgs,
+ null,
+ ).use { cursor ->
+ if (cursor != null && cursor.moveToFirst()) {
+ val index: Int = cursor.getColumnIndex(column)
+ return if (index >= 0) {
+ cursor.getString(index)
+ } else {
+ null
+ }
+ }
+ }
+ return null
+}
+
+private fun getPathInUri(uri: Uri): String? {
+ // As last resort, check if the full path is somehow contained in the uri path
+ val uriPath = uri.path ?: return null
+ // Some common path prefixes
+ val pathPrefixes = listOf("/storage", "/external_files")
+ for (prefix in pathPrefixes) {
+ if (uriPath.contains(prefix)) {
+ // make sure path starts with storage
+ val pathInUri = "/storage${uriPath.substring(
+ uriPath.indexOf(prefix) + prefix.length,
+ )}"
+ if (fileExists(pathInUri)) {
+ return pathInUri
+ }
+ }
+ }
+ return null
+}
+
+private fun isExternalStorageDocument(uri: Uri): Boolean {
+ return "com.android.externalstorage.documents" == uri.authority
+}
+
+private fun isDownloadsDocument(uri: Uri): Boolean {
+ return "com.android.providers.downloads.documents" == uri.authority
+}
+
+private fun isMediaDocument(uri: Uri): Boolean {
+ return "com.android.providers.media.documents" == uri.authority
+}
+
+private fun isGooglePhotosUri(uri: Uri): Boolean {
+ return "com.google.android.apps.photos.content" == uri.authority
+}
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index fc6947f172..3ecd62ea3d 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -106,6 +106,7 @@
import com.amaze.filemanager.filesystem.PasteHelper;
import com.amaze.filemanager.filesystem.RootHelper;
import com.amaze.filemanager.filesystem.files.FileUtils;
+import com.amaze.filemanager.filesystem.files.UriUtilsKt;
import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool;
import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo;
import com.amaze.filemanager.filesystem.ssh.SshClientUtils;
@@ -637,6 +638,25 @@ private void checkForExternalIntent(Intent intent) {
* http://teamamaze.xyz/open_file?path=path-to-file
*/
path = Utils.sanitizeInput(uri.getQueryParameter("path"));
+ } else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ File fromUri = null;
+ try {
+ String path = UriUtilsKt.fromUri(uri, this);
+ if (path != null) {
+ fromUri = new File(path);
+ }
+ } catch (Exception ignored) {
+ }
+
+ if (fromUri != null && fromUri.getParent() != null) {
+ path = Utils.sanitizeInput(fromUri.getParent());
+ scrollToFileName = Utils.sanitizeInput(fromUri.getName());
+ } else {
+ Toast.makeText(this, getString(R.string.error_file_not_found), Toast.LENGTH_LONG).show();
+ path = null;
+ scrollToFileName = null;
+ }
} else {
LOG.warn(getString(R.string.error_cannot_find_way_open));
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7d3f97ae2a..ca493642ba 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -794,6 +794,7 @@ You only need to do this once, until the next time you select a new location for
Share logs
Share captured logs via email / telegram
Open with Amaze
+ Show in Amaze
Confirmation
Are you sure you want to open following file?\n\nName:\n%s\n\nLocation:\n%s\n\nSize:\n%s\n\nMD5:\n%s\n\nSHA256:\n%s\n\n
Per Google Play policy mandates, apps are not allowed to update itself on its own. Please update app from Google Play.