Android 6: cannot share files anymore?
Asked Answered
C

4

32

I am sharing an image, and this code works properly for devices before Android 6:

Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
Uri uri = Uri.fromFile(new File(mFilename));
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
mContext.startActivity(Intent.createChooser(shareIntent, mChooserTitle));

However I get the toast error "can't attach empty files" when I try to share using Android 6.

I verified that the file exists and it's not zero-length.

Anyone has a solution for this?

Cowan answered 6/10, 2015 at 23:4 Comment(6)
Are you getting this Toast before or after the chooser? If after, what app did you choose? Also, where is mFilename pointing, exactly?Eyeopening
After the chooser, it happens with every app I tried. At least with Gmail and Hangout.Cowan
mFilename is inside the getExternalCacheDir(), starting with /storage/emulated/0/Android/data/Cowan
Have you run Gmail and Hangouts yet, on their own? If not, they will not have access to external storage, until you (the user) go through their runtime permissions request logic.Eyeopening
Yes! you are right!!!!!!!!!!!!!!!!!!!!!!!! I enabled the external storage permission of Gmail, and now it works!!!!!!Cowan
although it seems very silly to me that Gmail doesn't ask for the permission when I attempt to shareCowan
E
22

One limitation of the Android 6.0 runtime permission system is that there will be corner cases that cause problems. What you encountered is one: trying to share a file on external storage to an app that does not have the runtime permission checks in place for that particular UI path.

I say that this is a "corner case" because, for this bug in the receiving app to affect the user, the user cannot have previously used that app and granted the necessary permission. Either:

  • The user has never used that app before, yet it still trying to share content to it, or

  • The user revoked the permission via Settings, but did not realize that it would break this bit of functionality

Both of those are low probability events.

You, as the sender, have two main options:

  1. Switch away from using file:// Uri values, in favor of a file-serving ContentProvider like FileProvider, so the permission is no longer needed, or

  2. Just living with the corner case

Eyeopening answered 6/10, 2015 at 23:15 Comment(2)
Ok, I implemented the FileProvider, and it works fine!Cowan
@DanieleB: FWIW, I wrote up a blog post about this.Eyeopening
C
43

I solved it by implementing a FileProvider, as suggested by @CommonsWare

You first need to configure a FileProvider:

  • first, add the <provider> to your file manifest XML

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.myfileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_provider_paths" />
    </provider>
    
  • second, define your file paths in a separate XML file, I called it "file_provider_paths.xml"

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

you can find the complete explanation in this documentation page

after you have set up your file provider in XML, this is the code to share the image file:

Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
Uri fileUri = FileProvider.getUriForFile(mContext, "com.myfileprovider", new File(mFilename));
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
mContext.startActivity(Intent.createChooser(shareIntent, mChooserTitle));
Cowan answered 7/10, 2015 at 0:51 Comment(2)
Thank you very much for posting this - it helped me a great deal and works perfectly now. They only thing I had to work out was getting the path correct to the file.Mozellemozes
String filename=mCSVname.getText().toString()+".csv"; File filelocation = new File(getExternalFilesDir (null), filename); Uri fileUri = FileProvider.getUriForFile(mContext, "com.myfileprovider", filelocation);Mozellemozes
E
22

One limitation of the Android 6.0 runtime permission system is that there will be corner cases that cause problems. What you encountered is one: trying to share a file on external storage to an app that does not have the runtime permission checks in place for that particular UI path.

I say that this is a "corner case" because, for this bug in the receiving app to affect the user, the user cannot have previously used that app and granted the necessary permission. Either:

  • The user has never used that app before, yet it still trying to share content to it, or

  • The user revoked the permission via Settings, but did not realize that it would break this bit of functionality

Both of those are low probability events.

You, as the sender, have two main options:

  1. Switch away from using file:// Uri values, in favor of a file-serving ContentProvider like FileProvider, so the permission is no longer needed, or

  2. Just living with the corner case

Eyeopening answered 6/10, 2015 at 23:15 Comment(2)
Ok, I implemented the FileProvider, and it works fine!Cowan
@DanieleB: FWIW, I wrote up a blog post about this.Eyeopening
S
2

Alternatively, you don't need to have a ContentProvider / FileProvider . You can simply add the flag that grants read permission on the uri that is shared. Specifically, share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); should do the trick.

Susannasusannah answered 10/11, 2015 at 15:49 Comment(0)
F
0

Another way I used to workaround this issue was to get content provider style URI using MediaScannerConnection right after you wrote your file to the public directory:

        MediaScannerConnection.scanFile(context, new String[] {imageFile.toString()}, yourMimeType, new OnScanCompletedListener() {
            @Override
            public void onScanCompleted(String path, Uri uri) {
                //uri = "content://" style URI that is safe to attach to share intent
            }
        });

This may be a shorter solution for your needs.

Fustanella answered 11/10, 2015 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.