Skip to content

Commit

Permalink
Bugfix issue 341 save to photo gallery - Fixes #341, fixes #577 (#669)
Browse files Browse the repository at this point in the history
* GH-341 - GH-577 Fixed the Save Photo To Album starting from camera - Android

- saveToPhotoAlbum feature fixed by taken into count content - uri. (writeUncompressedImage method) ( see: #611 (comment) )
- make saveToPhotoAlbum future proof by using the MediaStore to insert the taken image
- made a method to calculate the compressFormat based on the encodingType (JPEG or PNG)
- layout of the performCrop method is adjusted
- removed unused rotate variable inside processResultFromGallery method

* Add extra VO class to the plugin.xml

* added package declaration to new VO

* GH-341 - GH-577 #669 (comment) listen to review
  • Loading branch information
PieterVanPoyer committed Oct 17, 2020
1 parent 43d6591 commit ebe0517
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 88 deletions.
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/ExifHelper.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/FileProvider.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/GalleryPathVO.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/xml/camera_provider_paths.xml" target-dir="res/xml" />

<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
Expand Down
171 changes: 83 additions & 88 deletions src/android/CameraLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Licensed to the Apache Software Foundation (ASF) under one
import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
Expand All @@ -34,6 +35,7 @@ Licensed to the Apache Software Foundation (ASF) under one
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
Expand All @@ -51,7 +53,6 @@ Licensed to the Apache Software Foundation (ASF) under one

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -405,58 +406,57 @@ public void getImage(int srcType, int returnType, int encodingType) {
}
}


/**
* Brings up the UI to perform crop on passed image URI
*
* @param picUri
*/
private void performCrop(Uri picUri, int destType, Intent cameraIntent) {
try {
Intent cropIntent = new Intent("com.android.camera.action.CROP");
// indicate image type and Uri
cropIntent.setDataAndType(picUri, "image/*");
// set crop properties
cropIntent.putExtra("crop", "true");
/**
* Brings up the UI to perform crop on passed image URI
*
* @param picUri
*/
private void performCrop(Uri picUri, int destType, Intent cameraIntent) {
try {
Intent cropIntent = new Intent("com.android.camera.action.CROP");
// indicate image type and Uri
cropIntent.setDataAndType(picUri, "image/*");
// set crop properties
cropIntent.putExtra("crop", "true");


// indicate output X and Y
if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth);
}
if (targetHeight > 0) {
cropIntent.putExtra("outputY", targetHeight);
}
if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
}
// create new file handle to get full resolution crop
croppedFilePath = createCaptureFile(this.encodingType, System.currentTimeMillis() + "").getAbsolutePath();
croppedUri = Uri.parse(croppedFilePath);
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cropIntent.putExtra("output", croppedUri);
// indicate output X and Y
if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth);
}
if (targetHeight > 0) {
cropIntent.putExtra("outputY", targetHeight);
}
if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
}
// create new file handle to get full resolution crop
croppedFilePath = createCaptureFile(this.encodingType, System.currentTimeMillis() + "").getAbsolutePath();
croppedUri = Uri.parse(croppedFilePath);
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cropIntent.putExtra("output", croppedUri);


// start the activity - we handle returning in onActivityResult
// start the activity - we handle returning in onActivityResult

if (this.cordova != null) {
this.cordova.startActivityForResult((CordovaPlugin) this,
cropIntent, CROP_CAMERA + destType);
if (this.cordova != null) {
this.cordova.startActivityForResult((CordovaPlugin) this,
cropIntent, CROP_CAMERA + destType);
}
} catch (ActivityNotFoundException anfe) {
LOG.e(LOG_TAG, "Crop operation not supported on this device");
try {
processResultFromCamera(destType, cameraIntent);
}
catch (IOException e)
{
e.printStackTrace();
LOG.e(LOG_TAG, "Unable to write to file");
}
}
} catch (ActivityNotFoundException anfe) {
LOG.e(LOG_TAG, "Crop operation not supported on this device");
try {
processResultFromCamera(destType, cameraIntent);
}
catch (IOException e)
{
e.printStackTrace();
LOG.e(LOG_TAG, "Unable to write to file");
}
}
}

