android.os.FileUriExposedException: file.jpg exposed beyond app through ClipData.Item.getUri()

74,697

Solution 1

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() method. In this way the VM ignores the file URI exposure.

Solution 2

This info is from: FileUriExposedException

This is only thrown for applications targeting N or higher. Applications targeting earlier SDK versions are allowed to share file:// Uri, but it's strongly discouraged.

So if your app/build.gradle file's compileSdkVersion (build target) is Android N (API level 24) or greater, this error is thrown if you write a file that can possibly get accessed by other apps. Most importantly, here is how you are supposed to do it moving forward:

taken from here: FileProvider

Change:

return Uri.fromFile(mediaFile);

to be

return FileProvider.getUriForFile(getApplicationContext(), getPackageName()+".fileprovider", mediaFile);

Note: if you control mydomain.com you could also replace getPackageName()+".fileprovider" with "com.mydomain.fileprovider" (the same goes for your AndroidManifest.xml below.

Also you need to add the following to your AndroidManifest.xml file right before your </application> tag

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>

Then you need to add a file named filepaths.xml to your app/src/main/res/xml directory with the following contents

<paths>
    <external-files-path name="Pictures" path="Pictures" />
</paths>

Note: for anybody else, we used external-files-path above, since Omer used getExternalFilesDir(Environment.DIRECTORY_PICTURES) in the code. For any other location, please check FileProvider under the "Specifying Available Files" section and change external-files-path to one of the following based on where your files are located:

files-path
cache-path
external-path
external-files-path
external-cache-path

Also, revise Pictures above to be your folder name.

One important anti-pattern to avoid is you should NOT use if (Build.VERSION.SDK_INT < 24) { to allow you to do it the old way, because it is not their Android version that requires this, it's your build version (I missed this the first time I coded it).

I know this is more work, however, this allows Android to allow only the other app you're sharing with, temporary access to the file, which is more secure for the user's privacy.

Share:
74,697

Related videos on Youtube

Omer
Author by

Omer

Updated on July 09, 2022

Comments

  • Omer
    Omer almost 2 years

    I try to do a button that open the camera and take picture. my code is here

    //for imports check on bottom of this code block
    
    public class HomeProfileActivity extends AppCompatActivity {
    //Button camera
    public static final String TAG = HomeProfileActivity.class.getSimpleName();
    public static final int REQUEST_TAKE_PHOTO = 0;
    public static final int REQUEST_TAKE_VIDEO = 1;
    public static final int REQUEST_PICK_PHOTO = 2;
    public static final int REQUEST_PICK_VIDEO = 3;
    public static final int MEDIA_TYPE_IMAGE = 4;
    public static final int MEDIA_TYPE_VIDEO = 5;
    
    private Uri mMediaUri;
    private ImageView photobutton;
    private Button buttonUploadImage, buttonTakeImage;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home_profile);
        ButterKnife.bind(this);
    }
    
    @OnClick(R.id.buttonTakeImage)
    void takePhoto() {
        mMediaUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
        if (mMediaUri == null) {
            Toast.makeText(this,
                    "There was a problem accessing your device's external storage.",
                    Toast.LENGTH_LONG).show();
        }
        else {
            Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, mMediaUri);
            startActivityForResult(takePhotoIntent, REQUEST_TAKE_PHOTO);
        }
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    
        if (resultCode == RESULT_OK){
            if (requestCode == REQUEST_TAKE_PHOTO) {
                Intent intent = new Intent(this, ViewImageActivity.class);
                intent.setData(mMediaUri);
                startActivity(intent);
            }
        }
        else if (resultCode != RESULT_CANCELED){
            Toast.makeText(this, "Sorry, there was an error", Toast.LENGTH_SHORT).show();
        }
    }
    
    private Uri getOutputMediaFileUri(int mediaType) {
        // check for external storage
        if (isExternalStorageAvailable()) {
            // get the URI
    
            // 1. Get the external storage directory
            File mediaStorageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    
            // 2. Create a unique file name
            String fileName = "";
            String fileType = "";
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    
            if (mediaType == MEDIA_TYPE_IMAGE) {
                fileName = "IMG_" + timeStamp;
                fileType = ".jpg";
            } else if (mediaType == MEDIA_TYPE_VIDEO) {
                fileName = "VID_" + timeStamp;
                fileType = ".mp4";
            } else {
                return null;
            }
            // 3. Create the file
            File mediaFile;
            try {
                mediaFile = File.createTempFile(fileName, fileType, mediaStorageDir);
                Log.i(TAG, "File: " + Uri.fromFile(mediaFile));
    
                // 4. Return the file's URI
                return Uri.fromFile(mediaFile);
            }
            catch (IOException e) {
                    Log.e(TAG, "Error creating file: " +
                            mediaStorageDir.getAbsolutePath() + fileName + fileType);
                }
            }
    
            // something went wrong
            return null;
        }
    
    private boolean isExternalStorageAvailable(){
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)){
            return true;
        }
        else {
            return false;
        }
    }
    
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.net.Uri;
    import android.os.Bundle;
    import android.os.Environment;
    import android.provider.MediaStore;
    import android.support.annotation.NonNull;
    import android.support.design.widget.BottomNavigationView;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.io.File;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import butterknife.ButterKnife;
    import butterknife.OnClick;
    

    I have also a problem with startActivityForResult in the method onclick and the import java.text.SimpleDateFormat; also jump in the exception runtime i am working with buildtoolsversion sdk 25

  • Thiago Queiroz
    Thiago Queiroz over 6 years
    This worked for me, thanks, anything i should know for a possible future problem or i'm good for a while?
  • Gundu Bandgar
    Gundu Bandgar over 6 years
    very nice answer, lots of Thanks.
  • Dave Sanders
    Dave Sanders over 6 years
    Just quick edit: the path="pictures" is case sensitive. When using Environment.DIRECTORY_PICTURES on my end, I had to capitalize it as path="Pictures", otherwise it couldn't figure out a path.
  • Jared
    Jared over 6 years
    @DaveSanders Thank you for noticing that, I have updated my answer to reflect the case of Environment.DIRECTORY_PICTURES, which evaluates to "Pictures" (Title case)
  • tamtom
    tamtom over 6 years
    StrictMode is disabled by default, and users need to enter "Developer's mode" in their Android to enable it. That makes the solution of using StrictMode irrelevant.
  • DILSHAD AHMAD
    DILSHAD AHMAD almost 6 years
    Thanks, working for me. Test Device Android : 7.0 targetSdkVersion 26
  • GeekWithGlasses
    GeekWithGlasses over 4 years
    Worked like a charm!
  • Pooja
    Pooja over 4 years
    Helpful. Thanks
  • Abu Nayem
    Abu Nayem almost 4 years
    Should I wrap this with only SDK version 24 or above
  • Girish
    Girish over 3 years
    @rahul sondarva Could you please answer the above questions..even I am having the same.
  • hornet2319
    hornet2319 about 3 years
    please not that androidX verrion of FileProvider has package androidx.core.content.FileProvider