The app is crashing when I'm trying to open a file. It works below Android Nougat, but on Android Nougat it crashes. It only crashes when I try to open a file from the SD card, not from the system partition. Some permission problem?
Sample code:
File file = new File("/storage/emulated/0/test.txt"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "text/*"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); // Crashes on this line
Log:
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
Edit:
When targeting Android Nougat, file://
URIs are not allowed anymore. We should use content://
URIs instead. However, my app needs to open files in root directories. Any ideas?
If your targetSdkVersion >= 24
, then we have to use FileProvider
class to give access to the particular file or folder to make them accessible for other apps. We create our own class inheriting FileProvider
in order to make sure our FileProvider doesn't conflict with FileProviders declared in imported dependencies as described here.
Steps to replace file://
URI with content://
URI:
<provider>
tag in AndroidManifest.xml
under <application>
tag. Specify a unique authority for the android:authorities
attribute to avoid conflicts, imported dependencies might specify ${applicationId}.provider
and other commonly used authorities.<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <application ... <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/provider_paths" /> </provider> </application> </manifest>
provider_paths.xml
file in res/xml
folder. A folder may be needed to be created if it doesn't exist yet. The content of the file is shown below. It describes that we would like to share access to the External Storage at root folder (path=".")
with the name external_files.<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="."/> </paths>
The final step is to change the line of code below in
Uri photoURI = Uri.fromFile(createImageFile());
to
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
Edit: If you're using an intent to make the system open your file, you may need to add the following line of code:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Please refer to the full code and solution that have been explained here.
Besides the solution using the FileProvider
, there is another way to work around this. Simply put
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
in Application.onCreate()
. In this way the VM ignores the file URI
exposure.
Method
builder.detectFileUriExposure()
enables the file exposure check, which is also the default behavior if we don't setup a VmPolicy.
I encountered a problem that if I use a content://
URI
to send something, some apps just can't understand it. And downgrading the target SDK
version is not allowed. In this case my solution is useful.
Update:
As mentioned in the comment, StrictMode is diagnostic tool, and is not supposed to be used for this problem. When I posted this answer a year ago, many apps can only receive File uris. They just crash when I tried to send a FileProvider uri to them. This is fixed in most apps now, so we should go with the FileProvider solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With