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
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With