912

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?

Thomas Vos
  • 12,271
  • 5
  • 33
  • 71
  • 65
    I feel like this was a mistake which makes life unnecessarily difficult for app developers. Having to bundle a "FileProvider" and "authority" with each app, seems like Enterprisey boilerplate. Having to add a flag to every file intent seems awkward and possibly unnecessary. Breaking the elegant concept of "paths" is unpleasant. And what's the benefit? Selectively granting storage access to apps (while most apps have full sdcard access, especially ones that work on files)? – nyanpasu64 Sep 17 '18 at 11:36
  • 3
    try this , small and perfect code https://stackoverflow.com/a/52695444/4997704 – Binesh Kumar Oct 08 '18 at 04:38
  • 5
    @nyanpasu64 I agree. Google has started to humiliates itself by some changes since API 19 – Ehsan Jan 12 '21 at 19:29
  • IMHO, I think Google basically wants to know where you save your files. reading the path from the Manifest. So, they can treat automatically that path... – Val Martinez Mar 25 '21 at 11:09

27 Answers27

1617

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.

logankilpatrick
  • 13,148
  • 7
  • 44
  • 125
Palash kosta
  • 16,470
  • 1
  • 11
  • 8
  • Thanks for answering. I already read that article some time ago. (See comment on that post), but it doesn't work in root directories. See my answer for a solution. – Thomas Vos Aug 10 '16 at 15:14
  • See also explanations from Android team here: https://code.google.com/p/android/issues/detail?id=203555#c3 – personne3000 Aug 30 '16 at 00:36
  • 77
    I just needed to add intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); – alorma Nov 22 '16 at 15:11
  • 29
    Will it work for all Android versions, or just from API 24? – android developer Nov 24 '16 at 17:43
  • 2
    `Caused by: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord` – Iman Marashi Dec 06 '16 at 18:11
  • 11
    this article helped me https://medium.com/@ali.muzaffar/what-is-android-os-fileuriexposedexception-and-what-you-can-do-about-it-70b9eb17c6d0#.54odzsnk4 – AbdulMomen عبدالمؤمن Mar 18 '17 at 16:41
  • Question. I'm using this in my Android Module, and the question is, if I add these lines and configuration into that module, would they be reused across my apps? Or should I define them in each and every application? – Saeed Neamati Jun 26 '17 at 19:32
  • 1
    add below lines if you are using internal cache to get uri. – Innocent Nov 15 '17 at 13:34
  • 2
    this gives me the error `java.lang.SecurityException: Permission Denial: opening provider....that is not exported..` – mrid Nov 17 '17 at 16:59
  • 12
    @rockhammer I just tested this with Android 5.0, 6.0, 7.1 and 8.1, it works in all cases. So the `(Build.VERSION.SDK_INT > M)` condition is useless. – Sébastien Dec 13 '17 at 17:20
  • About this line `Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());` I wanna download an apk file and open it, what should be the download uri to set destination? – Alireza Noorali Dec 17 '17 at 13:15
  • 72
    `FileProvider` should be extended only if you want to override any of the default behavior, otherwise use `android:name="android.support.v4.content.FileProvider"`. See https://developer.android.com/reference/android/support/v4/content/FileProvider.html – JP Ventura Jan 15 '18 at 21:56
  • can i provide a folder to save videos in it with this solution? – Orientos Mar 12 '18 at 13:05
  • Note that if you already setup another provider using the FileProvider class you need to extends your own FileProvider anyway – Lampione Mar 17 '18 at 00:43
  • 1
    Yes, The only error that I had, was a `intent.setFlags` after `intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);` – IgniteCoders May 09 '18 at 19:10
  • If you have multiple packages in the `BuildConfig`with different applicationID suffixes, you could get the authority this way: `String authority = getContext().getPackageName().replace(BuildConfig.BUILD_TYPE, "my.package.name.provider");` Any comments about this? – Tito Leiva Jun 19 '18 at 17:39
  • 5
    As shown in the linked code, in AndroidManifest.xml I used this: `android:authorities="${applicationId}.provider"` and in parse, this: `Uri uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file);` and this worked! Thank you! – educoutinho Sep 16 '18 at 23:52
  • https://stackoverflow.com/questions/53863913/sdcard-file-not-open-in-os-6-and-later – Bhanu Sharma Dec 20 '18 at 07:45
  • I can't believe so many other threads I read and how long I tried the exact thing that should be working before I found this comment. Thank you. – Nathan O'Kane Jan 17 '19 at 03:17
  • After implementing this code i'm having this issue: https://stackoverflow.com/questions/55391934/your-apps-are-using-a-content-provider-with-an-unsafe-implementation-of-openfi – AtifSayings Apr 10 '19 at 09:44
  • 1
    where is `res/xml` in Xamarin? – Daniel Reyhanian Apr 23 '19 at 22:00
  • 1
    What is `createImageFile()` because it is unknow or undefined for me ? – TheCoderGuy Apr 27 '19 at 14:10
  • @Spritzig It is a file object which you want to share. –  Apr 29 '19 at 06:51
  • 5
    The default provider for AndroidX is `android:name="androidx.core.content.FileProvider"` – Jack May 29 '19 at 21:00
  • 1
    Shouldn't this be: ```final Uri u = FileProvider.getUriForFile(getBaseContext(),getApplicationContext().getPackageName()+".fileprovider",fp);``` – B Porr Jun 08 '19 at 13:28
  • Doesn't work, the intent cannot be handled by external apps, at least `ACTION_VIEW` can't. This worked: https://stackoverflow.com/a/39619468/634821 – Violet Giraffe Jun 18 '19 at 18:28
  • Not need to create GenericFileProvider – Alex Jul 11 '19 at 08:55
  • 1
    I struggled for hours to find the `android:authorities` and `getUriForFile`'s `authority` parameter name MUST be exact same. The answer here is using `...fileprovider` at one place and `....provider` in another place, which misleaded a bit. – gary Jul 13 '19 at 04:24
  • How do I get the context for FileProvider.getUriForFile()? What should I be setting as the first parameter here? – Euridice01 Jul 24 '19 at 04:39
  • adding android:name="androidx.core.content.FileProvider" to above code fixed my problem. – Mohammed mansoor Nov 06 '19 at 03:48
  • The documentation to FileProvider says internal files to the app, but in my case I am trying g to open a file which is in download folder and available to all application, why do I need to use FileProvider here? – TheTechGuy Nov 11 '19 at 07:24
  • I used this method to request camera to save taken photo in specific location and it worked. But now I want to share created file to other apps, everything works fine, but other app (Gamil for example) does not recognize uri and places it in email receiver field (which is not correct, must attach it to email) – Homayoun Behzadian Nov 15 '19 at 10:32
  • For sharing a file, I used reverse engineering and found I must set generated uri by FileProvider, to extra Intent.EXTRA_STREAM instead of Data of Intent! – Homayoun Behzadian Nov 15 '19 at 16:54
  • 1
    Thank you. This is very useful! BTW I noticed that, as gary pointed out, we should use "fileprovider" instead of "provider" in the argument of "getUriFromFile" to match "android:authorities" in the manifest file. Otherwise there will be NullPointerException during runtime and the app will crash. – Darsen Lu Nov 18 '19 at 10:10
  • Even though this is a long process to handle such a simple thing I up voted it and please don't forget to add in manifest this permission: – Ishimwe Aubain Consolateur Sep 29 '20 at 13:18
  • is path "/storage/emulated/0/Android/data/my.app.package/files" or "/storage/emulated/0/Android/" ? – Elron Dec 19 '20 at 14:15
  • 1
    I tried to BuildConfig.APPLICATION_ID + ".provider" is what made it finally work for me – Mohsinali Feb 15 '21 at 06:02
  • 1
    How can I set the fixed the issues androd 11 java.lang.IllegalArgumentException: Failed to find configured root that contains /file:/storage/emulated/0/? – Manikandan K Apr 14 '21 at 05:03
  • 1
    All examples related have different text on that manifest line: android:authorities="${applicationId}.provider", what is applicationId? Is the package name formed as com.company_name.application.name? Then we must replace ".provider" by ".company_name"? – Windgate May 17 '21 at 13:26
  • add this as well in provider_paths.xml – Naval Kishor Jha Jun 13 '22 at 11:23
