Merging API 24 code with master including large refactor

This commit is contained in:
Joe Bowser 2016-09-01 15:11:33 -07:00
commit b63a0d83e0
3 changed files with 140 additions and 79 deletions

View File

@ -69,15 +69,29 @@
<config-file target="AndroidManifest.xml" parent="/*"> <config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</config-file> </config-file>
<config-file target="AndroidManifest.xml" parent="application">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</config-file>
<source-file src="src/android/CameraLauncher.java" target-dir="src/org/apache/cordova/camera" /> <source-file src="src/android/CameraLauncher.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/camera" /> <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/ExifHelper.java" target-dir="src/org/apache/cordova/camera" />
<source-file src="src/android/xml/provider_paths.xml" target-dir="res/xml" />
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle"> <js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
<clobbers target="CameraPopoverHandle" /> <clobbers target="CameraPopoverHandle" />
</js-module> </js-module>
<framework src="com.android.support:support-v4:24.1.1+" />
</platform> </platform>
<!-- amazon-fireos --> <!-- amazon-fireos -->

View File

@ -31,6 +31,8 @@ import java.util.Date;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CoreAndroid;
import org.apache.cordova.LOG; import org.apache.cordova.LOG;
import org.apache.cordova.PermissionHelper; import org.apache.cordova.PermissionHelper;
import org.apache.cordova.PluginResult; import org.apache.cordova.PluginResult;
@ -58,6 +60,8 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.support.v4.content.FileProvider;
import android.util.Base64; import android.util.Base64;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@ -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 scanMe; // Uri of image to be added to content store
private Uri croppedUri; private Uri croppedUri;
private ExifHelper exifData; // Exif data from source private ExifHelper exifData; // Exif data from source
private String applicationId;
/** /**
@ -130,6 +135,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/ */
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext; 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) CoreAndroid.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
if (action.equals("takePicture")) { if (action.equals("takePicture")) {
this.srcType = CAMERA; this.srcType = CAMERA;
@ -232,7 +241,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* img.src=result; * img.src=result;
* *
* @param returnType Set the type of image to return. * @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) { public void callTakePicture(int returnType, int encodingType) {
boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
@ -282,8 +291,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Specify file so that large image is captured and returned // Specify file so that large image is captured and returned
File photo = createCaptureFile(encodingType); File photo = createCaptureFile(encodingType);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); this.imageUri = FileProvider.getUriForFile(cordova.getActivity(),
this.imageUri = Uri.fromFile(photo); applicationId + ".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) { if (this.cordova != null) {
// Let's check to make sure the camera is actually installed. (Legacy Nexus 7 code) // Let's check to make sure the camera is actually installed. (Legacy Nexus 7 code)
@ -405,6 +418,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// set crop properties // set crop properties
cropIntent.putExtra("crop", "true"); cropIntent.putExtra("crop", "true");
// indicate output X and Y // indicate output X and Y
if (targetWidth > 0) { if (targetWidth > 0) {
cropIntent.putExtra("outputX", targetWidth); cropIntent.putExtra("outputX", targetWidth);
@ -418,8 +432,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
} }
// create new file handle to get full resolution crop // create new file handle to get full resolution crop
croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); 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); cropIntent.putExtra("output", croppedUri);
// start the activity - we handle returning in onActivityResult // start the activity - we handle returning in onActivityResult
if (this.cordova != null) { if (this.cordova != null) {
@ -450,9 +467,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// Create an ExifHelper to save the exif data that is lost during compression // Create an ExifHelper to save the exif data that is lost during compression
ExifHelper exif = new ExifHelper(); ExifHelper exif = new ExifHelper();
String sourcePath = (this.allowEdit && this.croppedUri != null) ? String sourcePath = (this.allowEdit && this.croppedUri != null) ?
FileHelper.stripFileProtocol(this.croppedUri.toString()) : FileHelper.stripFileProtocol(this.croppedUri.toString()) :
FileHelper.stripFileProtocol(this.imageUri.toString()); getFileNameFromUri(this.imageUri);
if (this.encodingType == JPEG) { if (this.encodingType == JPEG) {
try { try {
@ -476,9 +495,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
galleryUri = Uri.fromFile(new File(getPicturesPath())); galleryUri = Uri.fromFile(new File(getPicturesPath()));
if (this.allowEdit && this.croppedUri != null) { if (this.allowEdit && this.croppedUri != null) {
writeUncompressedImage(this.croppedUri, galleryUri); writeUncompressedImage(croppedUri, galleryUri);
} else { } else {
writeUncompressedImage(this.imageUri, galleryUri); Uri imageUri = Uri.fromFile(new File(getFileNameFromUri(this.imageUri)));
writeUncompressedImage(imageUri, galleryUri);
} }
refreshGallery(galleryUri); refreshGallery(galleryUri);
@ -522,9 +542,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + "")); Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
if (this.allowEdit && this.croppedUri != null) { if (this.allowEdit && this.croppedUri != null) {
writeUncompressedImage(this.croppedUri, uri); Uri croppedUri = Uri.fromFile(new File(getFileNameFromUri(this.croppedUri)));
writeUncompressedImage(croppedUri, uri);
} else { } else {
writeUncompressedImage(this.imageUri, uri); Uri imageUri = Uri.fromFile(new File(getFileNameFromUri(this.imageUri)));
writeUncompressedImage(imageUri, uri);
} }
this.callbackContext.success(uri.toString()); this.callbackContext.success(uri.toString());
@ -570,8 +592,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
bitmap = null; bitmap = null;
} }
private String getPicturesPath() private String getPicturesPath() {
{
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png"); String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
File storageDir = Environment.getExternalStoragePublicDirectory( File storageDir = Environment.getExternalStoragePublicDirectory(
@ -580,8 +601,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
return galleryPath; return galleryPath;
} }
private void refreshGallery(Uri contentUri) private void refreshGallery(Uri contentUri) {
{
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(contentUri); mediaScanIntent.setData(contentUri);
this.cordova.getActivity().sendBroadcast(mediaScanIntent); this.cordova.getActivity().sendBroadcast(mediaScanIntent);
@ -601,9 +621,16 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { 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 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; String modifiedPath = getTempDirectoryPath() + "/" + fileName;
OutputStream os = new FileOutputStream(modifiedPath); OutputStream os = new FileOutputStream(modifiedPath);
@ -630,7 +657,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
} }
/** /**
* Applies all needed transformation to the image received from the gallery. * Applies all needed transformation to the image received from the gallery.
* *
@ -710,8 +736,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
e.printStackTrace(); e.printStackTrace();
this.failPicture("Error retrieving image."); this.failPicture("Error retrieving image.");
} }
} } else {
else {
this.callbackContext.success(fileLocation); this.callbackContext.success(fileLocation);
} }
} }
@ -767,12 +792,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// If image available // If image available
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
try { try {
if(this.allowEdit) if (this.allowEdit) {
{ Uri tmpFile = FileProvider.getUriForFile(cordova.getActivity(),
Uri tmpFile = Uri.fromFile(createCaptureFile(this.encodingType)); applicationId + ".provider",
createCaptureFile(this.encodingType));
performCrop(tmpFile, destType, intent); performCrop(tmpFile, destType, intent);
} } else {
else {
this.processResultFromCamera(destType, intent); this.processResultFromCamera(destType, intent);
} }
} catch (IOException e) { } catch (IOException e) {
@ -801,11 +826,9 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
processResultFromGallery(finalDestType, i); processResultFromGallery(finalDestType, i);
} }
}); });
} } else if (resultCode == Activity.RESULT_CANCELED) {
else if (resultCode == Activity.RESULT_CANCELED) {
this.failPicture("Selection cancelled."); this.failPicture("Selection cancelled.");
} } else {
else {
this.failPicture("Selection did not complete!"); this.failPicture("Selection did not complete!");
} }
} }
@ -1152,6 +1175,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/** /**
* Cleans up after picture taking. Checking for duplicates and that kind of stuff. * Cleans up after picture taking. Checking for duplicates and that kind of stuff.
*
* @param newImage * @param newImage
*/ */
private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) { private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
@ -1203,6 +1227,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
/** /**
* Determine if we are storing the images in internal or external storage * Determine if we are storing the images in internal or external storage
*
* @return Uri * @return Uri
*/ */
private Uri whichContentStore() { private Uri whichContentStore() {
@ -1273,18 +1298,14 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
public void onRequestPermissionResult(int requestCode, String[] permissions, public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException int[] grantResults) throws JSONException {
{ for (int r : grantResults) {
for(int r:grantResults) if (r == PackageManager.PERMISSION_DENIED) {
{
if(r == PackageManager.PERMISSION_DENIED)
{
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR)); this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return; return;
} }
} }
switch(requestCode) switch (requestCode) {
{
case TAKE_PIC_SEC: case TAKE_PIC_SEC:
takePicture(this.destType, this.encodingType); takePicture(this.destType, this.encodingType);
break; break;
@ -1347,4 +1368,26 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callbackContext = callbackContext; 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;
}
} }

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>