1

I'm following this tutorial on taking pictures, displaying thumbnails and storing the full pictures on local public storage available to my application only.

The problem: EACCESS (Permission denied) when trying to access local storage for my application

11-12 10:36:30.765    3746-3746/com.test.example.photo W/System.err﹕ java.io.IOException: open failed: EACCES (Permission denied)
11-12 10:36:30.765    3746-3746/com.test.example.photo W/System.err﹕ at java.io.File.createNewFile(File.java:948)
11-12 10:36:30.765    3746-3746/com.test.example.photo W/System.err﹕ at java.io.File.createTempFile(File.java:1013)

I've looked at this question but it appears to be outdated as none of the solutions work any more today. This question also provides no working solutions. Other results and solutions I've seen and tried seem only vaguely related.

My manifest permissions

</application>
    <!-- PERMISSIONS -->
    <permission
        android:name="android.hardware.Camera.any"
        android:required="true" />
    <permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:required="true" />
    <!-- android:maxSdkVersion="18" seemingly does nothing-->
</manifest>

The method that crashes

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";

    //THIS IS WHERE IT CRASHES
    File storageDir = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
        imageFileName,  /* prefix */
        ".jpg",         /* suffix */
        storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = "file:" + image.getAbsolutePath();
    return image;
}

I am using an i9250 Galaxy Nexus 3 phone to run the examples, since my emulator doesn't have a camera and automatically GONEs the elements. My target SDK is 16 and I have updated my both my build tools and Android Studio to the latest versions.

I feel like I'm missing something obvious here, since taking pictures is so common in applications and I can't imagine it not working for everyone, but I'm stuck and I'd appreciate your guidance. I am quite new to android, the literature I'm primarily using is Beginning Android 4 Game Programming, Beginning Android 4 and Pro Android 4.

Thank you for your time!

Community
  • 1
  • 1
G_V
  • 2,396
  • 29
  • 44
  • http://stackoverflow.com/questions/22406061/howto-avoid-the-eacces-permission-denied-on-sdcard-with-kitkat-4-4-2-version – Naveen Tamrakar Nov 12 '14 at 10:27
  • I've seen and commented on that question before, where exactly does it provide a solution? I don't see it. I have tried various ways of granting permissions though android should automatically give me access to the public storage of my application, but no matter what, it just gets blocked. – G_V Nov 12 '14 at 10:32
  • To clarify, in the example Environment.getExternalStoragePublicDirectory is used, but this refers to the SD card. Using getExternalFilesDir gives local sandboxed storage as I understand it. The main difference being that your app can't access files it should have no permissions to and everything locally stored getting deleted when your app is uninstalled. – G_V Nov 13 '14 at 10:28

3 Answers3

3

Thanks for the help everyone, it works now!

Apparently I was using the SD card storage which required permissions as explained in permission vs uses-permisson instead of local sandboxed storage which requires no permissions starting from API level 19.

SD card access, requires write permission: Environment.getExternalStoragePublicDirectory

Sandboxed local storage for your app: getExternalFilesDir

I use this code for API level 16, it should require minimal effort to implement and change but if you encounter problems, leave a message and I'll try to help or clarify.

