Skip to content

Instantly share code, notes, and snippets.

@AliAzaz
Created August 9, 2021 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AliAzaz/db59aeef5c0b664fcc9bc67ac39a0b2a to your computer and use it in GitHub Desktop.
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
/*
* 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