The above answer which is given by tushar is abosulity correct, I have converted that code into kotlin, might be it helps someone.
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.text.TextUtils
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import kotlin.math.min
@SuppressLint("Recycle")
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "DEPRECATION")
object FileManager {
fun getPath(context: Context, uri: Uri): String? {
// check here to KITKAT or new version
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
var selection: String? = null
var selectionArgs: Array<String>? = null
// DocumentProvider
if (isKitKat) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
//val type = split[0]
val fullPath = getPathFromExtSD(split)
return if (fullPath !== "") {
fullPath
} else {
null
}
}
// DownloadsProvider
if (isDownloadsDocument(uri)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var cursor: Cursor? = null
try {
cursor = context.contentResolver.query(uri, arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val fileName: String = cursor.getString(0)
val path: String = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
if (!TextUtils.isEmpty(path)) {
return path
}
}
} finally {
cursor?.close()
}
val id: String = DocumentsContract.getDocumentId(uri)
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
return id.replaceFirst("raw:".toRegex(), "")
}
val contentUriPrefixesToTry = arrayOf("content://downloads/public_downloads", "content://downloads/my_downloads")
for (contentUriPrefix in contentUriPrefixesToTry) {
return try {
val contentUri: Uri = 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:".toRegex(), "")
}
var contentUri : Uri?= null
try {
contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
} catch (e: NumberFormatException) {
e.printStackTrace()
}
if (contentUri != null) {
return getDataColumn(context, contentUri, null, null)
}
}
}
// MediaProvider
if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).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)
}
if (isGoogleDriveUri(uri)) {
return getDriveFilePath(context,uri)
}
if (isWhatsAppFile(uri)) {
return getFilePathForWhatsApp(context, uri)
}
if ("content".equals(uri.scheme, ignoreCase = true)) {
if (isGooglePhotosUri(uri)) {
return uri.lastPathSegment
}
if (isGoogleDriveUri(uri)) {
return getDriveFilePath(context, uri)
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// return getFilePathFromURI(context,uri);
copyFileToInternalStorage(context, uri, "userfiles")
// return getRealPathFromURI(context,uri);
} else {
getDataColumn(context, uri, null, null)
}
}
if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
} else {
if (isWhatsAppFile(uri)) {
return getFilePathForWhatsApp(context, uri)
}
if ("content".equals(uri.scheme, ignoreCase = true)) {
val projection = arrayOf(MediaStore.Images.Media.DATA)
val cursor: Cursor
try {
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)!!
val columnIndex: Int = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
if (cursor.moveToFirst()) {
return cursor.getString(columnIndex)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
return 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(context: Context, uri: Uri): String? {
val returnUri: Uri = uri
val returnCursor: Cursor = context.contentResolver.query(returnUri, 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: Int = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
// val sizeIndex: Int = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name: String = returnCursor.getString(nameIndex)
// val size = returnCursor.getLong(sizeIndex).toString()
val file = File(context.cacheDir, name)
try {
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(file)
var read : Int
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable: Int = inputStream?.available()!!
//int bufferSize = 1024;
val bufferSize = min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream.read(buffers).also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
Timber.d("File Size Size ${file.length()}")
inputStream.close()
outputStream.close()
Timber.d("File Path Path ${file.path}")
Timber.d("File Size Size ${file.length()}")
} catch (e: Exception) {
Timber.e("Exception ${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(context: Context, uri: Uri, newDirName: String): String? {
val returnUri: Uri = uri
val returnCursor: Cursor = context.contentResolver.query(returnUri, 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: Int = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
//val sizeIndex: Int = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name: String = returnCursor.getString(nameIndex)
// val size = returnCursor.getLong(sizeIndex).toString()
val output: File = if (newDirName != "") {
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: InputStream? = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(output)
var read = 0
val bufferSize = 1024
val buffers = ByteArray(bufferSize)
while (inputStream?.read(buffers).also {
if (it != null) {
read = it
}
} != -1) {
outputStream.write(buffers, 0, read)
}
inputStream?.close()
outputStream.close()
} catch (e: Exception) {
Timber.e("Exception${e.message}")
}
return output.path
}
private fun getFilePathForWhatsApp(context: Context, uri: Uri): String? {
return copyFileToInternalStorage(context, 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: Int = 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
}
}
To launch
val intent = Intent(ACTION_GET_CONTENT)
intent.apply {
type = "application/pdf"
addCategory(Intent.CATEGORY_OPENABLE)
}
resultLauncher.launch(intent)
and you can get the result in your acivity
private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
var file: File? = null
val disposable = Single.fromCallable {
val filePath = FileManager.getPath(this, result?.data?.data!!)
file = File(filePath)
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe({
Timber.d("==File==Path===${file?.path}\nname==${file?.name}\nabsolutePath =${file?.absolutePath}")
}, {
it.printStackTrace()
})
}
}
loadThumbnail
method doesn't actually provide a uri to the thumbnail file. – Bedelia