mirror of
https://github.com/apache/cordova-android.git
synced 2025-01-19 23:42:53 +08:00
622 lines
18 KiB
Plaintext
Executable File
622 lines
18 KiB
Plaintext
Executable File
|
|
/**
|
|
* The order of events during page load and PhoneGap startup is as follows:
|
|
*
|
|
* onDOMContentLoaded Internal event that is received when the web page is loaded and parsed.
|
|
* window.onload Body onload event.
|
|
* onNativeReady Internal event that indicates the PhoneGap native side is ready.
|
|
* onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors).
|
|
* onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created
|
|
* onPhoneGapInfoReady Internal event fired when device properties are available
|
|
* onDeviceReady User event fired to indicate that PhoneGap is ready
|
|
* onResume User event fired to indicate a start/resume lifecycle event
|
|
*
|
|
* The only PhoneGap events that user code should register for are:
|
|
* onDeviceReady
|
|
* onResume
|
|
*
|
|
* Listeners can be registered as:
|
|
* document.addEventListener("deviceready", myDeviceReadyListener, false);
|
|
* document.addEventListener("resume", myResumeListener, false);
|
|
*/
|
|
|
|
if (typeof(DeviceInfo) != 'object')
|
|
DeviceInfo = {};
|
|
|
|
/**
|
|
* This represents the PhoneGap API itself, and provides a global namespace for accessing
|
|
* information about the state of PhoneGap.
|
|
* @class
|
|
*/
|
|
var PhoneGap = {
|
|
queue: {
|
|
ready: true,
|
|
commands: [],
|
|
timer: null
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Custom pub-sub channel that can have functions subscribed to it
|
|
*/
|
|
PhoneGap.Channel = function(type)
|
|
{
|
|
this.type = type;
|
|
this.handlers = {};
|
|
this.guid = 0;
|
|
this.fired = false;
|
|
this.enabled = true;
|
|
};
|
|
|
|
/**
|
|
* Subscribes the given function to the channel. Any time that
|
|
* Channel.fire is called so too will the function.
|
|
* Optionally specify an execution context for the function
|
|
* and a guid that can be used to stop subscribing to the channel.
|
|
* Returns the guid.
|
|
*/
|
|
PhoneGap.Channel.prototype.subscribe = function(f, c, g) {
|
|
// need a function to call
|
|
if (f == null) { return; }
|
|
|
|
var func = f;
|
|
if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, f); }
|
|
|
|
g = g || func.observer_guid || f.observer_guid || this.guid++;
|
|
func.observer_guid = g;
|
|
f.observer_guid = g;
|
|
this.handlers[g] = func;
|
|
return g;
|
|
};
|
|
|
|
/**
|
|
* Like subscribe but the function is only called once and then it
|
|
* auto-unsubscribes itself.
|
|
*/
|
|
PhoneGap.Channel.prototype.subscribeOnce = function(f, c) {
|
|
var g = null;
|
|
var _this = this;
|
|
var m = function() {
|
|
f.apply(c || null, arguments);
|
|
_this.unsubscribe(g);
|
|
}
|
|
if (this.fired) {
|
|
if (typeof c == "object" && f instanceof Function) { f = PhoneGap.close(c, f); }
|
|
f.apply(this, this.fireArgs);
|
|
} else {
|
|
g = this.subscribe(m);
|
|
}
|
|
return g;
|
|
};
|
|
|
|
/**
|
|
* Unsubscribes the function with the given guid from the channel.
|
|
*/
|
|
PhoneGap.Channel.prototype.unsubscribe = function(g) {
|
|
if (g instanceof Function) { g = g.observer_guid; }
|
|
this.handlers[g] = null;
|
|
delete this.handlers[g];
|
|
};
|
|
|
|
/**
|
|
* Calls all functions subscribed to this channel.
|
|
*/
|
|
PhoneGap.Channel.prototype.fire = function(e) {
|
|
if (this.enabled) {
|
|
var fail = false;
|
|
for (var item in this.handlers) {
|
|
var handler = this.handlers[item];
|
|
if (handler instanceof Function) {
|
|
var rv = (handler.apply(this, arguments)==false);
|
|
fail = fail || rv;
|
|
}
|
|
}
|
|
this.fired = true;
|
|
this.fireArgs = arguments;
|
|
return !fail;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Calls the provided function only after all of the channels specified
|
|
* have been fired.
|
|
*/
|
|
PhoneGap.Channel.join = function(h, c) {
|
|
var i = c.length;
|
|
var f = function() {
|
|
if (!(--i)) h();
|
|
}
|
|
for (var j=0; j<i; j++) {
|
|
(!c[j].fired?c[j].subscribeOnce(f):i--);
|
|
}
|
|
if (!i) h();
|
|
};
|
|
|
|
/**
|
|
* Boolean flag indicating if the PhoneGap API is available and initialized.
|
|
*/ // TODO: Remove this, it is unused here ... -jm
|
|
PhoneGap.available = DeviceInfo.uuid != undefined;
|
|
|
|
/**
|
|
* Add an initialization function to a queue that ensures it will run and initialize
|
|
* application constructors only once PhoneGap has been initialized.
|
|
* @param {Function} func The function callback you want run once PhoneGap is initialized
|
|
*/
|
|
PhoneGap.addConstructor = function(func) {
|
|
PhoneGap.onPhoneGapInit.subscribeOnce(function() {
|
|
try {
|
|
func();
|
|
} catch(e) {
|
|
if (typeof(debug['log']) == 'function') {
|
|
debug.log("Failed to run constructor: " + debug.processMessage(e));
|
|
} else {
|
|
alert("Failed to run constructor: " + e.message);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Adds a plugin object to window.plugins
|
|
*/
|
|
PhoneGap.addPlugin = function(name, obj) {
|
|
if ( !window.plugins ) {
|
|
window.plugins = {};
|
|
}
|
|
|
|
if ( !window.plugins[name] ) {
|
|
window.plugins[name] = obj;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* onDOMContentLoaded channel is fired when the DOM content
|
|
* of the page has been parsed.
|
|
*/
|
|
PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded');
|
|
|
|
/**
|
|
* onNativeReady channel is fired when the PhoneGap native code
|
|
* has been initialized.
|
|
*/
|
|
PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady');
|
|
|
|
/**
|
|
* onPhoneGapInit channel is fired when the web page is fully loaded and
|
|
* PhoneGap native code has been initialized.
|
|
*/
|
|
PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit');
|
|
|
|
/**
|
|
* onPhoneGapReady channel is fired when the JS PhoneGap objects have been created.
|
|
*/
|
|
PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady');
|
|
|
|
/**
|
|
* onPhoneGapInfoReady channel is fired when the PhoneGap device properties
|
|
* has been set.
|
|
*/
|
|
PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady');
|
|
|
|
/**
|
|
* onResume channel is fired when the PhoneGap native code
|
|
* resumes.
|
|
*/
|
|
PhoneGap.onResume = new PhoneGap.Channel('onResume');
|
|
|
|
/**
|
|
* onPause channel is fired when the PhoneGap native code
|
|
* pauses.
|
|
*/
|
|
PhoneGap.onPause = new PhoneGap.Channel('onPause');
|
|
|
|
// _nativeReady is global variable that the native side can set
|
|
// to signify that the native code is ready. It is a global since
|
|
// it may be called before any PhoneGap JS is ready.
|
|
if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); }
|
|
|
|
/**
|
|
* onDeviceReady is fired only after all PhoneGap objects are created and
|
|
* the device properties are set.
|
|
*/
|
|
PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady');
|
|
|
|
|
|
/**
|
|
* Create all PhoneGap objects once page has fully loaded and native side is ready.
|
|
*/
|
|
PhoneGap.Channel.join(function() {
|
|
|
|
// Start listening for XHR callbacks
|
|
PhoneGap.JSCallback();
|
|
|
|
// Run PhoneGap constructors
|
|
PhoneGap.onPhoneGapInit.fire();
|
|
|
|
// Fire event to notify that all objects are created
|
|
PhoneGap.onPhoneGapReady.fire();
|
|
|
|
}, [ 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() {
|
|
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();
|
|
}, false);
|
|
|
|
// Intercept calls to document.addEventListener and watch for deviceready
|
|
PhoneGap.m_document_addEventListener = document.addEventListener;
|
|
|
|
document.addEventListener = function(evt, handler, capture) {
|
|
var e = evt.toLowerCase();
|
|
if (e == 'deviceready') {
|
|
PhoneGap.onDeviceReady.subscribeOnce(handler);
|
|
} else if (e == 'resume') {
|
|
PhoneGap.onResume.subscribe(handler);
|
|
} else if (e == 'pause') {
|
|
PhoneGap.onPause.subscribe(handler);
|
|
} else {
|
|
PhoneGap.m_document_addEventListener.call(document, evt, handler);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param args
|
|
* @return
|
|
*/
|
|
PhoneGap.stringify = function(args) {
|
|
if (typeof JSON == "undefined") {
|
|
var s = "[";
|
|
for (var i=0; i<args.length; i++) {
|
|
if (i > 0) {
|
|
s = s + ",";
|
|
}
|
|
var type = typeof args[i];
|
|
if ((type == "number") || (type == "boolean")) {
|
|
s = s + args[i];
|
|
}
|
|
else if (args[i] instanceof Array) {
|
|
s = s + "[" + args[i] + "]";
|
|
}
|
|
else if (args[i] instanceof Object) {
|
|
var start = true;
|
|
s = s + '{';
|
|
for (var name in args[i]) {
|
|
if (!start) {
|
|
s = s + ',';
|
|
}
|
|
s = s + '"' + name + '":';
|
|
var nameType = typeof args[i][name];
|
|
if ((nameType == "number") || (nameType == "boolean")) {
|
|
s = s + args[i][name];
|
|
}
|
|
else {
|
|
s = s + '"' + args[i][name] + '"';
|
|
}
|
|
start=false;
|
|
}
|
|
s = s + '}';
|
|
}
|
|
else {
|
|
s = s + '"' + args[i] + '"';
|
|
}
|
|
}
|
|
s = s + "]";
|
|
return s;
|
|
}
|
|
else {
|
|
return JSON.stringify(args);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Does a deep clone of the object.
|
|
*
|
|
* @param obj
|
|
* @return
|
|
*/
|
|
PhoneGap.clone = function(obj) {
|
|
if(!obj) {
|
|
return obj;
|
|
}
|
|
|
|
if(obj instanceof Array){
|
|
var retVal = new Array();
|
|
for(var i = 0; i < obj.length; ++i){
|
|
retVal.push(PhoneGap.clone(obj[i]));
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
if (obj instanceof Function) {
|
|
return obj;
|
|
}
|
|
|
|
if(!(obj instanceof Object)){
|
|
return obj;
|
|
}
|
|
|
|
retVal = new Object();
|
|
for(i in obj){
|
|
if(!(i in retVal) || retVal[i] != obj[i]) {
|
|
retVal[i] = PhoneGap.clone(obj[i]);
|
|
}
|
|
}
|
|
return retVal;
|
|
};
|
|
|
|
PhoneGap.callbackId = 0;
|
|
PhoneGap.callbacks = {};
|
|
|
|
/**
|
|
* Execute a PhoneGap command in a queued fashion, to ensure commands do not
|
|
* execute with any race conditions, and only run when PhoneGap is ready to
|
|
* recieve them.
|
|
* @param {String} command Command to be run in PhoneGap, e.g. "ClassName.method"
|
|
* @param {String[]} [args] Zero or more arguments to pass to the method
|
|
*/
|
|
// TODO: Not used anymore, should be removed.
|
|
PhoneGap.exec = function(clazz, action, args) {
|
|
try {
|
|
var callbackId = 0;
|
|
var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), false);
|
|
eval("var v="+r+";");
|
|
|
|
// If status is OK, then return value back to caller
|
|
if (v.status == 0) {
|
|
return v.message;
|
|
}
|
|
|
|
// If error, then display error
|
|
else {
|
|
console.log("Error: Status="+r.status+" Message="+v.message);
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
console.log("Error: "+e);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Execute a PhoneGap command. It is up to the native side whether this action is synch or async.
|
|
* The native side can return:
|
|
* Synchronous: PluginResult object as a JSON string
|
|
* Asynchrounous: Empty string ""
|
|
* If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError,
|
|
* depending upon the result of the action.
|
|
*
|
|
* @param {Function} success The success callback
|
|
* @param {Function} fail The fail callback
|
|
* @param {String} service The name of the service to use
|
|
* @param {String} action Action to be run in PhoneGap
|
|
* @param {String[]} [args] Zero or more arguments to pass to the method
|
|
*/
|
|
PhoneGap.execAsync = function(success, fail, service, action, args) {
|
|
try {
|
|
var callbackId = service + PhoneGap.callbackId++;
|
|
if (success || fail) {
|
|
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);
|
|
|
|
// If a result was returned
|
|
if (r.length > 0) {
|
|
eval("var v="+r+";");
|
|
|
|
// If status is OK, then return value back to caller
|
|
if (v.status == 0) {
|
|
|
|
// If there is a success callback, then call it now with returned value
|
|
if (success) {
|
|
success(v.message);
|
|
delete PhoneGap.callbacks[callbackId];
|
|
}
|
|
return v.message;
|
|
}
|
|
|
|
// If error, then display error
|
|
else {
|
|
console.log("Error: Status="+r.status+" Message="+v.message);
|
|
|
|
// If there is a fail callback, then call it now with returned value
|
|
if (fail) {
|
|
fail(v.message);
|
|
delete PhoneGap.callbacks[callbackId];
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.log("Error: "+e);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called by native code when returning successful result from an action.
|
|
*
|
|
* @param callbackId
|
|
* @param args
|
|
*/
|
|
PhoneGap.callbackSuccess = function(callbackId, args) {
|
|
if (PhoneGap.callbacks[callbackId]) {
|
|
try {
|
|
if (PhoneGap.callbacks[callbackId].success) {
|
|
PhoneGap.callbacks[callbackId].success(args.message);
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log("Error in success callback: "+callbackId+" = "+e);
|
|
}
|
|
delete PhoneGap.callbacks[callbackId];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called by native code when returning error result from an action.
|
|
*
|
|
* @param callbackId
|
|
* @param args
|
|
*/
|
|
PhoneGap.callbackError = function(callbackId, args) {
|
|
if (PhoneGap.callbacks[callbackId]) {
|
|
try {
|
|
if (PhoneGap.callbacks[callbackId].fail) {
|
|
PhoneGap.callbacks[callbackId].fail(args.message);
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log("Error in error callback: "+callbackId+" = "+e);
|
|
}
|
|
delete PhoneGap.callbacks[callbackId];
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Internal function used to dispatch the request to PhoneGap. It processes the
|
|
* command queue and executes the next command on the list. If one of the
|
|
* arguments is a JavaScript object, it will be passed on the QueryString of the
|
|
* url, which will be turned into a dictionary on the other end.
|
|
* @private
|
|
*/
|
|
// TODO: Is this used?
|
|
PhoneGap.run_command = function() {
|
|
if (!PhoneGap.available || !PhoneGap.queue.ready)
|
|
return;
|
|
|
|
PhoneGap.queue.ready = false;
|
|
|
|
var args = PhoneGap.queue.commands.shift();
|
|
if (PhoneGap.queue.commands.length == 0) {
|
|
clearInterval(PhoneGap.queue.timer);
|
|
PhoneGap.queue.timer = null;
|
|
}
|
|
|
|
var uri = [];
|
|
var dict = null;
|
|
for (var i = 1; i < args.length; i++) {
|
|
var arg = args[i];
|
|
if (arg == undefined || arg == null)
|
|
arg = '';
|
|
if (typeof(arg) == 'object')
|
|
dict = arg;
|
|
else
|
|
uri.push(encodeURIComponent(arg));
|
|
}
|
|
var url = "gap://" + args[0] + "/" + uri.join("/");
|
|
if (dict != null) {
|
|
var query_args = [];
|
|
for (var name in dict) {
|
|
if (typeof(name) != 'string')
|
|
continue;
|
|
query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name]));
|
|
}
|
|
if (query_args.length > 0)
|
|
url += "?" + query_args.join("&");
|
|
}
|
|
document.location = url;
|
|
|
|
};
|
|
|
|
/**
|
|
* This is only for Android.
|
|
*
|
|
* Internal function that uses XHR to call into PhoneGap Java code and retrieve
|
|
* any JavaScript code that needs to be run. This is used for callbacks from
|
|
* Java to JavaScript.
|
|
*/
|
|
PhoneGap.JSCallback = function() {
|
|
var xmlhttp = new XMLHttpRequest();
|
|
|
|
// Callback function when XMLHttpRequest is ready
|
|
xmlhttp.onreadystatechange=function(){
|
|
if(xmlhttp.readyState == 4){
|
|
|
|
// If callback has JavaScript statement to execute
|
|
if (xmlhttp.status == 200) {
|
|
|
|
var msg = xmlhttp.responseText;
|
|
setTimeout(function() {
|
|
try {
|
|
var t = eval(msg);
|
|
}
|
|
catch (e) {
|
|
console.log("JSCallback Error: "+e);
|
|
}
|
|
}, 1);
|
|
setTimeout(PhoneGap.JSCallback, 1);
|
|
}
|
|
|
|
// If callback ping (used to keep XHR request from timing out)
|
|
else if (xmlhttp.status == 404) {
|
|
setTimeout(PhoneGap.JSCallback, 10);
|
|
}
|
|
|
|
// If error, restart callback server
|
|
else {
|
|
console.log("JSCallback Error: Request failed.");
|
|
CallbackServer.restartServer();
|
|
setTimeout(PhoneGap.JSCallback, 100);
|
|
}
|
|
}
|
|
}
|
|
|
|
xmlhttp.open("GET", "http://127.0.0.1:"+CallbackServer.getPort()+"/" , true);
|
|
xmlhttp.send();
|
|
};
|
|
|
|
/**
|
|
* Create a UUID
|
|
*
|
|
* @return
|
|
*/
|
|
PhoneGap.createUUID = function() {
|
|
return PhoneGap.UUIDcreatePart(4) + '-' +
|
|
PhoneGap.UUIDcreatePart(2) + '-' +
|
|
PhoneGap.UUIDcreatePart(2) + '-' +
|
|
PhoneGap.UUIDcreatePart(2) + '-' +
|
|
PhoneGap.UUIDcreatePart(6);
|
|
};
|
|
|
|
PhoneGap.UUIDcreatePart = function(length) {
|
|
var uuidpart = "";
|
|
for (var i=0; i<length; i++) {
|
|
var uuidchar = parseInt((Math.random() * 256)).toString(16);
|
|
if (uuidchar.length == 1) {
|
|
uuidchar = "0" + uuidchar;
|
|
}
|
|
uuidpart += uuidchar;
|
|
}
|
|
return uuidpart;
|
|
};
|
|
|
|
PhoneGap.close = function(context, func, params) {
|
|
if (typeof params === 'undefined') {
|
|
return function() {
|
|
return func.apply(context, arguments);
|
|
}
|
|
} else {
|
|
return function() {
|
|
return func.apply(context, params);
|
|
}
|
|
}
|
|
};
|
|
|