Search code examples
javaandroidandroid-sharingandroid-fileprovider

Android photo sharing with FileProvider


I have read all the answer about this argument but I receive always an error of the application that receive my photo.
The only way that worked for me, for all application, was this (It works because sd card files are public to all applications):

final File tmpFile = new File(context.getExternalCacheDir(), "exported.jpg");
Uri tmpFileUri = Uri.fromFile(tmpFile);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setDataAndType(tmpFileUri, "image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, tmpFileUri);
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_image)));



Now, I'm stuck on how to share a file that is located in a private folder. I used the code provided by the google documentation:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.test.myapp.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>
...
...
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal_files" path="/"/>
    <cache-path name="internal_cache" path="/" />
</paths>


This is the code to share files using the FileProvider but doesn't work with any application except whats up:

final File tmpFile = new File(context.getCacheDir(), "exported.jpg");
Uri tmpFileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", tmpFile);
//Remove the uri permission because we overwrite the file
context.revokeUriPermission(tmpFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

saveBitmapToPath(bitmap, tmpFile);
bitmap.recycle();

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setDataAndType(tmpFileUri, "image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, tmpFileUri);
//Grant again the permissions
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_image)));

Why do I keep getting errors in other applications, like this:
java.lang.SecurityException: Permission Denial: content://com.test.myapp.fileprovider/internal_cache/exported.jpg (pid=675, uid=10052) requires null
Or
IllegalArgumentException: Failed to find configuration root that contains content://com.test.myapp.fileprovider/internal_cache/exported.jpg


Solution

  • Finally looking at the source code of the receiving app, I got the solution.
    This is the complete, working code that I share.
    I hope to help somebody:

    <!-- AndroidManifest.xml -->
    <provider
        android:name="com.test.myapp.fileprovider.FileProvider"
        android:authorities="com.test.myapp.fileprovider"
        android:exported="true"
        tools:ignore="ExportedContentProvider" />
    


    //EntryPoint
    private void mySharer() {
        ArrayList<Uri> streamUris = new ArrayList<Uri>();
        for (int i = 0; i < 10; i++) {
            File tmpFile = new File(getContext().getCacheDir(), "tmp" + i + ".jpg");
            Uri tmp = FileProvider.getUriForFile("com.test.myapp.fileprovider", tmpFile);
            streamUris.add(tmp);
        }
    }
    
    //Share Intent creator
    public final void shareUris(ArrayList<Uri> streamUris) {
        if (!streamUris.isEmpty()) {
            Intent shareIntent = new Intent();
            shareIntent.putExtra(ShareCompat.EXTRA_CALLING_PACKAGE, getPackageName());
            shareIntent.putExtra(ShareCompat.EXTRA_CALLING_ACTIVITY, getComponentName());
            shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            shareIntent.setType("image/jpeg");
    
            if (streamUris.size() == 1) {
                shareIntent.setAction(Intent.ACTION_SEND);
                shareIntent.putExtra(Intent.EXTRA_STREAM, streamUris.get(0));
            } else {
                shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
                shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, streamUris);
            }
    
            //For multiple images copy all images in the baseDir and use startActivityForResult
            startActivityForResult(Intent.createChooser(shareIntent, getString(R.string.share_image)), 500);
        }
    }
    
    //onResult you can delete all temp images/files with specified extensions
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case 500:
                getContentResolver().delete(FileProvider.getUriForFile(getPackageName() + ".fileprovider", null), FileProvider.WHERE_EXTENSION, new String[]{"jpg"});
                break;
            default:
                break;
        }
    }
    
    /**
     * This class extends the ContentProvider
     */
    abstract class AbstractFileProvider extends ContentProvider {
    
        private final static String OPENABLE_PROJECTION_DATA = "_data";
        private final static String[] OPENABLE_PROJECTION = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, OPENABLE_PROJECTION_DATA };
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            if (projection == null) {
                projection = OPENABLE_PROJECTION;
            }
    
            final MatrixCursor cursor = new MatrixCursor(projection, 1);
            MatrixCursor.RowBuilder b = cursor.newRow();
    
            for (String col : projection) {
                if (OpenableColumns.DISPLAY_NAME.equals(col)) {
                    b.add(getFileName(uri));
                } else if (OpenableColumns.SIZE.equals(col)) {
                    b.add(getDataLength(uri));
                } else if (OPENABLE_PROJECTION_DATA.equals(col)) {
                    b.add(getFileName(uri));
                } else {
                    b.add(null);
                }
            }
    
            return cursor;
        }
    
        @Override
        public String getType(Uri uri) {
            return URLConnection.guessContentTypeFromName(uri.toString());
        }
    
        protected String getFileName(Uri uri) {
            return uri.getLastPathSegment();
        }
    
        protected long getDataLength(Uri uri) {
            return AssetFileDescriptor.UNKNOWN_LENGTH;
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues initialValues) {
            throw new RuntimeException("Operation not supported");
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
            throw new RuntimeException("Operation not supported");
        }
    
        @Override
        public int delete(Uri uri, String where, String[] whereArgs) {
            throw new RuntimeException("Operation not supported");
        }
    }
    
    /**
     * This class extends the AbstractFileProvider
     */
    public class FileProvider extends AbstractFileProvider {
    
        public static final String CONTENT_URI = "content://";
        private File baseDir;
    
        @Override
        public boolean onCreate() {
            baseDir = getContext().getCacheDir();
    
            if (baseDir != null && baseDir.exists()) {
                return true;
            }
    
            Log.e("FileProvider", "Can't access cache directory");
            return false;
        }
    
        @Override
        public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
            File f = new File(baseDir, uri.getPath());
    
            if (f.exists()) {
                return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
            }
    
            throw new FileNotFoundException(uri.getPath());
        }
    
        @Override
        protected long getDataLength(Uri uri) {
            File f = new File(baseDir, uri.getPath());
    
            return f.length();
        }
    
        public static Uri getUriForFile(String authority, File file) {
            return Uri.parse(CONTENT_URI + authority + "/" + file.getName());
        }
    }
    



    -------------EDIT: 05/11/16--------------
    Added support for multiple images:

    1. Copy all images in the baseDir folder
    2. Implement delete() method in the FileProvider
    3. Use startActivityForResult
    4. Listen onActivityResult
    5. Now you can delete all temp images

    For email attachment you must wait for email to be sent before delete the file, otherwise you'll send an empty attachment