Image share intent works for Gmail but crashes FB and twitter

I am trying to allow the user to share an image to other apps on the device. The image is inside the files/ subdirectory of my app's internal storage area. It works just fine with Gmail, but Facebook and Twitter both crash when responding to my intent.

EDIT: Google+ also works fine.

Here are the relevant sections of code.

In Application.xml

<provider 
    android:name="android.support.v4.content.FileProvider"
    android:authorities="org.iforce2d.myapp.MyActivity"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>

xml/filepaths.xml

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

Here is the sharing code in my activity:

File imagePath = new File(getContext().getFilesDir(), "shared");
File newFile = new File(imagePath, "snapshot.jpg");
Uri contentUri = FileProvider.getUriForFile(getContext(),
                     "org.iforce2d.myapp.MyActivity", newFile);    

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

List<ResolveInfo> resInfos = 
    getPackageManager().queryIntentActivities(shareIntent,
                                              PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo info : resInfos) {
    getContext().grantUriPermission(info.activityInfo.packageName, 
                                    contentUri,
                                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

startActivity(Intent.createChooser(shareIntent, "Share image..."));

The value of contentUri when logged is:

content://org.iforce2d.myapp.MyActivity/shared/snapshot.jpg

I have only checked this with Gmail, Facebook and Twitter as the receiving apps, but the results have been very consistent over a wide range of OS versions (from 2.2.1 to 4.4.3) and 7 devices include a Kindle.

Gmail works great. Image thumbnail appears in mail composition, and successfully attaches to mail when sent.

Twitter and Facebook both crash, as below.

Here are the stack traces from logcat showing the problem these two apps are having, it appears to be the same problem for both of them (this is taken from 4.4.3, but the errors were practically the same all the way back to 2.2.1 albeit with slightly different wording of the error message):

Caused by: 
  java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. 
  Make sure the Cursor is initialized correctly before accessing data from it.
    at android.database.CursorWindow.nativeGetString(Native Method)
    at android.database.CursorWindow.getString(CursorWindow.java:434)
    at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)
    at android.database.CursorWrapper.getString(CursorWrapper.java:114)
    at com.twitter.library.media.util.f.a(Twttr:95)
    ...

Caused by: 
  java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow.
  Make sure the Cursor is initialized correctly before accessing data from it.
    at android.database.CursorWindow.nativeGetString(Native Method)
    at android.database.CursorWindow.getString(CursorWindow.java:434)
    at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)
    at android.database.CursorWrapper.getString(CursorWrapper.java:114)
    at com.facebook.photos.base.media.MediaItemFactory.b(MediaItemFactory.java:233)
    ...

Given that sharing images on Facebook and Twitter is something that millions of people do all day long, I'm pretty shocked that it's so hard to implement :/

Can anybody spot something I'm doing wrong here?


Solution 1:

Twitter (wrongly) assumes that there will be a MediaStore.MediaColumns.DATA column. Starting in KitKat the MediaStore returns null, so luckily, Twitter gracefully handles nulls, and does the right thing.

public class FileProvider extends android.support.v4.content.FileProvider {

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Cursor source = super.query(uri, projection, selection, selectionArgs, sortOrder);

        String[] columnNames = source.getColumnNames();
        String[] newColumnNames = columnNamesWithData(columnNames);
        MatrixCursor cursor = new MatrixCursor(newColumnNames, source.getCount());

        source.moveToPosition(-1);
        while (source.moveToNext()) {
            MatrixCursor.RowBuilder row = cursor.newRow();
            for (int i = 0; i < columnNames.length; i++) {
                row.add(source.getString(i));
            }
        }

        return cursor;
    }

    private String[] columnNamesWithData(String[] columnNames) {
        for (String columnName : columnNames)
            if (MediaStore.MediaColumns.DATA.equals(columnName))
                return columnNames;

        String[] newColumnNames = Arrays.copyOf(columnNames, columnNames.length + 1);
        newColumnNames[columnNames.length] = MediaStore.MediaColumns.DATA;
        return newColumnNames;
    }
}

Solution 2:

EDIT -- The workaround described in this answer works, but the solution provided by Stefan is more elegant. It should be the accepted answer.

Also, as of January 2015, the Facebook app no longer has this bug (and now works with the standard FileProvider) but Twitter still does. Hopefully it will go completely away in the future, and neither of these will be necessary.


While the FileProvider object looks wonderful in theory, it doesn't seem to work when sharing to many third-party applications (while Google ones, like G+, Gmail, &c work perfectly). Either it doesn't expose sufficient information, or those applications just assume you're sharing files, and thus crash.

It's really frustrating! :/

The only reliable way I've found to share image files is to copy them to the external storage directory (you can create a .nomedia subdirectory to avoid them appearing in the Gallery app).

For example:

Bitmap bitmapToShare = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

File pictureStorage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File noMedia = new File(pictureStorage, ".nomedia");
if (!noMedia.exists())
    noMedia.mkdirs();

File file = new File(noMedia, "shared_image.png");
saveBitmapAsFile(bitmapToShare, file);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
shareIntent.setType("image/png");

startActivity(shareIntent);

Addendum: of course, you should also check getExternalStorageState() before trying to write/copy the file there.