1

I'm creating a basic app which starts the camera activity and then retrieves the image clicked.

I have necessary permissions in the manifest file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

My problem is the app was working fine in api level 22. I updated my phone to level 23 a few days ago and now the same source code is giving me this error:

E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/Pictures/SAT Practice App/IMG_20160113_123753.jpg: open failed: EACCES (Permission denied)

There are a lot of similar questions in stackoverflow, but most of them only talk about granting permissions which I already have in my manifest.

I personally checked the folder and found that the file exists. What I might be missing?

Here's my activity class for the reference:

public class MainActivity extends Activity {

    // Activity request codes
    private static final int CAMERA_CAPTURE_IMAGE_REQUEST_CODE = 100;
    public static final int MEDIA_TYPE_IMAGE = 1;

    // directory name to store captured images
    private static final String IMAGE_DIRECTORY_NAME = "SAT Practice";

    private Uri fileUri; // file url to store image
    private ImageView imgPreview;
    private Button btnCapturePicture;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imgPreview = (ImageView) findViewById(R.id.imgPreview);
        btnCapturePicture = (Button) findViewById(R.id.btnCapturePicture);

        /**
         * Capture image button click event
         */
        btnCapturePicture.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // capture picture
                captureImage();
            }
        });
    }

    /**
     * Capturing Camera Image will lauch camera app requrest image capture
     */
    private void captureImage() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);

        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);

        // start the image capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_IMAGE_REQUEST_CODE);
    }

    /**
     * Here we store the file url as it will be null after returning from camera
     * app
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        // save file url in bundle as it will be null on scren orientation
        // changes
        outState.putParcelable("file_uri", fileUri);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        // get the file url
        fileUri = savedInstanceState.getParcelable("file_uri");
    }


    /**
     * Receiving activity result method will be called after closing the camera
     * */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // if the result is capturing Image
        if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                // successfully captured the image
                // display it in image view
                previewCapturedImage();
            } else if (resultCode == RESULT_CANCELED) {
                // user cancelled Image capture
                Toast.makeText(getApplicationContext(),"User cancelled image capture", Toast.LENGTH_SHORT).show();
            } else {
                // failed to capture image
                Toast.makeText(getApplicationContext(),"Sorry! Failed to capture image", Toast.LENGTH_SHORT).show();
            }
        }
    }

    /**
     * Display image from a path to ImageView
     */
    private void previewCapturedImage() {
        try {
            imgPreview.setVisibility(View.VISIBLE);

            // bimatp factory
            BitmapFactory.Options options = new BitmapFactory.Options();

            // downsizing image as it throws OutOfMemory Exception for larger
            // images
            options.inSampleSize = 8;

            final Bitmap bitmap = BitmapFactory.decodeFile(fileUri.getPath(),
                    options);

            imgPreview.setImageBitmap(bitmap);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }

    /**
     * Creating file uri to store image
     */
    public Uri getOutputMediaFileUri(int type) {
        return Uri.fromFile(getOutputMediaFile(type));
    }

    /**
     * returning image
     */
    private static File getOutputMediaFile(int type) {

        // External sdcard location
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),IMAGE_DIRECTORY_NAME);

        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.i(IMAGE_DIRECTORY_NAME, "Oops! Failed create "+ IMAGE_DIRECTORY_NAME + " directory");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
                Locale.getDefault()).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
        } else {
            return null;
        }
        return mediaFile;
    }
}
Akeshwar Jha
  • 4,516
  • 8
  • 52
  • 91

2 Answers2

2

So, as Jörn pointed out, for API level 23 and higher, here's what the documentation says:

If the device is running Android 6.0 or higher, and your app's target SDK is 23 or higher: The app has to list the permissions in the manifest, and it must request each dangerous permission it needs while the app is running.

In this answer, we can see the list of permissions that are dangerous (like reading/writing to external storage, calendars, location access etc). In my case, I the dangerous permissions are reading and writing to the external storage. Therefore, in my clicklistener to start the camera, I asked user for the permissions as:

int MY_PERMISSIONS_REQUEST_READ_AND_WRITE_EXTERNAL_STORAGE;

btnCapturePicture.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            if((ContextCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
                    || (ContextCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED))

            {
                ActivityCompat.requestPermissions
                        (MainActivity.this, new String[]{
                                Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.WRITE_EXTERNAL_STORAGE
                        },MY_PERMISSIONS_REQUEST_READ_AND_WRITE_EXTERNAL_STORAGE);
            }
//captureImage() method will start the camera. All reading/writing stuff is implemented in this method
            captureImage(); 
        }
    });

When the app calls requestPermissions(), the system shows a standard dialog box to the user asking whether to allow the permissions or not.

Community
  • 1
  • 1
Akeshwar Jha
  • 4,516
  • 8
  • 52
  • 91
  • I am getting this error Variable 'MY_PERMISSIONS_REQUEST_READ_AND_WRITE_EXTERNAL_STORAGE' might not have been initialized. What should it be initialized to? – moberme May 25 '16 at 17:08
  • @moberme you have to initialize it with some number.. doesn't matter what. – Suisse Sep 19 '16 at 14:17
  • @Akeshwar it is asking every time for permission, how can we detect that we have already once gave permission? – Suisse Sep 19 '16 at 14:17
  • Why not use sharedpreferences to store a boolean variable? Initialize the boolean by false and once you give the permission, set the boolean to true. You can check that boolean before requesting the permission. That being said, ideally, once you give the permission, the app doesn't ask for it again. You can also check the permission status by `ContextCompat.checkSelfPermission(context, Manifest.permission.PERMISSION_NAME) == PackageManager.PERMISSION_GRANTED)` – Akeshwar Jha Sep 19 '16 at 15:00
  • Awesome, perfect! – Suisse Sep 19 '16 at 20:05
0

As user5038993 has described for target sdk 23 or higher we have to take permission for reading and writing any thing in device..once you click on allow you can do your stuff...

@Override
public void onClick(View v) {
    if((ContextCompat.checkSelfPermission(MainActivity.this,                                  
            Manifest.permission.READ_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED)       
                || (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED))

        {
            ActivityCompat.requestPermissions
                    (MainActivity.this, new String[]{
                            Manifest.permission.READ_EXTERNAL_STORAGE,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },MY_PERMISSIONS_REQUEST_READ_AND_WRITE_EXTERNAL_STORAGE);
        }
}