androidmime-typesandroid-contentresolverstorage-access-framework

Save android file preserving its mimetype


I am developing an Android app and need to save file to cache directory. I get file through ActivityResultContracts, which returns "content://" uri. Bellow is the function to download (virtual-) file to the cache directory

suspend fun createFileSchemeFromContentSchemeUri(context: Context, uri: Uri): Uri? = withContext(Dispatchers.IO) {
    if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
        try {
            val stream: InputStream? = getInputStream(context, uri)
            val mimetype: String? = context.contentResolver.getType(uri)
            val fileExtension: String? = MimeTypeMap.getSingleton()
                .getExtensionFromMimeType(mimetype)
                .let {
                    if (it == null) googleVndMimeTypeToExtension.get(mimetype)
                    else it
                }
            var filename: String? = context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                cursor.moveToFirst()
                cursor.getString(nameIndex)
            }

            if (stream == null || filename == null || fileExtension == null) return@withContext null

            filename = filename.removeSuffix(".$fileExtension")
            val file = File.createTempFile(filename, ".$fileExtension", context.externalCacheDir)

            stream.use { `in` ->
                file.outputStream().use { out ->
                    val buf = ByteArray(1024 * 1024) // 1MB

                    var len: Int
                    while (`in`.read(buf).also { len = it } > 0) {
                        out.write(buf, 0, len)
                    }
                }
            }

            file.toUri()
        } catch (_: Exception) {
            null
        }
    } else null
}

The problem is, that in before copying to output file it is possible to retrieve mimetype (not always, but most of the time), but after uri was returned and the same method way to read mimetype is used, i get null

What is the way to preserve original mimetype or "set" it manually?

I also tried to use FileDescriptor, but it didn't work

val fileDescriptor = outputStream.fd
fileDescriptor?.let {
    val fileOutputStream = FileOutputStream(fileDescriptor)
    fileOutputStream.use {
        FileUtils.copy(
            context.contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor,
            fileDescriptor
        )
    }
    
    fileOutputStream.flush()
    fileOutputStream.close()
    outputStream.close()
}

Solution

  • The answer is pretty easy. Turns out, that context.contentResolver.getType(uri) only works for "content://" based uri-s, which was not right for my case after file was saved to local cache (it became "file://"). Therefor, I updated method to handle both content- and file- based uri-s

    fun getMimeType(context: Context, uri: Uri): String? {
        if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
            return context.contentResolver.getType(uri)
        } else if (uri.scheme == ContentResolver.SCHEME_FILE) {
            val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
            return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension)
        } else return null
    }