android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()

Solution 1:

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:

  • Add a FileProvider <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>
  • Then create a 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.

Solution 2:

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.

Solution 3:

If targetSdkVersion is higher than 24, then FileProvider is used to grant access.

Create an xml file(Path: res\xml) provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>


Add a Provider in AndroidManifest.xml

    <provider
        android:name="android.support.v4.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>

If you are using androidx, the FileProvider path should be:

 android:name="androidx.core.content.FileProvider"

and replace

Uri uri = Uri.fromFile(fileImagePath);

to

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

Edit: While you're including the URI with an Intent make sure to add below line:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

and you are good to go. Hope it helps.

Solution 4:

If your app targets API 24+, and you still want/need to use file:// intents, you can use hacky way to disable the runtime check:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

Method StrictMode.disableDeathOnFileUriExposure is hidden and documented as:

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

Problem is that my app is not lame, but rather doesn't want to be crippled by using content:// intents which are not understood by many apps out there. For example, opening mp3 file with content:// scheme offers much fewer apps than when opening same over file:// scheme. I don't want to pay for Google's design faults by limiting my app's functionality.

Google wants developers to use content scheme, but the system is not prepared for this, for years apps were made to use Files not "content", files can be edited and saved back, while files served over content scheme can't be (can they?).

Solution 5:

If your targetSdkVersion is 24 or higher, you can not use file: Uri values in Intents on Android 7.0+ devices.

Your choices are:

  1. Drop your targetSdkVersion to 23 or lower, or

  2. Put your content on internal storage, then use FileProvider to make it available selectively to other apps

For example:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(from this sample project)