I'm using Kotlin, and in converting the file uri to a file, the file uri value starts with content://, so I can't access it. So you need to know the absolute path.
There are many java codes, but the code for kotlin does not exist, so I tried to convert it, but an error occurs in kotlin. Please help me
Not only Image file I want all type files
Thanks a lot
There is a separate mechanism for getting path from URI in the versions below Kitkat (getDataColumn(...) method will serve the purpose as gets the file path using ContentResolver).
In the versions above the Kitkat, we have to implement a separate logic for different file types, either they are picked from documents directory, Gallery, or downloads folder.
URIPathHelper.kt
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
class URIPathHelper {
fun getPath(context: Context, uri: Uri): String? {
val isKitKatorAbove = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// DocumentProvider
if (isKitKatorAbove && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
return getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
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.getContentResolver().query(uri, projection, selection, selectionArgs,null)
if (cursor != null && cursor.moveToFirst()) {
val column_index: Int = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
} finally {
if (cursor != null) cursor.close()
}
return null
}
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
}
You can later use it like
val uriPathHelper = URIPathHelper()
val filePath = uriPathHelper.getPath(this, user_uri)
Related
I'm newbie in Kotlin programming and I'm building a function to get a file from an URI, like this:
fun getFileFromUri(context: Context, uri: Uri): File? {
var path: String? = null
// DocumentProvider
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (DocumentsContract.isDocumentUri(context, uri)) { // TODO: 2015. 11. 17. KITKAT
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
path = Environment.getExternalStorageDirectory().absolutePath + "/" + split[1]
}
// TODO handle non-primary volumes
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
val id = DocumentsContract.getDocumentId(uri)
val contentUri: Uri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
java.lang.Long.valueOf(id)
)
path = getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) { // MediaProvider
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(
split[1]
)
path = getDataColumn(context, contentUri!!, selection, selectionArgs)
} // MediaStore (and general)
} else if ("content".equals(uri.getScheme(), ignoreCase = true)) {
path = getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.getScheme(), ignoreCase = true)) {
path = uri.getPath()
}
File(path)
} else {
val cursor: Cursor = context.getContentResolver().query(uri, null, null, null, null)!!
File(cursor.getString(cursor.getColumnIndex("_data")))
}
}
However, the last line, File(cursor.getString(cursor.getColumnIndex("_data"))), shows this error:
Value must be ≥ 0 but getColumnIndex can be -1
The code and the apk works, but I'm worried it may fail in the future. I try to add an:
if (cursor != null && cursor.moveToFirst()) {
...}
but it also shows an error. I'm not sure if it needs to return something but anyway, I'm not sure if the solution goes on that direction.
I'm struggling to find a clear answer to this question. I know it somehow involves using media store but I can't quite work it out.
Thank you.
Edit: if it helps, I'm using a Pixel 2 at the moment.
If you are targeting to Android 11 API, you cannot directly get access to the file paths, as there are many restrictions in API 30(Android R). As scoped storage API was introduced in Android 10(API 29), the storage is now divided into scoped storage(private storage) and shared storage(public storage). Scoped storage is a kind you can only have access to the files that are created in your scoped storage directory(i.e. /Android/data/ or /Android/media/). You cannot access files from shared storage (i.e. internal storage/external SD card storage etc.)
The shared storage is again further divided into Media and Download collection. Media collection stores Image, Audio and Video files. Download collection would take care of non-media files.
To learn in more details about scoped storage and shared storage refer this link- Scoped Storage in Android 10 & Android 11 .
To get the document directory path, I have created URI utils class that will get the path for the file from URI.
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.util.Log
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
class UriPathUtils
{
fun getRealPathFromURI(context: Context, uri: Uri): String? {
when {
// DocumentProvider
DocumentsContract.isDocumentUri(context, uri) -> {
when {
// ExternalStorageProvider
isExternalStorageDocument(uri) -> {
//Toast.makeText(context, "From Internal & External Storage dir", Toast.LENGTH_SHORT).show()
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
// This is for checking Main Memory
return if ("primary".equals(type, ignoreCase = true)) {
if (split.size > 1) {
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
} else {
Environment.getExternalStorageDirectory().toString() + "/"
}
// This is for checking SD Card
} else {
"storage" + "/" + docId.replace(":", "/")
}
}
isDownloadsDocument(uri) -> {
//Toast.makeText(context, "From Downloads dir", Toast.LENGTH_SHORT).show()
val fileName = getFilePath(context, uri)
if (fileName != null) {
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
}
var id = DocumentsContract.getDocumentId(uri)
if (id.startsWith("raw:")) {
id = id.replaceFirst("raw:".toRegex(), "")
val file = File(id)
if (file.exists()) return id
}
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
}
isMediaDocument(uri) -> {
//Toast.makeText(context, "From Documents dir", Toast.LENGTH_SHORT).show()
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
}
else -> {
//non-media files i.e documents and other files
contentUri = MediaStore.Files.getContentUri("external")
val selection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.MediaColumns.RELATIVE_PATH + "=?"
} else "_id=?"
val selectionArgs = arrayOf(Environment.DIRECTORY_DOCUMENTS)
return getMediaDocumentPath(context, contentUri,selection,selectionArgs)
}
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}
isGoogleDriveUri(uri) -> {
Toast.makeText(context, "From Google Drive", Toast.LENGTH_SHORT).show()
return getDriveFilePath(uri, context)
}
/*else -> {
Toast.makeText(context, "Unknown Directory", Toast.LENGTH_SHORT).show()
}*/
}
}
"content".equals(uri.scheme, ignoreCase = true) -> {
//Toast.makeText(context, "From Content Uri", Toast.LENGTH_SHORT).show()
// Return the remote address
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null)
}
"file".equals(uri.scheme, ignoreCase = true) -> {
//Toast.makeText(context, "From File Uri", Toast.LENGTH_SHORT).show()
return uri.path
}
}
return null
}
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 {
if (uri == null) return null
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 getMediaDocumentPath(context: Context, uri: Uri?,selection: String?, selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
if (uri == null) return null
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(column)
//Toast.makeText(context, "From Media Document --- Non Media Path ${cursor.getString(index)} ", Toast.LENGTH_SHORT).show()
return cursor.getString(index)
}
} finally {
cursor?.close()
}
return null
}
private fun getFilePath(context: Context, uri: Uri?): String? {
var cursor: Cursor? = null
val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME)
try {
if (uri == null) return null
cursor = context.contentResolver.query(uri, projection, null, null,
null)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
return cursor.getString(index)
}
} finally {
cursor?.close()
}
return null
}
private fun getDriveFilePath(uri: Uri, context: Context): String? {
val returnCursor = context.contentResolver.query(uri, null, null, null, null)
val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name = returnCursor.getString(nameIndex)
val size = returnCursor.getLong(sizeIndex).toString()
val file = File(context.cacheDir, name)
try {
val inputStream = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable = inputStream!!.available()
//int bufferSize = 1024;
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", e.message!!)
}
return file.path
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is ExternalStorageProvider.
*/
private fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is DownloadsProvider.
*/
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is MediaProvider.
*/
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is Google Photos.
*/
private fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is Google Photos.
*/
private fun isGoogleDriveUri(uri: Uri): Boolean {
return "com.google.android.apps.docs.storage" == uri.authority || "com.google.android.apps.docs.storage.legacy" == uri.authority
}
}
This utils class will help you to get your file path, by just calling this line ..
contentUri.let { UriPathUtils().getRealPathFromURI(this, it).toString() }
Cheers...!
Uri contentUri = MediaStore.Files.getContentUri("external");
String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";
String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS};
Cursor cursor = getContentResolver().query(contentUri, null, selection, selectionArgs, null);
This question already has answers here:
Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?
(5 answers)
Difference between .getPath() vs cursor in getting the real path of a file from uri in Android
(1 answer)
Android - Get real path of a .txt file selected from the file explorer
(1 answer)
onActivityResult's intent.getPath() doesn't give me the correct filename
(2 answers)
Getting the Absolute File Path from Content URI for searched images
(2 answers)
Closed 2 years ago.
this is my code to get a real file path from Uri. It is working fine below all devices except Android 10. Please help me out. Thanks in Advance
enter code here
fun getPathFromUri(
context: Conteenter code herext,
Uri: Uri
): String? {
val isKitKat: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId: String = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
// TODO handle non-primary volumes
} else if (isDownloadsDocument(uri)) {
val id: String = DocumentsContract.getDocumentId(uri)
val contentUri: Uri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
java.lang.Long.valueOf(id)
)
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId: String = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(
split[1]
)
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
// Return the remote address
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(
context,
uri,
null,
null
)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
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 {
if (cursor != null) cursor.close()
}
return null
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* #param Uri The Uri to check.
* #return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* #param Uri The Uri to check.
* #return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* #param Uri The Uri to check.
* #return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}`enter code here`
I am trying to open file picker and select files from installed applications. In this case I am trying to access scanned file from Adobe Scan. When I choose file I get following uri
content://com.adobe.scan.android.documents/document/root:1
But now I want to get file from this uri which I couldn't. I am using following class to get Real File Path. I dont know what logic I should add here which can access files from third party apps.
object RealFilePath {
fun getFile(context: Context, fileUri: Uri): File {
return File(getRealPathFromURI(context, fileUri)!!)
}
private fun getRealPathFromURI(context: Context, uri: Uri): String? {
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
// TODO handle non-primary volumes
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri: Uri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)
)
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(
split[1]
)
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(
uri.scheme,
ignoreCase = true
)
) {
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(
context,
uri,
null,
null
)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
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
}
}
Please help me with this logic or is there any library which can give me real file paths.
UPDATE
I have a Samsung Galaxy S8+ running 8.0.0 T-Mobile that it works fine on running
8.0.0
My Samsung Galaxy S9+ running 8.0.0 Verizon, it fails everytime with illegal argument.
My Samsung Galaxy S9+ running 8.0.0 T-Mobile has no issues and works fine
So this may be OEM specific model issue, but not
sure how to fix it yet. I have also tried rebooting the Phone, no
change in outcome.
Also, I opened the public downloads from within Evernote and saved the
file as an attachment to a Note, which tells me that Evernote is able
to access the public directory just fine and attach the file, so it is
possible to do on the device. Leading me to believe it is code
related.
So I've recently upgraded a project that was working just fine and it now has a bug now that it is compiling with build tools 28, for the latest version of Android.
So I have always used this PathUtil to get the file path I needed from an implicit intent to get file selection from the user. I'll share a link to the code that I am using for a long time now below.
PathUtil
It's just a utility class that checks the provider authority and gets the absolute path for the file you are attempting to read.
When the user selects a file from the public downloads directory it returns to onActivityResult with:
content://com.android.providers.downloads.documents/document/2025
Now the nice utility parses this out and tells me that this is a download directory file and is a document with id 2025. Thanks utility, that's a great start.
Next up is to use the content resolver to find the file absolute path.
This is what used to work, but no longer does :(.
Now the path utility simply uses the contract data that they most likely got from the core library themselves. I tried to import the provider class to avoid static strings, but it doesn't seem to be available, so I guess simply using matching strings is the best way to go for now.
Here is the core DownloadProvider for reference that is providing all the access for the content resolver.
DownloadProvider
NOTE* This DownloadProvider is Androids, not mine
Here is the code that builds the Uri for the contentProvider
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOAD_PATH), id.toLong())
return getDataColumn(context, contentUri, null, null)
the call references:
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 column_index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
}catch (ex: Exception){
A35Log.e("PathUtils", "Error getting uri for cursor to read file: ${ex.message}")
} finally {
if (cursor != null)
cursor.close()
}
return null
}
Essentially the contentUri to be resolved ends up being
content://downloads/public_downloads/2025
Then when you call the query method it throws:
java.lang.IllegalArgumentException: Unknown URI: content://downloads/public_downloads/2025
Things I've confirmed or tried
Read external permissions (comes with write, but did it anyway)
Write external permissions
Permissions are in manifest and retrieved at runtime
I've selected multiple different files to see if one is weird
I've confirmed permissions are granted in application settings
I've hard coded the Uri to /1 or even /#2052 on the end to try various ending types
I've researched the uriMatching on the core library to look for how it expects it to be formatted and ensured it matches
I've played around with all_downloads directory in the uri and that resolves!!, but with security exception so the resolver must exist.
I don't know what else to try, any help would be appreciated.
So I still have to do some backwards compatible testing, but I have successfully resolved my own problem after many hours of trial and error.
How I resolved it was to modify the isDownloadDirectory path flow of getPath. I don't know all the ripple effects yet though as QA will be starting on it tomorrow, i'll update if I learn anything new from this.
Use the direct URI to get the contentResolver for file name (NOTE* This is not a good way to get file name unless you are certain it is a local file according to Google, but for me, I am certain it is downloaded.)
Then next use the Environment external public download constants combined with the returned content resolver name to get your absolute path. The new code looks like this.
private val PUBLIC_DOWNLOAD_PATH = "content://downloads/public_downloads"
private val EXTERNAL_STORAGE_DOCUMENTS_PATH = "com.android.externalstorage.documents"
private val DOWNLOAD_DOCUMENTS_PATH = "com.android.providers.downloads.documents"
private val MEDIA_DOCUMENTS_PATH = "com.android.providers.media.documents"
private val PHOTO_CONTENTS_PATH = "com.google.android.apps.photos.content"
//HELPER METHODS
private fun isExternalStorageDocument(uri: Uri): Boolean {
return EXTERNAL_STORAGE_DOCUMENTS_PATH == uri.authority
}
private fun isDownloadsDocument(uri: Uri): Boolean {
return DOWNLOAD_DOCUMENTS_PATH == uri.authority
}
private fun isMediaDocument(uri: Uri): Boolean {
return MEDIA_DOCUMENTS_PATH == uri.authority
}
private fun isGooglePhotosUri(uri: Uri): Boolean {
return PHOTO_CONTENTS_PATH == uri.authority
}
fun getPath(context: Context, uri: Uri): String? {
if (DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(COLON.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
val storageDefinition: String
if (PRIMARY_LABEL.equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + FORWARD_SLASH + split[1]
} else {
if (Environment.isExternalStorageRemovable()) {
storageDefinition = EXTERNAL_STORAGE
} else {
storageDefinition = SECONDARY_STORAGE
}
return System.getenv(storageDefinition) + FORWARD_SLASH + split[1]
}
} else if (isDownloadsDocument(uri)) {
//val id = DocumentsContract.getDocumentId(uri) //MAY HAVE TO USE FOR OLDER PHONES, HAVE TO TEST WITH REGRESSION MODELS
//val contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOAD_PATH), id.toLong()) //SAME NOTE AS ABOVE
val fileName = getDataColumn(context, uri, null, null)
var uriToReturn: String? = null
if(fileName != null){
uriToReturn = Uri.withAppendedPath(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath), fileName).toString()
}
return uriToReturn
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(COLON.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if (IMAGE_PATH == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if (VIDEO_PATH == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if (AUDIO_PATH == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri!!, selection, selectionArgs)
}
} else if (CONTENT.equals(uri.scheme, ignoreCase = true)) {
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null)
} else if (FILE.equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
//val column = "_data" REMOVED IN FAVOR OF NULL FOR ALL
//val projection = arrayOf(column) REMOVED IN FAVOR OF PROJECTION FOR ALL
try {
cursor = context.contentResolver.query(uri, null, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME) //_display_name
return cursor.getString(columnIndex) //returns file name
}
}catch (ex: Exception){
A35Log.e(SSGlobals.SEARCH_STRING + "PathUtils", "Error getting uri for cursor to read file: ${ex.message}")
} finally {
if (cursor != null)
cursor.close()
}
return null
}
Thanks to Sam.
I do it's help of #Sam answer in java.
import android.annotation.SuppressLint;
import android.content.ContentResolver;
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.util.Log;
import android.widget.Switch;
import java.io.File;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import kotlin.Metadata;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.Regex;
import kotlin.text.StringsKt;
public class UtilsFile {
private final static String PUBLIC_DOWNLOAD_PATH = "content://downloads/public_downloads";
private final static String EXTERNAL_STORAGE_DOCUMENTS_PATH = "com.android.externalstorage.documents";
private final static String DOWNLOAD_DOCUMENTS_PATH = "com.android.providers.downloads.documents";
private final static String MEDIA_DOCUMENTS_PATH = "com.android.providers.media.documents";
private final static String PHOTO_CONTENTS_PATH = "com.google.android.apps.photos.content";
private Boolean isExternalStorageDocument(Uri uri) {
return EXTERNAL_STORAGE_DOCUMENTS_PATH.equals(uri.getAuthority());
}
private Boolean isPublicDocument(Uri uri) {
return PUBLIC_DOWNLOAD_PATH.equals(uri.getAuthority());
}
private Boolean isDownloadsDocument(Uri uri) {
return DOWNLOAD_DOCUMENTS_PATH.equals(uri.getAuthority());
}
private Boolean isMediaDocument(Uri uri) {
return MEDIA_DOCUMENTS_PATH.equals(uri.getAuthority());
}
private Boolean isGooglePhotosUri(Uri uri) {
return MEDIA_DOCUMENTS_PATH.equals(uri.getAuthority());
}
private Boolean isPhotoContentUri(Uri uri) {
return PHOTO_CONTENTS_PATH.equals(uri.getAuthority());
}
private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
//String column = "_data" REMOVED IN FAVOR OF NULL FOR ALL
//String projection = arrayOf(column) REMOVED IN FAVOR OF PROJECTION FOR ALL
try {
cursor = context.getContentResolver().query(uri, null, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME);
return cursor.getString(columnIndex);
}
} catch (Exception e) {
Log.e("PathUtils", "Error getting uri for cursor to read file: " + e.getMessage());
} finally {
assert cursor != null;
cursor.close();
}
return null;
}
public String getFullPathFromContentUri(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
String filePath="";
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}//non-primary e.g sd card
else {
if (Build.VERSION.SDK_INT > 20) {
//getExternalMediaDirs() added in API 21
File[] extenal = context.getExternalMediaDirs();
for (File f : extenal) {
filePath = f.getAbsolutePath();
if (filePath.contains(type)) {
int endIndex = filePath.indexOf("Android");
filePath = filePath.substring(0, endIndex) + split[1];
}
}
}else{
filePath = "/storage/" + type + "/" + split[1];
}
return filePath;
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
String fileName = getDataColumn(context, uri,null, null);
String uriToReturn = null;
if (fileName != null) {
uriToReturn = Uri.withAppendedPath(
Uri.parse(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()), fileName
).toString();
}
return uriToReturn;
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
else if (isPublicDocument(uri)){
String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse(PUBLIC_DOWNLOAD_PATH), Long.parseLong(id));
String[] projection = {MediaStore.Images.Media.DATA};
#SuppressLint("Recycle") Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
}
return null;
}
}
My solution was different
I was trying to get the path so I can copy the file to my application folder.
After not finding the answer I tried the following approach.
I use Xamarin Forms
// DownloadsProvider
else if (IsDownloadsDocument(uri))
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
//Hot fix for android oreo
bool res = MediaService.MoveAssetFromURI(uri, ctx, ref error);
return res ? "copied" : null;
}
else
{
string id = DocumentsContract.GetDocumentId(uri);
Android.Net.Uri contentUri = ContentUris.WithAppendedId(
Android.Net.Uri.Parse("content://downloads/public_downloads"), long.Parse(id));
//System.Diagnostics.Debug.WriteLine(contentUri.ToString());
return GetDataColumn(ctx, contentUri, null, null);
}
}
public static bool MoveAssetFromURI(Android.Net.Uri uri, Context ctx, ref string error)
{
string directory = PhotoApp.App.LastPictureFolder;
var type = ctx.ContentResolver.GetType(uri);
string assetName = FileName(ctx, uri);
string extension = System.IO.Path.GetExtension(assetName);
var filename = System.IO.Path.GetFileNameWithoutExtension(assetName);
var finalPath = $"{directory}/{filename}{extension}";
if (File.Exists(finalPath))
{
error = "File already exists at the destination";
return false;
}
if (extension != ".pdf" && extension == ".jpg" && extension == ".png")
{
error = "File extension not suported";
return false;
}
using (var input = ctx.ContentResolver.OpenInputStream(uri))
{
using (var fileStream = File.Create(finalPath))
{
//input.Seek(0, SeekOrigin.Begin);
input.CopyTo(fileStream);
}
}
if (extension == ".pdf")
{
var imagePDFIcon = BitmapFactory.DecodeResource(ctx.Resources, Resource.Drawable.icon_pdf);
var imagePDFPortrait = BitmapFactory.DecodeResource(ctx.Resources, Resource.Drawable.pdf_image);
using (var stream = new FileStream($"{directory}/{filename}", FileMode.Create))
{
imagePDFIcon.Compress(Bitmap.CompressFormat.Jpeg, 90, stream);
}
using (var stream = new FileStream($"{directory}/{filename}.jpg", FileMode.Create))
{
imagePDFPortrait.Compress(Bitmap.CompressFormat.Jpeg, 90, stream);
}
return true;
}
else
{
if (extension == ".jpg" || extension == ".png")
{
MoveImageFromGallery(finalPath);
File.Delete(finalPath);
return true;
}
}
return false;
So instead of trying to get the path I created an input stream and copied the stream to the location I wanted.
Hope this helps