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.

This commit is contained in:
Bryce Curtis 2011-02-27 20:07:24 -06:00
parent 1711fb07d7
commit 2bc7bd6768
8 changed files with 213 additions and 133 deletions

View File

@ -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();
});

View File

@ -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() {

View File

@ -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();
}

View File

@ -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,12 +293,18 @@ PhoneGap.Channel.join(function() {
// Start listening for XHR callbacks
setTimeout(function() {
if (CallbackServer.usePolling()) {
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
}
else {
var polling = prompt("usePolling", "gap_callbackServer:");
if (polling == "true") {
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.JSCallback();
}
}
}, 1);
// Run PhoneGap constructors
@ -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 {

View File

@ -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;
@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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;