androidandroid-contentproviderscoped-storage

createChooser() intent exception "requires the provider be exported, or grantUriPermission()"


The app needs to share a PDF file stored in the root of the cacheDir with other apps. The issue is seen on Android 12, possibly other versions too.

Manifest:

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

Provider paths:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path name="cache" path="." />
</paths>

Intent:

        val pdfFile = File(requireContext().cacheDir, pdfFileName)
        val fileUri: Uri = FileProvider.getUriForFile(
            requireContext().applicationContext,
            requireContext().packageName.toString() + ".provider",
            pdfFile
        )
        val intent = Intent()
        intent.action = Intent.ACTION_SEND
        intent.putExtra(Intent.EXTRA_STREAM, fileUri)
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        intent.type = "application/pdf"
        startActivity(Intent.createChooser(intent, "Share Document"))

The share sheet successfully opens but this exception always shows at that point and subsequently sharing to another app fails.

Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading 
androidx.core.content.FileProvider uri
content://uk.co.packagename.provider/cache/8BEDF7212-0DE46-42B0-9FA9-32C434BDD2F3HO.pdf
from pid=15363, uid=1000 requires the provider be exported, or grantUriPermission()

The provider as a whole cannot be exported and the URI permission appears to already be granted. I've read through the Android file sharing docs and many S/O answers but I cannot see what needs correcting, can you?


Solution

  • One of the limitations of FileProvider.getUriForFile() is that it does not check to see if the file exists. There are legit reasons for getting a Uri to a file that does not exist, such as for ACTION_IMAGE_CAPTURE. Still, it means that just getting the Uri is no guarantee that that the Uri is useful for reading content.

    Compounding that problem is that "does the file exist" via exists() feels like it may be a bit dicey, especially for external storage.

    So, it's pretty important to make sure that you have the right File object, and that it should point to an already-existing file, before you call getUriForFile().