341

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.

hqzxzwb
  • 4,661
  • 2
  • 14
  • 15
  • 1
    @LaurynasG From API 18 to 23, android does not check for file uri exposure by default. Calling this method enables this check. From API 24, android does this check by default. But we can disable it by setting a new `VmPolicy`. – hqzxzwb Jul 19 '17 at 18:31
  • Is there any other step needed for this to work? Doesn't work as it stands for my Moto G running Android 7.0. – CKP78 Nov 22 '17 at 17:38
  • so great answer that solve my confusion, thats seems so relationless but so important! – Shaode Lu Dec 18 '17 at 08:57
  • 6
    How this can solve this problem however , StrictMode is a diagnostic tools that should be enabled in developer mode not release mode ??? – Imene Noomene Jan 04 '18 at 15:02
  • 1
    @ImeneNoomene Actually we are disabling StrictMode here. It seems reasonable that StrictMode should not be enabled in release mode, but in fact Android enables some StrictMode options by default regardless of debug mode or release mode. But one way or another, this answer was meant only to be a workaround back when some target apps were not prepared for receiving content uris. Now that most apps have added support for content uris, we should use the FileProvider pattern. – hqzxzwb Jan 04 '18 at 15:54
  • 3
    @ImeneNoomene I am totally with you in your outrage. You're right, this is a diagnostic tool, or at least it was ages ago when I added it to my projects. This is super frustrating! `StrictMode.enableDefaults();`, which I run only on my development builds keeps this crash from happening - so I now have a production application which crashes but it doesn't crash when in development. So basically, enabling a diagnostic tool here hides a serious issue. Thanks @hqzxzwb for helping me demystify this. – Jon Jan 05 '18 at 19:08
  • If someone is coming from xamarin android side and having this issue with xlabs IMediaPicker, the solution is to just add those two lines before the base.OnCreate(bundle); – Adil H. Raza Apr 26 '18 at 11:09
  • This is way to easier approach then the selected answer. Creating file is not a problem , it can still be the old approach just to handover the file to an external player was creating a problem. – Srihari Karanth Dec 10 '18 at 07:41
  • works for me, just change the directory to context.getExternalCacheDir() – Dave Rincon Jul 24 '19 at 16:54
