'Take a photo and saving it in full size

I'm developing an app that has to take pictures from the camera and upload them to a FireBase database. However, when I create a temporary file and try to get the image from the storage to the ImageView I get the error:

2022-05-18 11:14:18.596 24790-24790/com.example.apptonia D/Exception: /storage/emulated/0/Pictures/.temp/picture8489350187277543939.jpg: open failed: EACCES (Permission denied)

I have been trying to solve the issue adding several permissions to the manifest as suggested in other similar questions, also adding code to the Activity. However, it doesnt work.

Here's my code for the activity:

...


import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.OnProgressListener;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;

import java.io.ByteArrayOutputStream;
import java.io.File;

public class TakePhotoActivity extends AppCompatActivity {

    private ImageView image;
    private Button takePhotoButton, guardarButton;
    private Bitmap photoTaken;
    private Uri mImageUri;
    static final int REQUEST_IMAGE_CAPTURE = 1;
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE};


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

        guardarButton = findViewById(R.id.buttonGuardar);
        image = findViewById(R.id.image);
        takePhotoButton = findViewById(R.id.takePhotoButton);

        takePhotoButton.setOnClickListener(v -> {
            Log.d("LOG", "CLICK BUTTON");
            dispatchTakePictureIntent();
        });

        FirebaseStorage storage = FirebaseStorage.getInstance();


        guardarButton.setOnClickListener(v -> {
            StorageReference storageRef = storage.getReference("/" + StaticUser.mail + "/" +
                    StaticUser.year + "/" + StaticUser.getTrimestre() + "/" + StaticUser.tipo + "/" +
                    StaticUser.getFecha() + StaticUser.getRandomString(40));


            //image.setDrawingCacheEnabled(true);
            //image.buildDrawingCache();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            photoTaken.compress(Bitmap.CompressFormat.JPEG, 80, baos);
            byte[] data = baos.toByteArray();

            UploadTask uploadTask = storageRef.putBytes(data);


            uploadTask.addOnFailureListener(exception -> Toast.makeText(getApplicationContext(),
                    "Subida incorrecta", Toast.LENGTH_LONG).show());

            uploadTask.addOnSuccessListener(taskSnapshot -> {
                // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
                // ...
                Toast.makeText(getApplicationContext(),
                        "Subida correcta!", Toast.LENGTH_LONG).show();


                Intent intent = new Intent(this, WelcomeActivity.class);
                startActivity(intent);
                finish();
            });

            uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
                @Override
                public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
                    double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
                    Toast.makeText(getApplicationContext(),
                            "Subiendo foto... " + progress + "%", Toast.LENGTH_SHORT).show();
                }
            });
        });
    }

    private void dispatchTakePictureIntent() {
        /*
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
         */
        try
        {
            verifyStoragePermissions(this);
            StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(builder.build());
            // place where to store camera taken picture
            Log.d("LOG", "CREATING PHOTO INTENT");
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            File photo;
            Log.d("LOG", "CREATING TEMPORARY FILE");
            photo = this.createTemporaryFile("picture", ".jpg");
            Log.d("LOG", "DELETING TEMPORARY FILE");
            photo.delete();
            mImageUri = Uri.fromFile(photo);
            Log.d("LOG", "PUTTING EXTRA IN MEDIASTORE");
            intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
            if (intent.resolveActivity(getPackageManager()) != null) {
                startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
            }
        }
        catch(Exception e)
        {
            Log.d("Error", e.getMessage());
            Toast.makeText(getApplicationContext(), "Please check SD card! Image shot is impossible!",
                    Toast.LENGTH_SHORT);
        }
    }

    private File createTemporaryFile(String part, String ext) throws Exception
    {
        Log.d("LOG", "getting tempDir");
        File tempDir= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        tempDir = new File(tempDir.getAbsolutePath()+"/.temp/");
        Log.d("LOG", tempDir.getAbsolutePath()+"/.temp/");
        if(!tempDir.exists())
        {
            Log.d("LOG", "creating dir");
            tempDir.mkdirs();
        }
        Log.d("LOG", "returning new file created");
        return File.createTempFile(part, ext, tempDir);
    }

    public void grabImage(ImageView imageView) {
        this.getContentResolver().notifyChange(mImageUri, null);
        ContentResolver cr = this.getContentResolver();
        Bitmap bitmap;
        try {
            bitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, mImageUri);
            imageView.setImageBitmap(bitmap);
        } catch (Exception e) {
            Toast.makeText(this, "Failed to load", Toast.LENGTH_SHORT).show();
            Log.d("Exception", e.getMessage());
        }
    }

    public static void verifyStoragePermissions(Activity activity) {
        // Check if we have write permission
        int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if (permission != PackageManager.PERMISSION_GRANTED) {
            // We don't have permission so prompt the user
            ActivityCompat.requestPermissions(
                    activity,
                    PERMISSIONS_STORAGE,
                    REQUEST_EXTERNAL_STORAGE
            );
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {

            try {
                ContentResolver cr = this.getContentResolver();
                Bitmap imageBitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, mImageUri);
                this.grabImage(image);
                photoTaken = imageBitmap;
                image.setImageBitmap(imageBitmap);
            }
            catch (Exception e) {
                Toast.makeText(this, "Failed to load", Toast.LENGTH_SHORT).show();
                Log.d("Exception", e.getMessage());

            }
        }
    }
}

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.apptonia">

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

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:preserveLegacyExternalStorage="true"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppTonia"
        android:usesCleartextTraffic="true"
        tools:targetApi="m">
        <activity
            android:name=".TakePhotoActivity"
            android:exported="false" />
        <activity
            android:name=".SelectFamilyActivity"
            android:exported="false" />
        <activity
            android:name=".MainActivity$SelectFamilyActivity"
            android:exported="false" />
        <activity
            android:name=".WelcomeActivity"
            android:exported="false"
            android:label="@string/title_activity_welcome"
            android:theme="@style/Theme.AppTonia.NoActionBar" />
        <activity
            android:name=".RegistroActivity"
            android:exported="false" />
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


