72

I have two apps : app1 and app2.

App2 has :

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.android.provider.ImageSharing"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
</provider>

paths.xml :

<paths>

     <files-path name="my_images" path="images/"/>

</paths>

App2 receives request in its Activity from App1 to get URI for an image. The App2 Activity does the following once URI is decided :

Intent intent = new Intent();

intent.setDataAndType(contentUri, getContentResolver().getType(contentUri));

int uid = Binder.getCallingUid();
String callingPackage = getPackageManager().getNameForUid(uid);

getApplicationContext().grantUriPermission(callingPackage, contentUri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);

setResult(Activity.RESULT_OK, intent);
finish();

On receiving the result back from App2, App1 does the following :

Uri imageUri = data.getData();
if(imageUri != null) {
    ImageView iv = (ImageView) layoutView.findViewById(R.id.imageReceived);
    iv.setImageURI(imageUri);
}

In App1, on returning from App2, I get the following exception :

java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{52a99eb0 3493:com.android.App1.app/u0a57} (pid=3493, uid=10057) that is not exported from uid 10058

What am I doing wrong ?

mrid
  • 5,782
  • 5
  • 28
  • 71
Jake
  • 16,329
  • 50
  • 126
  • 202
  • chk this http://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps?rq=1 – karan Jun 28 '14 at 14:20
  • @KaranMer but I am following those steps .. :( – Jake Jun 28 '14 at 14:21
  • Have you examined `callingPackage` to see if it is the value that you are expecting? – CommonsWare Jun 28 '14 at 14:28
  • @CommonsWare OK .. that does seem to be the problem. So how do I do it right ? I need to assign it to to the calling app. Thank you ! :) – Jake Jun 28 '14 at 14:32
  • 1
    @CommonsWare The Binder.callingUid() and getPackageManager().getNameForUid(uid) is giving me package name of App2 instead of App1 – Jake Jun 28 '14 at 14:38
  • did you ever find out the reason for the security exception? i'm getting the same exception when sending the intent – prostock Aug 27 '17 at 17:01
  • stackoverflow.com/a/74052302/987762 for answer – kalandar Oct 13 '22 at 08:05

12 Answers12

72

Turns out the only way to solve this is to grant permissions to all of the packages that might need it, like this:

List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
limlim
  • 3,115
  • 2
  • 34
  • 46
  • 3
    This might be the right solution mostly for pre-Lollipop versions. See comments on [that answer](http://stackoverflow.com/a/33652695/1181162) – Vince Aug 31 '16 at 14:47
  • 1
    Works on Android 8.0, thanks! (p.s. addFlags didn't work out) – Kirill Karmazin Feb 01 '19 at 19:19
  • 2
    The fact that this hack is the only solution that actually works (after many hours of wasted time) is truly shameful for the Android eco-system! – doctorram Sep 28 '21 at 18:22
44

Android <= Lollipop (API 22)

There's a great article by Lorenzo Quiroli that solves this issue for older Android versions.

He discovered that you need to manually set the ClipData of the Intent and set the permissions for it, like so:

if ( Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP ) {
    takePictureIntent.setClipData( ClipData.newRawUri( "", photoURI ) );
    takePictureIntent.addFlags( Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION );
}

I tested this on API 17 and it worked great. Couldn't find a solution anywhere that worked.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
40

First, I would try switching away from grantUriPermission() and simply put the FLAG_GRANT_READ_URI_PERMISSION on the Intent itself via addFlags() or setFlag().

If for some reason that does not work, you could try moving your getCallingUid() logic into onCreate() instead of wherever you have it, and see if you can find out the actual "caller" there.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • What if I access the provider directly via `ContentResolver.openInputStream()`? `grantUriPermission()` in the calling app gives me `java.lang.SecurityException: Uid 10248 does not have permission to uri 0 @ content://......` – WindRider Feb 17 '16 at 19:20
  • @JaswanthManigundan: [This `FileProvider` example in GitHub](https://github.com/commonsguy/cw-omnibus/tree/master/ContentProvider/V4FileProvider) uses `addFlags()`, as my answer suggests. – CommonsWare Apr 20 '17 at 11:08
  • Sorry, deleted my comment right after you posted the answer. I indeed made a mistake, adding a `setFlags` after an `addFlags`. Sorry and thank you! – Luis A. Florit Jun 02 '18 at 20:47
  • 3
    Doesn't work on Android 8.0. The only thing that helped was looping through all resolveInfos and grantUriPermission() to all of them. What a barbarian approach. Shame on you, Google – Kirill Karmazin Feb 01 '19 at 14:43
  • This way also doesn't work on Android 11. we should check all resolveInfos. – CodingBruceLee May 11 '22 at 00:12
26

Just add setData(contentUri); and based on requirement add addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); or addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

This solves the java.lang.SecurityException: Permission Denial

Verified.

This is done as per https://developer.android.com/reference/android/support/v4/content/FileProvider.html#Permissions

Ziem
  • 6,579
  • 8
  • 53
  • 86
RamiReddy
  • 1,069
  • 9
  • 11
15

How you can capture image using camera on Nougat using File Provider.

To read about about file provider follow this link File Provider

,and kit kat and marshmallow follow these steps. First of all ad tag provider under application tag in MainfestFile.

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

create a file name with (provider_paths.xml) under res folder enter image description here

<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>

I have solved this issue it comes on kit kitkat version

private void takePicture() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        Uri photoURI = null;
        try {
            File photoFile = createImageFileWith();
            path = photoFile.getAbsolutePath();
            photoURI = FileProvider.getUriForFile(MainActivity.this,
                    getString(R.string.file_provider_authority),
                    photoFile);

        } catch (IOException ex) {
            Log.e("TakePicture", ex.getMessage());
        }
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
            takePictureIntent.setClipData(ClipData.newRawUri("", photoURI));
            takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        startActivityForResult(takePictureIntent, PHOTO_REQUEST_CODE);
    }
}

  private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DCIM), "Camera");
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = "file:" + image.getAbsolutePath();
    return image;
}
Najaf Ali
  • 1,433
  • 16
  • 26