220

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.

starball
  • 20,030
  • 7
  • 43
  • 238
Pankaj Lilan
  • 4,245
  • 1
  • 29
  • 48
  • 2
    @MaksimKniazev Can you describe your error in brief? So that I can help you. – Pankaj Lilan Nov 16 '17 at 11:47
  • @Pankaj Lilan never mind, all good now, I made error on .provider. Works good now.Thank you for follow up – Yaroslav Dukal Nov 17 '17 at 04:27
  • 2
    @PankajLilan, I did exactly what did you said. But everytime that I open my pdf in the other application it appears blank (its saving correctly). Should I need to edit the xml? I already added the FLAG_GRANT_READ_URI_PERMISSION as well; – Felipe Castilhos Mar 07 '18 at 18:14
  • 2
    My mistake, I was adding the permission to the wrong intent. This is the best and the simpliest right answer. Thank you! – Felipe Castilhos Mar 07 '18 at 19:10
  • 3
    It throws exception java.lang.IllegalArgumentException: Failed to find configured root that contains / My file path is /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf. can you please help ? – Jagdish Bhavsar Mar 08 '18 at 13:07
  • @FelipeCastilhos im getting the same error you got, the blank screen, can you help me out. – Vikash Sharma May 21 '18 at 18:13
  • @VikashSharma Hi, like I said above I was adding the permission to the wrong intent. Check if you are setting up the things right – Felipe Castilhos May 22 '18 at 14:20
  • @FelipeCastilhos solved it. Just added Uri Permission to the intent in addFlags() – Vikash Sharma May 23 '18 at 08:40
  • Working on 8.1. Thanks. – Kedar Tendolkar Jun 22 '18 at 10:28
  • 1
    Don't know why but I had to add both READ and WRITE permissions : target.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) – box Oct 22 '18 at 08:54
  • @box It's because of your file will be creating one more copy of your selected image. So both permissions are needed. – Pankaj Lilan Oct 22 '18 at 09:09
  • 1
    above method works when i add a permission flag intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); – mehmoodnisar125 Jan 23 '19 at 09:36
  • @mehmoodnisar125 Thank you for your suggestion. I'll edit my answer with the same. – Pankaj Lilan Jan 23 '19 at 11:31
  • 1
    Can someone tell me why it throws exception `java.lang.IllegalArgumentException: Failed to find configured root` – user12346352 Jul 22 '19 at 12:14
  • 3
    THIS RIGHT HERE IS THE ANSWER! All the others are wrong. This is fresh and works with new code! – paakjis Aug 09 '19 at 11:03
  • @JagdishBhavsar change your provider.xml to ` ` – Binil George Jun 21 '22 at 12:08
  • What's the right way to configure `paths` with nested folders? I need to access `/sdcard/Music/mydir`, I tried `` and `new File(getExternalStoragePublicDirectory(DIRECTORY_MUSIC), "test")` and I get `IllegalArgumentException: Failed to find configured root`. I tried with and without `Music/` – vault Jan 04 '23 at 12:36
  • no such thing as a `FileProvider` class that I can use. It says it's non-existed – MwBakker May 26 '23 at 07:30
  • This answer worked for me with a couple tweaks provided in this answer: https://stackoverflow.com/a/56985494/700204 – trukvl Jun 03 '23 at 14:21
177

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?).

