My app includes the classic "Share This App" menu option, which takes two screenshots and send them by mail along with a promotional text.
It was working fine before migrating from Java to Kotlin, but now in Kotlin it started to throw an exception.
The weird thing is that even the exception (which can be seen in logcat) the process is working fine, the app is not crashing (at least not in a visible way) and the share process is done, for example, if you choose email the email is received along with the images.
Manifest.xml
<provider
android:name=".helpers.GenericFileProvider"
android:authorities="${applicationId}.GenericFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
GenericFileProvider:
package com.xxx.xxx.helpers
import androidx.core.content.FileProvider
class GenericFileProvider : FileProvider()
provider_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="."/>
</paths>
shareFile:
@JvmStatic
fun shareFile(uris: ArrayList<Uri?>?, fileType: String?) {
try {
val share = Intent(Intent.ACTION_SEND_MULTIPLE)
share.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
share.type = fileType
val strSubject = getStringResourceByName("mainmenu_sharethisapp_subject")
share.putExtra(Intent.EXTRA_SUBJECT, strSubject)
share.putParcelableArrayListExtra(Intent.EXTRA_TEXT, uris)
share.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
activity!!.get()!!.startActivity(
Intent.createChooser(
share,
getStringResourceByName("mainmenu_sharethisapp_sharethoughttitle")
)
)
} catch (ex: Exception) {
Toast.makeText(activity!!.get(), ex.message.toString(), Toast.LENGTH_LONG).show()
}
}
onImageProcessingFinished:
fun onImageProcessingFinished(ipr: ImageProcessingResult, activity: Activity) {
val uris = ArrayList<Uri?>()
for (file in ipr.screenShotFiles) {
val uri = FileProvider.getUriForFile(activity, activity.packageName + ".GenericFileProvider", file)
if (uri!=null) uris.add(uri)
}
AWDrawerMenu.listener = activity as IActionListeners
AWDrawerMenu.activity = WeakReference(activity)
AWDrawerMenu.shareFile(uris, "image/jpg")
}
Exception:
2022-06-16 22:01:08.427 11357-11378/com.xxx.xxx E/DatabaseUtils: Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading com.xxx.xxx.helpers.GenericFileProvider uri content://com.xxx.xxx.GenericFileProvider/cache/xxx.jpg from pid=6474, uid=1000 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:820)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:684)
at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:674)
at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:548)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:327)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
Edit 1:
If I replace the onImageProcessingFinished method by this next
fun onImageProcessingFinished(ipr: ImageProcessingResult, activity: Activity) {
val uris = ArrayList<Uri?>()
for (file in ipr.screenShotFiles) {
val uri = FileProvider.getUriForFile(activity, activity.packageName + ".helpers.GenericFileProvider", file)
if (uri!=null) uris.add(uri)
}
AWDrawerMenu.listener = activity as IActionListeners
AWDrawerMenu.activity = WeakReference(activity)
AWDrawerMenu.shareFile(uris, "image/jpg")
}
adding the .helpers before GenericFileProvider (which in the end is the complete package name for the GenericFileProvider), then the following exception is thrown (an in this case the app DO crashes):
2022-06-16 21:48:10.203 10694-10694/com.xxx.xxx E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xxx.xxx, PID: 10694
java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority com.xxx.xxx.helpers.GenericFileProvider
at androidx.core.content.FileProvider.parsePathStrategy(FileProvider.java:662)
at androidx.core.content.FileProvider.getPathStrategy(FileProvider.java:635)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:441)
at com.xxx.xxx.component.drawer.AWDrawer$Companion.onImageProcessingFinished(AWDrawer.kt:294)
at com.xxx.xxx.activities.main.MainActivity.onImageProcessingFinished(MainActivity.kt:448)
at com.xxx.xxx.activities.main.MainActivity.onShareThisApp$lambda-6(MainActivity.kt:443)
at com.xxx.xxx.activities.main.MainActivity.$r8$lambda$c-xNLWLGog-3ah1DvHN4y6HCHsw(Unknown Source:0)
at com.xxx.xxx.activities.main.MainActivity$$ExternalSyntheticLambda3.onComplete(Unknown Source:4)
at com.xxx.xxx.helpers.TaskRunner.executeAsync$lambda-1$lambda-0(TaskRunner.kt:24)
at com.xxx.xxx.helpers.TaskRunner.$r8$lambda$ItjAcdIefpXj05LbTtje15JKSPM(Unknown Source:0)
at com.xxx.xxx.helpers.TaskRunner$$ExternalSyntheticLambda0.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
I ended finding out I was having the exact same problem as described here:
Permission Denial while sharing file with FileProvider
And luckily the exact same solution worked for me.
I had to replace this:
@JvmStatic
fun shareFile(uris: ArrayList<Uri?>?, fileType: String?) {
try {
val share = Intent(Intent.ACTION_SEND_MULTIPLE)
share.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
share.type = fileType
val strSubject = getStringResourceByName("mainmenu_sharethisapp_subject")
share.putExtra(Intent.EXTRA_SUBJECT, strSubject)
share.putParcelableArrayListExtra(Intent.EXTRA_TEXT, uris)
share.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
activity!!.get()!!.startActivity(
Intent.createChooser(
share,
getStringResourceByName("mainmenu_sharethisapp_sharethoughttitle")
)
)
activity!!.get()!!.startActivity(share)
} catch (ex: Exception) {
Toast.makeText(activity!!.get(), ex.message.toString(), Toast.LENGTH_LONG).show()
}
}
by this:
@JvmStatic
fun shareFile(uris: ArrayList<Uri?>?, fileType: String?) {
try {
val share = Intent(Intent.ACTION_SEND_MULTIPLE)
share.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
share.type = fileType
val strSubject = getStringResourceByName("mainmenu_sharethisapp_subject")
share.putExtra(Intent.EXTRA_SUBJECT, strSubject)
share.putParcelableArrayListExtra(Intent.EXTRA_TEXT, uris)
share.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
/*activity!!.get()!!.startActivity(
Intent.createChooser(
share,
getStringResourceByName("mainmenu_sharethisapp_sharethoughttitle")
)
)*/
activity!!.get()!!.startActivity(share)
} catch (ex: Exception) {
Toast.makeText(activity!!.get(), ex.message.toString(), Toast.LENGTH_LONG).show()
}
}
The chooser looks a little bit different (don't know why), but it works.
Edit 1:
OK, the final working code:
@JvmStatic
fun shareFile(uris: ArrayList<Uri?>?, fileType: String?) {
try {
val strSubject = getStringResourceByName("mainmenu_sharethisapp_subject")
val strMessage = getStringResourceByName("mainmenu_sharethisapp_message")
val intent = Intent()
intent.type = fileType
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.putExtra(Intent.EXTRA_SUBJECT, strSubject)
intent.putExtra(Intent.EXTRA_TEXT, strMessage)
if (uris!!.size == 0) {
return
} else if (uris.size == 1) {
val uri = uris[0]!!
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.clipData = ClipData.newRawUri("", uri)
} else {
intent.action = Intent.ACTION_SEND_MULTIPLE
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
val clipData = ClipData.newRawUri("", uris[0])
for (i in 1 until uris.size) {
val uri = uris[i]!!
clipData.addItem(ClipData.Item(uri))
}
intent.clipData = clipData
}
activity!!.get()!!.startActivity(
Intent.createChooser(
intent, getStringResourceByName("mainmenu_sharethisapp_sharethoughttitle")
)
)
} catch (ex: Exception) {
Toast.makeText(activity!!.get(), ex.message.toString(), Toast.LENGTH_LONG).show()
}
}