Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open files with "Show in Amaze" option #4069

Open
wants to merge 6 commits into
base: release/4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@
<data android:scheme="com.amaze.filemanager" />
</intent-filter>

<intent-filter
android:label="@string/show_in_folder">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<data android:scheme="content" />
</intent-filter>

<intent-filter android:label="@string/open_with_amaze">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
Expand Down
278 changes: 278 additions & 0 deletions app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/*
* Copyright (C) 2014-2024 Arpit Khurana <[email protected]>, Vishal Nehra <[email protected]>,
* Emmanuel Messulam<[email protected]>, Raymond Lai <airwave209gt at gmail.com> 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 <http://www.gnu.org/licenses/>.
*/

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>): 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(

Check warning on line 146 in app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/src/main/java/com/amaze/filemanager/filesystem/files/UriUtils.kt#L146

The function getPathFromDownloads is too long (64). The maximum length is 60.
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>?,
): 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ You only need to do this once, until the next time you select a new location for
<string name="share_logs">Share logs</string>
<string name="share_logs_summary">Share captured logs via email / telegram</string>
<string name="open_with_amaze">Open with Amaze</string>
<string name="show_in_folder">Show in Amaze</string>
<string name="confirmation">Confirmation</string>
<string name="open_file_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</string>
<string name="error_google_play_cannot_update_myself">Per Google Play policy mandates, apps are not allowed to update itself on its own. Please update app from Google Play.</string>
Expand Down
Loading