2

I get android.os.StrictMode.onFileUriExposed exception when trying to send a file with a Intent. It's my understanding that this is because I'm targeting 24> and file:// is not supported anymore, content:// should be used.

First I would like to say that I have seen similar questions like this, this and i'v also seen this blog post.

But the problem is, all of the post refer to URI when taking a picture, in my case the file is saved successfully using Uri and now I want to send the image using Intent like below:

shareBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            Uri screenshotUri = Uri.parse("file://" + directoryToStore + "/" + filename);
            //the Uri above  - file:///storage/emulated/0/Android/data/myPackageName/files/SavedImages/test.jpeg
            sharingIntent.setType("image/jpeg");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
            startActivity(Intent.createChooser(sharingIntent, "Share image using"));
        }
    });

By doing the above I only get a crash on some devices running 19>. Testing on my Samsung J7Pro (Android 7.0 API 24) I don't get a crash.

I have seen that some of that answers say that I can use:

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

But it's not the preferred way.

So, my question is. How should I handle sending files when targeting 24>. Should I do a if/else statement checking the version like if (Build.VERSION.SDK_INT >= 19) { and then use normal Uri for devices running <24 and how should I change file:// to content://? I also don't understand why the crash only happens on some devices.


EDIT 1:

I have done what the answer below suggested but the file is not passed with the intent, instead I get a Toast saying Unable to attach file when I try to email the image.

Cœur
  • 37,241
  • 25
  • 195
  • 267
HB.
  • 4,116
  • 4
  • 29
  • 53
  • Can you check your logcat and see what error / stacktrace you get? – Arseny Levin Jul 22 '18 at 11:27
  • @ArsenyLevin I'm not getting any errors, when I press the share button I only get `ViewPostImeInputStage processPointer 1` then I get the chooser where I can select a app to share the image with. – HB. Jul 22 '18 at 11:32
  • @ArsenyLevin I have logged `Uri contentUri = FileProvider.getUriForFile(getApplication(),getApplication().getPackageName() + ". MyFileProvider", newFile);` and it returns `shared-files` instead of `SavedImages` – HB. Jul 22 '18 at 11:35
  • change the xml to be: `` – Arseny Levin Jul 22 '18 at 11:42
  • @ArsenyLevin I still get the same Toast as my edit above. The Uri now returns `content://my.packahe.name.MyFileProvider/SavedImages/test.jpeg` – HB. Jul 22 '18 at 11:59
  • check my edit below. based on docs here: https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles – Arseny Levin Jul 22 '18 at 12:15
  • @ArsenyLevin Thank you for taking the time to reply, but I now get this error -`Failed to find configured root that contains /data/data/my.package.name/files/SavedImages/test.jpeg` – HB. Jul 22 '18 at 12:22
  • sorry for the long ping-pong. try changing the xml element to be ` – Arseny Levin Jul 22 '18 at 12:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176512/discussion-between-hb-and-arseny-levin). – HB. Jul 22 '18 at 12:32

2 Answers2

3

The proper way to share files from your app is a content provider, specifically FileProvider.

Add your FileProvider to AnroidManifest.xml:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.MyFileProvider"
    android:exported="false"
    android:grantUriPermissions="true" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/shared_paths"/>
</provider>

Next, add the referenced resource, by adding an xml resource (Android Studio: right click on 'app' -> New -> Folder -> XML Resources fodler) Then, inside this new folder create an xml file named shared_paths.xml (should match value in AndroidManifest.xml). Contents for shared_paths.xml:

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

EDIT: changed element type from <files-path ../> to <external-path .. /> per documentation. IMPORTANT: Change the path attribute to fit your needs.

Lastly change you code like this:

shareBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            Uri screenshotUri = FileProvider.getUriForFile(context, context.getPackageName() + ".MyFileProvider", new File(directoryToStore, filename))
            //the Uri above  - file:///storage/emulated/0/Android/data/myPackageName/files/SavedImages/test.jpeg
            sharingIntent.setType("image/jpeg");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Intent chooserIntent = Intent.createChooser(sharingIntent, "Share image using");
            chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(chooserIntent);
        }
    });
Arseny Levin
  • 664
  • 4
  • 10
1

If you save your file in a public media folder in internal memory (i.e. for images /storage/emulated/0/DCIM/my.package.name/SavedImages/myImage.jpg) you can also share the file via it-s content-media uri (for image content://media/external/images/media/4711).

This way you do not need your own FileProvider but the file is available for all.

With a lcoal FileProvider in apps' private data directories the file is only available throug your app and will be deleted if you uninstall your app.

the purpose of a contentprovider is that you can read content from any uri as inputStream (i.e. form "uri= "https://stackoverflow.com/questions/...", "file:///storage/emulated/0/...", "ftp:....", "content:...") as long as there is a provider for this.

with api 25 Google enforces that "file:..." uri-s are not allowed any more

k3b
  • 14,517
  • 7
  • 53
  • 85
  • Thank you for your answer, I want to keep the files "private" and I want it to be deleted once the app is uninstalled. I only want my app to be able to access the files unless when I want to share the files from within y application. – HB. Jul 23 '18 at 11:36
  • for private files you need either a contentprovider or a documentProvider – k3b Jul 23 '18 at 11:42
  • Ok thank you. My minimum SDK is 16, should I check if SDK is 19< and then perform with the old way by using `file://`? – HB. Jul 23 '18 at 11:45
  • that depends on your client apps that receive the image. some can handle file:// some can handle content:// and some can handle both. use https://github.com/k3b/intent-intercept/ and https://github.com/k3b/ContentProviderHelper/ to analyse intents and contentprovider. my [imageapp](https://github.com/k3b/APhotoManager/wiki) can handle both and will soon get a config setting wther to expose file:// or content:// where default will be content:// – k3b Jul 23 '18 at 11:52
  • and it only gets more complicated.. Thank you for taking the time to reply to my question. – HB. Jul 23 '18 at 12:00
  • Last night I started thinking about your last comment. By the end of the month all apps should target 24+, so doesn't that mean that from the end of the month most apps will be able to handle `content://`? That was the point of forcing app developers to target 24+ in the first place, for security? – HB. Jul 24 '18 at 05:42
  • if the the send-receiver are all programmed by you, yes. if you use apps that where last modified 4 years ago it means no. when in doubt you should use "content:". after rethinking this topic: my app currently uses send/view via "file" and i will make an incompatible change to "content:" without config page. that means less programming, less gui elements less userdocumentation. – k3b Jul 24 '18 at 09:31