Created
August 9, 2021 09:00
-
-
Save AliAzaz/db59aeef5c0b664fcc9bc67ac39a0b2a to your computer and use it in GitHub Desktop.
Get full file path from a URI that points to a any document
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Credit to: https://stackoverflow.com/a/60642994/9764941 | |
* */ | |
object ImageMediaStoreFetching { | |
@JvmStatic | |
@SuppressLint("NewApi") | |
fun getPath(uri: Uri): String? { | |
val selection: String? | |
val selectionArgs: Array<String>? | |
// ExternalStorageProvider | |
if (isExternalStorageDocument(uri)) { | |
val docId = DocumentsContract.getDocumentId(uri) | |
val split = docId.split(":").toTypedArray() | |
val fullPath = getPathFromExtSD(split) | |
return if (fullPath != StringUtils.EMPTY) { | |
fullPath | |
} else { | |
null | |
} | |
} | |
// DownloadsProvider | |
else if (isDownloadsDocument(uri)) { | |
context.contentResolver.query( | |
uri, | |
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), | |
null, | |
null, | |
null | |
).use { cursor -> | |
if (cursor != null && cursor.moveToFirst()) { | |
val fileName = cursor.getString(0) | |
val path = Environment.getExternalStorageDirectory().toString() + | |
"/Download/" + fileName | |
if (!TextUtils.isEmpty(path)) { | |
return path | |
} | |
} | |
} | |
val id: String = DocumentsContract.getDocumentId(uri) | |
if (!TextUtils.isEmpty(id)) { | |
if (id.startsWith("raw:")) { | |
return id.replaceFirst("raw:".toRegex(), StringUtils.EMPTY) | |
} | |
val contentUriPrefixesToTry = arrayOf( | |
"content://downloads/public_downloads", | |
"content://downloads/my_downloads" | |
) | |
for (contentUriPrefix in contentUriPrefixesToTry) { | |
return try { | |
val contentUri = | |
ContentUris.withAppendedId(Uri.parse(contentUriPrefix), id.toLong()) | |
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:".toRegex(), StringUtils.EMPTY) | |
.replaceFirst("^raw:".toRegex(), StringUtils.EMPTY) | |
} | |
} | |
} | |
} | |
// MediaProvider | |
else if (isMediaDocument(uri)) { | |
val docId = DocumentsContract.getDocumentId(uri) | |
val split = docId.split(":").toTypedArray() | |
val type = split[0] | |
var contentUri: Uri? = null | |
when (type) { | |
"image" -> { | |
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI | |
} | |
"video" -> { | |
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI | |
} | |
"audio" -> { | |
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI | |
} | |
} | |
selection = "_id=?" | |
selectionArgs = arrayOf(split[1]) | |
return getDataColumn( | |
context, contentUri, selection, | |
selectionArgs | |
) | |
} else if (isGoogleDriveUri(uri)) { | |
return getDriveFilePath(uri) | |
} else if (isWhatsAppFile(uri)) { | |
return getFilePathForWhatsApp(uri) | |
} else if ("content".equals(uri.scheme, ignoreCase = true)) { | |
if (isGooglePhotosUri(uri)) { | |
return uri.lastPathSegment | |
} | |
if (isGoogleDriveUri(uri)) { | |
return getDriveFilePath(uri) | |
} | |
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | |
copyFileToInternalStorage(uri, "userfiles") | |
} else { | |
getDataColumn(context, uri, null, null) | |
} | |
} | |
return if ("file".equals(uri.scheme, ignoreCase = true)) { | |
uri.path | |
} else null | |
} | |
private fun fileExists(filePath: String): Boolean { | |
val file = File(filePath) | |
return file.exists() | |
} | |
private fun getPathFromExtSD(pathData: Array<String>): String { | |
val type = pathData[0] | |
val relativePath = "/" + pathData[1] | |
var fullPath: String | |
// 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 | |
} | |
} | |
// Environment.isExternalStorageRemovable() is `true` for external and internal storage | |
// so we cannot relay on it. | |
// | |
// instead, for each possible path, check if file exists | |
// we'll start with secondary storage as this could be our (physically) removable sd card | |
fullPath = System.getenv("SECONDARY_STORAGE") + relativePath | |
if (fileExists(fullPath)) { | |
return fullPath | |
} | |
fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath | |
return if (fileExists(fullPath)) { | |
fullPath | |
} else fullPath | |
} | |
private fun getDriveFilePath(uri: Uri): String { | |
val returnCursor = context.contentResolver.query(uri, null, null, null, null) | |
/* | |
* Get the column indexes of the data in the Cursor, | |
* * move to the first row in the Cursor, get the data, | |
* * and display it. | |
* */ | |
val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME) | |
returnCursor.moveToFirst() | |
val name = returnCursor.getString(nameIndex) | |
val file = File(context.cacheDir, name) | |
try { | |
val inputStream = context.contentResolver.openInputStream(uri) | |
val outputStream = FileOutputStream(file) | |
var read: Int | |
val maxBufferSize = 1024 * 1024 | |
val bytesAvailable = inputStream!!.available() | |
val bufferSize = Math.min(bytesAvailable, maxBufferSize) | |
val buffers = ByteArray(bufferSize) | |
while (inputStream.read(buffers).also { read = it } != -1) { | |
outputStream.write(buffers, 0, read) | |
} | |
Log.e("File Size", "Size " + file.length()) | |
inputStream.close() | |
outputStream.close() | |
Log.e("File Path", "Path " + file.path) | |
Log.e("File Size", "Size " + file.length()) | |
} catch (e: Exception) { | |
Log.e("Exception", Objects.requireNonNull(e.message)) | |
} | |
return file.path | |
} | |
/* | |
* Used for Android Q+ | |
* @param uri | |
* @param newDirName if you want to create a directory, you can set this variable | |
* @return | |
*/ | |
private fun copyFileToInternalStorage(uri: Uri, newDirName: String): String { | |
val returnCursor = context.contentResolver.query( | |
uri, arrayOf( | |
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE | |
), null, null, null | |
) | |
/* | |
* Get the column indexes of the data in the Cursor, | |
* * move to the first row in the Cursor, get the data, | |
* * and display it. | |
* */ | |
val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME) | |
returnCursor.moveToFirst() | |
val name = returnCursor.getString(nameIndex) | |
val output: File = if (newDirName != StringUtils.EMPTY) { | |
val dir = File(context.filesDir.toString() + "/" + newDirName) | |
if (!dir.exists()) { | |
dir.mkdir() | |
} | |
File(context.filesDir.toString() + "/" + newDirName + "/" + name) | |
} else { | |
File(context.filesDir.toString() + "/" + name) | |
} | |
try { | |
val inputStream = context.contentResolver.openInputStream(uri) | |
val outputStream = FileOutputStream(output) | |
var read: Int | |
val bufferSize = 1024 | |
val buffers = ByteArray(bufferSize) | |
while (inputStream!!.read(buffers).also { read = it } != -1) { | |
outputStream.write(buffers, 0, read) | |
} | |
inputStream.close() | |
outputStream.close() | |
} catch (e: Exception) { | |
Log.e("Exception", Objects.requireNonNull(e.message)) | |
} | |
return output.path | |
} | |
private fun getFilePathForWhatsApp(uri: Uri): String { | |
return copyFileToInternalStorage(uri, "whatsapp") | |
} | |
private fun getDataColumn( | |
context: Context, | |
uri: Uri?, | |
selection: String?, | |
selectionArgs: Array<String>? | |
): String? { | |
var cursor: Cursor? = null | |
val column = "_data" | |
val projection = arrayOf(column) | |
try { | |
cursor = context.contentResolver.query( | |
uri!!, projection, | |
selection, selectionArgs, null | |
) | |
if (cursor != null && cursor.moveToFirst()) { | |
val index = cursor.getColumnIndexOrThrow(column) | |
return cursor.getString(index) | |
} | |
} finally { | |
cursor?.close() | |
} | |
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 | |
} | |
private fun isWhatsAppFile(uri: Uri): Boolean { | |
return "com.whatsapp.provider.media" == uri.authority | |
} | |
private fun isGoogleDriveUri(uri: Uri): Boolean { | |
return "com.google.android.apps.docs.storage" == uri.authority || "com.google.android.apps.docs.storage.legacy" == uri.authority | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment