From 42babc54759e39f5030bca79ccc3e612f4dae84e Mon Sep 17 00:00:00 2001 From: Norman Breau Date: Fri, 25 Oct 2024 15:37:03 -0300 Subject: [PATCH] refactor(android): clean up image file path usages --- src/android/CameraLauncher.java | 583 +++++++++++++++++--------------- src/android/ExifHelper.java | 11 + 2 files changed, 323 insertions(+), 271 deletions(-) diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java index 07de43898..60aff3080 100644 --- a/src/android/CameraLauncher.java +++ b/src/android/CameraLauncher.java @@ -42,6 +42,8 @@ Licensed to the Apache Software Foundation (ASF) under one import android.provider.MediaStore; import androidx.core.content.FileProvider; import android.util.Base64; +import android.system.Os; +import android.system.OsConstants; import org.apache.cordova.BuildHelper; import org.apache.cordova.CallbackContext; @@ -52,8 +54,10 @@ Licensed to the Apache Software Foundation (ASF) under one import org.json.JSONArray; import org.json.JSONException; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -95,7 +99,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private static final String GET_All = "Get All"; private static final String CROPPED_URI_KEY = "croppedUri"; private static final String IMAGE_URI_KEY = "imageUri"; - private static final String IMAGE_FILE_PATH_KEY = "imageFilePath"; private static final String TAKE_PICTURE_ACTION = "takePicture"; @@ -114,7 +117,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private int targetWidth; // desired width of the image private int targetHeight; // desired height of the image private Uri imageUri; // Uri of captured image - private String imageFilePath; // File where the image is stored private int encodingType; // Type of encoding to use private int mediaType; // What type of media to retrieve private int destType; // Source type (needs to be saved for the permission handling) @@ -313,10 +315,11 @@ public void takePicture(int returnType, int encodingType) // Specify file so that large image is captured and returned File photo = createCaptureFile(encodingType); - this.imageFilePath = photo.getAbsolutePath(); - this.imageUri = FileProvider.getUriForFile(cordova.getActivity(), - applicationId + ".cordova.plugin.camera.provider", - photo); + this.imageUri = FileProvider.getUriForFile( + cordova.getActivity(), + applicationId + ".cordova.plugin.camera.provider", + photo + ); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); //We can write to this URI, this will hopefully allow us to write files to get to the next step intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -489,93 +492,65 @@ private void processResultFromCamera(int destType, Intent intent) throws IOExcep // Create an ExifHelper to save the exif data that is lost during compression ExifHelper exif = new ExifHelper(); - String sourcePath = (this.allowEdit && this.croppedUri != null) ? - this.croppedFilePath : - this.imageFilePath; - - if (this.encodingType == JPEG) { - try { - //We don't support PNG, so let's not pretend we do - exif.createInFile(sourcePath); - exif.readExifData(); - rotate = exif.getOrientation(); - - } catch (IOException e) { - e.printStackTrace(); - } + InputStream input = null; + String mimeType; + if (this.allowEdit && this.croppedUri != null) { + input = new FileInputStream(this.croppedFilePath); + mimeType = FileHelper.getMimeTypeForExtension(this.croppedFilePath); + } + else { + input = cordova.getActivity().getContentResolver().openInputStream(imageUri); + mimeType = FileHelper.getMimeType(imageUri.toString(), cordova); } - Bitmap bitmap = null; - Uri galleryUri = null; - - // CB-5479 When this option is given the unchanged image should be saved - // in the gallery and the modified image is saved in the temporary - // directory - if (this.saveToPhotoAlbum) { - GalleryPathVO galleryPathVO = getPicturesPath(); - galleryUri = Uri.fromFile(new File(galleryPathVO.getGalleryPath())); - - if (this.allowEdit && this.croppedUri != null) { - writeUncompressedImage(croppedUri, galleryUri); - } else { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { - writeTakenPictureToGalleryLowerThanAndroidQ(galleryUri); - } else { // Android Q or higher - writeTakenPictureToGalleryStartingFromAndroidQ(galleryPathVO); - } - } + if (input == null) { + throw new IOException("Unable to open result source."); } - // If sending base64 image back - if (destType == DATA_URL) { - bitmap = getScaledAndRotatedBitmap(sourcePath); + byte[] sourceData = readData(input); - if (bitmap == null) { - // Try to get the bitmap from intent. - bitmap = (Bitmap) intent.getExtras().get("data"); - } + try { + if (this.encodingType == JPEG) { + try { + //We don't support PNG, so let's not pretend we do + exif.createInFile(new ByteArrayInputStream(sourceData)); + exif.readExifData(); + rotate = exif.getOrientation(); - // Double-check the bitmap. - if (bitmap == null) { - LOG.d(LOG_TAG, "I either have a null image path or bitmap"); - this.failPicture("Unable to create bitmap!"); - return; + } catch (IOException e) { + e.printStackTrace(); + } } + Bitmap bitmap = null; + Uri galleryUri = null; - this.processPicture(bitmap, this.encodingType); - - if (!this.saveToPhotoAlbum) { - checkForDuplicateImage(DATA_URL); - } - } - - // If sending filename back - else if (destType == FILE_URI) { - // If all this is true we shouldn't compress the image. - if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 && - !this.correctOrientation) { + // CB-5479 When this option is given the unchanged image should be saved + // in the gallery and the modified image is saved in the temporary + // directory + if (this.saveToPhotoAlbum) { + GalleryPathVO galleryPathVO = getPicturesPath(); + galleryUri = Uri.fromFile(new File(galleryPathVO.getGalleryPath())); - // If we saved the uncompressed photo to the album, we can just - // return the URI we already created - if (this.saveToPhotoAlbum) { - this.callbackContext.success(galleryUri.toString()); + if (this.allowEdit && this.croppedUri != null) { + writeUncompressedImage(croppedUri, galleryUri); } else { - Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); - - if (this.allowEdit && this.croppedUri != null) { - Uri croppedUri = Uri.parse(croppedFilePath); - writeUncompressedImage(croppedUri, uri); - } else { - Uri imageUri = this.imageUri; - writeUncompressedImage(imageUri, uri); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + writeTakenPictureToGalleryLowerThanAndroidQ(galleryUri); + } else { // Android Q or higher + writeTakenPictureToGalleryStartingFromAndroidQ(galleryPathVO); } + } + } - this.callbackContext.success(uri.toString()); + // If sending base64 image back + if (destType == DATA_URL) { + bitmap = getScaledAndRotatedBitmap(sourceData, mimeType); + + if (bitmap == null) { + // Try to get the bitmap from intent. + bitmap = (Bitmap) intent.getExtras().get("data"); } - } else { - Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); - bitmap = getScaledAndRotatedBitmap(sourcePath); // Double-check the bitmap. if (bitmap == null) { @@ -585,34 +560,80 @@ 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 = getCompressFormatForEncodingType(encodingType); - - bitmap.compress(compressFormat, this.mQuality, os); - os.close(); + this.processPicture(bitmap, this.encodingType); - // Restore exif data to file - if (this.encodingType == JPEG) { - String exifPath; - exifPath = uri.getPath(); - //We just finished rotating it by an arbitrary orientation, just make sure it's normal - if(rotate != ExifInterface.ORIENTATION_NORMAL) - exif.resetOrientation(); - exif.createOutFile(exifPath); - exif.writeExifData(); + if (!this.saveToPhotoAlbum) { + checkForDuplicateImage(DATA_URL); } + } - // Send Uri back to JavaScript for viewing image - this.callbackContext.success(uri.toString()); + // If sending filename back + else if (destType == FILE_URI) { + // If all this is true we shouldn't compress the image. + if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 && + !this.correctOrientation) { + // If we saved the uncompressed photo to the album, we can just + // return the URI we already created + if (this.saveToPhotoAlbum) { + this.callbackContext.success(galleryUri.toString()); + } else { + Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); + + if (this.allowEdit && this.croppedUri != null) { + Uri croppedUri = Uri.parse(croppedFilePath); + writeUncompressedImage(croppedUri, uri); + } else { + Uri imageUri = this.imageUri; + writeUncompressedImage(imageUri, uri); + } + + this.callbackContext.success(uri.toString()); + } + } else { + Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); + bitmap = getScaledAndRotatedBitmap(sourceData, mimeType); + + // Double-check the bitmap. + if (bitmap == null) { + LOG.d(LOG_TAG, "I either have a null image path or bitmap"); + this.failPicture("Unable to create bitmap!"); + return; + } + + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri); + CompressFormat compressFormat = getCompressFormatForEncodingType(encodingType); + + bitmap.compress(compressFormat, this.mQuality, os); + os.close(); + + // Restore exif data to file + if (this.encodingType == JPEG) { + String exifPath; + exifPath = uri.getPath(); + //We just finished rotating it by an arbitrary orientation, just make sure it's normal + if (rotate != ExifInterface.ORIENTATION_NORMAL) + exif.resetOrientation(); + exif.createOutFile(exifPath); + exif.writeExifData(); + } + + // Send Uri back to JavaScript for viewing image + this.callbackContext.success(uri.toString()); + } + } else { + throw new IllegalStateException(); } - } else { - throw new IllegalStateException(); - } - this.cleanup(FILE_URI, this.imageUri, galleryUri, bitmap); - bitmap = null; + this.cleanup(FILE_URI, this.imageUri, galleryUri, bitmap); + bitmap = null; + input.close(); + } + catch (Exception e) { + input.close(); + throw e; + } } private void writeTakenPictureToGalleryLowerThanAndroidQ(Uri galleryUri) throws IOException { @@ -641,8 +662,7 @@ private CompressFormat getCompressFormatForEncodingType(int encodingType) { private GalleryPathVO getPicturesPath() { String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date()); String imageFileName = "IMG_" + timeStamp + getExtensionForEncodingType(); - File storageDir = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_PICTURES); + File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); storageDir.mkdirs(); return new GalleryPathVO(storageDir.getAbsolutePath(), imageFileName); } @@ -730,26 +750,77 @@ private void processResultFromGallery(int destType, Intent intent) { String uriString = uri.toString(); String mimeTypeOfGalleryFile = FileHelper.getMimeType(uriString, this.cordova); + InputStream input; + try { + input = cordova.getActivity().getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + this.failPicture("Unable to open gallery input stream"); + return; + } - // If you ask for video or the selected file cannot be processed - // there will be no attempt to resize any returned data. - if (this.mediaType == VIDEO || !isImageMimeTypeProcessable(mimeTypeOfGalleryFile)) { - this.callbackContext.success(uriString); - } else { + if (input == null) { + this.failPicture("Unable to open gallery input stream"); + return; + } - // This is a special case to just return the path as no scaling, - // rotating, nor compressing needs to be done - if (this.targetHeight == -1 && this.targetWidth == -1 && - destType == FILE_URI && !this.correctOrientation && - getMimetypeForEncodingType().equalsIgnoreCase(mimeTypeOfGalleryFile)) - { + try { + byte[] data = readData(input); + + // If you ask for video or the selected file cannot be processed + // there will be no attempt to resize any returned data. + if (this.mediaType == VIDEO || !isImageMimeTypeProcessable(mimeTypeOfGalleryFile)) { this.callbackContext.success(uriString); } else { - Bitmap bitmap = null; - try { - bitmap = getScaledAndRotatedBitmap(uriString); - } catch (IOException e) { - e.printStackTrace(); + + // This is a special case to just return the path as no scaling, + // rotating, nor compressing needs to be done + if (this.targetHeight == -1 && this.targetWidth == -1 && + destType == FILE_URI && !this.correctOrientation && + getMimetypeForEncodingType().equalsIgnoreCase(mimeTypeOfGalleryFile)) { + this.callbackContext.success(uriString); + } else { + Bitmap bitmap = null; + try { + bitmap = getScaledAndRotatedBitmap(data, mimeTypeOfGalleryFile); + } catch (IOException e) { + e.printStackTrace(); + } + if (bitmap == null) { + LOG.d(LOG_TAG, "I either have a null image path or bitmap"); + this.failPicture("Unable to create bitmap!"); + return; + } + + // If sending base64 image back + if (destType == DATA_URL) { + this.processPicture(bitmap, this.encodingType); + } + + // If sending filename back + else if (destType == FILE_URI) { + // Did we modify the image? + if ((this.targetHeight > 0 && this.targetWidth > 0) || + (this.correctOrientation && this.orientationCorrected) || + !mimeTypeOfGalleryFile.equalsIgnoreCase(getMimetypeForEncodingType())) { + try { + String modifiedPath = this.outputModifiedBitmap(bitmap, uri, mimeTypeOfGalleryFile); + // The modified image is cached by the app in order to get around this and not have to delete you + // application cache I'm adding the current system time to the end of the file url. + this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis()); + + } catch (Exception e) { + e.printStackTrace(); + this.failPicture("Error retrieving image: " + e.getLocalizedMessage()); + } + } else { + this.callbackContext.success(uriString); + } + } + if (bitmap != null) { + bitmap.recycle(); + bitmap = null; + } + System.gc(); } if (bitmap == null) { LOG.d(LOG_TAG, "I either have a null image path or bitmap"); @@ -789,6 +860,16 @@ else if (destType == FILE_URI) { } System.gc(); } + + input.close(); + } + catch (Exception e) { + try { + input.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + this.failPicture(e.getLocalizedMessage()); } } @@ -801,7 +882,7 @@ else if (destType == FILE_URI) { */ private boolean isImageMimeTypeProcessable(String mimeType) { return JPEG_MIME_TYPE.equalsIgnoreCase(mimeType) || PNG_MIME_TYPE.equalsIgnoreCase(mimeType) - || HEIC_MIME_TYPE.equalsIgnoreCase(mimeType); + || HEIC_MIME_TYPE.equalsIgnoreCase(mimeType); } /** @@ -849,8 +930,8 @@ else if (srcType == CAMERA) { try { if (this.allowEdit) { Uri tmpFile = FileProvider.getUriForFile(cordova.getActivity(), - applicationId + ".cordova.plugin.camera.provider", - createCaptureFile(this.encodingType)); + applicationId + ".cordova.plugin.camera.provider", + createCaptureFile(this.encodingType)); performCrop(tmpFile, destType, intent); } else { this.processResultFromCamera(destType, intent); @@ -910,7 +991,7 @@ private int exifToDegrees(int exifOrientation) { * @throws IOException */ private void writeUncompressedImage(InputStream fis, Uri dest) throws FileNotFoundException, - IOException { + IOException { OutputStream os = null; try { os = this.cordova.getActivity().getContentResolver().openOutputStream(dest); @@ -946,7 +1027,7 @@ private void writeUncompressedImage(InputStream fis, Uri dest) throws FileNotFou * @throws IOException */ private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException, - IOException { + IOException { InputStream fis = FileHelper.getInputStreamFromUriString(src.toString(), cordova); writeUncompressedImage(fis, dest); @@ -956,171 +1037,106 @@ private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundExcept /** * Return a scaled and rotated bitmap based on the target width and height * - * @param imageUrl + * @param data * @return * @throws IOException */ - private Bitmap getScaledAndRotatedBitmap(String imageUrl) throws IOException { + private Bitmap getScaledAndRotatedBitmap(byte[] data, String mimeType) throws IOException { // If no new width or height were specified, and orientation is not needed return the original bitmap if (this.targetWidth <= 0 && this.targetHeight <= 0 && !(this.correctOrientation)) { - InputStream fileStream = null; Bitmap image = null; try { - fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova); - image = BitmapFactory.decodeStream(fileStream); - } catch (OutOfMemoryError e) { + image = BitmapFactory.decodeStream(new ByteArrayInputStream(data)); + } catch (OutOfMemoryError e) { callbackContext.error(e.getLocalizedMessage()); - } catch (Exception e){ + } catch (Exception e) { callbackContext.error(e.getLocalizedMessage()); } - finally { - if (fileStream != null) { - try { - fileStream.close(); - } catch (IOException e) { - LOG.d(LOG_TAG, "Exception while closing file input stream."); - } - } - } return image; } - - /* Copy the inputstream to a temporary file on the device. - We then use this temporary file to determine the width/height/orientation. - This is the only way to determine the orientation of the photo coming from 3rd party providers (Google Drive, Dropbox,etc) - This also ensures we create a scaled bitmap with the correct orientation - - We delete the temporary file once we are done - */ - File localFile = null; - Uri galleryUri = null; int rotate = 0; try { - InputStream fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova); - if (fileStream != null) { - // Generate a temporary file - String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date()); - String fileName = "IMG_" + timeStamp + (getExtensionForEncodingType()); - localFile = new File(getTempDirectoryPath(), fileName); - galleryUri = Uri.fromFile(localFile); - writeUncompressedImage(fileStream, galleryUri); - try { - String mimeType = FileHelper.getMimeType(imageUrl.toString(), cordova); - if (JPEG_MIME_TYPE.equalsIgnoreCase(mimeType)) { - // ExifInterface doesn't like the file:// prefix - String filePath = galleryUri.toString().replace("file://", ""); - // read exifData of source - exifData = new ExifHelper(); - exifData.createInFile(filePath); - exifData.readExifData(); - // Use ExifInterface to pull rotation information - if (this.correctOrientation) { - ExifInterface exif = new ExifInterface(filePath); - rotate = exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)); - } + try { + if (JPEG_MIME_TYPE.equalsIgnoreCase(mimeType)) { + exifData = new ExifHelper(); + exifData.createInFile(new ByteArrayInputStream(data)); + exifData.readExifData(); + // Use ExifInterface to pull rotation information + if (this.correctOrientation) { + ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data)); + rotate = exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)); } - } catch (Exception oe) { - LOG.w(LOG_TAG,"Unable to read Exif data: "+ oe.toString()); - rotate = 0; } + } catch (Exception oe) { + LOG.w(LOG_TAG,"Unable to read Exif data: " + oe.toString()); + rotate = 0; } } catch (Exception e) { - LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString()); + LOG.e(LOG_TAG,"Exception while getting input stream: " + e.toString()); return null; } - try { - // figure out the original width and height of the image - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - InputStream fileStream = null; - try { - fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova); - BitmapFactory.decodeStream(fileStream, null, options); - } finally { - if (fileStream != null) { - try { - fileStream.close(); - } catch (IOException e) { - LOG.d(LOG_TAG, "Exception while closing file input stream."); - } - } - } + // figure out the original width and height of the image + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(new ByteArrayInputStream(data), null, options); + //CB-2292: WTF? Why is the width null? + if (options.outWidth == 0 || options.outHeight == 0) { + return null; + } - //CB-2292: WTF? Why is the width null? - if (options.outWidth == 0 || options.outHeight == 0) { - return null; - } + // User didn't specify output dimensions, but they need orientation + if (this.targetWidth <= 0 && this.targetHeight <= 0) { + this.targetWidth = options.outWidth; + this.targetHeight = options.outHeight; + } - // User didn't specify output dimensions, but they need orientation - if (this.targetWidth <= 0 && this.targetHeight <= 0) { - this.targetWidth = options.outWidth; - this.targetHeight = options.outHeight; - } + // Setup target width/height based on orientation + int rotatedWidth, rotatedHeight; + boolean rotated = false; + if (rotate == 90 || rotate == 270) { + rotatedWidth = options.outHeight; + rotatedHeight = options.outWidth; + rotated = true; + } else { + rotatedWidth = options.outWidth; + rotatedHeight = options.outHeight; + } - // Setup target width/height based on orientation - int rotatedWidth, rotatedHeight; - boolean rotated= false; - if (rotate == 90 || rotate == 270) { - rotatedWidth = options.outHeight; - rotatedHeight = options.outWidth; - rotated = true; - } else { - rotatedWidth = options.outWidth; - rotatedHeight = options.outHeight; - } + // determine the correct aspect ratio + int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight); - // determine the correct aspect ratio - int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight); + // Load in the smallest bitmap possible that is closest to the size we want + options.inJustDecodeBounds = false; + options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]); + Bitmap unscaledBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(data), null, options);; - // Load in the smallest bitmap possible that is closest to the size we want - options.inJustDecodeBounds = false; - options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]); - Bitmap unscaledBitmap = null; - try { - fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova); - unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options); - } finally { - if (fileStream != null) { - try { - fileStream.close(); - } catch (IOException e) { - LOG.d(LOG_TAG, "Exception while closing file input stream."); - } - } - } - if (unscaledBitmap == null) { - return null; - } + if (unscaledBitmap == null) { + return null; + } - int scaledWidth = (!rotated) ? widthHeight[0] : widthHeight[1]; - int scaledHeight = (!rotated) ? widthHeight[1] : widthHeight[0]; + int scaledWidth = (!rotated) ? widthHeight[0] : widthHeight[1]; + int scaledHeight = (!rotated) ? widthHeight[1] : widthHeight[0]; - Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, scaledWidth, scaledHeight, true); - if (scaledBitmap != unscaledBitmap) { - unscaledBitmap.recycle(); - unscaledBitmap = null; - } - if (this.correctOrientation && (rotate != 0)) { - Matrix matrix = new Matrix(); - matrix.setRotate(rotate); - try { - scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); - this.orientationCorrected = true; - } catch (OutOfMemoryError oom) { - this.orientationCorrected = false; - } - } - return scaledBitmap; - } finally { - // delete the temporary copy - if (localFile != null) { - localFile.delete(); - } + Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, scaledWidth, scaledHeight, true); + if (scaledBitmap != unscaledBitmap) { + unscaledBitmap.recycle(); + unscaledBitmap = null; } + if (this.correctOrientation && (rotate != 0)) { + Matrix matrix = new Matrix(); + matrix.setRotate(rotate); + try { + scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); + this.orientationCorrected = true; + } catch (OutOfMemoryError oom) { + this.orientationCorrected = false; + } + } + return scaledBitmap; } /** @@ -1198,11 +1214,11 @@ public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, */ private Cursor queryImgDB(Uri contentStore) { return this.cordova.getActivity().getContentResolver().query( - contentStore, - new String[]{MediaStore.Images.Media._ID}, - null, - null, - null); + contentStore, + new String[]{MediaStore.Images.Media._ID}, + null, + null, + null); } /** @@ -1372,10 +1388,6 @@ public Bundle onSaveInstanceState() { state.putString(IMAGE_URI_KEY, this.imageUri.toString()); } - if (this.imageFilePath != null) { - state.putString(IMAGE_FILE_PATH_KEY, this.imageFilePath); - } - return state; } @@ -1397,14 +1409,9 @@ public void onRestoreStateForActivityResult(Bundle state, CallbackContext callba } if (state.containsKey(IMAGE_URI_KEY)) { - //I have no idea what type of URI is being passed in this.imageUri = Uri.parse(state.getString(IMAGE_URI_KEY)); } - if (state.containsKey(IMAGE_FILE_PATH_KEY)) { - this.imageFilePath = state.getString(IMAGE_FILE_PATH_KEY); - } - this.callbackContext = callbackContext; } @@ -1416,4 +1423,38 @@ private boolean hasPermissions(String[] permissions) { } return true; } + + /** + * Gets the ideal buffer size for processing streams of data. + * + * @return The page size of the device. + */ + private int getPageSize() { + // Get the page size of the device. Most devices will be 4096 (4kb) + // Newer devices may be 16kb + long ps = Os.sysconf(OsConstants._SC_PAGE_SIZE); + + // sysconf returns a long because it's a general purpose API + // the expected value of a page size should not exceed an int, + // but we guard it here to avoid integer overflow just in case + if (ps > Integer.MAX_VALUE) { + ps = Integer.MAX_VALUE; + } + + return (int) ps; + } + + private byte[] readData(InputStream input) throws IOException { + if (input == null) { + return null; + } + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int bytesRead; + byte[] dataChunk = new byte[getPageSize()]; + while ((bytesRead = input.read(dataChunk)) != -1) { + buffer.write(dataChunk, 0, bytesRead); + } + return buffer.toByteArray(); + } } diff --git a/src/android/ExifHelper.java b/src/android/ExifHelper.java index 5160a2f6a..a6b9b79eb 100644 --- a/src/android/ExifHelper.java +++ b/src/android/ExifHelper.java @@ -19,6 +19,7 @@ Licensed to the Apache Software Foundation (ASF) under one package org.apache.cordova.camera; import java.io.IOException; +import java.io.InputStream; import android.media.ExifInterface; @@ -56,6 +57,16 @@ public void createInFile(String filePath) throws IOException { this.inFile = new ExifInterface(filePath); } + /** + * The file before it is compressed + * + * @param input + * @throws IOException + */ + public void createInFile(InputStream input) throws IOException { + this.inFile = new ExifInterface(input); + } + /** * The file after it has been compressed *