访问公共下载文件onActivityResult Android 28三星Galaxy S9 +(Verizon)

Sam*_*Sam 6 android kotlin android-permissions android-storage


UPDATE

我有运行8.0.0 T-Mobile的三星Galaxy S8 +,它在运行8.0.0时运行良好

我的三星Galaxy S9 +运行8.0.0 Verizon,每次都有非法参数失败.

我的三星Galaxy S9 +运行8.0.0 T-Mobile没有问题,工作正常

所以这可能是OEM特定的型号问题,但不知道如何解决它.我也试过重启电话,没有改变结果.

此外,我从Evernote中打开了公共下载并将文件保存为Note的附件,这告诉我Evernote能够正常访问公共目录并附加文件,因此可以在设备上执行.让我相信它与代码相关.


所以我最近升级了一个工作得很好的项目,现在它已经出现了一个错误,它正在使用构建工具28进行编译,用于最新版本的Android.

所以我一直使用这个PathUtil从隐含的意图中获取我需要的文件路径,以便从用户那里获取文件.我将在下面分享我正在使用的代码的链接.

PathUtil

它只是一个实用程序类,它检查提供程序权限并获取您尝试读取的文件的绝对路径.

当用户从公共下载目录中选择一个文件时,它返回onActivityResult:

content://com.android.providers.downloads.documents/document/2025
Run Code Online (Sandbox Code Playgroud)

现在,nice实用程序解析了这个并告诉我这是一个下载目录文件,并且是一个id为2025的文档.感谢实用程序,这是一个很好的开始.

接下来是使用内容解析器来查找文件绝对路径.这是以前的工作,但不再是:(.

现在路径实用程序只使用他们最有可能从核心库本身获得的合同数据.我试图导入提供程序类以避免静态字符串,但它似乎不可用,所以我想简单地使用匹配字符串是现在最好的方法.

以下是提供内容解析器所有访问权限的核心DownloadProvider供参考. DownloadProvider

注*此DownloadProvider是Androids,不是我的

以下是为contentProvider构建Uri的代码

 val id = DocumentsContract.getDocumentId(uri)
 val contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOAD_PATH), id.toLong())
 return getDataColumn(context, contentUri, null, null)
Run Code Online (Sandbox Code Playgroud)

来电参考:

    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
    }
Run Code Online (Sandbox Code Playgroud)

基本上,要解决的contentUri最终会被解决

内容://下载/ public_downloads/2025

然后,当您调用查询方法时,它会抛出:

java.lang.IllegalArgumentException:未知URI:content:// downloads/public_downloads/2025

我已经确认或尝试过的事情

  1. 读取外部权限(随附写入,但无论如何都要执行)
  2. 写外部权限
  3. 权限在清单中并在运行时检索
  4. 我选择了多个不同的文件来查看是否有一个奇怪的
  5. 我已在应用程序设置中确认了权限
  6. 我已经将Uri硬编码为/ 1或甚至/#2052,以尝试各种结束类型
  7. 我已经研究了核心库上的uriMatching,以寻找它希望如何格式化并确保它匹配的方式
  8. 我已经在uri中使用了all_downloads目录并且解决了!!但是由于安全性异常所以解析器必须存在.

我不知道还有什么可以尝试,任何帮助将不胜感激.

Sam*_*Sam 6

所以我仍然需要做一些向后兼容的测试,但经过数小时的反复试验,我已经成功地解决了我自己的问题。

我的解决方法是修改 getPath 的 isDownloadDirectory 路径流。我还不知道所有的连锁反应,尽管明天 QA 将开始,如果我从中学到任何新东西,我会更新。

使用直接 URI 获取文件名的 contentResolver(注意* 这不是获取文件名的好方法,除非您确定它是根据 Google 的本地文件,但对我来说,我确定它已下载。)

然后接下来使用 Environment 外部公共下载常量结合返回的内容解析器名称来获取您的绝对路径。新代码如下所示。

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
    }
Run Code Online (Sandbox Code Playgroud)