diff --git a/plugin.xml b/plugin.xml index 5b7ab1d94..7cbd19e85 100644 --- a/plugin.xml +++ b/plugin.xml @@ -73,6 +73,7 @@ + diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java index cc8a0e3c8..32a27d4e7 100644 --- a/src/android/CameraLauncher.java +++ b/src/android/CameraLauncher.java @@ -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; @@ -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; @@ -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; @@ -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. @@ -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 @@ -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(); @@ -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); } @@ -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(); @@ -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); @@ -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 * @@ -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)) { diff --git a/src/android/GalleryPathVO.java b/src/android/GalleryPathVO.java new file mode 100644 index 000000000..4f2e7af33 --- /dev/null +++ b/src/android/GalleryPathVO.java @@ -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; + } +}