You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by rk...@apache.org on 2016/04/04 20:19:35 UTC
cordova-plugin-media-capture git commit: CB-10670,
CB-10994 android: Marshmallow permissions
Repository: cordova-plugin-media-capture
Updated Branches:
refs/heads/master 086789870 -> 7646d75bc
CB-10670, CB-10994 android: Marshmallow permissions
Fixes security exceptions caused by a missing request
for WRITE_EXTERNAL_STORAGE when capturing an image and
not requesting the CAMERA permission in the case where
it has been inserted into the manifest by some other
plugin.
This closes #59
Project: http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/commit/7646d75b
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/tree/7646d75b
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/diff/7646d75b
Branch: refs/heads/master
Commit: 7646d75bcac0ec745f3c02dec0cea8cfe819463e
Parents: 0867898
Author: Richard Knoll <ri...@gmail.com>
Authored: Mon Mar 21 15:21:30 2016 -0700
Committer: Richard Knoll <ri...@gmail.com>
Committed: Wed Mar 30 13:46:11 2016 -0700
----------------------------------------------------------------------
README.md | 2 +
plugin.xml | 26 +-
src/android/Capture.java | 424 +++++++++++++++++++--------------
src/android/PendingRequests.java | 132 ++++++++++
src/android/PermissionHelper.java | 138 +++++++++++
www/CaptureError.js | 2 +
6 files changed, 529 insertions(+), 195 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/7646d75b/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 7877681..4bdc3da 100644
--- a/README.md
+++ b/README.md
@@ -423,6 +423,8 @@ Each `MediaFile` object describes a captured media file.
- `CaptureError.CAPTURE_NO_MEDIA_FILES`: The user exits the camera or audio capture application before capturing anything.
+- `CaptureError.CAPTURE_PERMISSION_DENIED`: The user denied a permission required to perform the given capture request.
+
- `CaptureError.CAPTURE_NOT_SUPPORTED`: The requested capture operation is not supported.
## CaptureErrorCB
http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/7646d75b/plugin.xml
----------------------------------------------------------------------
diff --git a/plugin.xml b/plugin.xml
index 37679f3..b9528af 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -30,13 +30,13 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<keywords>cordova,media,capture</keywords>
<repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture.git</repo>
<issue>https://issues.apache.org/jira/browse/CB/component/12320646</issue>
-
+
<dependency id="cordova-plugin-file" version="^4.0.0" />
<js-module src="www/CaptureAudioOptions.js" name="CaptureAudioOptions">
<clobbers target="CaptureAudioOptions" />
</js-module>
-
+
<js-module src="www/CaptureImageOptions.js" name="CaptureImageOptions">
<clobbers target="CaptureImageOptions" />
</js-module>
@@ -44,7 +44,7 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<js-module src="www/CaptureVideoOptions.js" name="CaptureVideoOptions">
<clobbers target="CaptureVideoOptions" />
</js-module>
-
+
<js-module src="www/CaptureError.js" name="CaptureError">
<clobbers target="CaptureError" />
</js-module>
@@ -52,11 +52,11 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<js-module src="www/MediaFileData.js" name="MediaFileData">
<clobbers target="MediaFileData" />
</js-module>
-
+
<js-module src="www/MediaFile.js" name="MediaFile">
<clobbers target="MediaFile" />
</js-module>
-
+
<js-module src="www/capture.js" name="capture">
<clobbers target="navigator.device.capture" />
</js-module>
@@ -68,7 +68,7 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<param name="android-package" value="org.apache.cordova.mediacapture.Capture"/>
</feature>
</config-file>
-
+
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
@@ -77,8 +77,10 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<source-file src="src/android/Capture.java" target-dir="src/org/apache/cordova/mediacapture" />
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/mediacapture" />
+ <source-file src="src/android/PermissionHelper.java" target-dir="src/org/apache/cordova/mediacapture" />
+ <source-file src="src/android/PendingRequests.java" target-dir="src/org/apache/cordova/mediacapture" />
</platform>
-
+
<!-- amazon-fireos -->
<platform name="amazon-fireos">
<config-file target="res/xml/config.xml" parent="/*">
@@ -86,7 +88,7 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<param name="android-package" value="org.apache.cordova.mediacapture.Capture"/>
</feature>
</config-file>
-
+
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
@@ -96,7 +98,7 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
<source-file src="src/android/Capture.java" target-dir="src/org/apache/cordova/mediacapture" />
<source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/mediacapture" />
</platform>
-
+
<!-- ubuntu -->
<platform name="ubuntu">
@@ -122,16 +124,16 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
</platform>
<!-- ios -->
- <platform name="ios">
+ <platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="Capture">
- <param name="ios-package" value="CDVCapture" />
+ <param name="ios-package" value="CDVCapture" />
</feature>
</config-file>
<header-file src="src/ios/CDVCapture.h" />
<source-file src="src/ios/CDVCapture.m" />
<resource-file src="src/ios/CDVCapture.bundle" />
-
+
<framework src="CoreGraphics.framework" />
<framework src="MobileCoreServices.framework" />
</platform>
http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/7646d75b/src/android/Capture.java
----------------------------------------------------------------------
diff --git a/src/android/Capture.java b/src/android/Capture.java
index 8afed36..9e60c38 100644
--- a/src/android/Capture.java
+++ b/src/android/Capture.java
@@ -35,14 +35,17 @@ import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
-import org.apache.cordova.PluginResult;
+import org.apache.cordova.mediacapture.PendingRequests.Request;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import android.Manifest;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
@@ -67,14 +70,13 @@ public class Capture extends CordovaPlugin {
// private static final int CAPTURE_APPLICATION_BUSY = 1;
// private static final int CAPTURE_INVALID_ARGUMENT = 2;
private static final int CAPTURE_NO_MEDIA_FILES = 3;
+ private static final int CAPTURE_PERMISSION_DENIED = 4;
+
+ private boolean cameraPermissionInManifest; // Whether or not the CAMERA permission is declared in AndroidManifest.xml
+
+ private final PendingRequests pendingRequests = new PendingRequests();
- private CallbackContext callbackContext; // The callback context from which we were invoked.
- private long limit; // the number of pics/vids/clips to take
- private int duration; // optional max duration of video recording in seconds
- private JSONArray results; // The array of results to be returned to the user
private int numPics; // Number of pictures before capture activity
- private int quality; // Quality level for video capture 0 low, 1 high
- //private CordovaInterface cordova;
// public void setContext(Context mCtx)
// {
@@ -85,33 +87,50 @@ public class Capture extends CordovaPlugin {
// }
@Override
- public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
- this.callbackContext = callbackContext;
- this.limit = 1;
- this.duration = 0;
- this.results = new JSONArray();
- this.quality = 1;
+ protected void pluginInitialize() {
+ super.pluginInitialize();
- JSONObject options = args.optJSONObject(0);
- if (options != null) {
- limit = options.optLong("limit", 1);
- duration = options.optInt("duration", 0);
- quality = options.optInt("quality", 1);
+ // CB-10670: The CAMERA permission does not need to be requested unless it is declared
+ // in AndroidManifest.xml. This plugin does not declare it, but others may and so we must
+ // check the package info to determine if the permission is present.
+
+ cameraPermissionInManifest = false;
+ try {
+ PackageManager packageManager = this.cordova.getActivity().getPackageManager();
+ String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
+ if (permissionsInPackage != null) {
+ for (String permission : permissionsInPackage) {
+ if (permission.equals(Manifest.permission.CAMERA)) {
+ cameraPermissionInManifest = true;
+ break;
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // We are requesting the info for our package, so this should
+ // never be caught
+ LOG.e(LOG_TAG, "Failed checking for CAMERA permission in manifest", e);
}
+ }
+ @Override
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("getFormatData")) {
JSONObject obj = getFormatData(args.getString(0), args.getString(1));
callbackContext.success(obj);
return true;
}
- else if (action.equals("captureAudio")) {
- this.captureAudio();
+
+ JSONObject options = args.optJSONObject(0);
+
+ if (action.equals("captureAudio")) {
+ this.captureAudio(pendingRequests.createRequest(CAPTURE_AUDIO, options, callbackContext));
}
else if (action.equals("captureImage")) {
- this.captureImage();
+ this.captureImage(pendingRequests.createRequest(CAPTURE_IMAGE, options, callbackContext));
}
else if (action.equals("captureVideo")) {
- this.captureVideo(duration, quality);
+ this.captureVideo(pendingRequests.createRequest(CAPTURE_VIDEO, options, callbackContext));
}
else {
return false;
@@ -201,10 +220,10 @@ public class Capture extends CordovaPlugin {
/**
* Sets up an intent to capture audio. Result handled by onActivityResult()
*/
- private void captureAudio() {
+ private void captureAudio(Request req) {
Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION);
- this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_AUDIO);
+ this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode);
}
private String getTempDirectoryPath() {
@@ -221,24 +240,40 @@ public class Capture extends CordovaPlugin {
/**
* Sets up an intent to capture images. Result handled by onActivityResult()
*/
- private void captureImage() {
- // Save the number of images currently on disk for later
- this.numPics = queryImgDB(whichContentStore()).getCount();
+ private void captureImage(Request req) {
+ boolean needExternalStoragePermission =
+ !PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
+
+ boolean needCameraPermission = cameraPermissionInManifest &&
+ !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
+
+ if (needExternalStoragePermission || needCameraPermission) {
+ if (needExternalStoragePermission && needCameraPermission) {
+ PermissionHelper.requestPermissions(this, req.requestCode, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA});
+ } else if (needExternalStoragePermission) {
+ PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
+ } else {
+ PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.CAMERA);
+ }
+ } else {
+ // Save the number of images currently on disk for later
+ this.numPics = queryImgDB(whichContentStore()).getCount();
- Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
+ Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
- // Specify file so that large image is captured and returned
- File photo = new File(getTempDirectoryPath(), "Capture.jpg");
- try {
- // the ACTION_IMAGE_CAPTURE is run under different credentials and has to be granted write permissions
- createWritableFile(photo);
- } catch (IOException ex) {
- this.fail(createErrorObject(CAPTURE_INTERNAL_ERR, ex.toString()));
- return;
- }
- intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
+ // Specify file so that large image is captured and returned
+ File photo = new File(getTempDirectoryPath(), "Capture.jpg");
+ try {
+ // the ACTION_IMAGE_CAPTURE is run under different credentials and has to be granted write permissions
+ createWritableFile(photo);
+ } catch (IOException ex) {
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, ex.toString()));
+ return;
+ }
+ intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
- this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_IMAGE);
+ this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode);
+ }
}
private static void createWritableFile(File file) throws IOException {
@@ -249,14 +284,18 @@ public class Capture extends CordovaPlugin {
/**
* Sets up an intent to capture video. Result handled by onActivityResult()
*/
- private void captureVideo(int duration, int quality) {
- Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
+ private void captureVideo(Request req) {
+ if(cameraPermissionInManifest && !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA)) {
+ PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.CAMERA);
+ } else {
+ Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
- if(Build.VERSION.SDK_INT > 7){
- intent.putExtra("android.intent.extra.durationLimit", duration);
- intent.putExtra("android.intent.extra.videoQuality", quality);
+ if(Build.VERSION.SDK_INT > 7){
+ intent.putExtra("android.intent.extra.durationLimit", req.duration);
+ intent.putExtra("android.intent.extra.videoQuality", req.quality);
+ }
+ this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode);
}
- this.cordova.startActivityForResult((CordovaPlugin) this, intent, CAPTURE_VIDEO);
}
/**
@@ -269,153 +308,147 @@ public class Capture extends CordovaPlugin {
* @throws JSONException
*/
public void onActivityResult(int requestCode, int resultCode, final Intent intent) {
+ final Request req = pendingRequests.get(requestCode);
// Result received okay
if (resultCode == Activity.RESULT_OK) {
- // An audio clip was requested
- if (requestCode == CAPTURE_AUDIO) {
-
- final Capture that = this;
- Runnable captureAudio = new Runnable() {
-
- @Override
- public void run() {
- // Get the uri of the audio clip
- Uri data = intent.getData();
- // create a file object from the uri
- results.put(createMediaFile(data));
-
- if (results.length() >= limit) {
- // Send Uri back to JavaScript for listening to audio
- that.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
- } else {
- // still need to capture more audio clips
- captureAudio();
- }
- }
- };
- this.cordova.getThreadPool().execute(captureAudio);
- } else if (requestCode == CAPTURE_IMAGE) {
- // For some reason if I try to do:
- // Uri data = intent.getData();
- // It crashes in the emulator and on my phone with a null pointer exception
- // To work around it I had to grab the code from CameraLauncher.java
-
- final Capture that = this;
- Runnable captureImage = new Runnable() {
- @Override
- public void run() {
- try {
- // TODO Auto-generated method stub
- // Create entry in media store for image
- // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
- ContentValues values = new ContentValues();
- values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, IMAGE_JPEG);
- Uri uri = null;
- try {
- uri = that.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
- } catch (UnsupportedOperationException e) {
- LOG.d(LOG_TAG, "Can't write to external media storage.");
- try {
- uri = that.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
- } catch (UnsupportedOperationException ex) {
- LOG.d(LOG_TAG, "Can't write to internal media storage.");
- that.fail(createErrorObject(CAPTURE_INTERNAL_ERR, "Error capturing image - no media storage found."));
- return;
- }
- }
- FileInputStream fis = new FileInputStream(getTempDirectoryPath() + "/Capture.jpg");
- OutputStream os = that.cordova.getActivity().getContentResolver().openOutputStream(uri);
- byte[] buffer = new byte[4096];
- int len;
- while ((len = fis.read(buffer)) != -1) {
- os.write(buffer, 0, len);
- }
- os.flush();
- os.close();
- fis.close();
-
- // Add image to results
- results.put(createMediaFile(uri));
-
- checkForDuplicateImage();
-
- if (results.length() >= limit) {
- // Send Uri back to JavaScript for viewing image
- that.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
- } else {
- // still need to capture more images
- captureImage();
- }
- } catch (IOException e) {
- e.printStackTrace();
- that.fail(createErrorObject(CAPTURE_INTERNAL_ERR, "Error capturing image."));
- }
+ Runnable processActivityResult = new Runnable() {
+ @Override
+ public void run() {
+ switch(req.action) {
+ case CAPTURE_AUDIO:
+ onAudioActivityResult(req, intent);
+ break;
+ case CAPTURE_IMAGE:
+ onImageActivityResult(req);
+ break;
+ case CAPTURE_VIDEO:
+ onVideoActivityResult(req, intent);
+ break;
}
- };
- this.cordova.getThreadPool().execute(captureImage);
- } else if (requestCode == CAPTURE_VIDEO) {
-
- final Capture that = this;
- Runnable captureVideo = new Runnable() {
-
- @Override
- public void run() {
-
- Uri data = null;
-
- if (intent != null){
- // Get the uri of the video clip
- data = intent.getData();
- }
-
- if( data == null){
- File movie = new File(getTempDirectoryPath(), "Capture.avi");
- data = Uri.fromFile(movie);
- }
-
- // create a file object from the uri
- if(data == null)
- {
- that.fail(createErrorObject(CAPTURE_NO_MEDIA_FILES, "Error: data is null"));
- }
- else
- {
- results.put(createMediaFile(data));
-
- if (results.length() >= limit) {
- // Send Uri back to JavaScript for viewing video
- that.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
- } else {
- // still need to capture more video clips
- captureVideo(duration, quality);
- }
- }
- }
- };
- this.cordova.getThreadPool().execute(captureVideo);
- }
+ }
+ };
+
+ this.cordova.getThreadPool().execute(processActivityResult);
}
// If canceled
else if (resultCode == Activity.RESULT_CANCELED) {
// If we have partial results send them back to the user
- if (results.length() > 0) {
- this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
+ if (req.results.length() > 0) {
+ pendingRequests.resolveWithSuccess(req);
}
// user canceled the action
else {
- this.fail(createErrorObject(CAPTURE_NO_MEDIA_FILES, "Canceled."));
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NO_MEDIA_FILES, "Canceled."));
}
}
// If something else
else {
// If we have partial results send them back to the user
- if (results.length() > 0) {
- this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, results));
+ if (req.results.length() > 0) {
+ pendingRequests.resolveWithSuccess(req);
}
// something bad happened
else {
- this.fail(createErrorObject(CAPTURE_NO_MEDIA_FILES, "Did not complete!"));
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NO_MEDIA_FILES, "Did not complete!"));
+ }
+ }
+ }
+
+
+ public void onAudioActivityResult(Request req, Intent intent) {
+ // Get the uri of the audio clip
+ Uri data = intent.getData();
+ // create a file object from the uri
+ req.results.put(createMediaFile(data));
+
+ if (req.results.length() >= req.limit) {
+ // Send Uri back to JavaScript for listening to audio
+ pendingRequests.resolveWithSuccess(req);
+ } else {
+ // still need to capture more audio clips
+ captureAudio(req);
+ }
+ }
+
+ public void onImageActivityResult(Request req) {
+ // For some reason if I try to do:
+ // Uri data = intent.getData();
+ // It crashes in the emulator and on my phone with a null pointer exception
+ // To work around it I had to grab the code from CameraLauncher.java
+ try {
+ // Create entry in media store for image
+ // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
+ ContentValues values = new ContentValues();
+ values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, IMAGE_JPEG);
+ Uri uri = null;
+ try {
+ uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ } catch (UnsupportedOperationException e) {
+ LOG.d(LOG_TAG, "Can't write to external media storage.");
+ try {
+ uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
+ } catch (UnsupportedOperationException ex) {
+ LOG.d(LOG_TAG, "Can't write to internal media storage.");
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error capturing image - no media storage found."));
+ return;
+ }
+ }
+ FileInputStream fis = new FileInputStream(getTempDirectoryPath() + "/Capture.jpg");
+ OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
+ byte[] buffer = new byte[4096];
+ int len;
+ while ((len = fis.read(buffer)) != -1) {
+ os.write(buffer, 0, len);
+ }
+ os.flush();
+ os.close();
+ fis.close();
+
+ // Add image to results
+ req.results.put(createMediaFile(uri));
+
+ checkForDuplicateImage();
+
+ if (req.results.length() >= req.limit) {
+ // Send Uri back to JavaScript for viewing image
+ pendingRequests.resolveWithSuccess(req);
+ } else {
+ // still need to capture more images
+ captureImage(req);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error capturing image."));
+ }
+ }
+
+ public void onVideoActivityResult(Request req, Intent intent) {
+ Uri data = null;
+
+ if (intent != null){
+ // Get the uri of the video clip
+ data = intent.getData();
+ }
+
+ if( data == null){
+ File movie = new File(getTempDirectoryPath(), "Capture.avi");
+ data = Uri.fromFile(movie);
+ }
+
+ // create a file object from the uri
+ if(data == null) {
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NO_MEDIA_FILES, "Error: data is null"));
+ }
+ else {
+ req.results.put(createMediaFile(data));
+
+ if (req.results.length() >= req.limit) {
+ // Send Uri back to JavaScript for viewing video
+ pendingRequests.resolveWithSuccess(req);
+ } else {
+ // still need to capture more video clips
+ captureVideo(req);
}
}
}
@@ -492,16 +525,6 @@ public class Capture extends CordovaPlugin {
}
/**
- * Send error message to JavaScript.
- *
- * @param err
- */
- public void fail(JSONObject err) {
- this.callbackContext.error(err);
- }
-
-
- /**
* Creates a cursor that can be used to determine how many images we have.
*
* @return a cursor
@@ -544,4 +567,39 @@ public class Capture extends CordovaPlugin {
return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI;
}
}
+
+ private void executeRequest(Request req) {
+ switch (req.action) {
+ case CAPTURE_AUDIO:
+ this.captureAudio(req);
+ break;
+ case CAPTURE_IMAGE:
+ this.captureImage(req);
+ break;
+ case CAPTURE_VIDEO:
+ this.captureVideo(req);
+ break;
+ }
+ }
+
+ public void onRequestPermissionResult(int requestCode, String[] permissions,
+ int[] grantResults) throws JSONException {
+ Request req = pendingRequests.get(requestCode);
+
+ if (req != null) {
+ boolean success = true;
+ for(int r:grantResults) {
+ if (r == PackageManager.PERMISSION_DENIED) {
+ success = false;
+ break;
+ }
+ }
+
+ if (success) {
+ executeRequest(req);
+ } else {
+ pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_PERMISSION_DENIED, "Permission denied."));
+ }
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/7646d75b/src/android/PendingRequests.java
----------------------------------------------------------------------
diff --git a/src/android/PendingRequests.java b/src/android/PendingRequests.java
new file mode 100644
index 0000000..5676bd9
--- /dev/null
+++ b/src/android/PendingRequests.java
@@ -0,0 +1,132 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+package org.apache.cordova.mediacapture;
+
+import android.util.SparseArray;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Holds the pending javascript requests for the plugin
+ */
+public class PendingRequests {
+ private int currentReqId = 0;
+ private SparseArray<Request> requests = new SparseArray<Request>();
+
+ /**
+ * Creates a request and adds it to the array of pending requests. Each created request gets a
+ * unique result code for use with startActivityForResult() and requestPermission()
+ * @param action The action this request corresponds to (capture image, capture audio, etc.)
+ * @param options The options for this request passed from the javascript
+ * @param callbackContext The CallbackContext to return the result to
+ * @return The newly created Request object with a unique result code
+ * @throws JSONException
+ */
+ public synchronized Request createRequest(int action, JSONObject options, CallbackContext callbackContext) throws JSONException {
+ Request req = new Request(action, options, callbackContext);
+ requests.put(req.requestCode, req);
+ return req;
+ }
+
+ /**
+ * Gets the request corresponding to this request code
+ * @param requestCode The request code for the desired request
+ * @return The request corresponding to the given request code or null if such a
+ * request is not found
+ */
+ public synchronized Request get(int requestCode) {
+ return requests.get(requestCode);
+ }
+
+ /**
+ * Removes the request from the array of pending requests and sends an error plugin result
+ * to the CallbackContext that contains the given error object
+ * @param req The request to be resolved
+ * @param error The error to be returned to the CallbackContext
+ */
+ public synchronized void resolveWithFailure(Request req, JSONObject error) {
+ req.callbackContext.error(error);
+ requests.remove(req.requestCode);
+ }
+
+ /**
+ * Removes the request from the array of pending requests and sends a successful plugin result
+ * to the CallbackContext that contains the result of the request
+ * @param req The request to be resolved
+ */
+ public synchronized void resolveWithSuccess(Request req) {
+ req.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, req.results));
+ requests.remove(req.requestCode);
+ }
+
+
+ /**
+ * Each request gets a unique ID that represents its request code when calls are made to
+ * Activities and for permission requests
+ * @return A unique request code
+ */
+ private synchronized int incrementCurrentReqId() {
+ return currentReqId ++;
+ }
+
+ /**
+ * Holds the options and CallbackContext for a capture request made to the plugin.
+ */
+ public class Request {
+
+ // Unique int used to identify this request in any Android Permission or Activity callbacks
+ public int requestCode;
+
+ // The action that this request is performing
+ public int action;
+
+ // The number of pics/vids/audio clips to take (CAPTURE_IMAGE, CAPTURE_VIDEO, CAPTURE_AUDIO)
+ public long limit = 1;
+
+ // Optional max duration of recording in seconds (CAPTURE_VIDEO only)
+ public int duration = 0;
+
+ // Quality level for video capture 0 low, 1 high (CAPTURE_VIDEO only)
+ public int quality = 1;
+
+ // The array of results to be returned to the javascript callback on success
+ public JSONArray results = new JSONArray();
+
+ // The callback context for this plugin request
+ private CallbackContext callbackContext;
+
+ private Request(int action, JSONObject options, CallbackContext callbackContext) throws JSONException {
+ this.callbackContext = callbackContext;
+ this.action = action;
+
+ if (options != null) {
+ this.limit = options.optLong("limit", 1);
+ this.duration = options.optInt("duration", 0);
+ this.quality = options.optInt("quality", 1);
+ }
+
+ this.requestCode = incrementCurrentReqId();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/7646d75b/src/android/PermissionHelper.java
----------------------------------------------------------------------
diff --git a/src/android/PermissionHelper.java b/src/android/PermissionHelper.java
new file mode 100644
index 0000000..e4d2f98
--- /dev/null
+++ b/src/android/PermissionHelper.java
@@ -0,0 +1,138 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.mediacapture;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.LOG;
+
+import android.content.pm.PackageManager;
+
+/**
+ * This class provides reflective methods for permission requesting and checking so that plugins
+ * written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
+ */
+public class PermissionHelper {
+ private static final String LOG_TAG = "CordovaPermissionHelper";
+
+ /**
+ * Requests a "dangerous" permission for the application at runtime. This is a helper method
+ * alternative to cordovaInterface.requestPermission() that does not require the project to be
+ * built with cordova-android 5.0.0+
+ *
+ * @param plugin The plugin the permission is being requested for
+ * @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
+ * along with the result of the permission request
+ * @param permission The permission to be requested
+ */
+ public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
+ PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
+ }
+
+ /**
+ * Requests "dangerous" permissions for the application at runtime. This is a helper method
+ * alternative to cordovaInterface.requestPermissions() that does not require the project to be
+ * built with cordova-android 5.0.0+
+ *
+ * @param plugin The plugin the permissions are being requested for
+ * @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
+ * along with the result of the permissions request
+ * @param permissions The permissions to be requested
+ */
+ public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
+ try {
+ Method requestPermission = CordovaInterface.class.getDeclaredMethod(
+ "requestPermissions", CordovaPlugin.class, int.class, String[].class);
+
+ // If there is no exception, then this is cordova-android 5.0.0+
+ requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions);
+ } catch (NoSuchMethodException noSuchMethodException) {
+ // cordova-android version is less than 5.0.0, so permission is implicitly granted
+ LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions));
+
+ // Notify the plugin that all were granted by using more reflection
+ deliverPermissionResult(plugin, requestCode, permissions);
+ } catch (IllegalAccessException illegalAccessException) {
+ // Should never be caught; this is a public method
+ LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException);
+ } catch(InvocationTargetException invocationTargetException) {
+ // This method does not throw any exceptions, so this should never be caught
+ LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException);
+ }
+ }
+
+ /**
+ * Checks at runtime to see if the application has been granted a permission. This is a helper
+ * method alternative to cordovaInterface.hasPermission() that does not require the project to
+ * be built with cordova-android 5.0.0+
+ *
+ * @param plugin The plugin the permission is being checked against
+ * @param permission The permission to be checked
+ *
+ * @return True if the permission has already been granted and false otherwise
+ */
+ public static boolean hasPermission(CordovaPlugin plugin, String permission) {
+ try {
+ Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class);
+
+ // If there is no exception, then this is cordova-android 5.0.0+
+ return (Boolean) hasPermission.invoke(plugin.cordova, permission);
+ } catch (NoSuchMethodException noSuchMethodException) {
+ // cordova-android version is less than 5.0.0, so permission is implicitly granted
+ LOG.d(LOG_TAG, "No need to check for permission " + permission);
+ return true;
+ } catch (IllegalAccessException illegalAccessException) {
+ // Should never be caught; this is a public method
+ LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException);
+ } catch(InvocationTargetException invocationTargetException) {
+ // This method does not throw any exceptions, so this should never be caught
+ LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException);
+ }
+ return false;
+ }
+
+ private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
+ // Generate the request results
+ int[] requestResults = new int[permissions.length];
+ Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
+
+ try {
+ Method onRequestPermissionResult = CordovaPlugin.class.getDeclaredMethod(
+ "onRequestPermissionResult", int.class, String[].class, int[].class);
+
+ onRequestPermissionResult.invoke(plugin, requestCode, permissions, requestResults);
+ } catch (NoSuchMethodException noSuchMethodException) {
+ // Should never be caught since the plugin must be written for cordova-android 5.0.0+ if it
+ // made it to this point
+ LOG.e(LOG_TAG, "NoSuchMethodException when delivering permissions results", noSuchMethodException);
+ } catch (IllegalAccessException illegalAccessException) {
+ // Should never be caught; this is a public method
+ LOG.e(LOG_TAG, "IllegalAccessException when delivering permissions results", illegalAccessException);
+ } catch(InvocationTargetException invocationTargetException) {
+ // This method may throw a JSONException. We are just duplicating cordova-android's
+ // exception handling behavior here; all it does is log the exception in CordovaActivity,
+ // print the stacktrace, and ignore it
+ LOG.e(LOG_TAG, "InvocationTargetException when delivering permissions results", invocationTargetException);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/7646d75b/www/CaptureError.js
----------------------------------------------------------------------
diff --git a/www/CaptureError.js b/www/CaptureError.js
index 6fb7a47..9892709 100644
--- a/www/CaptureError.js
+++ b/www/CaptureError.js
@@ -34,6 +34,8 @@ CaptureError.CAPTURE_APPLICATION_BUSY = 1;
CaptureError.CAPTURE_INVALID_ARGUMENT = 2;
// User exited camera application or audio capture application before capturing anything.
CaptureError.CAPTURE_NO_MEDIA_FILES = 3;
+// User denied permissions required to perform the capture request.
+CaptureError.CAPTURE_PERMISSION_DENIED = 4;
// The requested capture operation is not supported.
CaptureError.CAPTURE_NOT_SUPPORTED = 20;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org