3

You need to set permission of specific package name, after that you can able to access it..

context.grantUriPermission("com.android.App1.app", fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
Jd Prajapati
  • 1,953
  • 13
  • 24
3

I solved the problem that way:

        Intent sIntent = new Intent("com.appname.ACTION_RETURN_FILE").setData(uri);
        List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(sIntent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            activity.grantUriPermission(FILE_PROVIDER_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        sIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        activity.setResult(RESULT_OK, sIntent);
Rainmaker
  • 10,294
  • 9
  • 54
  • 89
2

Thanks, @CommonsWare for this advice.

My problem was with the calling package. For some reason, Binder.callingUid() and getPackageManager().getNameForUid(uid) was giving me package name of App2 instead of App1.

I tried calling it in App2's onCreate as well as onResume, but no joy.

I used the following to solve it :

getApplicationContext().grantUriPermission(getCallingPackage(), 
          contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

Turns out, activity has dedicated API for this. See here.

Riddhi Shah
  • 3,092
  • 5
  • 36
  • 56
Jake
  • 16,329
  • 50
  • 126
  • 202
2

Make sure the authority string is unique per app. I cloned another app with the same authority string which caused the error

File file = new File("whatever");
URI uri = FileProvider.getUriForFile(context, "unique authority string", file);
54ka
  • 3,501
  • 2
  • 9
  • 24
רן קלנר
  • 21
  • 1
  • 1
0

in my case I was to share a picture with text and solution was to add both flags, read FLAG_GRANT_READ_URI_PERMISSION and write FLAG_GRANT_WRITE_URI_PERMISSION as below:

        Intent intent = new Intent(android.content.Intent.ACTION_SEND);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    intent.setType("image/png");
    intent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.txtShareAppTitle));
    intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.txtShareApp) ;
    startActivity(intent);
0

I got it that way

try{
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        takePictureIntent.resolveActivity(packageManager)?.also {
                val photoFile: File? = try {
                    createImageFile()
                } catch (ex: IOException) {
                    null
                }
            photoFile?.also {
                val photoURI: Uri = FileProvider.getUriForFile(
                    this,
                    "$packageName.fileprovider",
                    it
                )
                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
                        val permissionTemp =  Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                        grantUriPermission(packageName, Uri.fromFile(photoFile), permissionTemp)
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile))
                    }else{
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    }
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}
    catch (ex: IOException){
    Log.d("IOException", "${ex.printStackTrace()}")
}

file_provider_paths.xml

<paths>
   <external-files-path name="my_images" path="/" />
   <external-path name="my_images" path="/"/>
   <external-path name="external" path="." />
   <external-files-path name="external_files" path="." />
   <files-path name="files" path="." />
</paths>

and then just follow the documentation Take photos

Dharman
  • 30,962
  • 25
  • 85
  • 135
AllanRibas
  • 678
  • 5
  • 14
0

If anyone is getting this issue on new versions of Android, make sure that the authority string that you are passing to getUriForFile() is the same as what you declared in the manifest file.