From 12f63de24cda5c496a796883c4771d76ffe96168 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 3 Feb 2024 21:41:28 +0100 Subject: [PATCH 1/5] add intent filter for ACTION_VIEW --- app/src/main/AndroidManifest.xml | 8 ++++++++ app/src/main/res/values/strings.xml | 1 + 2 files changed, 9 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1db42ba617..11fd264876 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -111,6 +111,14 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e2478e10d7..5d7a49d3fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -793,6 +793,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. From 1c7422506f25c3e6ff967708b2660bcb10f2c00d Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 3 Feb 2024 23:05:07 +0100 Subject: [PATCH 2/5] add function to get path from uri --- .../filemanager/filesystem/files/UriUtils.kt | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt 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..4ce33c3b46 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt @@ -0,0 +1,250 @@ +/* + * 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) + val uriPath = uri.path + if (path != null) { + return path + } else if (fileExists(uri.path)) { + // Check if the full path is the uri path + return uri.path + } else if (uriPath != null && uriPath.contains("/storage")) { + // As last resort, check if the full path is somehow contained in the uri + val pathInUri = uriPath.substring(uriPath.indexOf("/storage")) + if (fileExists(pathInUri)) { + return pathInUri + } + } + } + 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 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 +} From 48eb40eca32fcfb963b06d76dada17a547deac72 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 3 Feb 2024 23:05:46 +0100 Subject: [PATCH 3/5] resolve uri from intents from new intent filter --- .../ui/activities/MainActivity.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 ff894315c9..386a427e15 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 @@ -101,6 +101,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; @@ -631,6 +632,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)); } From f75ed3ef52e2c960aa433b0f9aff20d478db3f20 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Sat, 3 Feb 2024 23:49:12 +0100 Subject: [PATCH 4/5] add some more hacky ways to get path --- .../filemanager/filesystem/files/UriUtils.kt | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) 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 index 4ce33c3b46..e90a64abe9 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt @@ -84,18 +84,14 @@ fun fromUri(uri: Uri, context: Context): String? { return uri.lastPathSegment } val path = getDataColumn(context, uri, null, null) - val uriPath = uri.path if (path != null) { return path } else if (fileExists(uri.path)) { // Check if the full path is the uri path return uri.path - } else if (uriPath != null && uriPath.contains("/storage")) { - // As last resort, check if the full path is somehow contained in the uri - val pathInUri = uriPath.substring(uriPath.indexOf("/storage")) - if (fileExists(pathInUri)) { - return pathInUri - } + } else { + // Check if the full path is contained in the uri path + return getPathInUri(uri) } } if ("file".equals(uri.scheme, ignoreCase = true)) { @@ -233,6 +229,25 @@ private fun getDataColumn( 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 } From 0e84c95f266c709b6981fb8d2e7f035078a09d6e Mon Sep 17 00:00:00 2001 From: Vishnu Sanal T Date: Mon, 14 Oct 2024 23:11:03 +0530 Subject: [PATCH 5/5] chore: spotless --- .../filemanager/filesystem/files/UriUtils.kt | 93 +++++++++++-------- 1 file changed, 53 insertions(+), 40 deletions(-) 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 index e90a64abe9..e5037c74f2 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt @@ -36,7 +36,10 @@ import java.io.File * * Adapted from: https://github.com/saparkhid/AndroidFileNamePicker/blob/main/javautil/FileUtils.java */ -fun fromUri(uri: Uri, context: Context): String? { +fun fromUri( + uri: Uri, + context: Context, +): String? { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { val docId = DocumentsContract.getDocumentId(uri) @@ -58,25 +61,27 @@ fun fromUri(uri: Uri, context: Context): String? { 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") + 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) } - else -> return getDataColumn(context, uri, null, null) - } val selection = "_id=?" - val selectionArgs = arrayOf( - split[1] - ) + val selectionArgs = + arrayOf( + split[1], + ) return getDataColumn(context, contentUri, selection, selectionArgs) } if ("content".equals(uri.scheme, ignoreCase = true)) { @@ -138,7 +143,10 @@ private fun getPathFromExtSD(pathData: List): String? { } } -private fun getPathFromDownloads(uri: Uri, context: Context): String? { +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( @@ -146,14 +154,16 @@ private fun getPathFromDownloads(uri: Uri, context: Context): String? { arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, 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 + val fileName = + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME), + ) + val path = + Environment.getExternalStorageDirectory() + .toString() + "/Download/" + fileName if (!TextUtils.isEmpty(path)) { return path } @@ -164,17 +174,19 @@ private fun getPathFromDownloads(uri: Uri, context: Context): String? { if (id.startsWith("raw:")) { return id.replaceFirst("raw:", "") } - val contentUriPrefixesToTry = arrayOf( - "content://downloads/public_downloads", - "content://downloads/my_downloads" - ) + 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) - ) + 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 @@ -189,10 +201,11 @@ private fun getPathFromDownloads(uri: Uri, context: Context): String? { return id.replaceFirst("raw:", "") } return try { - val contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), - java.lang.Long.valueOf(id) - ) + val contentUri = + ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + java.lang.Long.valueOf(id), + ) getDataColumn(context, contentUri, null, null) } catch (e: NumberFormatException) { null @@ -205,7 +218,7 @@ private fun getDataColumn( context: Context, uri: Uri, selection: String?, - selectionArgs: Array? + selectionArgs: Array?, ): String? { val column = MediaStore.Files.FileColumns.DATA val projection = arrayOf(column) @@ -215,7 +228,7 @@ private fun getDataColumn( projection, selection, selectionArgs, - null + null, ).use { cursor -> if (cursor != null && cursor.moveToFirst()) { val index: Int = cursor.getColumnIndex(column) @@ -238,7 +251,7 @@ private fun getPathInUri(uri: Uri): String? { if (uriPath.contains(prefix)) { // make sure path starts with storage val pathInUri = "/storage${uriPath.substring( - uriPath.indexOf(prefix) + prefix.length + uriPath.indexOf(prefix) + prefix.length, )}" if (fileExists(pathInUri)) { return pathInUri