From 2bc7bd6768be3d82eab2e1b4f295abebffa79359 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Sun, 27 Feb 2011 20:07:24 -0600 Subject: [PATCH] Worked around JavaScript bridge exception for Android 2.3. Use "prompt" instead of calling objects directly. There were several objects called from JavaScript, including BrowserKey, so key events had to be reworked. --- framework/assets/js/app.js | 26 +++++- framework/assets/js/device.js | 14 ++- framework/assets/js/keyevent.js | 32 ------- framework/assets/js/phonegap.js.base | 92 +++++++++++++------ framework/src/com/phonegap/App.java | 42 ++++++++- framework/src/com/phonegap/BrowserKey.java | 51 ---------- .../src/com/phonegap/CallbackServer.java | 2 +- framework/src/com/phonegap/DroidGap.java | 87 ++++++++++++++---- 8 files changed, 213 insertions(+), 133 deletions(-) delete mode 100755 framework/assets/js/keyevent.js delete mode 100755 framework/src/com/phonegap/BrowserKey.java diff --git a/framework/assets/js/app.js b/framework/assets/js/app.js index 6ee03256..5a45cdda 100755 --- a/framework/assets/js/app.js +++ b/framework/assets/js/app.js @@ -3,7 +3,7 @@ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation + * Copyright (c) 2010-2011, IBM Corporation */ /** @@ -63,3 +63,27 @@ App.prototype.clearHistory = function() { App.prototype.addService = function(serviceType, className) { PhoneGap.exec(null, null, "App", "addService", [serviceType, className]); }; + +/** + * Override the default behavior of the Android back button. + * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. + * + * Note: The user should not have to call this method. Instead, when the user + * registers for the "backbutton" event, this is automatically done. + * + * @param override T=override, F=cancel override + */ +App.prototype.overrideBackbutton = function(override) { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]); +}; + +/** + * Exit and terminate the application. + */ +App.prototype.exitApp = function() { + return PhoneGap.exec(null, null, "App", "exitApp", []); +}; + +PhoneGap.addConstructor(function() { + navigator.app = window.app = new App(); +}); diff --git a/framework/assets/js/device.js b/framework/assets/js/device.js index 88d5a55d..59d57880 100755 --- a/framework/assets/js/device.js +++ b/framework/assets/js/device.js @@ -3,7 +3,7 @@ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation + * Copyright (c) 2010-2011, IBM Corporation */ /** @@ -62,30 +62,36 @@ Device.prototype.getInfo = function(successCallback, errorCallback) { }; /* + * DEPRECATED * This is only for Android. * * You must explicitly override the back button. */ Device.prototype.overrideBackButton = function() { - BackButton.override(); + console.log("Device.overrideBackButton() is deprecated. Use App.overrideBackbutton(true)."); + app.overrideBackbutton(true); }; /* + * DEPRECATED * This is only for Android. * * This resets the back button to the default behaviour */ Device.prototype.resetBackButton = function() { - BackButton.reset(); + console.log("Device.resetBackButton() is deprecated. Use App.overrideBackbutton(false)."); + app.overrideBackbutton(false); }; /* + * DEPRECATED * This is only for Android. * * This terminates the activity! */ Device.prototype.exitApp = function() { - BackButton.exitApp(); + console.log("Device.exitApp() is deprecated. Use App.exitApp()."); + app.exitApp(); }; PhoneGap.addConstructor(function() { diff --git a/framework/assets/js/keyevent.js b/framework/assets/js/keyevent.js deleted file mode 100755 index a81bc997..00000000 --- a/framework/assets/js/keyevent.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -function KeyEvent() { -} - -KeyEvent.prototype.backTrigger = function() { - var e = document.createEvent('Events'); - e.initEvent('backKeyDown'); - document.dispatchEvent(e); -}; - -KeyEvent.prototype.menuTrigger = function() { - var e = document.createEvent('Events'); - e.initEvent('menuKeyDown'); - document.dispatchEvent(e); -}; - -KeyEvent.prototype.searchTrigger = function() { - var e = document.createEvent('Events'); - e.initEvent('searchKeyDown'); - document.dispatchEvent(e); -}; - -if (document.keyEvent === null || typeof document.keyEvent === 'undefined') { - window.keyEvent = document.keyEvent = new KeyEvent(); -} diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 750ba752..8866a377 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -3,7 +3,7 @@ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation + * Copyright (c) 2010-2011, IBM Corporation */ @@ -293,11 +293,17 @@ PhoneGap.Channel.join(function() { // Start listening for XHR callbacks setTimeout(function() { - if (CallbackServer.usePolling()) { + if (PhoneGap.UsePolling) { PhoneGap.JSCallbackPolling(); } else { - PhoneGap.JSCallback(); + var polling = prompt("usePolling", "gap_callbackServer:"); + if (polling == "true") { + PhoneGap.JSCallbackPolling(); + } + else { + PhoneGap.JSCallback(); + } } }, 1); @@ -307,6 +313,8 @@ PhoneGap.Channel.join(function() { // Fire event to notify that all objects are created PhoneGap.onPhoneGapReady.fire(); + // Fire onDeviceReady event once all constructors have run and PhoneGap info has been + // received from native side, and any user defined initialization channels. PhoneGap.Channel.join(function() { // Turn off app loading dialog @@ -320,22 +328,6 @@ PhoneGap.Channel.join(function() { }, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); -/** - * Fire onDeviceReady event once all constructors have run and PhoneGap info has been - * received from native side. - */ - /* -PhoneGap.Channel.join(function() { - // Turn off app loading dialog - navigator.notification.activityStop(); - - PhoneGap.onDeviceReady.fire(); - - // Fire the onresume event, since first one happens before JavaScript is loaded - PhoneGap.onResume.fire(); -}, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]); -*/ - // Listen for DOMContentLoaded and notify our channel subscribers document.addEventListener('DOMContentLoaded', function() { PhoneGap.onDOMContentLoaded.fire(); @@ -355,11 +347,41 @@ document.addEventListener = function(evt, handler, capture) { } } else if (e === 'pause') { PhoneGap.onPause.subscribe(handler); - } else { + } + else { + // If subscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); + } + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); } }; +// Intercept calls to document.removeEventListener and watch for events that +// are generated by PhoneGap native code +PhoneGap.m_document_removeEventListener = document.removeEventListener; + +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); + } + + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); +}; + +/** + * Method to fire event from native code + */ +PhoneGap.fireEvent = function(type) { + var e = document.createEvent('Events'); + e.initEvent(type); + document.dispatchEvent(e); +}; + /** * If JSON not included, use our own stringify. (Android 1.6) * The restriction on ours is that it must be an array of simple types. @@ -495,8 +517,7 @@ PhoneGap.exec = function(success, fail, service, action, args) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } - // Note: Device returns string, but for some reason emulator returns object - so convert to string. - var r = ""+PluginManager.exec(service, action, callbackId, this.stringify(args), true); + var r = prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); // If a result was returned if (r.length > 0) { @@ -672,6 +693,13 @@ PhoneGap.JSCallbackToken = null; * Java to JavaScript. */ PhoneGap.JSCallback = function() { + + // If polling flag was changed, start using polling from now on + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + return; + } + var xmlhttp = new XMLHttpRequest(); // Callback function when XMLHttpRequest is ready @@ -718,7 +746,7 @@ PhoneGap.JSCallback = function() { // If error, restart callback server else { console.log("JSCallback Error: Request failed."); - CallbackServer.restartServer(); + prompt("restartServer", "gap_callbackServer:"); PhoneGap.JSCallbackPort = null; PhoneGap.JSCallbackToken = null; setTimeout(PhoneGap.JSCallback, 100); @@ -727,10 +755,10 @@ PhoneGap.JSCallback = function() { }; if (PhoneGap.JSCallbackPort === null) { - PhoneGap.JSCallbackPort = CallbackServer.getPort(); + PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); } if (PhoneGap.JSCallbackToken === null) { - PhoneGap.JSCallbackToken = CallbackServer.getToken(); + PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); } xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); xmlhttp.send(); @@ -742,6 +770,11 @@ PhoneGap.JSCallback = function() { */ PhoneGap.JSCallbackPollingPeriod = 50; +/** + * Flag that can be set by the user to force polling to be used or force XHR to be used. + */ +PhoneGap.UsePolling = false; // T=use polling, F=use XHR + /** * This is only for Android. * @@ -750,7 +783,14 @@ PhoneGap.JSCallbackPollingPeriod = 50; * Java to JavaScript. */ PhoneGap.JSCallbackPolling = function() { - var msg = CallbackServer.getJavascript(); + + // If polling flag was changed, stop using polling from now on + if (!PhoneGap.UsePolling) { + PhoneGap.JSCallback(); + return; + } + + var msg = prompt("", "gap_poll:"); if (msg) { setTimeout(function() { try { diff --git a/framework/src/com/phonegap/App.java b/framework/src/com/phonegap/App.java index 4f88f30a..cb3342bc 100755 --- a/framework/src/com/phonegap/App.java +++ b/framework/src/com/phonegap/App.java @@ -3,7 +3,7 @@ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation + * Copyright (c) 2010-2011, IBM Corporation */ package com.phonegap; @@ -17,7 +17,7 @@ import com.phonegap.api.PluginResult; * This class exposes methods in DroidGap that can be called from JavaScript. */ public class App extends Plugin { - + /** * Executes the request and returns PluginResult. * @@ -45,6 +45,16 @@ public class App extends Plugin { } else if (action.equals("addService")) { this.addService(args.getString(0), args.getString(1)); + } + else if (action.equals("overrideBackbutton")) { + this.overrideBackbutton(args.getBoolean(0)); + } + else if (action.equals("isBackbuttonOverridden")) { + boolean b = this.isBackbuttonOverridden(); + return new PluginResult(status, b); + } + else if (action.equals("exitApp")) { + this.exitApp(); } return new PluginResult(status, result); } catch (JSONException e) { @@ -132,4 +142,32 @@ public class App extends Plugin { public void addService(String serviceType, String className) { this.ctx.addService(serviceType, className); } + + /** + * Override the default behavior of the Android back button. + * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. + * + * @param override T=override, F=cancel override + */ + public void overrideBackbutton(boolean override) { + System.out.println("WARNING: Back Button Default Behaviour will be overridden. The backbutton event will be fired!"); + ((DroidGap)this.ctx).bound = override; + } + + /** + * Return whether the Android back button is overridden by the user. + * + * @return boolean + */ + public boolean isBackbuttonOverridden() { + return ((DroidGap)this.ctx).bound; + } + + /** + * Exit the Android application. + */ + public void exitApp() { + ((DroidGap)this.ctx).finish(); + } + } diff --git a/framework/src/com/phonegap/BrowserKey.java b/framework/src/com/phonegap/BrowserKey.java deleted file mode 100755 index 940f641c..00000000 --- a/framework/src/com/phonegap/BrowserKey.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - */ -package com.phonegap; - -import android.app.Activity; -import android.util.Log; -import android.webkit.WebView; - -/* - * This class literally exists to protect DroidGap from Javascript directly. - * - * - */ - -public class BrowserKey { - - DroidGap mAction; - boolean bound; - WebView mView; - - BrowserKey(WebView view, DroidGap action) - { - bound = false; - mAction = action; - } - - public void override() - { - Log.d("PhoneGap", "WARNING: Back Button Default Behaviour will be overridden. The backKeyDown event will be fired!"); - bound = true; - } - - public boolean isBound() - { - return bound; - } - - public void reset() - { - bound = false; - } - - public void exitApp() - { - mAction.finish(); - } -} diff --git a/framework/src/com/phonegap/CallbackServer.java b/framework/src/com/phonegap/CallbackServer.java index e99692d0..2fce8a1e 100755 --- a/framework/src/com/phonegap/CallbackServer.java +++ b/framework/src/com/phonegap/CallbackServer.java @@ -69,7 +69,7 @@ public class CallbackServer implements Runnable { /** * Indicates that polling should be used instead of XHR. */ - private boolean usePolling; + private boolean usePolling = true; /** * Security token to prevent other apps from accessing this callback server via XHR diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 88d275dd..8829dfee 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -3,11 +3,12 @@ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation + * Copyright (c) 2010-2011, IBM Corporation */ package com.phonegap; import org.json.JSONArray; +import org.json.JSONException; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -26,6 +27,7 @@ import android.view.Window; import android.view.WindowManager; import android.webkit.JsResult; import android.webkit.WebChromeClient; +import android.webkit.JsPromptResult; import android.webkit.WebSettings; import android.webkit.WebStorage; import android.webkit.WebView; @@ -110,7 +112,7 @@ public class DroidGap extends PhonegapActivity { protected WebViewClient webViewClient; private LinearLayout root; - private BrowserKey mKey; + boolean bound = false; public CallbackServer callbackServer; protected PluginManager pluginManager; protected boolean cancelLoadUrl = false; @@ -267,12 +269,6 @@ public class DroidGap extends PhonegapActivity { private void bindBrowser(WebView appView) { this.callbackServer = new CallbackServer(); this.pluginManager = new PluginManager(appView, this); - this.mKey = new BrowserKey(appView, this); - - // This creates the new javascript interfaces for PhoneGap - appView.addJavascriptInterface(this.pluginManager, "PluginManager"); - appView.addJavascriptInterface(this.mKey, "BackButton"); - appView.addJavascriptInterface(this.callbackServer, "CallbackServer"); this.addService("App", "com.phonegap.App"); this.addService("Geolocation", "com.phonegap.GeoBroker"); @@ -659,10 +655,6 @@ public class DroidGap extends PhonegapActivity { // Load blank page so that JavaScript onunload is called this.appView.loadUrl("about:blank"); - // Clean up objects - if (this.mKey != null) { - } - // Forward to plugins this.pluginManager.onDestroy(); @@ -764,6 +756,69 @@ public class DroidGap extends PhonegapActivity { return true; } + /** + * Tell the client to display a prompt dialog to the user. + * If the client returns true, WebView will assume that the client will + * handle the prompt dialog and call the appropriate JsPromptResult method. + * + * @param view + * @param url + * @param message + * @param defaultValue + * @param result + */ + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { + + // Calling PluginManager.exec() to call a native service using + // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); + if (defaultValue.substring(0, 4).equals("gap:")) { + JSONArray array; + try { + array = new JSONArray(defaultValue.substring(4)); + String service = array.getString(0); + String action = array.getString(1); + String callbackId = array.getString(2); + boolean async = array.getBoolean(3); + String r = pluginManager.exec(service, action, callbackId, message, async); + result.confirm(r); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + // Polling for JavaScript messages + else if (defaultValue.equals("gap_poll:")) { + String r = callbackServer.getJavascript(); + result.confirm(r); + } + + // Calling into CallbackServer + else if (defaultValue.equals("gap_callbackServer:")) { + String r = ""; + if (message.equals("usePolling")) { + r = ""+callbackServer.usePolling(); + } + else if (message.equals("restartServer")) { + callbackServer.restartServer(); + } + else if (message.equals("getPort")) { + r = Integer.toString(callbackServer.getPort()); + } + else if (message.equals("getToken")) { + r = callbackServer.getToken(); + } + result.confirm(r); + } + + // Show dialog + else { + //@TODO: + result.confirm(""); + } + return true; + } + } /** @@ -1016,8 +1071,8 @@ public class DroidGap extends PhonegapActivity { if (keyCode == KeyEvent.KEYCODE_BACK) { // If back key is bound, then send event to JavaScript - if (mKey.isBound()) { - this.appView.loadUrl("javascript:document.keyEvent.backTrigger()"); + if (this.bound) { + this.appView.loadUrl("javascript:PhoneGap.fireEvent('backbutton');"); } // If not bound @@ -1037,12 +1092,12 @@ public class DroidGap extends PhonegapActivity { // If menu key else if (keyCode == KeyEvent.KEYCODE_MENU) { - appView.loadUrl("javascript:keyEvent.menuTrigger()"); + this.appView.loadUrl("javascript:PhoneGap.fireEvent('menubutton');"); } // If search key else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - appView.loadUrl("javascript:keyEvent.searchTrigger()"); + this.appView.loadUrl("javascript:PhoneGap.fireEvent('searchbutton');"); } return false;