Solution 1:[1]

Let me explain the whole process.

First you have to declare permissions in the manifest and you don't have CAMERA permission right now which should be there as well as WRITE_EXTERNAL_STORAGE which is already present.

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

Now you have to ask for these permissions in real time just like this, PERMISSION_REQUEST_CODE can be any constant value:

    private void requestPermission() {

        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == PERMISSION_REQUEST_CODE) {

            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Permission granted successfully", Toast.LENGTH_LONG).show();
                showCameraGalleryBottomSheet();
            } else {
                Toast.makeText(this, "Permission denied", Toast.LENGTH_LONG).show();
            }
        }
    }

You have to call requestPermission() before calling the startCamera() method which I am gonna explain below.

    private void startCamera() {

        ContentValues values = new ContentValues(1);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        fileUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        startActivityForResult(intent, CAPTURE_IMAGE);
    }

Here CAPTURE_IMAGE can be any constant value you can initialize and grab the result using it in onActivityResult().

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == CAPTURE_IMAGE && resultCode == RESULT_OK) {
            processCapturedPhoto();
        }
    }

To process the captured photo, you can use the method below:

    private void processCapturedPhoto() {

        try {
            String[] columns = {MediaStore.Images.ImageColumns.DATA};

            Cursor cursor = getContentResolver().query(Uri.parse(fileUri.toString()), columns, null, null, null);
            cursor.moveToFirst();

            String photoPath = cursor.getString(0);
            cursor.close();

            File file = new File(photoPath);
            uriImage = Uri.fromFile(file);

            Bitmap bitmap = null;

            try {
                bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uriImage);
            } catch (IOException e) {
                e.printStackTrace();
            }

            imageView.setImageBitmap(handleSamplingAndRotationBitmap(uriImage));

            uploadImageToFirebaseStorage();

        } catch (CursorIndexOutOfBoundsException | IOException ex) {
            ex.printStackTrace();

            String[] columns = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_ADDED};
            Cursor cursor = getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, columns, null, null, "${MediaStore.MediaColumns.DATE_ADDED} DESC");

            int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();

            String photoPath = cursor.getString(columnIndex);
            cursor.close();

            File file = new File(photoPath);
            uriImage = Uri.fromFile(file);

            Bitmap bitmap = null;

            try {
                bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uriImage);
            } catch (IOException e) {
                e.printStackTrace();
            }

            imageView.setImageBitmap(bitmap);

            uploadImageToFirebaseStorage();
        }
    }

