Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix issue 341 save to photo gallery - Fixes #341, fixes #577 #669

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}