'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 |
