diff --git a/plugin.xml b/plugin.xml
index 668954c..fe07bb6 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -69,16 +69,31 @@
+
+
+
+
+
+
+
-
+
-
+
+
+
diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java
index 69cb5db..e3ee20b 100644
--- a/src/android/CameraLauncher.java
+++ b/src/android/CameraLauncher.java
@@ -29,8 +29,10 @@ import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
+import org.apache.cordova.BuildHelper;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.LOG;
import org.apache.cordova.PermissionHelper;
import org.apache.cordova.PluginResult;
@@ -58,6 +60,8 @@ import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import android.support.v4.content.FileProvider;
import android.util.Base64;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -99,7 +103,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
private int targetWidth; // desired width of the image
private int targetHeight; // desired height of the image
- private Uri imageUri; // Uri of captured image
+ private CordovaUri imageUri; // Uri of captured image
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)
@@ -118,6 +122,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private Uri scanMe; // Uri of image to be added to content store
private Uri croppedUri;
private ExifHelper exifData; // Exif data from source
+ private String applicationId;
/**
@@ -130,6 +135,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
+ //Adding an API to CoreAndroid to get the BuildConfigValue
+ //This allows us to not make this a breaking change to embedding
+ this.applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
+ this.applicationId = preferences.getString("applicationId", this.applicationId);
+
if (action.equals("takePicture")) {
this.srcType = CAMERA;
@@ -232,7 +242,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* img.src=result;
*
* @param returnType Set the type of image to return.
- * @param encodingType JPEG or PNG
+ * @param encodingType Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
*/
public void callTakePicture(int returnType, int encodingType) {
boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
@@ -282,8 +292,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 = new CordovaUri(FileProvider.getUriForFile(cordova.getActivity(),
+ applicationId + ".provider",
+ photo));
+ intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri.getCorrectUri());
+ //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)
@@ -399,33 +413,37 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/
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");
+ 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) {
+
+ // indicate output X and Y
+ if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth);
- }
- if (targetHeight > 0) {
+ }
+ if (targetHeight > 0) {
cropIntent.putExtra("outputY", targetHeight);
- }
- if (targetHeight > 0 && targetWidth > 0 && targetWidth == 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
- croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
- cropIntent.putExtra("output", croppedUri);
+ }
+ // create new file handle to get full resolution crop
+ croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
+ 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
- if (this.cordova != null) {
- this.cordova.startActivityForResult((CordovaPlugin) this,
- cropIntent, CROP_CAMERA + destType);
- }
+ // start the activity - we handle returning in onActivityResult
+
+ 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 {
@@ -450,9 +468,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());
+ FileHelper.stripFileProtocol(this.croppedUri.toString()) :
+ this.imageUri.getFilePath();
+
if (this.encodingType == JPEG) {
try {
@@ -475,10 +495,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
if (this.saveToPhotoAlbum) {
galleryUri = Uri.fromFile(new File(getPicturesPath()));
- if(this.allowEdit && this.croppedUri != null) {
- writeUncompressedImage(this.croppedUri, galleryUri);
+ if (this.allowEdit && this.croppedUri != null) {
+ writeUncompressedImage(croppedUri, galleryUri);
} else {
- writeUncompressedImage(this.imageUri, galleryUri);
+ Uri imageUri = this.imageUri.getFileUri();
+ writeUncompressedImage(imageUri, galleryUri);
}
refreshGallery(galleryUri);
@@ -490,7 +511,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.
@@ -521,10 +542,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 = this.imageUri.getFileUri();
+ writeUncompressedImage(imageUri, uri);
}
this.callbackContext.success(uri.toString());
@@ -566,12 +589,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
throw new IllegalStateException();
}
- this.cleanup(FILE_URI, this.imageUri, galleryUri, bitmap);
+ this.cleanup(FILE_URI, this.imageUri.getFileUri(), galleryUri, bitmap);
bitmap = null;
}
- private String getPicturesPath()
- {
+ 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(
@@ -580,8 +602,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
return galleryPath;
}
- private void refreshGallery(Uri contentUri)
- {
+ private void refreshGallery(Uri contentUri) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(contentUri);
this.cordova.getActivity().sendBroadcast(mediaScanIntent);
@@ -601,9 +622,16 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private String outputModifiedBitmap(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");
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
- String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
+ //String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
String modifiedPath = getTempDirectoryPath() + "/" + fileName;
OutputStream os = new FileOutputStream(modifiedPath);
@@ -630,12 +658,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
-
/**
* 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();
@@ -710,8 +737,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
e.printStackTrace();
this.failPicture("Error retrieving image.");
}
- }
- else {
+ } else {
this.callbackContext.success(fileLocation);
}
}
@@ -727,10 +753,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
* 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) {
@@ -767,12 +793,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// 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(),
+ applicationId + ".provider",
+ createCaptureFile(this.encodingType));
performCrop(tmpFile, destType, intent);
- }
- else {
+ } else {
this.processResultFromCamera(destType, intent);
}
} catch (IOException e) {
@@ -801,11 +827,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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!");
}
}
@@ -847,14 +871,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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.");
}
}
}
@@ -918,7 +942,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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.");
}
}
}
@@ -1126,8 +1150,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* @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;
@@ -1144,7 +1168,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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);
@@ -1152,6 +1176,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
* 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) {
@@ -1203,6 +1228,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/**
* Determine if we are storing the images in internal or external storage
+ *
* @return Uri
*/
private Uri whichContentStore() {
@@ -1251,7 +1277,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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);
@@ -1259,9 +1285,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
}
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");
}
@@ -1273,18 +1299,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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;
@@ -1313,12 +1335,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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) {
- state.putString("imageUri", this.imageUri.toString());
+ if (this.imageUri != null) {
+ state.putString("imageUri", this.imageUri.getFileUri().toString());
}
return state;
@@ -1337,14 +1359,38 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
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")) {
- this.imageUri = Uri.parse(state.getString("imageUri"));
+ if (state.containsKey("imageUri")) {
+ //I have no idea what type of URI is being passed in
+ this.imageUri = new CordovaUri(Uri.parse(state.getString("imageUri")));
}
this.callbackContext = callbackContext;
}
-}
\ No newline at end of file
+
+ /*
+ * 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;
+
+ }
+
+
+}
diff --git a/src/android/CordovaUri.java b/src/android/CordovaUri.java
new file mode 100644
index 0000000..65ba878
--- /dev/null
+++ b/src/android/CordovaUri.java
@@ -0,0 +1,85 @@
+package org.apache.cordova.camera;
+
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.support.v4.content.FileProvider;
+
+import java.io.File;
+
+/*
+ * This class exists because Andorid FilesProvider doesn't work on Android 4.4.4 and below and throws
+ * weird errors. I'm not sure why writing to shared cache directories is somehow verboten, but it is
+ * and this error is irritating for a Compatibility library to have.
+ *
+ */
+
+public class CordovaUri {
+
+ private Uri androidUri;
+ private String fileName;
+ private Uri fileUri;
+
+ /*
+ * We always expect a FileProvider string to be passed in for the file that we create
+ *
+ */
+ CordovaUri (Uri inputUri)
+ {
+ //Determine whether the file is a content or file URI
+ if(inputUri.getScheme().equals("content"))
+ {
+ androidUri = inputUri;
+ fileName = getFileNameFromUri(androidUri);
+ fileUri = Uri.parse("file://" + fileName);
+ }
+ else
+ {
+ fileUri = inputUri;
+ fileName = FileHelper.stripFileProtocol(inputUri.toString());
+ }
+ }
+
+ public Uri getFileUri()
+ {
+ return fileUri;
+ }
+
+ public String getFilePath()
+ {
+ return fileName;
+ }
+
+ /*
+ * This only gets called by takePicture
+ */
+
+ public Uri getCorrectUri()
+ {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ return androidUri;
+ else
+ return fileUri;
+ }
+
+ /*
+ * 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;
+
+ }
+}
diff --git a/src/android/xml/provider_paths.xml b/src/android/xml/provider_paths.xml
new file mode 100644
index 0000000..ffa74ab
--- /dev/null
+++ b/src/android/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file