diff --git a/plugin.xml b/plugin.xml index 99769ab..eb24adb 100644 --- a/plugin.xml +++ b/plugin.xml @@ -69,6 +69,17 @@ + + + + + @@ -76,9 +87,11 @@ - + - + + + diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java index 9db9911..2f4a1e0 100644 --- a/src/android/CameraLauncher.java +++ b/src/android/CameraLauncher.java @@ -36,6 +36,7 @@ import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.LOG; import org.apache.cordova.PermissionHelper; import org.apache.cordova.PluginResult; +import org.apache.mobilespec.BuildConfig; import org.json.JSONArray; import org.json.JSONException; @@ -56,6 +57,8 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.support.v4.content.FileProvider; import android.util.Base64; import android.util.Log; import android.content.pm.PackageManager; @@ -281,8 +284,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect // Specify file so that large image is captured and returned File photo = createCaptureFile(encodingType); - intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); - this.imageUri = Uri.fromFile(photo); + this.imageUri = FileProvider.getUriForFile(cordova.getActivity(), + BuildConfig.APPLICATION_ID + ".provider", + photo); + intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, this.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); if (this.cordova != null) { // Let's check to make sure the camera is actually installed. (Legacy Nexus 7 code) @@ -416,7 +423,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect cropIntent.putExtra("aspectY", 1); } // create new file handle to get full resolution crop - croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); + croppedUri = FileProvider.getUriForFile(cordova.getActivity(), + BuildConfig.APPLICATION_ID + ".provider", + createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); cropIntent.putExtra("output", croppedUri); // start the activity - we handle returning in onActivityResult @@ -449,9 +458,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect // Create an ExifHelper to save the exif data that is lost during compression ExifHelper exif = new ExifHelper(); + String sourcePath = (this.allowEdit && this.croppedUri != null) ? - FileHelper.stripFileProtocol(this.croppedUri.toString()) : - FileHelper.stripFileProtocol(this.imageUri.toString()); + getFileNameFromUri(this.croppedUri) : + getFileNameFromUri(this.imageUri); + if (this.encodingType == JPEG) { try { @@ -472,12 +483,15 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect // in the gallery and the modified image is saved in the temporary // directory if (this.saveToPhotoAlbum) { - galleryUri = Uri.fromFile(new File(getPicutresPath())); + galleryUri = Uri.fromFile(new File(getPicturesPath())); - if(this.allowEdit && this.croppedUri != null) { - writeUncompressedImage(this.croppedUri, galleryUri); + + if (this.allowEdit && this.croppedUri != null) { + Uri croppedUri = Uri.fromFile(new File(getFileNameFromUri(this.croppedUri))); + writeUncompressedImage(croppedUri, galleryUri); } else { - writeUncompressedImage(this.imageUri, galleryUri); + Uri imageUri = Uri.fromFile(new File(getFileNameFromUri(this.imageUri))); + writeUncompressedImage(imageUri, galleryUri); } refreshGallery(galleryUri); @@ -489,7 +503,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect if (bitmap == null) { // Try to get the bitmap from intent. - bitmap = (Bitmap)intent.getExtras().get("data"); + bitmap = (Bitmap) intent.getExtras().get("data"); } // Double-check the bitmap. @@ -523,10 +537,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect } else { Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); - if(this.allowEdit && this.croppedUri != null) { - writeUncompressedImage(this.croppedUri, uri); + if (this.allowEdit && this.croppedUri != null) { + Uri croppedUri = Uri.fromFile(new File(getFileNameFromUri(this.croppedUri))); + writeUncompressedImage(croppedUri, uri); } else { - writeUncompressedImage(this.imageUri, uri); + Uri imageUri = Uri.fromFile(new File(getFileNameFromUri(this.imageUri))); + writeUncompressedImage(imageUri, uri); } this.callbackContext.success(uri.toString()); @@ -575,32 +591,30 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect bitmap = null; } -private String getPicutresPath() -{ - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png"); - File storageDir = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_PICTURES); - String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName; - return galleryPath; -} + private String getPicturesPath() { + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png"); + File storageDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName; + return galleryPath; + } -private void refreshGallery(Uri contentUri) -{ - Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - mediaScanIntent.setData(contentUri); - this.cordova.getActivity().sendBroadcast(mediaScanIntent); -} + private void refreshGallery(Uri contentUri) { + Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + mediaScanIntent.setData(contentUri); + this.cordova.getActivity().sendBroadcast(mediaScanIntent); + } -private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { + private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { // Some content: URIs do not map to file paths (e.g. picasa). String realPath = FileHelper.getRealPath(uri, this.cordova); // Get filename from uri String fileName = realPath != null ? - realPath.substring(realPath.lastIndexOf('/') + 1) : - "modified." + (this.encodingType == JPEG ? "jpg" : "png"); + realPath.substring(realPath.lastIndexOf('/') + 1) : + "modified." + (this.encodingType == JPEG ? "jpg" : "png"); String modifiedPath = getTempDirectoryPath() + "/" + fileName; @@ -632,12 +646,11 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { } - -/** + /** * Applies all needed transformation to the image received from the gallery. * - * @param destType In which form should we return the image - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param destType In which form should we return the image + * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ private void processResultFromGallery(int destType, Intent intent) { Uri uri = intent.getData(); @@ -658,8 +671,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { // and there will be no attempt to resize any returned data if (this.mediaType != PICTURE) { this.callbackContext.success(fileLocation); - } - else { + } else { // 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 && @@ -709,8 +721,8 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { // If sending filename back else if (destType == FILE_URI || destType == NATIVE_URI) { // Did we modify the image? - if ( (this.targetHeight > 0 && this.targetWidth > 0) || - (this.correctOrientation && this.orientationCorrected) ) { + if ((this.targetHeight > 0 && this.targetWidth > 0) || + (this.correctOrientation && this.orientationCorrected)) { try { String modifiedPath = this.ouputModifiedBitmap(bitmap, uri); // The modified image is cached by the app in order to get around this and not have to delete you @@ -721,8 +733,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { e.printStackTrace(); this.failPicture("Error retrieving image."); } - } - else { + } else { this.callbackContext.success(fileLocation); } } @@ -738,10 +749,10 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { /** * Called when the camera view exits. * - * @param requestCode The request code originally supplied to startActivityForResult(), - * allowing you to identify who this result came from. - * @param resultCode The integer result code returned by the child activity through its setResult(). - * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ public void onActivityResult(int requestCode, int resultCode, Intent intent) { @@ -778,12 +789,12 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { // If image available if (resultCode == Activity.RESULT_OK) { try { - if(this.allowEdit) - { - Uri tmpFile = Uri.fromFile(createCaptureFile(this.encodingType)); + if (this.allowEdit) { + Uri tmpFile = FileProvider.getUriForFile(cordova.getActivity(), + BuildConfig.APPLICATION_ID + ".provider", + createCaptureFile(this.encodingType)); performCrop(tmpFile, destType, intent); - } - else { + } else { this.processResultFromCamera(destType, intent); } } catch (IOException e) { @@ -812,11 +823,9 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { processResultFromGallery(finalDestType, i); } }); - } - else if (resultCode == Activity.RESULT_CANCELED) { + } else if (resultCode == Activity.RESULT_CANCELED) { this.failPicture("Selection cancelled."); - } - else { + } else { this.failPicture("Selection did not complete!"); } } @@ -824,7 +833,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { private int getImageOrientation(Uri uri) { int rotate = 0; - String[] cols = { MediaStore.Images.Media.ORIENTATION }; + String[] cols = {MediaStore.Images.Media.ORIENTATION}; try { Cursor cursor = cordova.getActivity().getContentResolver().query(uri, cols, null, null, null); @@ -855,13 +864,10 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2); } - try - { + try { bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); exif.resetOrientation(); - } - catch (OutOfMemoryError oom) - { + } catch (OutOfMemoryError oom) { // You can run out of memory if the image is very large: // http://simonmacdonald.blogspot.ca/2012/07/change-to-camera-code-in-phonegap-190.html // If this happens, simply do not rotate the image and return it unmodified. @@ -897,14 +903,14 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { try { os.close(); } catch (IOException e) { - LOG.d(LOG_TAG,"Exception while closing output stream."); + LOG.d(LOG_TAG, "Exception while closing output stream."); } } if (fis != null) { try { fis.close(); } catch (IOException e) { - LOG.d(LOG_TAG,"Exception while closing file input stream."); + LOG.d(LOG_TAG, "Exception while closing file input stream."); } } } @@ -953,7 +959,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { try { fileStream.close(); } catch (IOException e) { - LOG.d(LOG_TAG,"Exception while closing file input stream."); + LOG.d(LOG_TAG, "Exception while closing file input stream."); } } } @@ -972,14 +978,13 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { try { fileStream.close(); } catch (IOException e) { - LOG.d(LOG_TAG,"Exception while closing file input stream."); + LOG.d(LOG_TAG, "Exception while closing file input stream."); } } } //CB-2292: WTF? Why is the width null? - if(options.outWidth == 0 || options.outHeight == 0) - { + if (options.outWidth == 0 || options.outHeight == 0) { return null; } @@ -998,7 +1003,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { try { fileStream.close(); } catch (IOException e) { - LOG.d(LOG_TAG,"Exception while closing file input stream."); + LOG.d(LOG_TAG, "Exception while closing file input stream."); } } } @@ -1067,15 +1072,15 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { * @return */ public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) { - final float srcAspect = (float)srcWidth / (float)srcHeight; - final float dstAspect = (float)dstWidth / (float)dstHeight; + final float srcAspect = (float) srcWidth / (float) srcHeight; + final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect > dstAspect) { return srcWidth / dstWidth; } else { return srcHeight / dstHeight; } - } + } /** * Creates a cursor that can be used to determine how many images we have. @@ -1085,7 +1090,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { private Cursor queryImgDB(Uri contentStore) { return this.cordova.getActivity().getContentResolver().query( contentStore, - new String[] { MediaStore.Images.Media._ID }, + new String[]{MediaStore.Images.Media._ID}, null, null, null); @@ -1093,6 +1098,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { /** * Cleans up after picture taking. Checking for duplicates and that kind of stuff. + * * @param newImage */ private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) { @@ -1144,6 +1150,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { /** * Determine if we are storing the images in internal or external storage + * * @return Uri */ private Uri whichContentStore() { @@ -1192,7 +1199,7 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { private void scanForGallery(Uri newImage) { this.scanMe = newImage; - if(this.conn != null) { + if (this.conn != null) { this.conn.disconnect(); } this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this); @@ -1200,9 +1207,9 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { } public void onMediaScannerConnected() { - try{ + try { this.conn.scanFile(this.scanMe.toString(), "image/*"); - } catch (java.lang.IllegalStateException e){ + } catch (java.lang.IllegalStateException e) { LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture"); } @@ -1214,18 +1221,14 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { public void onRequestPermissionResult(int requestCode, String[] permissions, - int[] grantResults) throws JSONException - { - for(int r:grantResults) - { - if(r == PackageManager.PERMISSION_DENIED) - { + int[] grantResults) throws JSONException { + for (int r : grantResults) { + if (r == PackageManager.PERMISSION_DENIED) { this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR)); return; } } - switch(requestCode) - { + switch (requestCode) { case TAKE_PIC_SEC: takePicture(this.destType, this.encodingType); break; @@ -1254,11 +1257,11 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { state.putBoolean("correctOrientation", this.correctOrientation); state.putBoolean("saveToPhotoAlbum", this.saveToPhotoAlbum); - if(this.croppedUri != null) { + if (this.croppedUri != null) { state.putString("croppedUri", this.croppedUri.toString()); } - if(this.imageUri != null) { + if (this.imageUri != null) { state.putString("imageUri", this.imageUri.toString()); } @@ -1278,14 +1281,36 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { this.correctOrientation = state.getBoolean("correctOrientation"); this.saveToPhotoAlbum = state.getBoolean("saveToPhotoAlbum"); - if(state.containsKey("croppedUri")) { + if (state.containsKey("croppedUri")) { this.croppedUri = Uri.parse(state.getString("croppedUri")); } - if(state.containsKey("imageUri")) { + if (state.containsKey("imageUri")) { this.imageUri = Uri.parse(state.getString("imageUri")); } this.callbackContext = callbackContext; } + +/* + * This is dirty, but it does the job. + * + * Since the FilesProvider doesn't really provide you a way of getting a URL from the file, + * and since we actually need the Camera to create the file for us most of the time, we don't + * actually write the file, just generate the location based on a timestamp, we need to get it + * back from the Intent. + * + * However, the FilesProvider preserves the path, so we can at least write to it from here, since + * we own the context in this case. + */ + + + private String getFileNameFromUri(Uri uri) { + String fullUri = uri.toString(); + String partial_path = fullUri.split("external_files")[1]; + File external_storage = Environment.getExternalStorageDirectory(); + String path = external_storage.getAbsolutePath() + partial_path; + return path; + + } }