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.
Error is due to the way Xiaomi handles URI permissions
Try below code :
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="logs" path="logs/"/>
</paths>
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 :
Make sure MIUI optimizations are not interfering with your application's behavior.
Check applicationId:
Ensure that BuildConfig.APPLICATION_ID
correctly reflects your package name.