Pointer Null
  • 39,597
  • 13
  • 90
  • 111
  • 3
    "while files served over content scheme can't be (can they?)." -- sure, if you have write access to the content. `ContentResolver` has both `openInputStream()` and `openOutputStream()`. A less-hacky way of doing this is to just [configure the VM rules yourself](https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html), and do not enable the `file` `Uri` rule. – CommonsWare Feb 25 '17 at 17:13
  • Thanks a lot @pointer-null ! Your hack might have [solved the issues](https://github.com/xroche/httrack-android/commit/12798a64c0b2bc24129eb6e3069400ca2a456c9f) my users had on the Android version of HTTrack, after hours of unsuccessful digging in the API documentation. The truth is that allowing a single URI through FLAG_GRANT_READ_URI_PERMISSION is just useless when you have a file hierarchy to browse underneath (ie. a mirrored website) – xroche Apr 09 '17 at 13:46
  • 1
    Exactly. It is hard work when you built your entire app, then find out after targeting 25 all of your camera methods break. This works for me until I get time to do it the right way. – Matt W May 25 '17 at 23:11
  • 5
    Works on Android 7. Thanks – Anton Kizema May 29 '17 at 14:18
  • Thanks this worked. Correct or not, I do not know. But thanks this worked. Will look for a more robust solution in the future but you saved me a lot of time. – soynerdito Jul 24 '17 at 20:50
  • 4
    Works on Android 8 too, tested on Huawei Nexus 6P. – Gonzalo Ledezma Torres Aug 07 '17 at 15:40
  • I added this to the main onCreate Method, didn't work on A5 2017 ( Android 7.0 ) .. – Ali Obeid Oct 29 '17 at 21:03
  • 1
    Android 7.0 (S8+) and Emulator 8.0 is worked. Thanks – hungtdo Oct 31 '17 at 04:23
  • 1
    Works on Android 8 Oreo - tested on Pixel 2 – Mohit Singh Nov 11 '17 at 13:36
  • 1
    Application.onCreate – Pointer Null Nov 20 '17 at 12:40
  • can anyone confirm it works in production? (after deployed to app store) – Maria Feb 18 '18 at 13:35
  • @PointerNull Thanks for the solution works perfectly (Nexus 5, Samsung J7,Meizu MX4) – Chinthaka Devinda Feb 21 '18 at 10:51
  • 4
    I confirm this is working on production (I have more then 500,000 users), currently version 8.1 is the highest version, and it works on it. – Eli Apr 22 '18 at 12:26
  • @CommonsWare After reading your comment I configured the VM rules myself and it worked as expected! Thanks! But Official documentation says "... so you should never leave StrictMode enabled in applications distributed on Google Play" at https://developer.android.com/reference/android/os/StrictMode.html#setVmPolicy(android.os.StrictMode.VmPolicy) – Jenix Oct 14 '18 at 17:29
  • Actually I don't need to detect any violations with StrictMode. All I need is just disabling the runtime check for file:// intents. So, in this case, may I leave the `StrictMode` lines in release versions? Just to be sure, I do use File Provider almost always but I'm recently facing some exceptions. – Jenix Oct 14 '18 at 17:29
  • @Jenix: That documentation is misleading. `StrictMode` is *always* enabled. What differs is what you are watching for and what penalty you apply. Personally, I recommend disabling crashing penalties for release versions, though using those for debug builds, so you can catch mistakes in your testing. – CommonsWare Oct 14 '18 at 17:39
  • @CommonsWare Ah now that's clear! Thank you very much. – Jenix Oct 14 '18 at 18:19
  • On Pie, it will not work anymore https://developer.android.com/about/versions/pie/restrictions-non-sdk-interfaces – FindOutIslamNow Jan 05 '19 at 06:21
  • do we need to put this inside onCreate() method only? – gumuruh Jan 16 '21 at 18:58
  • Call it one time at app startup. – Pointer Null Jan 19 '21 at 08:01
  • Solution was great, but it stops working on Android 10 :( tested on virtual and physical device – Val Martinez Mar 25 '21 at 12:00
  • -1. Not only this is a hack, but it also uses unnecessary reflection. The answer by hqz is better if someone wants to use this kind of workaround. – Xam Feb 09 '23 at 13:32
92

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)

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks for the answer. What happens when I use this with files on the `/system` partition? Every app should be able to acces this partition without root. – Thomas Vos Jul 05 '16 at 12:56
  • 2
    @SuperThomasLab: I would not count on everything in `/system` being world-readable. That being said, my guess is that you will still get this exception. I suspect that they are just checking the scheme and are not trying to determine if the file truly is world-readable. However, `FileProvider` will not help you, as you cannot teach it to serve from `/system`. You could [create a custom strategy for my `StreamProvider`](https://github.com/commonsguy/cwac-provider/blob/master/docs/EXTENDING.markdown#supporting-other-stream-locations), or roll your own `ContentProvider`, to get past the problem. – CommonsWare Jul 05 '16 at 13:11
  • Still thinking how I'm going to solve this.. The app I am updating with Android N support is a root browser. But now you can't open any files anymore in root directories. (`/data`, `/system`), because of this "good change". – Thomas Vos Jul 05 '16 at 16:11
  • @SuperThomasLab: The simple "solution" is to drop your `targetSdkVersion` for now. I am not a root kind of guy, but I am a bit surprised that you didn't need your own `ContentProvider` before, to allow arbitrary apps to read files that they cannot ordinarily read. I assumed that root browser apps had a `ContentProvider` that, under the covers, used `su`/`sudo`/whatever to stream out the content to whatever they were launching with `ACTION_VIEW`. – CommonsWare Jul 05 '16 at 16:17
  • Yes, other apps (and my app) use terminal commands to load file data. (With su rights) That's why I'm still searching for a correct way to get the actual path from a content Uri, so I can use it in a terminal command :) – Thomas Vos Jul 05 '16 at 16:20
  • @SuperThomasLab: "That's why I'm still searching for a correct way to get the actual path from a content Uri" -- for your own `ContentProvider`, you are the one defining the `Uri` structure. So, use your own custom authority (as that's needed anyway), and have `getPath()` of the `Uri` return your actual filesystem path. Or, if you are worried about leaking filesystem information that way, maintain a simple `LRUCache` mapping some string token (e.g., UUID) to the filesystem path, and use the token in the `Uri`. – CommonsWare Jul 05 '16 at 16:33
  • @CommonsWare so the key point here as I take it is target sdk >= 24 AND running 7.0+ OS. Meaning that on older OS's running an app targeted to sdk >= you can still use file uris? I ask because I have some older devices running 5.0 and 6.0, in which the video intent does not work with a content uri. – lostintranslation Jun 16 '17 at 21:47
  • @lostintranslation: "Meaning that on older OS's running an app targeted to sdk >= you can still use file uris?" -- correct. "in which the video intent does not work with a content uri" -- that has nothing to do with Android OS version and everything to do with the apps that the user has installed. – CommonsWare Jun 16 '17 at 21:54
  • @CommonsWare 'everything to do with the apps that the user has installed' - kind of. Samsung S5 native camera app running Marshmallow does not work with content URI from my app built with targetSDK > 24, only a file URI. I have to check OS version and use file URI if < N and content URI if >= N. – lostintranslation Jun 16 '17 at 22:17
  • @lostintranslation: "Samsung S5 native camera app running Marshmallow does not work with content URI from my app built with targetSDK > 24, only a file URI" -- yes, but bear in mind that it is entirely possible that the user's device on newer OS versions still has a buggy camera app. Or, that the user installs a third-party camera app that sucks. Your check is not a bad one, but do not assume that it will cover all cases. – CommonsWare Jun 16 '17 at 22:22
  • @CommonsWare thanks for your time. And I agree, which is what had me worried in the first place. How would one determine what type of URI to send to the intent? Possible to cover all cases? – lostintranslation Jun 16 '17 at 23:13
  • @lostintranslation: "How would one determine what type of URI to send to the intent?" -- if you are using `ACTION_IMAGE_CAPTURE`, generally speaking, you're screwed. Any time the `Uri` is passed via an extra, rather than the 'data' facet of the `Intent` (e.g., the way `ACTION_VIEW` works), there is no filtering on scheme or anything, and so neither you nor the recipient have any way to negotiate or determine a mutually agreeable scheme. :-( – CommonsWare Jun 16 '17 at 23:15
  • 1
    what are the most important drawbacks to dropping targetSdkVersion to 23? thnx – rommex Jun 29 '17 at 14:06
  • 2
    @rommex: I do not know what qualifies as "most important". For example, users who work in split-screen mode or on freeform multi-window devices (Chromebooks, Samsung DeX) will be told that your app may not work with multi-window. Whether that is important or not is up to you. – CommonsWare Jun 29 '17 at 14:34
  • @CommonsWare Can I call `startActivity()` from a worker thread? I know this is a different question and already asked and answered several times on stackoverflow, and really sorry to ask here, but I'm a little confused. I saw a few people said doing so caused an Exception on some devices while many said it's okay to call from a worker thread because `startActivity()` itself internally executes in the UI thread. I also tested myself and it worked fine. But due to some others' comments I'm always using `runOnUiThread()`. It's very redundant and annoying. I want to know your thoughts on this. – Jenix Oct 11 '18 at 00:53
  • @Jenix: I do not recall trying to start an activity from a background thread, and definitely not in the context of this question or answer. Sorry! – CommonsWare Oct 11 '18 at 10:53
  • Just wanted to ask you a simple yes or no question because it was already asked but still I was curious what the android master would think. Thanks! – Jenix Oct 11 '18 at 10:59
  • Going back to an older api version is not really a solution, but a temporary fix – Daniel Benedykt Apr 28 '19 at 19:14
50

First you need to add a provider to your AndroidManifest

  <application
    ...>
    <activity>
    .... 
    </activity>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.your.package.fileProvider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
  </application>

now create a file in xml resource folder (if using android studio you can hit Alt + Enter after highlighting file_paths and select create a xml resource option)

Next in the file_paths file enter

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <external-path path="Android/data/com.your.package/" name="files_root" />
  <external-path path="." name="external_storage_root" />
</paths>

This example is for external-path you can refere here for more options. This will allow you to share files which are in that folder and its sub-folder.

Now all that's left is to create the intent as follows:

    MimeTypeMap mime = MimeTypeMap.getSingleton();
    String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
    String type = mime.getMimeTypeFromExtension(ext);
    try {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
            intent.setDataAndType(contentUri, type);
        } else {
            intent.setDataAndType(Uri.fromFile(newFile), type);
        }
        startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
    } catch (ActivityNotFoundException anfe) {
        Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
    }

EDIT: I added the root folder of the sd card in the file_paths. I have tested this code and it does work.

Karn Patel
  • 521
  • 3
  • 13
  • 2
    Thank you for this. I also want to let you know that there is a better way to get the file extension. `String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());` Also, I recommend anyone looking for answers to read through [FileProvider](https://developer.android.com/reference/android/support/v4/content/FileProvider.html) first and understand what you are dealing with here with file permissions in Android N and above. There are options for internal storage vs. external storage and also for regular files-path vs. cache-paths. – praneetloke Dec 31 '16 at 16:57
  • 2
    I was getting the following exception: `java.lang.IllegalArgumentException: Failed to find configured root ...` and the only thing that worked was `` on the xml file instead of ` – steliosf Jun 30 '17 at 20:07
  • don't write your package name inside the path, use something like this if need to access to all your root folder and not only the private files folder. –  Feb 04 '21 at 06:21
43

My Solution was to 'Uri.parse' the File Path as String, instead of using Uri.fromFile().

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
    uri = Uri.fromFile(file);
} else {
    uri = Uri.parse(file.getPath()); // My work-around for SDKs up to 29.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Seems that fromFile() uses A file pointer, which I suppose could be insecure when memory addresses are exposed to all apps. But A file path String never hurt anybody, so it works without throwing FileUriExposedException.

Tested on API levels 9 to 29! Successfully opens the text file for editing in another app. Does not require FileProvider, nor the Android Support Library at all. And this will not work right on API level 30(Android 11) or newer, as getExternalStorageDirectory() has been deprecated.

CrazyJ36
  • 599
  • 4
  • 4
  • I wish I'd seen this first. I didn't prove it worked for me, but it's so much less cumbersome than FileProvider. – Dale Nov 20 '18 at 16:52
  • A note on why this actually works: It's not the File pointer that casues the issue, but the fact that the exception only occurs if you have a path with 'file://', which is automatically prepended with fromFile, but not with parse. – Xmister Jan 24 '19 at 09:53
  • 7
    This does not get exception, but it can't send the file to the related app either. So, not worked for me. – Serdar Samancıoğlu Mar 14 '19 at 14:11
  • 2
    This will fail on Android 10 and higher, as you cannot assume that the other app has access to external storage via the filesystem. – CommonsWare Nov 08 '19 at 00:10
  • @CommonsWare : Could you pls tell the best solution. I have few files inside /storage/emulated/0/MyApp/ folder & few inside my getFilesDir(). I need to open them in respective app via Intent. The file can be of any type. image, video, pdf, xls, doc etc – AndroidGuy Dec 22 '20 at 07:27
  • 1
    @AndroidGuy: Use `FileProvider` and its `getUriForFile()` method. – CommonsWare Dec 22 '20 at 11:55
26

@palash k answer is correct and worked for internal storage files, but in my case I want to open files from external storage also, my app crashed when open file from external storage like sdcard and usb, but I manage to solve the issue by modifying provider_paths.xml from the accepted answer

change the provider_paths.xml like below

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

<external-path path="Android/data/${applicationId}/" name="files_root" />

<root-path
    name="root"
    path="/" />

</paths>

and in java class(No change as the accepted answer just a small edit)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

This help me to fix the crash for files from external storages, Hope this will help some one having same issue as mine :)

Vini.g.fer
  • 11,639
  • 16
  • 61
  • 90
Ramz
  • 7,116
  • 6
  • 63
  • 88
  • 1
    Where did you find about `` had no effect for open files from external storage. – t0m Mar 16 '17 at 11:23
  • i find this from various search results, let me check again and get back to u asap – Ramz Mar 16 '17 at 11:40
  • also the external storage you mention is sd card or inbuilt storage? – Ramz Mar 16 '17 at 11:41
  • Sorry for inaccuracy. I meant `Android/data/${applicationId}/` in SDcard. – t0m Mar 20 '17 at 08:36
  • 1
    Need to add this to the intent: intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); – s-hunter Feb 17 '18 at 15:25
  • this works great, thank you! the only thing is that android studio highlights root-path as unallowed XML tag, but its compiling and working file. – artman Jul 05 '18 at 15:57
26

Just paste the below code in activity onCreate().

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

It will ignore URI exposure.

Happy coding :-)

Math
  • 19
  • 5
Ripdaman Singh
  • 596
  • 6
  • 8
23

Just paste the below code in Activity onCreate():

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

It will ignore URI exposure.

hata
  • 11,633
  • 6
  • 46
  • 69
Kaushal Sachan
  • 1,175
  • 11
  • 8
18

Using the fileProvider is the way to go. But you can use this simple workaround:

WARNING: It will be fixed in next Android release - https://issuetracker.google.com/issues/37122890#comment4

replace:

startActivity(intent);

by

startActivity(Intent.createChooser(intent, "Your title"));
Maksim Ostrovidov
  • 10,720
  • 8
  • 42
  • 57
Simon
  • 1,890
  • 19
  • 26
13

I used Palash's answer given above but it was somewhat incomplete, I had to provide permission like this

Intent intent = new Intent(Intent.ACTION_VIEW);
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));

        List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }else {
        uri = Uri.fromFile(new File(path));
    }

    intent.setDataAndType(uri, "application/vnd.android.package-archive");

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    startActivity(intent);
Atiq
  • 14,435
  • 6
  • 54
  • 69
13

Just paste the below code in Activity onCreate():

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

It will ignore URI exposure.

hata
  • 11,633
  • 6
  • 46
  • 69
AnilS
  • 795
  • 1
  • 11
  • 15
  • 1
    This, this will remove the strictmode policies. and will ignore the security warning. Not a good solution. – PeterPazmandi Oct 16 '19 at 04:31
  • 1
    It also will fail on Android 10 and higher, as you cannot assume that the other app has access to external storage via the filesystem. – CommonsWare Nov 08 '19 at 00:09
  • 1
    Why a few people duplicate [this answer](https://stackoverflow.com/a/48275478/3501958) of @KaushalSachan? – hata Nov 11 '21 at 13:59
11

Here my solution:

in Manifest.xml

<application
            android:name=".main.MainApp"
            android:allowBackup="true"
            android:icon="@drawable/ic_app"
            android:label="@string/application_name"
            android:logo="@drawable/ic_app_logo"
            android:theme="@style/MainAppBaseTheme">

        <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>

in 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>

in my fragment I has the next code:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

Тhat's all you need.

Also not need to create

public class GenericFileProvider extends FileProvider {}

I test on Android 5.0, 6.0 and Android 9.0 and it's success work.

Alex
  • 1,857
  • 4
  • 17
  • 34
  • I have tested this solution, and it works fine with a small change: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra(Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image/png" startActivity(Intent.createChooser(intent, "Share image via")) It works fin on Android 7 and 8. – PeterPazmandi Oct 16 '19 at 04:29
7

add this two line in onCreate

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

Share method

File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
hata
  • 11,633
  • 6
  • 46
  • 69
DEVSHK
  • 833
  • 11
  • 10
  • This will fail on Android 10 and higher, as you cannot assume that the other app has access to external storage via the filesystem. – CommonsWare Nov 08 '19 at 00:10
4

For downloading pdf from server , add below code in your service class. Hope this is helpful for you.

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf");
    intent = new Intent(Intent.ACTION_VIEW);
    //Log.e("pathOpen", file.getPath());

    Uri contentUri;
    contentUri = Uri.fromFile(file);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

    if (Build.VERSION.SDK_INT >= 24) {

        Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
        intent.setDataAndType(apkURI, "application/pdf");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    } else {

        intent.setDataAndType(contentUri, "application/pdf");
    }

And yes , don't forget to add permissions and provider in your manifest.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application

<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>

</application>
4

I don't know why, I did everything exactly the same as Pkosta (https://stackoverflow.com/a/38858040 ) but kept getting error:

java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

I wasted hours on this issue. The culprit? Kotlin.

val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intent was actually setting getIntent().addFlags instead of operating on my newly declared playIntent.

nyanpasu64
  • 2,805
  • 2
  • 23
  • 31
4
As of Android N, in order to work around this issue, you need to use the FileProvider API

There are 3 main steps here as mentioned below

Step 1: Manifest Entry

<manifest ...>
    <application ...>
        <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>
    </application>
</manifest>

Step 2: Create XML file 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>

Step 3: Code changes

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);
Suraj Bahadur
  • 3,730
  • 3
  • 27
  • 55
4

I spent almost a day trying to figure out why I was getting this exception. After lots of struggle, this config worked perfectly (Kotlin):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths" />
</provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path name="movies_csv_files" path="."/>
</paths>

Intent itself

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

I explain the whole process here.

lomza
  • 9,412
  • 15
  • 70
  • 85
2

I have just done following if android version > 24

File fl = new File(url);
    Uri uri = Uri.fromFile(fl);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (android.os.Build.VERSION.SDK_INT>=24)
    {
        Context context = getApplicationContext();
        uri = FileProvider.getUriForFile(
                context,
                context.getApplicationContext()
                        .getPackageName() + ".provider", fl);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(uri, mimetype);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

Following this link then https://medium.com/@ali.muzaffar/what-is-android-os-fileuriexposedexception-and-what-you-can-do-about-it-70b9eb17c6d0#.54odzsnk4

SWIK
  • 714
  • 7
  • 12
1

@Pkosta 's answer is one way of doing this.

Besides using FileProvider, you can also insert the file into MediaStore (especially for image and video files), because files in MediaStore are accessible to every app:

The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.

For example, you can insert a video file to MediaStore like this:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUri is like content://media/external/video/media/183473, which can be passed directly to Intent.putExtra:

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

This works for me, and save the hassles of using FileProvider.

NeoWang
  • 17,361
  • 24
  • 78
  • 126
1

i put this method so imageuri path easily get in content.

enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), 
    inImage, "Title", null);
    return Uri.parse(path);
}
Jitu Batiya
  • 159
  • 9
1

I know this is a pretty old question but this answer is for future viewers. So I've encountered a similar problem and after researching, I've found an alternative to this approach.

Your Intent here for eg: To view your image from your path in Kotlin

 val intent = Intent()
 intent.setAction(Intent.ACTION_VIEW)
 val file = File(currentUri)
 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
 val contentURI = getContentUri(context!!, file.absolutePath)
 intent.setDataAndType(contentURI,"image/*")
 startActivity(intent)

Main Function below

private fun getContentUri(context:Context, absPath:String):Uri? {
        val cursor = context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            arrayOf<String>(MediaStore.Images.Media._ID),
            MediaStore.Images.Media.DATA + "=? ",
            arrayOf<String>(absPath), null)
        if (cursor != null && cursor.moveToFirst())
        {
            val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
            return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
        }
        else if (!absPath.isEmpty())
        {
            val values = ContentValues()
            values.put(MediaStore.Images.Media.DATA, absPath)
            return context.getContentResolver().insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        }
        else
        {
            return null
        }
    }

Likewise, instead of an image, you can use any other file format like pdf and in my case, it worked just fine

0

I wanted to share images from the app's scoped storage and that's where I was getting this exception. Searched for hours and then, at last, I found this blog.

It's a bit long so I am sharing the gist here but I will recommend you to go through it.

The bottom line is you can't share anything from the app's scoped storage. Also in Android 12, the intent chooser bottom dialog shows the preview of the image you are sharing which is super cool by the way, but it can't load the preview from the scoped storage URI.

The solution is to create a copy of the file you 'intent' to share in the cache directory.

val cachePath = File(externalCacheDir, "my_images/")
cachePath.mkdirs()
val bitmap = loadImageFromStorage(currentQuote.bookId)
val file = File(cachePath, "cache.png")
val fileOutputStream: FileOutputStream
try {
    fileOutputStream = FileOutputStream(file)
    bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
    fileOutputStream.flush()
    fileOutputStream.close()
} catch (e: FileNotFoundException) {
    e.printStackTrace()
} catch (e: IOException) {
    e.printStackTrace()
}
val cacheImageUri: Uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
            
val intent = Intent(Intent.ACTION_SEND).apply {
    clipData = ClipData.newRawUri(null, cacheImageUri)
    putExtra(Intent.EXTRA_STREAM, cacheImageUri)
    type = "image/ *"
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, null))
            

And this is how I am loading file from scoped storage

fun Context.loadImageFromStorage(path: String): Bitmap? {
    try {
        val file = getFile(path)
        val bitmap = BitmapFactory.decodeStream(FileInputStream(file))
        return bitmap
    } catch (e: Exception) {
        e.printStackTrace()

        //Returning file from public storage in case the file is stored in public storage 
        return BitmapFactory.decodeStream(FileInputStream(File(path)))
    }
    
    return null
}


fun Context.getFile(path: String): File? {
    val cw = ContextWrapper(this)
    val directory = cw.getDir("image_dir", Context.MODE_PRIVATE)
    if (!directory.exists())
        directory.mkdir()
    try {
        val fileName = directory.absolutePath + "/" + path.split("/").last()
        return File(fileName)
    } catch (e: Exception) {
        e.printStackTrace()
    }
    
    return null
}

Lastly, don't forget to update your provider_paths.xml file

<external-cache-path name="external_cache" path="." />

<external-cache-path name="external_files" path="my_images/"/>
Shunan
  • 3,165
  • 6
  • 28
  • 48
-1

Xamarin.Android

Note: The path xml/provider_paths.xml (.axml) couldn't be resolved, even after making the xml folder under Resources (maybe it can be put in an existing location like Values, didn't try), so I resorted to this which works for now. Testing showed that it only needs to be called once per application run (which makes sense being that it changes the operational state of the host VM).

Note: xml needs to be capitalized, so Resources/Xml/provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");                
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");                
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);
samus
  • 6,102
  • 6
  • 31
  • 69
-1

Simply let it ignore the URI Exposure... Add it after on create

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build()); 
hata
  • 11,633
  • 6
  • 46
  • 69
Anurag Bhalekar
  • 830
  • 7
  • 9
  • This will fail on Android 10 and higher, as you cannot assume that the other app has access to external storage via the filesystem. – CommonsWare Nov 08 '19 at 00:10
  • This don´t must be used in a producction app. – Jorgesys Nov 19 '19 at 17:59
  • @CommonsWare but this shouldn't have worked.. I was getting fileuriexposedexception only on Huawei but adding this strangely solves it while nothing else worked !! – shadygoneinsane Apr 22 '21 at 10:05
  • 1
    Why a few people duplicate [this answer](https://stackoverflow.com/a/48275478/3501958) of @KaushalSachan? – hata Nov 11 '21 at 14:00
-1

This works

 val uri = if (Build.VERSION.SDK_INT < 24) Uri.fromFile(file) else Uri.parse(file.path)
                val shareIntent = Intent().apply {
                    action = Intent.ACTION_SEND
                    type = "application/pdf"
                    putExtra(Intent.EXTRA_STREAM, uri)
                    putExtra(
                        Intent.EXTRA_SUBJECT,
                        "Purchase Bill..."
                    )
                    putExtra(
                        Intent.EXTRA_TEXT,
                        "Sharing Bill purchase items..."
                    )
                }
                startActivity(Intent.createChooser(shareIntent, "Share Via"))
Aalishan Ansari
  • 641
  • 6
  • 8