You can initialize private Uri uriImage = null; globally. You can also use Bitmap directly to set it to ImageView but there maybe some rotation issues so to handle that, I use these rotation sampling helper methods defined below (These will be called automatically inside processCapturedPhoto() so you don't have to call them specifically):

    private Bitmap handleSamplingAndRotationBitmap(Uri selectedImage) throws IOException {
        int MAX_HEIGHT = 1024;
        int MAX_WIDTH = 1024;

        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        InputStream imageStream = getContentResolver().openInputStream(selectedImage);
        BitmapFactory.decodeStream(imageStream, null, options);
        imageStream.close();

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        imageStream = getContentResolver().openInputStream(selectedImage);
        Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);
        if (img != null) img = rotateImageIfRequired(img, selectedImage);
        else {
            Toast.makeText(this, "Image not available", Toast.LENGTH_SHORT).show();
            return null;
        }
        return img;
    }

    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            int heightRatio = Math.round(((float) height / (float) reqHeight));
            int widthRatio = Math.round(((float) width / (float) reqWidth));
            inSampleSize = Math.min(heightRatio, widthRatio);
            float totalPixels = (float) (width * height);
            float totalReqPixelsCap = (float) (reqWidth * reqHeight * 2);
            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }

    private Bitmap rotateImageIfRequired(Bitmap img, Uri selectedImage) throws IOException {
        ExifInterface ei = new ExifInterface(selectedImage.getPath());

        switch (ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                return rotateImage(img, 90);
            case ExifInterface.ORIENTATION_ROTATE_180:
                return rotateImage(img, 180);
            case ExifInterface.ORIENTATION_ROTATE_270:
                return rotateImage(img, 270);
            default:
                return img;
        }
    }

    private Bitmap rotateImage(Bitmap img, int degree) {
        Matrix matrix = new Matrix();
        matrix.postRotate((float) degree);
        Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
        img.recycle();
        return rotatedImg;
    }

After the rotation and sampling, the uploadImageToFirebaseStorage() method will be called to upload the image to firebase.

    private void uploadImageToFirebaseStorage() {

        final StorageReference refProfileImage = FirebaseStorage.getInstance().getReference("Images/" + System.currentTimeMillis() + ".jpg");

        if (uriImage != null) {

            refProfileImage.putFile(uriImage).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                @Override
                public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {

                    refProfileImage.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                        @Override
                        public void onSuccess(Uri uri) {

                            urlImage = uri.toString();

                            Log.d("TAGOO", urlImage);
                        }
                    }).addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {

                            Toast.makeText(NotesActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {

                    Toast.makeText(NotesActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
                }
            });

        } else {

            Toast.makeText(this, "Some error occurred while uploading to database", Toast.LENGTH_SHORT).show();
        }
    }

urlImage can be a String variable defined globally to fetch the uploaded image link and you can save this link however you want. E.g; if a user uploaded his profile picture then you can save this image link in his realtime database node to access it later using his UID.

Let me know if something is unclear :)

Solution 2:[2]

Check the differnce between Intent(MediaStore.ACTION_IMAGE_CAPTURE) and Intent("MediaStore.ACTION_IMAGE_CAPTURE")

Solution 3:[3]

To create and read external storage after Android 10, you need special privilege.

As per your requirment, i think you don't need to save image at external public storage. Internal storage is enough and safe to serve your FireBase requirment on updated devices. You can have a look on the link

Please check the code below. I have a line on dispatchTakePictureIntent and commented the createTemporaryFile function

    private void dispatchTakePictureIntent() {
        Log.d("LOG", "CREATING PHOTO INTENT");
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File photo;
        Log.d("LOG", "CREATING TEMPORARY FILE");
        //photo = this.createTemporaryFile("picture", ".jpg");
        photo = new File(getApplicationContext().getFilesDir(), "picture.jpg");
        Log.d("LOG", "DELETING TEMPORARY FILE");
        if(photo.exists()) photo.delete();
        mImageUri = Uri.fromFile(photo);
        Log.d("LOG", "PUTTING EXTRA IN MEDIASTORE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
        }
    }

Hope this helps.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Stanislav Bondar
Solution 3 Muhammad Ehsanul Hoque