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/05/10 22:57:57 UTC

cordova-plugin-media-capture git commit: CB-10554: Implementing plugin save/restore API for Android

Repository: cordova-plugin-media-capture
Updated Branches:
  refs/heads/master 2df337cb1 -> b2e29fc88


CB-10554: Implementing plugin save/restore API for Android

Adds two document events that can be subscribed to on
Android to receive the results of callbacks that were
pending when the webview was destroyed

This closes #51, closes #60


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/b2e29fc8
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/tree/b2e29fc8
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/diff/b2e29fc8

Branch: refs/heads/master
Commit: b2e29fc88c548d5aadb7c49784e3498ae7d7213c
Parents: 2df337c
Author: Richard Knoll <ri...@gmail.com>
Authored: Tue Apr 26 11:54:43 2016 -0700
Committer: Richard Knoll <ri...@gmail.com>
Committed: Tue May 10 15:52:54 2016 -0700

----------------------------------------------------------------------
 README.md                        | 38 ++++++++++++++
 plugin.xml                       |  8 +++
 src/android/Capture.java         |  9 ++++
 src/android/PendingRequests.java | 96 +++++++++++++++++++++++++++++++++++
 www/android/init.js              | 44 ++++++++++++++++
 www/capture.js                   | 20 ++------
 www/helpers.js                   | 44 ++++++++++++++++
 7 files changed, 243 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 39caacb..8546d3b 100644
--- a/README.md
+++ b/README.md
@@ -637,3 +637,41 @@ Supports the following `MediaFileData` properties:
 - __width__: Supported: image and video files only.
 
 - __duration__: Supported: audio and video files only.
+
+## Android Lifecycle Quirks
+
+When capturing audio, video, or images on the Android platform, there is a chance that the
+application will get destroyed after the Cordova Webview is pushed to the background by
+the native capture application. See the [Android Lifecycle Guide][android-lifecycle] for
+a full description of the issue. In this case, the success and failure callbacks passed
+to the capture method will not be fired and instead the results of the call will be
+delivered via a document event that fires after the Cordova [resume event][resume-event].
+
+In your app, you should subscribe to the two possible events like so:
+
+```javascript
+function onDeviceReady() {
+    // pendingcaptureresult is fired if the capture call is successful
+    document.addEventListener('pendingcaptureresult', function(mediaFiles) {
+        // Do something with result
+    });
+
+    // pendingcaptureerror is fired if the capture call is unsuccessful
+    document.addEventListener('pendingcaptureerror', function(error) {
+        // Handle error case
+    });
+}
+
+// Only subscribe to events after deviceready fires
+document.addEventListener('deviceready', onDeviceReady);
+```
+
+It is up you to track what part of your code these results are coming from. Be sure to
+save and restore your app's state as part of the [pause][pause-event] and
+[resume][resume-event] events as appropriate. Please note that these events will only
+fire on the Android platform and only when the Webview was destroyed during a capture
+operation.
+
+[android-lifecycle]: http://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#lifecycle-guide
+[pause-event]: http://cordova.apache.org/docs/en/latest/cordova/events/events.html#pause
+[resume-event]: http://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/plugin.xml
----------------------------------------------------------------------
diff --git a/plugin.xml b/plugin.xml
index 318854a..d4082aa 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -58,6 +58,10 @@ xmlns:rim="http://www.blackberry.com/ns/widgets"
         <clobbers target="MediaFile" />
     </js-module>
 
+    <js-module src="www/helpers.js" name="helpers">
+        <runs />
+    </js-module>
+
     <js-module src="www/capture.js" name="capture">
         <clobbers target="navigator.device.capture" />
     </js-module>
@@ -79,6 +83,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/PendingRequests.java" target-dir="src/org/apache/cordova/mediacapture" />
+
+        <js-module src="www/android/init.js" name="init">
+            <runs />
+        </js-module>
     </platform>
 
     <!-- amazon-fireos -->

http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/src/android/Capture.java
----------------------------------------------------------------------
diff --git a/src/android/Capture.java b/src/android/Capture.java
index 0fb0edb..befca96 100644
--- a/src/android/Capture.java
+++ b/src/android/Capture.java
@@ -27,6 +27,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
 import android.os.Build;
+import android.os.Bundle;
 
 import org.apache.cordova.file.FileUtils;
 import org.apache.cordova.file.LocalFilesystemURL;
@@ -603,4 +604,12 @@ public class Capture extends CordovaPlugin {
             }
         }
     }
