diff --git a/framework/src/org/apache/cordova/AndroidWebView.java b/framework/src/org/apache/cordova/AndroidWebView.java index d0b6d05c..6ad1af6d 100755 --- a/framework/src/org/apache/cordova/AndroidWebView.java +++ b/framework/src/org/apache/cordova/AndroidWebView.java @@ -113,7 +113,7 @@ public class AndroidWebView extends WebView implements CordovaWebView { // Use two-phase init so that the control will work with XML layouts. @Override - public void init(CordovaInterface cordova, List pluginEntries, + public void init(final CordovaInterface cordova, List pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist, CordovaPreferences preferences) { if (this.cordova != null) { @@ -127,9 +127,23 @@ public class AndroidWebView extends WebView implements CordovaWebView { pluginManager = new PluginManager(this, this.cordova, pluginEntries); cookieManager = new AndroidCookieManager(this); resourceApi = new CordovaResourceApi(this.getContext(), pluginManager); - bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName()); + NativeToJsMessageQueue nativeToJsMessageQueue = new NativeToJsMessageQueue(); + nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode()); + nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(this, cordova)); + nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() { + @Override + public void setNetworkAvailable(boolean value) { + AndroidWebView.this.setNetworkAvailable(value); + } + + @Override + public void runOnUiThread(Runnable r) { + cordova.getActivity().runOnUiThread(r); + } + })); + bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue, this.cordova.getActivity().getPackageName()); initWebViewSettings(); - pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid"); + pluginManager.addService(CoreAndroid.PLUGIN_NAME, CoreAndroid.class.getCanonicalName()); pluginManager.init(); if (this.viewClient == null) { diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java index a6970606..6f9db66a 100755 --- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java +++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java @@ -18,16 +18,10 @@ */ package org.apache.cordova; -import java.lang.reflect.Field; -import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.LinkedList; -import org.apache.cordova.CordovaInterface; -import org.apache.cordova.PluginResult; - -import android.os.Message; import android.util.Log; -import android.webkit.WebView; /** * Holds the list of messages to be sent to the WebView. @@ -63,7 +57,7 @@ public class NativeToJsMessageQueue { /** * The array of listeners that can be used to send messages to JS. */ - private final BridgeMode[] registeredListeners; + private ArrayList bridgeModes = new ArrayList(); /** * When null, the bridge is disabled. This occurs during page transitions. @@ -72,32 +66,26 @@ public class NativeToJsMessageQueue { */ private BridgeMode activeBridgeMode; - private final CordovaInterface cordova; - private final CordovaWebView webView; - - public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) { - this.cordova = cordova; - this.webView = webView; - registeredListeners = new BridgeMode[4]; - registeredListeners[0] = new PollingBridgeMode(); - registeredListeners[1] = new LoadUrlBridgeMode(); - registeredListeners[2] = new OnlineEventsBridgeMode(); - registeredListeners[3] = new PrivateApiBridgeMode(); - reset(); + public void addBridgeMode(BridgeMode bridgeMode) { + bridgeModes.add(bridgeMode); } - + public boolean isBridgeEnabled() { return activeBridgeMode != null; } + public boolean isEmpty() { + return queue.isEmpty(); + } + /** * Changes the bridge mode. */ public void setBridgeMode(int value) { - if (value < -1 || value >= registeredListeners.length) { + if (value < -1 || value >= bridgeModes.size()) { Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value); } else { - BridgeMode newMode = value < 0 ? null : registeredListeners[value]; + BridgeMode newMode = value < 0 ? null : bridgeModes.get(value); if (newMode != activeBridgeMode) { Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName())); synchronized (this) { @@ -105,7 +93,7 @@ public class NativeToJsMessageQueue { if (newMode != null) { newMode.reset(); if (!paused && !queue.isEmpty()) { - newMode.onNativeToJsMessageAvailable(); + newMode.onNativeToJsMessageAvailable(this); } } } @@ -146,7 +134,7 @@ public class NativeToJsMessageQueue { if (activeBridgeMode == null) { return null; } - activeBridgeMode.notifyOfFlush(fromOnlineEvent); + activeBridgeMode.notifyOfFlush(this, fromOnlineEvent); if (queue.isEmpty()) { return null; } @@ -179,7 +167,7 @@ public class NativeToJsMessageQueue { /** * Same as popAndEncode(), except encodes in a form that can be executed as JS. */ - private String popAndEncodeAsJs() { + public String popAndEncodeAsJs() { synchronized (this) { int length = queue.size(); if (length == 0) { @@ -260,7 +248,7 @@ public class NativeToJsMessageQueue { } queue.add(message); if (!paused) { - activeBridgeMode.onNativeToJsMessageAvailable(); + activeBridgeMode.onNativeToJsMessageAvailable(this); } } } @@ -275,133 +263,94 @@ public class NativeToJsMessageQueue { if (!value) { synchronized (this) { if (!queue.isEmpty() && activeBridgeMode != null) { - activeBridgeMode.onNativeToJsMessageAvailable(); + activeBridgeMode.onNativeToJsMessageAvailable(this); } } } } - private abstract class BridgeMode { - abstract void onNativeToJsMessageAvailable(); - void notifyOfFlush(boolean fromOnlineEvent) {} + public static abstract class BridgeMode { + abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue); + void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {} void reset() {} } /** Uses JS polls for messages on a timer.. */ - private class PollingBridgeMode extends BridgeMode { - @Override void onNativeToJsMessageAvailable() { + public static class NoOpBridgeMode extends BridgeMode { + @Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) { } } /** Uses webView.loadUrl("javascript:") to execute messages. */ - private class LoadUrlBridgeMode extends BridgeMode { - final Runnable runnable = new Runnable() { - public void run() { - String js = popAndEncodeAsJs(); - if (js != null) { - webView.loadUrlIntoView("javascript:" + js, false); + public static class LoadUrlBridgeMode extends BridgeMode { + private final CordovaWebView webView; + private final CordovaInterface cordova; + + public LoadUrlBridgeMode(CordovaWebView webView, CordovaInterface cordova) { + this.webView = webView; + this.cordova = cordova; + } + + @Override + public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + String js = queue.popAndEncodeAsJs(); + if (js != null) { + webView.loadUrl("javascript:" + js); + } } - } - }; - - @Override void onNativeToJsMessageAvailable() { - cordova.getActivity().runOnUiThread(runnable); + }); } } /** Uses online/offline events to tell the JS when to poll for messages. */ - private class OnlineEventsBridgeMode extends BridgeMode { + public static class OnlineEventsBridgeMode extends BridgeMode { + private final OnlineEventsBridgeModeDelegate delegate; private boolean online; private boolean ignoreNextFlush; - final Runnable toggleNetworkRunnable = new Runnable() { - public void run() { - if (!queue.isEmpty()) { - ignoreNextFlush = false; - webView.setNetworkAvailable(online); - } - } - }; - final Runnable resetNetworkRunnable = new Runnable() { - public void run() { - online = false; - // If the following call triggers a notifyOfFlush, then ignore it. - ignoreNextFlush = true; - webView.setNetworkAvailable(true); - } - }; - @Override void reset() { - cordova.getActivity().runOnUiThread(resetNetworkRunnable); + public interface OnlineEventsBridgeModeDelegate { + void setNetworkAvailable(boolean value); + void runOnUiThread(Runnable r); } - @Override void onNativeToJsMessageAvailable() { - cordova.getActivity().runOnUiThread(toggleNetworkRunnable); + + public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) { + this.delegate = delegate; + } + + @Override + public void reset() { + delegate.runOnUiThread(new Runnable() { + public void run() { + online = false; + // If the following call triggers a notifyOfFlush, then ignore it. + ignoreNextFlush = true; + delegate.setNetworkAvailable(true); + } + }); + } + + @Override + public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { + delegate.runOnUiThread(new Runnable() { + public void run() { + if (!queue.isEmpty()) { + ignoreNextFlush = false; + delegate.setNetworkAvailable(online); + } + } + }); } // Track when online/offline events are fired so that we don't fire excess events. - @Override void notifyOfFlush(boolean fromOnlineEvent) { + @Override + public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) { if (fromOnlineEvent && !ignoreNextFlush) { online = !online; } } } - - /** - * Uses Java reflection to access an API that lets us eval JS. - * Requires Android 3.2.4 or above. - */ - private class PrivateApiBridgeMode extends BridgeMode { - // Message added in commit: - // http://omapzoom.org/?p=platform/frameworks/base.git;a=commitdiff;h=9497c5f8c4bc7c47789e5ccde01179abc31ffeb2 - // Which first appeared in 3.2.4ish. - private static final int EXECUTE_JS = 194; - - Method sendMessageMethod; - Object webViewCore; - boolean initFailed; - @SuppressWarnings("rawtypes") - private void initReflection() { - Object webViewObject = webView; - Class webViewClass = WebView.class; - try { - Field f = webViewClass.getDeclaredField("mProvider"); - f.setAccessible(true); - webViewObject = f.get(webView); - webViewClass = webViewObject.getClass(); - } catch (Throwable e) { - // mProvider is only required on newer Android releases. - } - - try { - Field f = webViewClass.getDeclaredField("mWebViewCore"); - f.setAccessible(true); - webViewCore = f.get(webViewObject); - - if (webViewCore != null) { - sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class); - sendMessageMethod.setAccessible(true); - } - } catch (Throwable e) { - initFailed = true; - Log.e(LOG_TAG, "PrivateApiBridgeMode failed to find the expected APIs.", e); - } - } - - @Override void onNativeToJsMessageAvailable() { - if (sendMessageMethod == null && !initFailed) { - initReflection(); - } - // webViewCore is lazily initialized, and so may not be available right away. - if (sendMessageMethod != null) { - String js = popAndEncodeAsJs(); - Message execJsMessage = Message.obtain(null, EXECUTE_JS, js); - try { - sendMessageMethod.invoke(webViewCore, execJsMessage); - } catch (Throwable e) { - Log.e(LOG_TAG, "Reflection message bridge failed.", e); - } - } - } - } private static class JsMessage { final String jsPayloadOrCallbackId; final PluginResult pluginResult;