Most of the explanation is in the code as commentary

    //OnClick hook, requires implements View.OnClickListener to work
    public void takePicture(View v) {
        dispatchTakePictureIntent();
    }

    private void dispatchTakePictureIntent() {
        //Create intent to capture an image from the camera
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Ensure that there's a camera activity to handle the intent
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            // Create the directory File where the photo should go, do NOT try to create the image file itself
            File photoFile = null;
            try {
                //mCurrentPhotoPath is a File outside of the methods, so all methods know the last directory for the last picture taken
                mCurrentPhotoPath = createImageFile();
                photoFile = mCurrentPhotoPath;
            } catch (IOException ex) {
                // Error occurred while creating the File
                ex.printStackTrace();
            }
            // Continue only if the File was successfully created
            if (photoFile != null) {
                //photoFile MUST be a directory or the camera will hang on an internal
                //error and will refuse to store the picture,
                //resulting in not being able to to click accept
                //MediaStore will automatically store a jpeg for you in the specific directory and add the filename to the path
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            //unique name, can be pretty much whatever you want
            imageId = generateImageId(); 
            //Get file.jpg as bitmap from MediaStore's returned File object
            Bitmap imageBitmap = BitmapFactory.decodeFile(mCurrentPhotoPath.getAbsolutePath()); 
            //resize it to fit the screen
            imageBitmap = Bitmap.createScaledBitmap(imageBitmap,300,300,false); 
            //Some ImageView in your layout.xml
            ImageView imageView = (ImageView)findViewById(R.id.imageView); 
            imageView.setImageBitmap(imageBitmap);
            Bitmap thumbnail =  makeThumbnail(mCurrentPhotoPath);
            ImageView thumbnail = (ImageView)findViewById(R.id.thumbnail); 
            thumbnail.setImageBitmap(imageBitmap);
        }
    }

    private File createImageFile() throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        //completely optional subdirectory structure
        storageDir = new File(storageDir, "custom_directory");
        return storageDir;
    }    

    private Bitmap makeThumbnail(File currentPhotoPath) {
        // Get the dimensions of the View, I strongly recommend creating a <dimens> resource for dip scaled pixels
        int targetW = 45;
        int targetH = 80;

        // Get the dimensions of the bitmap
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(currentPhotoPath.getAbsolutePath(), bmOptions);
        int photoW = bmOptions.outWidth;
        int photoH = bmOptions.outHeight;

        // Determine how much to scale down the image
        int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

        // Decode the image file into a Bitmap sized to fill the View
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;
        bmOptions.inPurgeable = true;

        Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath.getAbsolutePath(), bmOptions);
        return bitmap;
    }

    private long generateImageId() {
        return Calendar.getInstance().getTimeInMillis();
    }

Android 5.0, API 21, will use the Camera2 API where all of this will be hidden far away, from what I understand. You can read about it here

Community
  • 1
  • 1
G_V
  • 2,396
  • 29
  • 44
1

try this:

private File getDir() {
File sdDir = Environment
  .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
return new File(sdDir, "Your_photo_dir_here");

}

then:

 File pictureFileDir = getDir();

if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) {

  Log.d("TAG", "Can't create directory to save image.");
  return;

}


SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmss");
String date = dateFormat.format(new Date());
String photoFile = "myphoto_" + date + ".jpg";

String filename = pictureFileDir.getPath() + File.separator + photoFile;

File pictureFile = new File(filename);

try {
  FileOutputStream fos = new FileOutputStream(pictureFile);
  fos.write(data);
  fos.close();

} catch (Exception error) {
  Log.d("TAG", "File" + filename + "not saved: "
      + error.getMessage());

}
andreasperelli
  • 1,034
  • 2
  • 11
  • 40
  • While not directly solving the issue at hand, still very useful code. May I suggest adding commentary to it as to clarify exactly what is going on in which section? Not so much for me as I have been reading into cameras, content providers and other voodoo for over 20 hours this week, but for other newcomers to android. I especially like the bit where you add custom folders to the pictures because I was planning on doing just that after fixing the storage and retrieval issue. – G_V Nov 13 '14 at 10:19
-1

Instead of permission tag use uses-permission Add this in manifest

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
justDroid
  • 343
  • 2
  • 9
  • This actually fixed the error spam, but it still isn't writing. If I add takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); it actually does write, but the whole application crashes when I try to retrieve the thumbnail because the mysterious default "data" tag in extras seems to get overwritten. I'll try to fix that and post the fixed code. – G_V Nov 12 '14 at 12:11
  • I also understand that I actually should avoid using WRITE_EXTERNAL_STORAGE if at all possible since I only need my sandboxed storage, yet without this permission I can't access my sandbox. I'm guessing this is a bug. – G_V Nov 12 '14 at 12:14
  • At first access denied, because the tutorial for some reason suggests calling the SD card, which requires permissions. I've changed the permission as you suggested, which resolved this. I've also changed Environment.getExternalStoragePublicDirectory to getExternalFilesDir because of security reasons and I dislike just grabbing every permission I could maybe need at some point. What I am trying to do now is: 1. Capture image 2. Display thumbnail on screen, save full picture to pictures/mydirs 3. add onClick where the picture is loaded based on the thumbnail tapped. – G_V Nov 13 '14 at 10:06
  • The problem with this is data.getExtras("data") is written only once somewhere in code out of my control. I can either save the full picture, which empties "data", causing a hard nullpointer crash OR retrieve the thumbnail and not store the picture. What I am going to try to do is 1. Store full picture 2. Use content provider to generate and retrieve thumbnail from picture 3. add to screen to be clicked. I've also posted this as a documentation issue, as the example crashes itself by overriding the generated "data" tag without any warning or explanation. – G_V Nov 13 '14 at 10:10