androidandroid-contentprovider

android - Permission Denial: reading androidx.core.content.FileProvider uri content / Xiaomi specific


In my Android application, some logs are written to files by date (for example, to the file 20.06.2024.txt) with the ability to collect all files in a zip archive and share through the application/send in an email. The archive file is attached to the letter without any problems on all devices, except for one of the 6 devices on which this was tested - Xiaomi. On this device, I see an error in the logs:

Writing exception to parcel java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.own.vpn.provider/files/logs/VPN_logs.zip from pid=6231, uid=1000 requires the provider to be exported , or grantUriPermission()

My provider defined in manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="LockedOrientationActivity"
    package="com.own.vpn">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
    <uses-permission-sdk-23 android:name="android.permission.QUERY_ALL_PACKAGES" />

    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="http" />
        </intent>
    </queries>

    <application
        android:name=".presentation.App"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/vpn"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/Theme.SplashScreen"
        tools:targetApi="34">
        <activity
            android:name=".presentation.screens.applist.view.AppListActivity"
            android:exported="false"
            android:label="@string/protected_applications"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.VPN" />
        <activity
            android:name=".presentation.screens.log.view.LogActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.VPN" />
        <activity
            android:name=".presentation.screens.loglist.view.LogListActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:label="@string/event_log"
            android:theme="@style/Theme.VPN" />
        <activity
            android:name=".presentation.screens.settings.view.SettingsActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:label="@string/settings"
            android:theme="@style/Theme.VPN" />
        <activity
            android:name=".presentation.screens.main.view.MainActivity"
            android:exported="true"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.VPN.SplashScreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".presentation.service.vpnservice.MyVpnService"
            android:exported="false"
            android:foregroundServiceType="specialUse"
            android:permission="android.permission.BIND_VPN_SERVICE">
            <intent-filter>
                <action android:name="android.net.VpnService" />
            </intent-filter>
        </service>
        <service
            android:name=".presentation.service.aidlapiservice.AIDLApiService"
            android:exported="true"
            tools:ignore="ExportedService">
            <intent-filter>
                <action android:name="com.own.vpn.REMOTE_CONNECTION" />
            </intent-filter>
        </service>

        <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_provider_paths" />
        </provider>
    </application>
</manifest>

file_provider_paths.xml

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

Share intent calling

private const val FILE_TYPE_TXT = "txt/*"
private const val FILE_TYPE_ZIP = "application/zip"
private const val AUTHORITY_EXTENSION = ".provider"

class DefaultShareRouter(
    private val context: Context
) : ShareRouter {

    override fun goToFileSharing(logFile: File, shareWith: ShareWith) {
        val fileUri: Uri = FileProvider.getUriForFile(
            context,
            "${BuildConfig.APPLICATION_ID}$AUTHORITY_EXTENSION",
            logFile
        )

        val fileType = if (logFile.extension == LOG_FILE_EXTENSION_TXT)
            FILE_TYPE_TXT
        else
            FILE_TYPE_ZIP

        when (shareWith) {
            ShareWith.ANY_APP -> openApplicationChooser(fileUri, fileType)
            ShareWith.EMAIL -> openEmailIntent(fileUri, fileType)
        }
    }

    private fun openEmailIntent(fileUri: Uri, fileType: String) {
        // Receivers list
        val mailReceivers = arrayOf(getString(context, R.string.mail_receiver))

        val emailIntent = Intent(Intent.ACTION_SEND).apply {
            // File
            setDataAndType(fileUri, fileType)
            putExtra(Intent.EXTRA_STREAM, fileUri)

            // E-mail
            putExtra(Intent.EXTRA_EMAIL, mailReceivers)
            putExtra(
                Intent.EXTRA_SUBJECT,
                getString(context, R.string.mail_subject)
            )
            putExtra(
                Intent.EXTRA_TEXT,
                getString(context, R.string.mail_text)
            )
        }

        val emailApps = context.packageManager.queryIntentActivities(
            Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")),
            0
        )
        val emailAppsIntents = emailApps.map { resolveInfo ->
            Intent(emailIntent).apply {
                setPackage(resolveInfo.activityInfo.packageName)
            }
        }
            .distinctBy { it.`package` }
            .toMutableList()

        if (emailAppsIntents.isNotEmpty()) {
            val chooserIntent = Intent.createChooser(
                // removeAt(0) - first intent used as default for chooser
                emailAppsIntents.removeAt(0),
                getString(context, R.string.choose_email_application)
            ).apply {
                putExtra(Intent.EXTRA_INITIAL_INTENTS, emailAppsIntents.toTypedArray())
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
            context.startActivity(chooserIntent)
        } else {
            Toast.makeText(
                context,
                getString(context, R.string.no_email_application),
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    private fun openApplicationChooser(fileUri: Uri, fileType: String) {
        val intent = Intent(Intent.ACTION_SEND).apply {
            // Attachment
            setDataAndType(fileUri, fileType)
            putExtra(Intent.EXTRA_STREAM, fileUri)
        }

        // Applications chooser
        val shareIntent = Intent.createChooser(
            intent,
            null
        ).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
        startActivity(context, shareIntent, null)
    }
}

UPD First error i get when chooser is opened. When i select any app (for example, gmail) - i get another error: Failed to find provider info for com.own.vpn.provider. But on other devices all works fine.


Solution

  • Error is due to the way Xiaomi handles URI permissions

    Try below code :

    1. file_provider_paths.xml:
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <files-path name="logs" path="logs/"/>
    </paths>
    
    1. Use this function goToFileSharing :
    override fun goToFileSharing(logFile: File, shareWith: ShareWith) {
        
        val fileUri: Uri = FileProvider.getUriForFile(
            context,
            "${BuildConfig.APPLICATION_ID}$AUTHORITY_EXTENSION", // AUTHORITY_EXTENSION = ".provider"
            logFile
        )
    
        val fileType = if (logFile.extension == LOG_FILE_EXTENSION_TXT)
            FILE_TYPE_TXT
        else
            FILE_TYPE_ZIP
    
        openApplicationChooser(fileUri, fileType)
    }
    
    private fun openApplicationChooser(fileUri: Uri, fileType: String) {
        val intent = Intent(Intent.ACTION_SEND).apply {
            // E-mail attachment
            setDataAndType(fileUri, fileType)
            putExtra(Intent.EXTRA_STREAM, fileUri)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }
    
        // Applications chooser
        val shareIntent = Intent.createChooser(
            intent,
            null
        ).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
        startActivity(context, shareIntent, null)
    }
    

    Note :