commit 197dcd2ae7a39747c587816d8bb7e7e693974134 Author: Ryan McGrath Date: Wed May 11 21:06:42 2011 +0900 "Open Sourcing" this, perhaps people will find it interesting/useful. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..639cdd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +local.properties diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100755 index 0000000..b580340 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cd5b253 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009 - 2010 Ryan McGrath + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.txt b/README.txt new file mode 100755 index 0000000..5a9fa0e --- /dev/null +++ b/README.txt @@ -0,0 +1,63 @@ +Katanakana! (カタナカナ!) +--------------------------------------------------------------------------------------------------------------------------- +Katanakana is an Android application built using the **[Phonegap Mobile Framework](http://www.phonegap.com/)** that +aims to teach people one of the Japanese alphabets, Katakana. Katakana is often noted to be one of the more useful +languages to have some sense of if you're visiting Japan, as it's largely used on menus and for words that aren't +of Japanese origin. + +It aims to teach through a somewhat algorithmic approach; what this basically entails is a simple formula that watches +for how often a character is incorrectly identified by the user, and will adjust its "resurfacing" time based on the average +amount of seconds it seems to take a person to commit a character to short term memory. This app is built to try and use +short term memory as a jumping off point; **[for more information on the approach used here, consult the initial release article](http://venodesigns.net/2011/03/06/hacking-the-human-brain/)**. + +Something to note is that this application was built over the course of roughly 3 hours; it's an initial first shot/attempt +that may or may not be 100% beneficial to people, but with refinement could be quite interesting. Katanakana is released under +an MIT license in the hopes that others might find the approach interesting, and further extend it to the benefit of any party +interested in learning Japanese. + + +The Version of Katanakana in the Android Market... +--------------------------------------------------------------------------------------------------------------------------- +The version of this application is listed on the Android Market, and priced at 99 cents USD. The original author keeps it on +there for the hell of it, but anyone is free to repurpose this and upload it to the market for themselves, just give proper +attribution as the MIT license specifies. :) + +**[Katanakana on the Android Market](https://market.android.com/details?id=com.phonegap.Katanakana)** + + +Questions, Comments? +--------------------------------------------------------------------------------------------------------------------------- +Email: **ryan [at] venodesigns _dot_ net** +Twitter: **[@ryanmcgrath](http://twitter.com/ryanmcgrath)** +Web: **[Veno Designs](http://venodesigns.net/)** + + +Requirements for Compiling +------------------------------------------- +**[Android Development Environment](http://developer.android.com/sdk/index.html)** +**[Ant, if you're using a Terminal]()** +**[Eclipse, if you're a Java-head who uses IDEs](http://www.eclipse.org/)** + + +Compiling This Yourself (Eclipse) +------------------------------------------- +Eclipse has an Android plugin to facilitate this. I don't use it because +Eclipse and IDEs just get in my way (what this basically means is that I don't even +know where to begin with this). Documentation should be abound, though, as it's an official +Google supported method for Android development. + +If you work with this enough, feel free to submit a pull request for this section of the README, +other Eclipse users would surely appreciate it! + + +Compiling This Yourself (Terminal) +------------------------------------------- +To compile/run on a phone/device in debug mode, run (in the root directory): + + ant -v debug install + +To compile/store a signed version, **[generate an application keystore](http://developer.android.com/guide/publishing/app-signing.html)** and run: + + ant release + +It should ask you for a keystore password/etc; if you did the generation step, you should know them. ;) diff --git a/assets/www/index.html b/assets/www/index.html new file mode 100755 index 0000000..9406ed5 --- /dev/null +++ b/assets/www/index.html @@ -0,0 +1,520 @@ + + + + + + Katanakana + + + + + +
+

You Can Do This!

+

+ Learning any of the Japanese alphabets can seem like a daunting task because there's + just so many characters. Thing is, this doesn't have to be difficult! +

+ +

+ The human brain works in a mysterious fashion, and everybody has their own point at which the brain commits something + to memory. Katanakana watches how you identify characters, and will figure out when your brain has the best chance of + storing the relations you need to read Japanese. +

+ +

+ Once this is determined, it'll make you re-identify those characters + at the ideal moment, and you'll be good to go! +

+ + +
+ + + + + + + diff --git a/assets/www/phonegap.0.9.4.js b/assets/www/phonegap.0.9.4.js new file mode 100755 index 0000000..593f6cd --- /dev/null +++ b/assets/www/phonegap.0.9.4.js @@ -0,0 +1,3498 @@ +/* + * 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 + */ + + +/** + * 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(); + } + var len = i; + for (var j=0; j + * + * @param name The plugin name + * @param obj The plugin object + */ +PhoneGap.addPlugin = function(name, obj) { + if (!window.plugins[name]) { + window.plugins[name] = obj; + } + else { + console.log("Error: Plugin "+name+" already exists."); + } +} + +/** + * 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'); + + +// Array of channels that must fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]; + +// Hashtable of user defined channels that must also fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsMap = {}; + +/** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up PhoneGap's "deviceready" event until the feature has been initialized + * and PhoneGap.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ +PhoneGap.waitForInitialization = function(feature) { + if (feature) { + var channel = new PhoneGap.Channel(feature); + PhoneGap.deviceReadyChannelsMap[feature] = channel; + PhoneGap.deviceReadyChannelsArray.push(channel); + } +}; + +/** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ +PhoneGap.initializationComplete = function(feature) { + var channel = PhoneGap.deviceReadyChannelsMap[feature]; + if (channel) { + channel.fire(); + } +}; + +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + setTimeout(function() { + if (CallbackServer.usePolling()) { + PhoneGap.JSCallbackPolling(); + } + else { + PhoneGap.JSCallback(); + } + }, 1); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + + 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.deviceReadyChannelsArray); + +}, [ 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(); +}, 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); + if (PhoneGap.onDeviceReady.fired) { + PhoneGap.onResume.fire(); + } + } else if (e == 'pause') { + PhoneGap.onPause.subscribe(handler); + } else { + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +/** + * 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 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 (args[i][name] != null) { + if (!start) { + s = s + ','; + } + s = s + '"' + name + '":'; + var nameType = typeof args[i][name]; + if ((nameType == "number") || (nameType == "boolean")) { + s = s + args[i][name]; + } + else if ((typeof args[i][name]) == 'function') { + // don't copy the functions + s = s + '""'; + } + else if (args[i][name] instanceof Object) { + s = s + this.stringify(args[i][name]); + } + else { + s = s + '"' + args[i][name] + '"'; + } + start=false; + } + } + s = s + '}'; + } + else { + var a = args[i].replace(/\\/g, '\\\\'); + a = a.replace(/"/g, '\\"'); + s = s + '"' + a + '"'; + } + } + 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; + } + + if (obj instanceof Date) { + 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 = {}; +PhoneGap.callbackStatus = { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }; + + +/** + * 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.exec = 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 == PhoneGap.callbackStatus.OK) { + + // If there is a success callback, then call it now with returned value + if (success) { + try { + success(v.message); + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status == PhoneGap.callbackStatus.NO_RESULT) { + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + + // 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) { + try { + fail(v.message); + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + 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]) { + + // If result is to be sent to callback + if (args.status == PhoneGap.callbackStatus.OK) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + 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); + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + 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; + +}; + +PhoneGap.JSCallbackPort = null; +PhoneGap.JSCallbackToken = null; + +/** + * 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) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + 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 security error + else if (xmlhttp.status == 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status == 503) { + console.log("JSCallback Error: Service unavailable. Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status == 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, restart callback server + else { + console.log("JSCallback Error: Request failed."); + CallbackServer.restartServer(); + PhoneGap.JSCallbackPort = null; + PhoneGap.JSCallbackToken = null; + setTimeout(PhoneGap.JSCallback, 100); + } + } + } + + if (PhoneGap.JSCallbackPort == null) { + PhoneGap.JSCallbackPort = CallbackServer.getPort(); + } + if (PhoneGap.JSCallbackToken == null) { + PhoneGap.JSCallbackToken = CallbackServer.getToken(); + } + xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); + xmlhttp.send(); +}; + +/** + * The polling period to use with JSCallbackPolling. + * This can be changed by the application. The default is 50ms. + */ +PhoneGap.JSCallbackPollingPeriod = 50; + +/** + * This is only for Android. + * + * Internal function that uses polling 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.JSCallbackPolling = function() { + var msg = CallbackServer.getJavascript(); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallbackPolling, 1); + } + else { + setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); + } +}; + +/** + * 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 frequency + 10 sec + PhoneGap.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); + + // Start watch timer + var id = PhoneGap.createUUID(); + navigator.accelerometer.timers[id] = setInterval(function() { + PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); + }, (frequency ? frequency : 1)); + + return id; +}; + +/** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ +Accelerometer.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.accelerometer.timers[id] != undefined) { + clearInterval(navigator.accelerometer.timers[id]); + delete navigator.accelerometer.timers[id]; + } +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer(); +}); +/* + * 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 + */ + +/** + * Constructor + */ +function App() { +} + +/** + * Clear the resource cache. + */ +App.prototype.clearCache = function() { + PhoneGap.exec(null, null, "App", "clearCache", []); +}; + +/** + * Load the url into the webview. + * + * @param url The URL to load + * @param props Properties that can be passed in to the activity: + * wait: int => wait msec before loading URL + * loadingDialog: "Title,Message" => display a native loading dialog + * hideLoadingDialogOnPage: boolean => hide loadingDialog when page loaded instead of when deviceready event occurs. + * loadInWebView: boolean => cause all links on web page to be loaded into existing web view, instead of being loaded into new browser. + * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error + * errorUrl: URL => URL to load if there's an error loading specified URL with loadUrl(). Should be a local URL such as file:///android_asset/www/error.html"); + * keepRunning: boolean => enable app to keep running in background + * + * Example: + * App app = new App(); + * app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); + */ +App.prototype.loadUrl = function(url, props) { + PhoneGap.exec(null, null, "App", "loadUrl", [url, props]); +}; + +/** + * Cancel loadUrl that is waiting to be loaded. + */ +App.prototype.cancelLoadUrl = function() { + PhoneGap.exec(null, null, "App", "cancelLoadUrl", []); +}; + +/** + * Clear web history in this web view. + * Instead of BACK button loading the previous web page, it will exit the app. + */ +App.prototype.clearHistory = function() { + PhoneGap.exec(null, null, "App", "clearHistory", []); +}; + +/** + * Add a class that implements a service. + * + * @param serviceType + * @param className + */ +App.prototype.addService = function(serviceType, className) { + PhoneGap.exec(null, null, "App", "addService", [serviceType, className]); +}; +/* + * 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 + */ + +/** + * This class provides access to the device camera. + * + * @constructor + */ +Camera = function() { + this.successCallback = null; + this.errorCallback = null; + this.options = null; +}; + +/** + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; +Camera.prototype.DestinationType = Camera.DestinationType; + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=DATA_URL. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +Camera.prototype.getPicture = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback != "function") { + console.log("Camera Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Camera Error: errorCallback is not a function"); + return; + } + + this.options = options; + var quality = 80; + if (options.quality) { + quality = this.options.quality; + } + var destinationType = Camera.DestinationType.DATA_URL; + if (this.options.destinationType) { + destinationType = this.options.destinationType; + } + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof this.options.sourceType == "number") { + sourceType = this.options.sourceType; + } + PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.camera == "undefined") navigator.camera = new Camera(); +}); +/* + * 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 + */ + +/** + * This class provides access to device Compass data. + * @constructor + */ +function Compass() { + /** + * The last known Compass position. + */ + this.lastHeading = null; + + /** + * List of compass watch timers + */ + this.timers = {}; +}; + +Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; + +/** + * Asynchronously aquires the current heading. + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) + */ +Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback != "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Get heading + PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); +}; + +/** + * Asynchronously aquires the heading repeatedly at a given interval. + * + * @param {Function} successCallback The function to call each time the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { + + // Default interval (100 msec) + var frequency = (options != undefined) ? options.frequency : 100; + + // successCallback required + if (typeof successCallback != "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Make sure compass timeout > frequency + 10 sec + PhoneGap.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Compass", "getTimeout", []); + + // Start watch timer to get headings + var id = PhoneGap.createUUID(); + navigator.compass.timers[id] = setInterval( + function() { + PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); + }, (frequency ? frequency : 1)); + + return id; +}; + + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchHeading. + */ +Compass.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.compass.timers[id]) { + clearInterval(navigator.compass.timers[id]); + delete navigator.compass.timers[id]; + } +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.compass == "undefined") navigator.compass = new Compass(); +}); +/* + * 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 + */ + +/** +* Contains information about a single contact. +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {ContactField[]} phoneNumbers array of phone numbers +* @param {ContactField[]} emails array of email addresses +* @param {ContactAddress[]} addresses array of addresses +* @param {ContactField[]} ims instant messaging user ids +* @param {ContactOrganization[]} organizations +* @param {DOMString} revision date contact was last updated +* @param {DOMString} birthday contact's birthday +* @param {DOMString} gender contact's gender +* @param {DOMString} note user notes about contact +* @param {ContactField[]} photos +* @param {ContactField[]} categories +* @param {ContactField[]} urls contact's web sites +* @param {DOMString} timezone the contacts time zone +*/ +var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, revision, birthday, gender, note, photos, categories, urls, timezone) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.revision = revision || null; + this.birthday = birthday || null; + this.gender = gender || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] + this.timezone = timezone || null; +}; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + if (this.id == null) { + var errorObj = new ContactError(); + errorObj.code = ContactError.NOT_FOUND_ERROR; + errorCB(errorObj); + } + else { + PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = PhoneGap.clone(this); + clonedContact.id = null; + clonedContact.rawId = null; + // Loop through and clear out any id's in phones, emails, etc. + if (clonedContact.phoneNumbers) { + for (i=0; i][;base64], + * + * @param file The name of the file + */ +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = file; + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } + + var me = this; + + // Read file + navigator.fileMgr.readAsDataURL(file, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; + +/** + * Read file and return data as a binary data. + * + * @param file The name of the file + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +/** + * Read file and return data as a binary data. + * + * @param file The name of the file + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +//----------------------------------------------------------------------------- +// File Writer +//----------------------------------------------------------------------------- + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @param filePath the file to write to + * @param append if true write to the end of the file, otherwise overwrite the file + */ +function FileWriter(filePath, append) { + this.fileName = ""; + this.length = 0; + if (filePath) { + var f = navigator.fileMgr.getFileProperties(filePath); + this.fileName = f.name; + this.length = f.size; + } + // default is to write at the beginning of the file + this.position = (append !== true) ? 0 : this.length; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw FileError.INVALID_STATE_ERR; + } + + // set error + var error = new FileError(); + error.code = error.ABORT_ERR; + this.error = error; + + // If error callback + if (typeof this.onerror == "function") { + var evt = File._createEvent("error", this); + this.onerror(evt); + } + // If abort callback + if (typeof this.onabort == "function") { + var evt = File._createEvent("abort", this); + this.onabort(evt); + } + + this.readyState = FileWriter.DONE; + + // If write end callback + if (typeof this.onwriteend == "function") { + var evt = File._createEvent("writeend", this); + this.onwriteend(evt); + } +}; + +/** + * @Deprecated: use write instead + * + * @param file to write the data to + * @param text to be written + * @param bAppend if true write to end of file, otherwise overwrite the file + */ +FileWriter.prototype.writeAsText = function(file, text, bAppend) { + // Throw an exception if we are already writing a file + if (this.readyState == FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + if (bAppend != true) { + bAppend = false; // for null values + } + + this.fileName = file; + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart == "function") { + var evt = File._createEvent("writestart", me); + me.onwritestart(evt); + } + + // Write file + navigator.fileMgr.writeAsText(file, text, bAppend, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save result + me.result = r; + + // If onwrite callback + if (typeof me.onwrite == "function") { + var evt = File._createEvent("write", me); + me.onwrite(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + } + ); + +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState == FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart == "function") { + var evt = File._createEvent("writestart", me); + me.onwritestart(evt); + } + + // Write file + navigator.fileMgr.write(this.fileName, text, this.position, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // So if the user wants to keep appending to the file + me.length = Math.max(me.length, me.position + r); + // position always increases by bytes written because file would be extended + me.position += r; + + // If onwrite callback + if (typeof me.onwrite == "function") { + var evt = File._createEvent("write", me); + me.onwrite(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + } + ); + +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + if (!offset) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger then file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState == FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart == "function") { + var evt = File._createEvent("writestart", me); + me.onwritestart(evt); + } + + // Write file + navigator.fileMgr.truncate(this.fileName, size, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r);; + + // If onwrite callback + if (typeof me.onwrite == "function") { + var evt = File._createEvent("write", me); + me.onwrite(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend == "function") { + var evt = File._createEvent("writeend", me); + me.onwriteend(evt); + } + } + ); +}; + +/* + * 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 + */ + +/** + * FileTransfer uploads a file to a remote server. + */ +function FileTransfer() {}; + +/** + * FileUploadResult + */ +function FileUploadResult() { + this.bytesSent = 0; + this.responseCode = null; + this.response = null; +}; + +/** + * FileTransferError + */ +function FileTransferError() { + this.code = null; +}; + +FileTransferError.FILE_NOT_FOUND_ERR = 1; +FileTransferError.INVALID_URL_ERR = 2; +FileTransferError.CONNECTION_ERR = 3; + +/** +* Given an absolute file path, uploads a file on the device to a remote server +* using a multipart HTTP request. +* @param filePath {String} Full path of the file on the device +* @param server {String} URL of the server to receive the file +* @param successCallback (Function} Callback to be invoked when upload has completed +* @param errorCallback {Function} Callback to be invoked upon error +* @param options {FileUploadOptions} Optional parameters such as file name and mimetype +*/ +FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, debug) { + + // check for options + var fileKey = null; + var fileName = null; + var mimeType = null; + var params = null; + if (options) { + fileKey = options.fileKey; + fileName = options.fileName; + mimeType = options.mimeType; + if (options.params) { + params = options.params; + } + else { + params = {}; + } + } + + PhoneGap.exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, debug]); +}; + +/** + * Options to customize the HTTP request used to upload files. + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + */ +function FileUploadOptions(fileKey, fileName, mimeType, params) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.params = params || null; +}; +/* + * 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 + */ + +/** + * This class provides access to device GPS data. + * @constructor + */ +function Geolocation() { + + // The last known GPS position. + this.lastPosition = null; + + // Geolocation listeners + this.listeners = {}; +}; + +/** + * Position error object + * + * @param code + * @param message + */ +function PositionError(code, message) { + this.code = code; + this.message = message; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +/** + * Asynchronously aquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ +Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { + if (navigator._geo.listeners["global"]) { + console.log("Geolocation Error: Still waiting for previous getCurrentPosition() request."); + try { + errorCallback(new PositionError(PositionError.TIMEOUT, "Geolocation Error: Still waiting for previous getCurrentPosition() request.")); + } catch (e) { + } + return; + } + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } + navigator._geo.listeners["global"] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.exec(null, null, "Geolocation", "getCurrentLocation", [enableHighAccuracy, timeout, maximumAge]); +} + +/** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) { + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.frequency != "undefined") { + maximumAge = options.frequency; + } + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } + var id = PhoneGap.createUUID(); + navigator._geo.listeners[id] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.exec(null, null, "Geolocation", "start", [id, enableHighAccuracy, timeout, maximumAge]); + return id; +}; + +/* + * Native callback when watch position has a new position. + * PRIVATE METHOD + * + * @param {String} id + * @param {Number} lat + * @param {Number} lng + * @param {Number} alt + * @param {Number} altacc + * @param {Number} head + * @param {Number} vel + * @param {Number} stamp + */ +Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, stamp) { + var coords = new Coordinates(lat, lng, alt, altacc, head, vel); + var loc = new Position(coords, stamp); + try { + if (lat == "undefined" || lng == "undefined") { + navigator._geo.listeners[id].fail(new PositionError(PositionError.POSITION_UNAVAILABLE, "Lat/Lng are undefined.")); + } + else { + navigator._geo.lastPosition = loc; + navigator._geo.listeners[id].success(loc); + } + } + catch (e) { + console.log("Geolocation Error: Error calling success callback function."); + } + + if (id == "global") { + delete navigator._geo.listeners["global"]; + } +}; + +/** + * Native callback when watch position has an error. + * PRIVATE METHOD + * + * @param {String} id The ID of the watch + * @param {Number} code The error code + * @param {String} msg The error message + */ +Geolocation.prototype.fail = function(id, code, msg) { + try { + navigator._geo.listeners[id].fail(new PositionError(code, msg)); + } + catch (e) { + console.log("Geolocation Error: Error calling error callback function."); + } +}; + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ +Geolocation.prototype.clearWatch = function(id) { + PhoneGap.exec(null, null, "Geolocation", "stop", [id]); + delete navigator._geo.listeners[id]; +}; + +/** + * Force the PhoneGap geolocation to be used instead of built-in. + */ +Geolocation.usingPhoneGap = false; +Geolocation.usePhoneGap = function() { + if (Geolocation.usingPhoneGap) { + return; + } + Geolocation.usingPhoneGap = true; + + // Set built-in geolocation methods to our own implementations + // (Cannot replace entire geolocation, but can replace individual methods) + navigator.geolocation.setLocation = navigator._geo.setLocation; + navigator.geolocation.getCurrentPosition = navigator._geo.getCurrentPosition; + navigator.geolocation.watchPosition = navigator._geo.watchPosition; + navigator.geolocation.clearWatch = navigator._geo.clearWatch; + navigator.geolocation.start = navigator._geo.start; + navigator.geolocation.stop = navigator._geo.stop; +}; + +PhoneGap.addConstructor(function() { + navigator._geo = new Geolocation(); + + // No native geolocation object for Android 1.x, so use PhoneGap geolocation + if (typeof navigator.geolocation == 'undefined') { + navigator.geolocation = navigator._geo; + Geolocation.usingPhoneGap = true; + } +}); + +/* + * 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(); +} +/* + * 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 + */ + +/** + * List of media objects. + * PRIVATE + */ +PhoneGap.mediaObjects = {}; + +/** + * Object that receives native callbacks. + * PRIVATE + */ +PhoneGap.Media = function() {}; + +/** + * Get the media object. + * PRIVATE + * + * @param id The media object id (string) + */ +PhoneGap.Media.getMediaObject = function(id) { + return PhoneGap.mediaObjects[id]; +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param status The status code (int) + * @param msg The status message (string) + */ +PhoneGap.Media.onStatus = function(id, msg, value) { + var media = PhoneGap.mediaObjects[id]; + + // If state update + if (msg == Media.MEDIA_STATE) { + if (value == Media.MEDIA_STOPPED) { + if (media.successCallback) { + media.successCallback(); + } + } + if (media.statusCallback) { + media.statusCallback(value); + } + } + else if (msg == Media.MEDIA_DURATION) { + media._duration = value; + } + else if (msg == Media.MEDIA_ERROR) { + if (media.errorCallback) { + media.errorCallback(value); + } + } +}; + +/** + * This class provides access to the device media, interfaces to both sound and video + * + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() - OPTIONAL + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) - OPTIONAL + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) - OPTIONAL + * @param positionCallback The callback to be called when media position has changed. + * positionCallback(long position) - OPTIONAL + */ +Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) { + + // successCallback optional + if (successCallback && (typeof successCallback != "function")) { + console.log("Media Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Media Error: errorCallback is not a function"); + return; + } + + // statusCallback optional + if (statusCallback && (typeof statusCallback != "function")) { + console.log("Media Error: statusCallback is not a function"); + return; + } + + // statusCallback optional + if (positionCallback && (typeof positionCallback != "function")) { + console.log("Media Error: positionCallback is not a function"); + return; + } + + this.id = PhoneGap.createUUID(); + PhoneGap.mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this.positionCallback = positionCallback; + this._duration = -1; + this._position = -1; +}; + +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_ERROR = 9; + +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; + +// TODO: Will MediaError be used? +/** + * This class contains information about any Media errors. + * @constructor + */ +function MediaError() { + this.code = null, + this.message = ""; +}; + +MediaError.MEDIA_ERR_ABORTED = 1; +MediaError.MEDIA_ERR_NETWORK = 2; +MediaError.MEDIA_ERR_DECODE = 3; +MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; + +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function() { + PhoneGap.exec(null, null, "Media", "startPlayingAudio", [this.id, this.src]); +}; + +/** + * Stop playing audio file. + */ +Media.prototype.stop = function() { + return PhoneGap.exec(null, null, "Media", "stopPlayingAudio", [this.id]); +}; + +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + PhoneGap.exec(null, null, "Media", "pausePlayingAudio", [this.id]); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + * + * @return + */ +Media.prototype.getCurrentPosition = function(success, fail) { + PhoneGap.exec(success, fail, "Media", "getCurrentPositionAudio", [this.id]); +}; + +/** + * Start recording audio file. + */ +Media.prototype.startRecord = function() { + PhoneGap.exec(null, null, "Media", "startRecordingAudio", [this.id, this.src]); +}; + +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + PhoneGap.exec(null, null, "Media", "stopRecordingAudio", [this.id]); +}; + +/** + * Release the resources. + */ +Media.prototype.release = function() { + PhoneGap.exec(null, null, "Media", "release", [this.id]); +}; + +/* + * 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 + */ + +/** + * This class contains information about any NetworkStatus. + * @constructor + */ +function NetworkStatus() { + //this.code = null; + //this.message = ""; +}; + +NetworkStatus.NOT_REACHABLE = 0; +NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; +NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2; + +/** + * This class provides access to device Network data (reachability). + * @constructor + */ +function Network() { + /** + * The last known Network status. + * { hostName: string, ipAddress: string, + remoteHostStatus: int(0/1/2), internetConnectionStatus: int(0/1/2), localWiFiConnectionStatus: int (0/2) } + */ + this.lastReachability = null; +}; + +/** + * Called by the geolocation framework when the reachability status has changed. + * @param {Reachibility} reachability The current reachability status. + */ +// TODO: Callback from native code not implemented for Android +Network.prototype.updateReachability = function(reachability) { + this.lastReachability = reachability; +}; + +/** + * Determine if a URI is reachable over the network. + + * @param {Object} uri + * @param {Function} callback + * @param {Object} options (isIpAddress:boolean) + */ +Network.prototype.isReachable = function(uri, callback, options) { + var isIpAddress = false; + if (options && options.isIpAddress) { + isIpAddress = options.isIpAddress; + } + PhoneGap.exec(callback, null, "Network Status", "isReachable", [uri, isIpAddress]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.network == "undefined") navigator.network = new Network(); +}); + +/* + * 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 + */ + +/** + * This class provides access to notifications on the device. + */ +function Notification() { +} + +/** + * Open a native alert dialog, with a customizable title and button text. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} completeCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Alert) + * @param {String} buttonLabel Label of the close button (default: OK) + */ +Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) { + var _title = (title || "Alert"); + var _buttonLabel = (buttonLabel || "OK"); + PhoneGap.exec(completeCallback, null, "Notification", "alert", [message,_title,_buttonLabel]); +}; + +/** + * Open a native confirm dialog, with a customizable title and button text. + * The result that the user selects is returned to the result callback. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} resultCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Confirm) + * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') + */ +Notification.prototype.confirm = function(message, resultCallback, title, buttonLabels) { + var _title = (title || "Confirm"); + var _buttonLabels = (buttonLabels || "OK,Cancel"); + PhoneGap.exec(resultCallback, null, "Notification", "confirm", [message,_title,_buttonLabels]); +}; + +/** + * Start spinning the activity indicator on the statusbar + */ +Notification.prototype.activityStart = function() { + PhoneGap.exec(null, null, "Notification", "activityStart", ["Busy","Please wait..."]); +}; + +/** + * Stop spinning the activity indicator on the statusbar, if it's currently spinning + */ +Notification.prototype.activityStop = function() { + PhoneGap.exec(null, null, "Notification", "activityStop", []); +}; + +/** + * Display a progress dialog with progress bar that goes from 0 to 100. + * + * @param {String} title Title of the progress dialog. + * @param {String} message Message to display in the dialog. + */ +Notification.prototype.progressStart = function(title, message) { + PhoneGap.exec(null, null, "Notification", "progressStart", [title, message]); +}; + +/** + * Set the progress dialog value. + * + * @param {Number} value 0-100 + */ +Notification.prototype.progressValue = function(value) { + PhoneGap.exec(null, null, "Notification", "progressValue", [value]); +}; + +/** + * Close the progress dialog. + */ +Notification.prototype.progressStop = function() { + PhoneGap.exec(null, null, "Notification", "progressStop", []); +}; + +/** + * Causes the device to blink a status LED. + * + * @param {Integer} count The number of blinks. + * @param {String} colour The colour of the light. + */ +Notification.prototype.blink = function(count, colour) { + // NOT IMPLEMENTED +}; + +/** + * Causes the device to vibrate. + * + * @param {Integer} mills The number of milliseconds to vibrate for. + */ +Notification.prototype.vibrate = function(mills) { + PhoneGap.exec(null, null, "Notification", "vibrate", [mills]); +}; + +/** + * Causes the device to beep. + * On Android, the default notification ringtone is played "count" times. + * + * @param {Integer} count The number of beeps. + */ +Notification.prototype.beep = function(count) { + PhoneGap.exec(null, null, "Notification", "beep", [count]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); +}); + +/* + * 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 + */ + +/** + * This class contains position information. + * @param {Object} lat + * @param {Object} lng + * @param {Object} acc + * @param {Object} alt + * @param {Object} altacc + * @param {Object} head + * @param {Object} vel + * @constructor + */ +function Position(coords, timestamp) { + this.coords = coords; + this.timestamp = (timestamp != 'undefined') ? timestamp : new Date().getTime(); +} + +function Coordinates(lat, lng, alt, acc, head, vel, altacc) { + /** + * The latitude of the position. + */ + this.latitude = lat; + /** + * The longitude of the position, + */ + this.longitude = lng; + /** + * The accuracy of the position. + */ + this.accuracy = acc; + /** + * The altitude of the position. + */ + this.altitude = alt; + /** + * The direction the device is moving at the position. + */ + this.heading = head; + /** + * The velocity with which the device is moving at the position. + */ + this.speed = vel; + /** + * The altitude accuracy of the position. + */ + this.altitudeAccuracy = (altacc != 'undefined') ? altacc : null; +} + +/** + * This class specifies the options for requesting position data. + * @constructor + */ +function PositionOptions() { + /** + * Specifies the desired position accuracy. + */ + this.enableHighAccuracy = true; + /** + * The timeout after which if position data cannot be obtained the errorCallback + * is called. + */ + this.timeout = 10000; +} + +/** + * This class contains information about any GSP errors. + * @constructor + */ +function PositionError() { + this.code = null; + this.message = ""; +} + +PositionError.UNKNOWN_ERROR = 0; +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; +/* + * 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 + */ + +/* + * This is purely for the Android 1.5/1.6 HTML 5 Storage + * I was hoping that Android 2.0 would deprecate this, but given the fact that + * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required + */ + +/** + * Storage object that is called by native code when performing queries. + * PRIVATE METHOD + */ +var DroidDB = function() { + this.queryQueue = {}; +}; + +/** + * Callback from native code when query is complete. + * PRIVATE METHOD + * + * @param id Query id + */ +DroidDB.prototype.completeQuery = function(id, data) { + var query = this.queryQueue[id]; + if (query) { + try { + delete this.queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + + // Save query results + var r = new DroidDB_Result(); + r.rows.resultSet = data; + r.rows.length = data.length; + try { + if (typeof query.successCallback == 'function') { + query.successCallback(query.tx, r); + } + } catch (ex) { + console.log("executeSql error calling user success callback: "+ex); + } + + tx.queryComplete(id); + } + } catch (e) { + console.log("executeSql error: "+e); + } + } +}; + +/** + * Callback from native code when query fails + * PRIVATE METHOD + * + * @param reason Error message + * @param id Query id + */ +DroidDB.prototype.fail = function(reason, id) { + var query = this.queryQueue[id]; + if (query) { + try { + delete this.queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + tx.queryList = {}; + + try { + if (typeof query.errorCallback == 'function') { + query.errorCallback(query.tx, reason); + } + } catch (ex) { + console.log("executeSql error calling user error callback: "+ex); + } + + tx.queryFailed(id, reason); + } + + } catch (e) { + console.log("executeSql error: "+e); + } + } +}; + +var DatabaseShell = function() { +}; + +/** + * Start a transaction. + * Does not support rollback in event of failure. + * + * @param process {Function} The transaction function + * @param successCallback {Function} + * @param errorCallback {Function} + */ +DatabaseShell.prototype.transaction = function(process, errorCallback, successCallback) { + var tx = new DroidDB_Tx(); + tx.successCallback = successCallback; + tx.errorCallback = errorCallback; + try { + process(tx); + } catch (e) { + console.log("Transaction error: "+e); + if (tx.errorCallback) { + try { + tx.errorCallback(e); + } catch (ex) { + console.log("Transaction error calling user error callback: "+e); + } + } + } +}; + +/** + * Transaction object + * PRIVATE METHOD + */ +var DroidDB_Tx = function() { + + // Set the id of the transaction + this.id = PhoneGap.createUUID(); + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + + // Query list + this.queryList = {}; +}; + +/** + * Mark query in transaction as complete. + * If all queries are complete, call the user's transaction success callback. + * + * @param id Query id + */ +DroidDB_Tx.prototype.queryComplete = function(id) { + delete this.queryList[id]; + + // If no more outstanding queries, then fire transaction success + if (this.successCallback) { + var count = 0; + for (var i in this.queryList) { + count++; + } + if (count == 0) { + try { + this.successCallback(); + } catch(e) { + console.log("Transaction error calling user success callback: " + e); + } + } + } +}; + +/** + * Mark query in transaction as failed. + * + * @param id Query id + * @param reason Error message + */ +DroidDB_Tx.prototype.queryFailed = function(id, reason) { + + // The sql queries in this transaction have already been run, since + // we really don't have a real transaction implemented in native code. + // However, the user callbacks for the remaining sql queries in transaction + // will not be called. + this.queryList = {}; + + if (this.errorCallback) { + try { + this.errorCallback(reason); + } catch(e) { + console.log("Transaction error calling user error callback: " + e); + } + } +}; + +/** + * SQL query object + * PRIVATE METHOD + * + * @param tx The transaction object that this query belongs to + */ +var DroidDB_Query = function(tx) { + + // Set the id of the query + this.id = PhoneGap.createUUID(); + + // Add this query to the queue + droiddb.queryQueue[this.id] = this; + + // Init result + this.resultSet = []; + + // Set transaction that this query belongs to + this.tx = tx; + + // Add this query to transaction list + this.tx.queryList[this.id] = this; + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + +} + +/** + * Execute SQL statement + * + * @param sql SQL statement to execute + * @param params Statement parameters + * @param successCallback Success callback + * @param errorCallback Error callback + */ +DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { + + // Init params array + if (typeof params == 'undefined') { + params = []; + } + + // Create query and add to queue + var query = new DroidDB_Query(this); + droiddb.queryQueue[query.id] = query; + + // Save callbacks + query.successCallback = successCallback; + query.errorCallback = errorCallback; + + // Call native code + PhoneGap.exec(null, null, "Storage", "executeSql", [sql, params, query.id]); +}; + +/** + * SQL result set that is returned to user. + * PRIVATE METHOD + */ +DroidDB_Result = function() { + this.rows = new DroidDB_Rows(); +}; + +/** + * SQL result set object + * PRIVATE METHOD + */ +DroidDB_Rows = function() { + this.resultSet = []; // results array + this.length = 0; // number of rows +}; + +/** + * Get item from SQL result set + * + * @param row The row number to return + * @return The row object + */ +DroidDB_Rows.prototype.item = function(row) { + return this.resultSet[row]; +}; + +/** + * Open database + * + * @param name Database name + * @param version Database version + * @param display_name Database display name + * @param size Database size in bytes + * @return Database object + */ +DroidDB_openDatabase = function(name, version, display_name, size) { + PhoneGap.exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); + var db = new DatabaseShell(); + return db; +}; + + +/** + * For browsers with no localStorage we emulate it with SQLite. Follows the w3c api. + * TODO: Do similar for sessionStorage. + */ + +var CupcakeLocalStorage = function() { + try { + + this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440); + var storage = {}; + this.db.transaction( + function (transaction) { + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + transaction.executeSql('SELECT * FROM storage', [], function(tx, result) { + for(var i = 0; i < result.rows.length; i++) { + storage[result.rows.item(i)['id']] = result.rows.item(i)['body']; + } + PhoneGap.initializationComplete("cupcakeStorage"); + }); + + }, + function (err) { + alert(err.message); + } + ); + this.setItem = function(key, val) { + console.log('set'); + storage[key] = val; + + this.db.transaction( + function (transaction) { + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + + transaction.executeSql('REPLACE INTO storage (id, body) values(?,?)', [key,val]); + } + ); + } + this.getItem = function(key) { + return storage[key]; + } + this.removeItem = function(key) { + delete storage[key]; + this.db.transaction( + function (transaction) { + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + + transaction.executeSql('DELETE FROM storage where id=?', [key]); + } + ); + + } + + } catch(e) { + alert("Database error "+e+"."); + return; + } +}; +PhoneGap.addConstructor(function() { + if (typeof window.openDatabase == "undefined") { + navigator.openDatabase = window.openDatabase = DroidDB_openDatabase; + window.droiddb = new DroidDB(); + } + + if (typeof window.localStorage == "undefined") { + navigator.localStorage = window.localStorage = new CupcakeLocalStorage(); + PhoneGap.waitForInitialization("cupcakeStorage"); + } +}); + diff --git a/assets/www/zepto.js b/assets/www/zepto.js new file mode 100644 index 0000000..80a023a --- /dev/null +++ b/assets/www/zepto.js @@ -0,0 +1,22 @@ +var Zepto=function(){function f(a){return a.filter(function(c){return c!==i&&c!==null})}function h(a){return a.reduce(function(c,d){return c.concat(d)},[])}function n(a){return a.replace(/-+(.)?/g,function(c,d){return d?d.toUpperCase():""})}function j(a,c){this.dom=a||[];this.length=this.dom.length;this.selector=c||""}function b(a,c){if(a==g)return new j;else if(c!==i)return b(c).find(a);else if(typeof a==="function")return b(g).ready(a);else{var d;if(a instanceof j)d=a.dom;else{if(a instanceof Array)d= +f(a);else{if(a instanceof Element||a===window)d=[a];else{if(q.test(a)){e.innerHTML=(""+a).trim();d=l.call(e.childNodes);e.innerHTML="";d=d}else d=o(g,a);d=d}d=d}d=d}return new j(d,a)}}var l=[].slice,k,m,o,q,e,g=window.document,i;if(String.prototype.trim===i)String.prototype.trim=function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")};q=/^\s*<.+>/;e=g.createElement("div");b.extend=function(a,c){for(k in c)a[k]=c[k];return a};b.qsa=o=function(a,c){return l.call(a.querySelectorAll(c))};b.fn={ready:function(a){g.addEventListener("DOMContentLoaded", +a,false);return this},get:function(a){return a===i?this.dom:this.dom[a]},size:function(){return this.length},remove:function(){return this.each(function(){this.parentNode.removeChild(this)})},each:function(a){this.dom.forEach(function(c,d){a.call(c,d,c)});return this},filter:function(a){return b(this.dom.filter(function(c){return o(c.parentNode,a).indexOf(c)>=0}))},is:function(a){return this.length>0&&b(this.dom[0]).filter(a).length>0},eq:function(a){return b(this.get(a))},first:function(){return b(this.get(0))}, +last:function(){return b(this.get(this.length-1))},find:function(a){var c;c=this.length==1?o(this.get(0),a):h(this.dom.map(function(d){return o(d,a)}));return b(c)},closest:function(a,c){var d=this.dom[0],p=o(c!==i?c:g,a);if(p.length===0)d=null;for(;d&&d!==g&&p.indexOf(d)<0;)d=d.parentNode;return b(d)},parents:function(a){for(var c=[],d=this.get();d.length>0;)d=f(d.map(function(p){if((p=p.parentNode)&&p!==g&&c.indexOf(p)<0){c.push(p);return p}}));c=b(c);return a===i?c:c.filter(a)},parent:function(a){var c, +d=[];this.each(function(){if((c=this.parentNode)&&d.indexOf(c)<0)d.push(c)});d=b(d);return a===i?d:d.filter(a)},pluck:function(a){return this.dom.map(function(c){return c[a]})},show:function(){return this.css("display","block")},hide:function(){return this.css("display","none")},prev:function(){return b(this.pluck("previousElementSibling"))},next:function(){return b(this.pluck("nextElementSibling"))},html:function(a){return a===i?this.length>0?this.dom[0].innerHTML:null:this.each(function(c){this.innerHTML= +typeof a=="function"?a(c,this.innerHTML):a})},text:function(a){return a===i?this.length>0?this.dom[0].innerText:null:this.each(function(){this.innerText=a})},attr:function(a,c){return typeof a=="string"&&c===i?this.length>0&&this.dom[0].nodeName==="INPUT"&&this.dom[0].type==="text"&&a==="value"?this.val():this.length>0?this.dom[0].getAttribute(a)||i:null:this.each(function(d){if(typeof a=="object")for(k in a)this.setAttribute(k,a[k]);else this.setAttribute(a,typeof c=="function"?c(d,this.getAttribute(a)): +c)})},removeAttr:function(a){return this.each(function(){this.removeAttribute(a)})},val:function(a){return a===i?this.length>0?this.dom[0].value:null:this.each(function(){this.value=a})},offset:function(){var a=this.dom[0].getBoundingClientRect();return{left:a.left+g.body.scrollLeft,top:a.top+g.body.scrollTop,width:a.width,height:a.height}},css:function(a,c){if(c===i&&typeof a=="string")return this.dom[0].style[n(a)];m="";for(k in a)m+=k+":"+a[k]+";";if(typeof a=="string")m=a+":"+c;return this.each(function(){this.style.cssText+= +";"+m})},index:function(a){return this.dom.indexOf(b(a).get(0))},hasClass:function(a){return RegExp("(^|\\s)"+a+"(\\s|$)").test(this.dom[0].className)},addClass:function(a){return this.each(function(){!b(this).hasClass(a)&&(this.className+=(this.className?" ":"")+a)})},removeClass:function(a){return this.each(function(){this.className=this.className.replace(RegExp("(^|\\s)"+a+"(\\s|$)")," ").trim()})},toggleClass:function(a,c){return this.each(function(){c!==i&&!c||b(this).hasClass(a)?b(this).removeClass(a): +b(this).addClass(a)})}};["width","height"].forEach(function(a){b.fn[a]=function(){return this.offset()[a]}});var r={append:"beforeEnd",prepend:"afterBegin",before:"beforeBegin",after:"afterEnd"};for(k in r)b.fn[k]=function(a){return function(c){return this.each(function(d,p){if(c instanceof j){dom=c.dom;if(a=="afterBegin"||a=="afterEnd")for(var s=0;s0&&l<=250)h.isDoubleTap=true;h.last=b}).bind("touchmove",function(j){h.x2=j.touches[0].pageX}).bind("touchend",function(){if(h.isDoubleTap){f(h.target).trigger("doubleTap");h={}}else if(h.x2>0){Math.abs(h.x1-h.x2)>30&&f(h.target).trigger("swipe")&& +f(h.target).trigger("swipe"+(h.x1-h.x2>0?"Left":"Right"));h.x1=h.x2=h.last=0}else if("last"in h)n=setTimeout(function(){n=null;f(h.target).trigger("tap");h={}},250)}).bind("touchcancel",function(){h={}})});["swipe","swipeLeft","swipeRight","doubleTap","tap"].forEach(function(j){f.fn[j]=function(b){return this.bind(j,b)}})})(Zepto); +(function(f){function h(){}var n=0;f.ajaxJSONP=function(b){var l;l="jsonp"+ ++n;window[l]=b.success;var k=document.createElement("script");f(k).attr({src:b.url.replace(/callback=\?/,"callback="+l)});f("head").append(k)};f.ajax=function(b){b=b||{};if(b.url&&/callback=\?/.test(b.url))return f.ajaxJSONP(b);var l=b.data,k=b.success||h,m=b.error||h,o=j[b.dataType],q=b.contentType,e=new XMLHttpRequest;e.onreadystatechange=function(){if(e.readyState==4)if(e.status>=200&&e.status<300||e.status==0)if(o=="application/json"){var g, +i=false;try{g=JSON.parse(e.responseText)}catch(r){i=r}i?m(e,"parsererror",i):k(g,"success",e)}else k(e.responseText,"success",e);else m(e,"error")};e.open(b.type||"GET",b.url||window.location,true);o&&e.setRequestHeader("Accept",o);if(l instanceof Object){l=JSON.stringify(l);q=q||"application/json"}q&&e.setRequestHeader("Content-Type",q);e.setRequestHeader("X-Requested-With","XMLHttpRequest");e.send(l)};var j=f.ajax.mimeTypes={json:"application/json",xml:"application/xml",html:"text/html",text:"text/plain"}; +f.get=function(b,l){f.ajax({url:b,success:l})};f.post=function(b,l,k,m){if(l instanceof Function){m=m||k;k=l;l=null}f.ajax({type:"POST",url:b,data:l,success:k,dataType:m})};f.getJSON=function(b,l){f.ajax({url:b,success:l,dataType:"json"})};f.fn.load=function(b,l){if(!this.dom.length)return this;var k=this,m=b.split(/\s/),o;if(m.length>1){b=m[0];o=m[1]}f.get(b,function(q){k.html(o?f(document.createElement("div")).html(q).find(o).html():q);l&&l()});return this}})(Zepto); +(function(f){var h=[],n;f.fn.remove=function(){return this.each(function(j){if(j.tagName=="IMG"){h.push(j);j.src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";n&&clearTimeout(n);n=setTimeout(function(){h=[]},6E4)}j.parentNode.removeChild(j)})}})(Zepto); diff --git a/build.properties b/build.properties new file mode 100755 index 0000000..1345d07 --- /dev/null +++ b/build.properties @@ -0,0 +1,18 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. +#key.store=kana-key.keystore +#key.alias=kanalulz diff --git a/build.xml b/build.xml new file mode 100755 index 0000000..61e09f8 --- /dev/null +++ b/build.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/default.properties b/default.properties new file mode 100755 index 0000000..9d79b12 --- /dev/null +++ b/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-4 diff --git a/libs/phonegap.0.9.4.jar b/libs/phonegap.0.9.4.jar new file mode 100755 index 0000000..b99d128 Binary files /dev/null and b/libs/phonegap.0.9.4.jar differ diff --git a/proguard.cfg b/proguard.cfg new file mode 100755 index 0000000..12dd039 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,36 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..d636a88 Binary files /dev/null and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..d636a88 Binary files /dev/null and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..d636a88 Binary files /dev/null and b/res/drawable-mdpi/icon.png differ diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100755 index 0000000..a440100 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,11 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100755 index 0000000..7c88b76 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Katanakana + Snap + diff --git a/src/com/phonegap/Katanakana/Katanakana.java b/src/com/phonegap/Katanakana/Katanakana.java new file mode 100755 index 0000000..33e6e50 --- /dev/null +++ b/src/com/phonegap/Katanakana/Katanakana.java @@ -0,0 +1,23 @@ +package com.phonegap.Katanakana; + +import android.app.Activity; +import android.os.Bundle; +import com.phonegap.*; + +public class Katanakana extends DroidGap { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.init(); + + /* By default, the Android Webkit instance shows scrollbars, but our + App should scale enough that the existence of scrollbars should never + NEED to be indicated. This'll kill them by default. Scrolling/panning + around is still totally supported. + */ + super.appView.setVerticalScrollBarEnabled(false); + super.appView.setHorizontalScrollBarEnabled(false); + + super.loadUrl("file:///android_asset/www/index.html"); + } +}