/**
* Applies all needed transformation to the image received from the camera.
Expand Down Expand Up @@ -494,16 +494,18 @@ private void processResultFromCamera(int destType, Intent intent) throws IOExcep
// in the gallery and the modified image is saved in the temporary
// directory
if (this.saveToPhotoAlbum) {
galleryUri = Uri.fromFile(new File(getPicturesPath()));
GalleryPathVO galleryPathVO = getPicturesPath();
galleryUri = Uri.fromFile(new File(galleryPathVO.getGalleryPath()));

if (this.allowEdit && this.croppedUri != null) {
writeUncompressedImage(croppedUri, galleryUri);
} else {
Uri imageUri = this.imageUri;
writeUncompressedImage(imageUri, galleryUri);
if (Build.VERSION.SDK_INT <= 28) { // Between LOLLIPOP_MR1 and P, can be changed later to the constant Build.VERSION_CODES.P
writeTakenPictureToGalleryLowerThanAndroidQ(galleryUri);
} else { // Android Q or higher
writeTakenPictureToGalleryStartingFromAndroidQ(galleryPathVO);
}
}

refreshGallery(galleryUri);
}

// If sending base64 image back
Expand Down Expand Up @@ -567,9 +569,7 @@ else if (destType == FILE_URI) {

// Add compressed version of captured image to returned media store Uri
OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
CompressFormat compressFormat = encodingType == JPEG ?
CompressFormat.JPEG :
CompressFormat.PNG;
CompressFormat compressFormat = getCompressFormatForEncodingType(encodingType);

bitmap.compress(compressFormat, this.mQuality, os);
os.close();
Expand Down Expand Up @@ -597,18 +597,41 @@ else if (destType == FILE_URI) {
bitmap = null;
}

private String getPicturesPath() {
private void writeTakenPictureToGalleryLowerThanAndroidQ(Uri galleryUri) throws IOException {
writeUncompressedImage(imageUri, galleryUri);
refreshGallery(galleryUri);
}

private void writeTakenPictureToGalleryStartingFromAndroidQ(GalleryPathVO galleryPathVO) throws IOException {
// Starting from Android Q, working with the ACTION_MEDIA_SCANNER_SCAN_FILE intent is deprecated
// https://developer.android.com/reference/android/content/Intent#ACTION_MEDIA_SCANNER_SCAN_FILE
// we must start working with the MediaStore from Android Q on.
ContentResolver resolver = this.cordova.getActivity().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, galleryPathVO.getGalleryFileName());
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimetypeForFormat(encodingType));
Uri galleryOutputUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

InputStream fileStream = org.apache.cordova.camera.FileHelper.getInputStreamFromUriString(imageUri.toString(), cordova);
writeUncompressedImage(fileStream, galleryOutputUri);
}

private CompressFormat getCompressFormatForEncodingType(int encodingType) {
return encodingType == JPEG ? CompressFormat.JPEG : CompressFormat.PNG;
}

private GalleryPathVO getPicturesPath() {
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION);
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
storageDir.mkdirs();
String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName;
return galleryPath;
return new GalleryPathVO(storageDir.getAbsolutePath(), imageFileName);
}

private void refreshGallery(Uri contentUri) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
// Starting from Android Q, working with the ACTION_MEDIA_SCANNER_SCAN_FILE intent is deprecated
mediaScanIntent.setData(contentUri);
this.cordova.getActivity().sendBroadcast(mediaScanIntent);
}
Expand Down Expand Up @@ -640,9 +663,7 @@ private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
String modifiedPath = getTempDirectoryPath() + "/" + fileName;

OutputStream os = new FileOutputStream(modifiedPath);
CompressFormat compressFormat = this.encodingType == JPEG ?
CompressFormat.JPEG :
CompressFormat.PNG;
CompressFormat compressFormat = getCompressFormatForEncodingType(this.encodingType);

bitmap.compress(compressFormat, this.mQuality, os);
os.close();
Expand Down Expand Up @@ -679,7 +700,6 @@ private void processResultFromGallery(int destType, Intent intent) {
return;
}
}
int rotate = 0;

String fileLocation = FileHelper.getRealPath(uri, this.cordova);
LOG.d(LOG_TAG, "File location is: " + fileLocation);
Expand Down Expand Up @@ -900,34 +920,11 @@ private void writeUncompressedImage(InputStream fis, Uri dest) throws FileNotFou
private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
IOException {

FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
InputStream fis = FileHelper.getInputStreamFromUriString(src.toString(), cordova);
writeUncompressedImage(fis, dest);

}

/**
* Create entry in media store for image
*
* @return uri
*/
private Uri getUriFromMediaStore() {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, JPEG_MIME_TYPE);
Uri uri;
try {
uri = this.cordova.getActivity().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (RuntimeException e) {
LOG.d(LOG_TAG, "Can't write to external media storage.");
try {
uri = this.cordova.getActivity().getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
} catch (RuntimeException ex) {
LOG.d(LOG_TAG, "Can't write to internal media storage.");
return null;
}
}
return uri;
}

/**
* Return a scaled and rotated bitmap based on the target width and height
*
Expand Down Expand Up @@ -1258,9 +1255,7 @@ private Uri whichContentStore() {
*/
public void processPicture(Bitmap bitmap, int encodingType) {
ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream();
CompressFormat compressFormat = encodingType == JPEG ?
CompressFormat.JPEG :
CompressFormat.PNG;
CompressFormat compressFormat = getCompressFormatForEncodingType(encodingType);

try {
if (bitmap.compress(compressFormat, mQuality, jpeg_data)) {
Expand Down
25 changes: 25 additions & 0 deletions src/android/GalleryPathVO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.apache.cordova.camera;

public class GalleryPathVO {
private final String galleryPath;
private String picturesDirectory;
private String galleryFileName;

public GalleryPathVO(String picturesDirectory, String galleryFileName) {
this.picturesDirectory = picturesDirectory;
this.galleryFileName = galleryFileName;
this.galleryPath = this.picturesDirectory + "/" + this.galleryFileName;
}

public String getGalleryPath() {
return galleryPath;
}

public String getPicturesDirectory() {
return picturesDirectory;
}

public String getGalleryFileName() {
return galleryFileName;
}
}

0 comments on commit ebe0517

Please sign in to comment.