diff --git a/bin/templates/cordova/run b/bin/templates/cordova/run index 9641ab2b..47720589 100755 --- a/bin/templates/cordova/run +++ b/bin/templates/cordova/run @@ -66,7 +66,7 @@ elif [[ "$#" -eq 1 ]] ; then $DIR/build $DIR/lib/install-device elif [[ $1 == "--emulator" ]] ; then - #$DIR/build + $DIR/build run_on_emulator elif [[ $1 =~ "--target=" ]]; then $DIR/build diff --git a/framework/src/org/apache/cordova/ExposedJsApi.java b/framework/src/org/apache/cordova/ExposedJsApi.java index 7702d350..40ab1e31 100755 --- a/framework/src/org/apache/cordova/ExposedJsApi.java +++ b/framework/src/org/apache/cordova/ExposedJsApi.java @@ -48,9 +48,9 @@ import org.json.JSONException; jsMessageQueue.setPaused(true); try { - boolean wasSync = pluginManager.exec(service, action, callbackId, arguments); + pluginManager.exec(service, action, callbackId, arguments); String ret = ""; - if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) { + if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) { ret = jsMessageQueue.popAndEncode(); } return ret; diff --git a/framework/src/org/apache/cordova/api/PluginEntry.java b/framework/src/org/apache/cordova/api/PluginEntry.java index 9b9af6bc..f66dcda0 100755 --- a/framework/src/org/apache/cordova/api/PluginEntry.java +++ b/framework/src/org/apache/cordova/api/PluginEntry.java @@ -63,6 +63,19 @@ public class PluginEntry { this.onload = onload; } + /** + * Alternate constructor + * + * @param service The name of the service + * @param plugin The plugin associated with this entry + */ + public PluginEntry(String service, CordovaPlugin plugin) { + this.service = service; + this.plugin = plugin; + this.pluginClass = plugin.getClass().getName(); + this.onload = false; + } + /** * Create plugin object. * If plugin is already created, then just return it. diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index a8191073..0a42b3ab 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -22,7 +22,9 @@ import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaWebView; import org.json.JSONException; import org.xmlpull.v1.XmlPullParserException; @@ -55,6 +57,8 @@ public class PluginManager { // This would allow how all URLs are handled to be offloaded to a plugin protected HashMap urlMap = new HashMap(); + private AtomicInteger numPendingUiExecs; + /** * Constructor. * @@ -65,6 +69,7 @@ public class PluginManager { this.ctx = ctx; this.app = app; this.firstRun = true; + this.numPendingUiExecs = new AtomicInteger(0); } /** @@ -86,6 +91,9 @@ public class PluginManager { this.clearPluginObjects(); } + // Insert PluginManager service + this.addService(new PluginEntry("PluginManager", new PluginManagerService())); + // Start up all plugins that have onload specified this.startupPlugins(); } @@ -200,15 +208,29 @@ public class PluginManager { * this is an async plugin call. * @param rawArgs An Array literal string containing any arguments needed in the * plugin execute method. - * @return Whether the task completed synchronously. */ - public boolean exec(String service, String action, String callbackId, String rawArgs) { - CordovaPlugin plugin = this.getPlugin(service); + public void exec(final String service, final String action, final String callbackId, final String rawArgs) { + if (numPendingUiExecs.get() > 0) { + numPendingUiExecs.getAndIncrement(); + this.ctx.getActivity().runOnUiThread(new Runnable() { + public void run() { + execHelper(service, action, callbackId, rawArgs); + numPendingUiExecs.getAndDecrement(); + } + }); + } else { + Log.d(TAG, "running exec normally"); + execHelper(service, action, callbackId, rawArgs); + } + } + + private void execHelper(final String service, final String action, final String callbackId, final String rawArgs) { + CordovaPlugin plugin = getPlugin(service); if (plugin == null) { Log.d(TAG, "exec() call to unknown plugin: " + service); PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION); app.sendPluginResult(cr, callbackId); - return true; + return; } try { CallbackContext callbackContext = new CallbackContext(callbackId, app); @@ -216,19 +238,16 @@ public class PluginManager { if (!wasValidAction) { PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION); app.sendPluginResult(cr, callbackId); - return true; } - return callbackContext.isFinished(); } catch (JSONException e) { PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION); app.sendPluginResult(cr, callbackId); - return true; } } @Deprecated - public boolean exec(String service, String action, String callbackId, String jsonArgs, boolean async) { - return exec(service, action, callbackId, jsonArgs); + public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) { + exec(service, action, callbackId, jsonArgs); } /** @@ -400,4 +419,26 @@ public class PluginManager { LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml"); LOG.e(TAG, "====================================================================================="); } + + private class PluginManagerService extends CordovaPlugin { + @Override + public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { + if ("startup".equals(action)) { + // The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response + // to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible + // that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue + // to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening, + // javascript always sends a "startup" exec upon loading a new page which causes all future exec calls to happen on the UI + // thread (and hence after onPageStarted) until there are no more pending exec calls remaining. + numPendingUiExecs.getAndIncrement(); + ctx.getActivity().runOnUiThread(new Runnable() { + public void run() { + numPendingUiExecs.getAndDecrement(); + } + }); + return true; + } + return false; + } + } }