+
+    public Bundle onSaveInstanceState() {
+        return pendingRequests.toBundle();
+    }
+
+    public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {
+        pendingRequests.setLastSavedState(state, callbackContext);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/src/android/PendingRequests.java
----------------------------------------------------------------------
diff --git a/src/android/PendingRequests.java b/src/android/PendingRequests.java
index 5676bd9..1679cbc 100644
--- a/src/android/PendingRequests.java
+++ b/src/android/PendingRequests.java
@@ -19,9 +19,11 @@
 
 package org.apache.cordova.mediacapture;
 
+import android.os.Bundle;
 import android.util.SparseArray;
 
 import org.apache.cordova.CallbackContext;
+import org.apache.cordova.LOG;
 import org.apache.cordova.PluginResult;
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -31,9 +33,17 @@ import org.json.JSONObject;
  * Holds the pending javascript requests for the plugin
  */
 public class PendingRequests {
+    private static final String LOG_TAG = "PendingCaptureRequests";
+
+    private static final String CURRENT_ID_KEY = "currentReqId";
+    private static final String REQUEST_KEY_PREFIX = "request_";
+
     private int currentReqId = 0;
     private SparseArray<Request> requests = new SparseArray<Request>();
 
+    private Bundle lastSavedState;
+    private CallbackContext resumeContext;
+
     /**
      * 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()
@@ -56,6 +66,19 @@ public class PendingRequests {
      *                      request is not found
      */
     public synchronized Request get(int requestCode) {
+        // Check to see if this request was saved
+        if (lastSavedState != null && lastSavedState.containsKey(REQUEST_KEY_PREFIX + requestCode)) {
+            Request r = new Request(lastSavedState.getBundle(REQUEST_KEY_PREFIX + requestCode), this.resumeContext, requestCode);
+            requests.put(requestCode, r);
+
+            // Only one of the saved requests will get restored, because that's all cordova-android
+            // supports. Having more than one is an extremely unlikely scenario anyway
+            this.lastSavedState = null;
+            this.resumeContext = null;
+
+            return r;
+        }
+
         return requests.get(requestCode);
     }
 
@@ -91,10 +114,55 @@ public class PendingRequests {
     }
 
     /**
+     * Restore state saved by calling toBundle along with a callbackContext to be used in
+     * delivering the results of a pending callback
+     *
+     * @param lastSavedState    The bundle received from toBundle()
+     * @param resumeContext     The callbackContext to return results to
+     */
+    public synchronized void setLastSavedState(Bundle lastSavedState, CallbackContext resumeContext) {
+        this.lastSavedState = lastSavedState;
+        this.resumeContext = resumeContext;
+        this.currentReqId = lastSavedState.getInt(CURRENT_ID_KEY);
+    }
+
+    /**
+     * Save the current pending requests to a bundle for saving when the Activity gets destroyed.
+     *
+     * @return  A Bundle that can be used to restore state using setLastSavedState()
+     */
+    public synchronized Bundle toBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(CURRENT_ID_KEY, currentReqId);
+
+        for (int i = 0; i < requests.size(); i++) {
+            Request r = requests.valueAt(i);
+            int requestCode = requests.keyAt(i);
+            bundle.putBundle(REQUEST_KEY_PREFIX + requestCode, r.toBundle());
+        }
+
+        if (requests.size() > 1) {
+            // This scenario is hopefully very unlikely because there isn't much that can be
+            // done about it. Should only occur if an external Activity is launched while
+            // there is a pending permission request and the device is on low memory
+            LOG.w(LOG_TAG, "More than one media capture request pending on Activity destruction. Some requests will be dropped!");
+        }
+
+        return bundle;
+    }
+
+    /**
      * Holds the options and CallbackContext for a capture request made to the plugin.
      */
     public class Request {
 
+        // Keys for use in saving requests to a bundle
+        private static final String ACTION_KEY = "action";
+        private static final String LIMIT_KEY = "limit";
+        private static final String DURATION_KEY = "duration";
+        private static final String QUALITY_KEY = "quality";
+        private static final String RESULTS_KEY = "results";
+
         // Unique int used to identify this request in any Android Permission or Activity callbacks
         public int requestCode;
 
@@ -128,5 +196,33 @@ public class PendingRequests {
 
             this.requestCode = incrementCurrentReqId();
         }
+
+        private Request(Bundle bundle, CallbackContext callbackContext, int requestCode) {
+            this.callbackContext = callbackContext;
+            this.requestCode = requestCode;
+            this.action = bundle.getInt(ACTION_KEY);
+            this.limit = bundle.getLong(LIMIT_KEY);
+            this.duration = bundle.getInt(DURATION_KEY);
+            this.quality = bundle.getInt(QUALITY_KEY);
+
+            try {
+                this.results = new JSONArray(bundle.getString(RESULTS_KEY));
+            } catch(JSONException e) {
+                // This should never be caught
+                LOG.e(LOG_TAG, "Error parsing results for request from saved bundle", e);
+            }
+        }
+
+        private Bundle toBundle() {
+            Bundle bundle = new Bundle();
+
+            bundle.putInt(ACTION_KEY, this.action);
+            bundle.putLong(LIMIT_KEY, this.limit);
+            bundle.putInt(DURATION_KEY, this.duration);
+            bundle.putInt(QUALITY_KEY, this.quality);
+            bundle.putString(RESULTS_KEY, this.results.toString());
+
+            return bundle;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/www/android/init.js
----------------------------------------------------------------------
diff --git a/www/android/init.js b/www/android/init.js
new file mode 100644
index 0000000..ace39e2
--- /dev/null
+++ b/www/android/init.js
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var cordova = require('cordova'),
+    helpers = require('./helpers');
+
+var SUCCESS_EVENT = "pendingcaptureresult";
+var FAILURE_EVENT = "pendingcaptureerror";
+
+var sChannel = cordova.addStickyDocumentEventHandler(SUCCESS_EVENT);
+var fChannel = cordova.addStickyDocumentEventHandler(FAILURE_EVENT);
+
+// We fire one of two events in the case where the activity gets killed while
+// the user is capturing audio, image, video, etc. in a separate activity
+document.addEventListener("deviceready", function() {
+    document.addEventListener("resume", function(event) {
+        if (event.pendingResult && event.pendingResult.pluginServiceName === "Capture") {
+            if (event.pendingResult.pluginStatus === "OK") {
+                var mediaFiles = helpers.wrapMediaFiles(event.pendingResult.result);
+                sChannel.fire(mediaFiles);
+            } else {
+                fChannel.fire(event.pendingResult.result);
+            }
+        }
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/www/capture.js
----------------------------------------------------------------------
diff --git a/www/capture.js b/www/capture.js
index fd17474..11c916b 100644
--- a/www/capture.js
+++ b/www/capture.js
@@ -20,7 +20,7 @@
 */
 
 var exec = require('cordova/exec'),
-    MediaFile = require('./MediaFile');
+    helpers = require('./helpers');
 
 /**
  * Launches a capture of different types.
@@ -32,24 +32,12 @@ var exec = require('cordova/exec'),
  */
 function _capture(type, successCallback, errorCallback, options) {
     var win = function(pluginResult) {
-        var mediaFiles = [];
-        var i;
-        for (i = 0; i < pluginResult.length; i++) {
-            var mediaFile = new MediaFile();
-            mediaFile.name = pluginResult[i].name;
-
-            // Backwards compatibility
-            mediaFile.localURL = pluginResult[i].localURL || pluginResult[i].fullPath;
-            mediaFile.fullPath = pluginResult[i].fullPath;
-            mediaFile.type = pluginResult[i].type;
-            mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate;
-            mediaFile.size = pluginResult[i].size;
-            mediaFiles.push(mediaFile);
-        }
-        successCallback(mediaFiles);
+        successCallback(helpers.wrapMediaFiles(pluginResult));
     };
     exec(win, errorCallback, "Capture", type, [options]);
 }
+
+
 /**
  * The Capture interface exposes an interface to the camera and microphone of the hosting device.
  */

http://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture/blob/b2e29fc8/www/helpers.js
----------------------------------------------------------------------
diff --git a/www/helpers.js b/www/helpers.js
new file mode 100644
index 0000000..df726d9
--- /dev/null
+++ b/www/helpers.js
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+var MediaFile = require('./MediaFile');
+
+function wrapMediaFiles(pluginResult) {
+    var mediaFiles = [];
+    var i;
+    for (i = 0; i < pluginResult.length; i++) {
+        var mediaFile = new MediaFile();
+        mediaFile.name = pluginResult[i].name;
+
+        // Backwards compatibility
+        mediaFile.localURL = pluginResult[i].localURL || pluginResult[i].fullPath;
+        mediaFile.fullPath = pluginResult[i].fullPath;
+        mediaFile.type = pluginResult[i].type;
+        mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate;
+        mediaFile.size = pluginResult[i].size;
+        mediaFiles.push(mediaFile);
+    }
+    return mediaFiles;
+}
+
+module.exports = {
+    wrapMediaFiles: wrapMediaFiles
+};
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org