Compare commits

..

124 Commits

Author SHA1 Message Date
Animesh Kumar
9270d13981 Added some exception handling 2011-03-07 13:34:16 -08:00
Animesh Kumar
83e89909ed added readyState support 2011-03-07 13:34:16 -08:00
Animesh Kumar
98d51b18e2 added readyState support 2011-03-07 13:34:16 -08:00
Animesh Kumar
28f27e89e4 refactored code, added license, added onError handler 2011-03-07 13:34:16 -08:00
Animesh Kumar
8ede3b4cc9 added websocket support 2011-03-07 13:34:16 -08:00
Fil Maj
939b70243d Use icon with no width/height if specified. Set default icon to highest-resolution icon when possible. 2011-03-07 13:27:52 -08:00
Fil Maj
d44bb7a9d8 Fix so that if not all icons are specified, doesnt error the build out. 2011-03-07 13:27:52 -08:00
Fil Maj
dccc29daf2 Syntax fixes to my ruby :P 2011-03-07 13:27:52 -08:00
Fil Maj
b402efd1f7 First pass at extracting icon width/height info and assigning to proper resolution dirs (i.e. ldpi, mdpi, hdpi) during build. 2011-03-07 13:27:52 -08:00
macdonst
0c3a8fb9f7 File API: System and Directories
http://www.w3.org/TR/file-system-api/

User can retrieve PERSISTENT and TEMPORARY file systems, list their
contents, and manipulate files and directories within them.

Modify existing FileWriter implementation
-----------------------------------------

  - Change the way user creates a FileWriter.  User must either pass a
    File object to the FileWriter constructor, or use the
    FileEntry.createWriter() method.

  - Drop support for the 'filePath' and 'append' parameters in the
    FileWriter constructor.  The file path is determined from either the
    File object passed to the FileWriter constructor, or the FileEntry
    object used to create the FileWriter.  To append to a file, use the
    FileWriter object's seek method:

    // writer is a FileWriter object
    // seek to length of file to append
    writer.seek(writer.length);

Replace FileMgr JavaScript APIs not specified in any File API spec
------------------------------------------------------------------

  - Remove navigator.fileMgr.createDirectory(dirName) function.  To
    create a directory, use the DirectoryEntry.getDirectory() method,
    which is part of the File API: Directories and System spec.  Set
    the Flags.create to 'true':

    // directory is a DirectoryEntry object
    directory.getDirectory(path, {create:true}, successCB, failCB);

  - Remove navigator.fileMgr.getRootPaths() function.  To retrieve the
    root file systems, use the window.requestFileSystem() function,
    which is part of the File API: Directories and System spec.

  - Remove navigator.fileMgr.getFileProperties(fileName) function.  To
    get the properties of a file, use the FileEntry.file() method, which
    is part of the File API: Directories and System spec.

  - Remove navigator.fileMgr.deleteFile(fileName) function.  To delete a
    file, use the Entry.remove() method, which is part of the File API:
    Directories and System spec.

  - Remove navigator.fileMgr.deleteDirectory(dirName) function.  To
    delete a directory, use the Entry.remove() (if it is empty), or
    DirectoryEntry.removeRecursively() methods, which are part of the
    File API: Directories and System spec.

Clean up existing FileManager native code.  Move some functionality to
file utility class.
2011-03-05 04:26:31 +08:00
paulb777
64d4337d5f Update index.html to Add networking to example 2011-02-28 23:49:14 +08:00
paulb777
a9e1751812 Add networking to example and fix contacts 2011-02-28 23:46:26 +08:00
Bryce Curtis
2bc7bd6768 Worked around JavaScript bridge exception for Android 2.3. Use "prompt" instead of calling objects directly. There were several objects called from JavaScript, including BrowserKey, so key events had to be reworked. 2011-02-27 20:07:24 -06:00
Fil Maj
1711fb07d7 Small patch to build script: ruby needs double quotes to interpolate variables into it properly. 2011-02-16 16:26:01 -08:00
paulb777
6f4673f590 JSLint clean JavaScript sources. No fatal errors remain. Options can turn off rest of warnings 2011-02-15 16:10:09 -08:00
Vadim Voituk
5e858f8bc3 Added CupcakeLocalStorage.clear() method (in according to http://dev.w3.org/html5/webstorage/#the-storage-interface) 2011-02-04 12:08:22 -08:00
Fil Maj
691b093ccd Upped script version in assets. 2011-02-04 11:55:20 -08:00
Fil Maj
99002f9dce Fix for build: version needs to be included in .jar and .js generated files. 2011-02-04 11:35:05 -08:00
Fil Maj
b07072c12b Fix for ticket 86 (build fail if phonegap-android dir is located under a dir with "lib" in it). Also bug fix in build if config.xml didnt contain an <icon> element. 2011-02-04 11:20:22 -08:00
Bryce Curtis
36dd964ba4 Logging status from wrong object. 2011-02-03 21:11:06 -06:00
macdonst
f848527c28 Upping version to 0.9.4 2011-02-03 09:48:31 +08:00
Bryce Curtis
6aa055f46e Change super.setProperty() to use super.set<type>Property() in example comments. 2011-02-02 14:37:09 -06:00
macdonst
7952668cf7 Throwing error on FileWriter.abort() if writer is not in the correct state. Lining up with iPhone and BlackBerry 2011-02-03 02:26:49 +08:00
macdonst
a0c761664d Call onwriteend not onloadend in FileWriter.abort() 2011-02-03 02:08:41 +08:00
macdonst
9fd9cf55cf Adding version number to phonegap jar/js files 2011-02-03 01:51:59 +08:00
macdonst
3c9089b9c7 Enable hardware volume control buttons in DroidGap applications 2011-02-02 23:33:01 +08:00
Bryce Curtis
f220489543 Disable picture listener once event has occurred. 2011-02-01 11:25:01 -06:00
Bryce Curtis
b65f9517db Merge branch 'filmaj-splashscreenfix' 2011-02-01 11:00:02 -06:00
Bryce Curtis
1a0de5f626 Merge branch 'splashscreenfix' of https://github.com/filmaj/phonegap-android into filmaj-splashscreenfix 2011-02-01 10:46:38 -06:00
Fil Maj
3c0bef6cc1 Ticket 81: Tweak to label of local path to index.html in example app, now properly shows actual project-relative path. 2011-01-31 17:51:15 -08:00
Fil Maj
040194157f Ticket 80: running "droidgap gen example" leads to recursive directory creation. README fix included. 2011-01-31 17:48:56 -08:00
macdonst
7ebf8130e4 Set type to url for returned photos 2011-01-29 04:19:06 +08:00
macdonst
64310dc85c Fixing clone issue adding photos, removing relationships 2011-01-27 05:59:22 +08:00
macdonst
cd2e86af2f Removing excess logging in contact.save() 2011-01-27 03:44:27 +08:00
macdonst
b353f3608d Updating to latest W3C spec 2011-01-27 03:41:27 +08:00
macdonst
cda154209d Fixing merge issue 2011-01-26 11:22:08 -05:00
Sveinung Kval Bakken
e3c72fa915 Will now use a "smarter" approach to finding an account for Contact.save, the order of account search will be:
1. Exchange provider
2. Google
3. Any valid email address account
2011-01-25 11:36:08 +01:00
Fil Maj
9354b429f3 Fix for ticket #55: if phonegap source was on a path with "bin" in it would cause major fail. 2011-01-24 15:51:26 -08:00
Fil Maj
b1f0c037bd Getting rid of black screen between native loading screen and actual PhoneGap app. 2011-01-24 12:43:28 -08:00
macdonst
726f1094d9 Fixing bug found by tiny hippos 2011-01-25 03:05:59 +08:00
macdonst
1b8ab156df Adding http: and file: support when saving a contact photo. 2011-01-25 01:58:31 +08:00
macdonst
ee01b5058f Adding support to set a Contact photo 2011-01-22 01:52:20 +08:00
macdonst
03ea8a0b5a Enable the return of photos in a Contact object 2011-01-20 04:27:40 +08:00
macdonst
f090f9a70c Merge branch 'master' of https://github.com/filmaj/phonegap-android into filmaj 2011-01-17 11:54:52 -05:00
Bryce Curtis
b7abc2c344 Skip over beginning / in request when comparing to token. 2011-01-16 15:15:24 -06:00
Fil Maj
53bdf2dd6b Fix for specifying icon in config.xml; the @icon attribute would get overriden by defaults when going from create => classic::build. 2011-01-15 23:12:07 -08:00
Joe Bowser
b9e1b1d280 Adding Blank HTML page 2011-01-13 16:27:54 -08:00
Bryce Curtis
9051b157f8 Ticket 63: Android CallbackServer crashes on external attacks. 2011-01-13 14:45:15 -06:00
macdonst
f16d9b01b7 Fixing geo listner callback fail to send 3 args instead of 4. 2011-01-14 02:07:47 +08:00
macdonst
2a9bc2ddf8 Fixing issue where Android 2.1 and 2.2 don't return the same results on contact.find() 2011-01-14 02:02:21 +08:00
macdonst
6e39c46b07 Middle name for contact being updated incorrectly 2011-01-12 21:58:20 +08:00
macdonst
567ca94245 Adding debug mode so FileTransfer will accept self signed SSL certificates 2011-01-12 10:32:26 +08:00
macdonst
812a4b32b4 Adding file key properly 2011-01-07 23:17:05 +08:00
Bryce Curtis
023df10f31 Allow features/modules to initialize code before deviceready fires. CupcakeLocalStorage uses this capability to delay deviceready until local storage has been read and inited. 2011-01-06 22:50:13 -06:00
macdonst
8d513e2765 Remaining FileUploader to FileTransfer 2011-01-07 01:43:12 +08:00
Bryce Curtis
1eae6786c4 Better memory management when taking pictures. 2011-01-06 11:12:14 -06:00
macdonst
73f278963b Adding File Upload functionality 2011-01-06 07:09:07 +08:00
macdonst
54eff557d9 Guard against null request in Android 1.5/1.6 2011-01-06 04:08:23 +08:00
Bryce Curtis
a7415bcfc9 Support all URIs by passing them to their default activity. This works for market:// and content://. 2011-01-04 13:22:25 -06:00
macdonst
b6bd9ad5b8 Support Market Uri 2011-01-05 03:03:38 +08:00
macdonst
f71d9deb5e Fixing mimetypes for content:// Uri's. 2011-01-05 02:45:04 +08:00
macdonst
115b428a9d Fixing issue where Date's aren't cloned 2010-12-30 00:53:06 +08:00
macdonst
92a1e4a2d9 Remove destroyed AudioPlayer from list of AudioPlayers 2010-12-23 02:51:39 +08:00
macdonst
2504db13d7 Adding release method to Media object 2010-12-23 02:44:52 +08:00
macdonst
43c72e684c Setting content type properly in readAsDataURL 2010-12-23 00:26:38 +08:00
stevengill
46f0bf60c4 Merge branch 'master' of github.com:phonegap/phonegap-android 2010-12-16 13:29:52 -08:00
macdonst
8bad4eb7eb Fixing issue where Camera returned a content URI that File Reader could not read 2010-12-17 05:08:45 +08:00
paulb777
fbe96f891b 1. Split out js to main.js 2. Show a contact 3. Toggle accelerometer 4. More device info 5. Enable scrolling 2010-12-15 11:24:01 -08:00
Bryce Curtis
ab8950a5af Re-enable multitasking in onResume Java callback so that onResume JS handlers are called - it was being re-enabled too soon. 2010-12-09 14:13:23 -06:00
Bryce Curtis
26adfb6346 If multitasking is turned on (keepRunning=true), then temporarily disable it when starting a new activity that returns a result - such as camera. 2010-12-06 16:48:06 -06:00
Bryce Curtis
43b6b6d34e Update version number to 0.9.3 in preparation for next release. 2010-12-03 18:15:00 -06:00
Bryce Curtis
04ddc68dfd Fix bug with saving name in new contact. 2010-12-03 14:14:37 -06:00
Bryce Curtis
42cd10cf56 Need license header since it is removed by minification. 2010-12-01 16:13:22 -06:00
Bryce Curtis
5f3bc33f8e Update comments. 2010-12-01 11:21:49 -06:00
Bryce Curtis
2131070ee9 Add JavaScript minification using YUICompressor. 2010-11-30 19:00:30 -06:00
Bryce Curtis
b2a82975e5 Merge branch 'jos3000-master' 2010-11-29 12:30:49 -06:00
Bryce Curtis
ddeba91faf Merge branch 'master' of https://github.com/jos3000/phonegap-android into jos3000-master 2010-11-29 12:27:00 -06:00
macdonst
6e572f05e4 Put trailing / into getRootPaths() to remain consistent with BBW 2010-11-26 00:45:02 +08:00
macdonst
50b435c4d1 Following File API spec. 2010-11-25 03:11:43 +08:00
Bryce Curtis
af5c5dc021 Update splash screen example and list of properties that can be set in commented code. 2010-11-23 09:53:43 -06:00
Bryce Curtis
87fd9665fe Merge branch 'localStorage' of https://github.com/ascorbic/phonegap-android into ascorbic-localStorage 2010-11-21 17:58:49 -06:00
Bryce Curtis
5e9ca84b40 Expose certain methods from DroidGap to JavaScript so that a PhoneGap web app can better control program configuration and flow. 2010-11-21 17:33:13 -06:00
Bryce Curtis
090ad56d0b Don't need special method to load a splash screen. Instead, the regular loadUrl() and clearHistory() can be used. 2010-11-21 16:47:35 -06:00
Bryce Curtis
e3ebfea064 Improve handling of timeout error when loading URL, and enable WebViewClient to be overridden by app, so developer can intercept webview events. 2010-11-21 16:42:00 -06:00
Bryce Curtis
44761f87d2 Remove comment for unused parameter. 2010-11-21 16:31:49 -06:00
Bryce Curtis
04e3ceac96 Define window.plugins object so plugins can check to see if they have already been created. 2010-11-21 16:30:46 -06:00
Bryce Curtis
afc7e605ff Fix bug when not doing cast - temp isn't defined. 2010-11-20 21:23:15 -06:00
macdonst
1c5aa6cd00 Adding a cast for contacts.find() 2010-11-20 01:42:52 +08:00
macdonst
c1a87ebaaa Adding and optional call to cast Plugin Result 2010-11-20 01:42:43 +08:00
macdonst
46babe7a48 Calling correct events from FileWriter.abort() 2010-11-17 15:30:08 -05:00
Bryce Curtis
0dc64d2aa7 Merge branch 'master' of github.com:phonegap/phonegap-android 2010-11-16 18:15:15 -06:00
Bryce Curtis
1d9e522bd9 Fix quality issue with base64 encoded images. Quality parameter wasn't being set. 2010-11-16 18:14:24 -06:00
macdonst
5dcac6d7fe Fixing issue in File Reader/Writer when newlines in file 2010-11-16 13:30:18 -05:00
macdonst
07418a3606 Small fix to File API 2010-11-17 00:06:49 +08:00
Bryce Curtis
0e08af98ca Better way to handle splash screen when back button pressed. 2010-11-15 16:32:55 -06:00
Bryce Curtis
b8b1ad8421 Add property that lets a PhoneGap app continue to run when another Android app or activity is started. 2010-11-14 17:33:06 -06:00
Bryce Curtis
4fa1f40b44 Add load URL capability, and enable an HTML file to be used as a splash screen. 2010-11-12 22:38:27 -06:00
macdonst
5f55ebf1d9 Adding Contact.save() for Android 1.X and Android 2.X 2010-11-13 05:34:44 +08:00
Bryce Curtis
9798de7efa Remove unused and unneeded getPort() method. 2010-11-12 12:53:34 -06:00
Bryce Curtis
102745875c Allow user to set the loading dialog message. Change default from show to not shown. 2010-11-11 22:24:20 -06:00
Bryce Curtis
dce0d93df8 Replace deprecated debug.log with console.log. 2010-11-11 22:03:12 -06:00
Bryce Curtis
1428ac5ed5 Add error checking for PhoneGap.addPlugin(). 2010-11-11 21:58:07 -06:00
Bryce Curtis
4f1bc1401f Add delay so splash screen can be shown for a specific amount of time. 2010-11-11 21:56:56 -06:00
Bryce Curtis
28ff6e1150 Merged code for bryfox: Re-add support for search & menu key triggers.
See original commit: 799515fa7b
2010-11-11 16:20:32 -06:00
Bryce Curtis
5ffe5fa3c5 Merged code for imhotep: Cleaner way for handling splashscreens.
See original commit: 1761cbb3dc
2010-11-11 15:59:35 -06:00
Bryce Curtis
49341356d7 Add comments to onKeyDown() method. 2010-11-11 14:08:55 -06:00
Bryce Curtis
e8b85f6cf7 Fix formatting and rearrange method order. 2010-11-11 14:00:56 -06:00
Bryce Curtis
4b2398b487 Add properties to DroidGap that can be set when the intent/activity is called. This enables the developer to show app loading dialog, splashscreen, or set other properties. 2010-11-11 11:34:12 -06:00
Bryce Curtis
10f3313ed5 Use polling if PhoneGap app is loaded from server, since XHR doesn't work to localhost due to cross-domain security policy. 2010-11-10 14:19:17 -06:00
Matt Kane
46664c6494 Remove dependency on JSON support 2010-11-10 08:44:33 +00:00
Matt Kane
8ce7e61ed7 Adds localStorage support to older versions 2010-11-10 08:34:59 +00:00
Matt Kane
912458c679 Fix order of args to match w3c spec. 2010-11-09 22:58:13 +08:00
macdonst
e117b95057 Fixing issue with addEventListener and Sencha 2010-11-09 21:48:48 +08:00
Jos Shepherd
3a0101261d Move data transfer for storage to completeQuery for speed up 2010-11-06 20:01:22 +00:00
Bryce Curtis
48d3bc09f3 Add method for dynamic loading of a JavaScript file. 2010-11-05 16:00:58 -05:00
Bryce Curtis
0b3e27b3fa Load url into DroidGap if it was passed in to intent. 2010-11-05 14:50:48 -05:00
Bryce Curtis
b66535a17d Introduce PhonegapActivity class to separate plugin development from base Phonegap. 2010-11-06 03:10:28 +08:00
Jos Shepherd
fdc78e1b08 Fix for troublesome values being returned from DroidDB stores. Stick to one level of JSON serialization. 2010-11-05 17:03:05 +00:00
Bryce Curtis
36064c564e Reduce timeout from 30 sec to 10 sec for CallbackServer. Some devices have shorter timeouts than others for XHR. 2010-11-04 13:07:15 -05:00
Bryce Curtis
7102810283 Fix problem with CallbackServer for certain HTC phones. 2010-11-03 22:45:14 -05:00
Bryce Curtis
ab4d4e22da Include the device's name in Device.name per API docs. 2010-11-03 10:23:12 -05:00
Bryce Curtis
80c15de606 Return error conditions from CallbackServer instead of just closing connection. 2010-11-01 13:59:08 -05:00
Matt Kane
be5cac6d0b This is being called from java code, but callback didn't exist 2010-11-01 05:58:24 +08:00
Matt Kane
2bb67ee4b0 Change use "geo:" instead of WebView.SCHEME_GEO
The constant is "geo:0,0?q=", which means it doesn't match urls that specify the coordinates. The gmap app can handle these though, so we can pass any geo: url to it.
2010-11-01 05:50:55 +08:00
69 changed files with 7158 additions and 1566 deletions

View File

@@ -31,7 +31,7 @@ Commands:
<pre>
help ...... See this message. Type help [command name] to see specific help topics.
gen ....... Generate an example PhoneGap application to current directory.
gen ....... Generate the example PhoneGap application to current directory (or optionally provide an output directory as parameter).
create .... Creates an Android compatible project from a WWW folder.
classic ... Backwards support for droidgap script. Run "droidgap help classic" for more info.
update .... Copy a fresh phonegap.jar and phonegap.js into a valid PhoneGap/Android project.
@@ -41,8 +41,8 @@ Commands:
Quickstart:
<pre>
$ droidgap gen example
$ cd example
$ droidgap gen exampleapp
$ cd exampleapp
$ ant debug install && adb logcat
</pre>

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.9.4

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env ruby
ROOT = File.expand_path(File.dirname(__FILE__).gsub('bin',''))
ROOT = File.expand_path(File.dirname(__FILE__).gsub(/bin$/,''))
require 'fileutils'
require File.join(ROOT, "lib", "generate.rb")
require File.join(ROOT, "lib", "classic.rb")
@@ -60,7 +60,7 @@ if ARGV.first.nil? || ARGV.first == 'help'
Commands:
help ...... See this message. Type help [command name] to see specific help topics.
gen ....... Generate an example PhoneGap application to current directory.
gen ....... Generate the example PhoneGap application to current directory (or optionally provide an output directory as parameter).
create .... Creates an Android compatible project from a WWW folder.
classic ... Backwards support for droidgap script. Run "droidgap help classic" for more info.
update .... Copy a fresh phonegap.jar and phonegap.js into a valid PhoneGap/Android project.
@@ -68,8 +68,8 @@ if ARGV.first.nil? || ARGV.first == 'help'
Quickstart:
$ droidgap gen example
$ cd example
$ droidgap gen exampleapp
$ cd exampleapp
$ ant debug install && adb logcat
EOF
@@ -79,11 +79,13 @@ if ARGV.first.nil? || ARGV.first == 'help'
DroidGap Generate
-----------------
Generate an example PhoneGap application to path supplied or current working directory if none is supplied.
Generate the example PhoneGap application to path supplied or current working directory if none is supplied.
Usage:
droidgap gen [path]
NOTE: Do *not* run "droidgap gen example" - you will end up with a recursive directory problem.
EOF

View File

@@ -5,141 +5,32 @@
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>PhoneGap</title>
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8">
<script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8">
var deviceInfo = function(){
document.getElementById("platform").innerHTML = device.platform;
document.getElementById("version").innerHTML = device.version;
document.getElementById("uuid").innerHTML = device.uuid;
}
var getLocation = function() {
var suc = function(p){
alert(p.coords.latitude + " " + p.coords.longitude);
};
var fail = function(){};
navigator.geolocation.getCurrentPosition(suc,fail);
}
var beep = function(){
navigator.notification.beep(2);
}
var vibrate = function(){
navigator.notification.vibrate(0);
}
var getContact = function(){
var suc = function(c){ alert("Contact 4: " + c.contacts[3].name); };
var fail = function(){};
navigator.ContactManager.get(suc, fail);
}
var watchAccel = function() {
var suc = function(a){
document.getElementById('x').innerHTML = roundNumber(a.x);
document.getElementById('y').innerHTML = roundNumber(a.y);
document.getElementById('z').innerHTML = roundNumber(a.z);
};
var fail = function(){};
var opt = {};
opt.frequency = 100;
timer = navigator.accelerometer.watchAcceleration(suc,fail,opt);
}
function roundNumber(num) {
var dec = 3;
var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
return result;
}
var preventBehavior = function(e) {
e.preventDefault();
};
<script type="text/javascript" charset="utf-8" src="phonegap.0.9.4.min.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
function show_pic()
{
var viewport = document.getElementById('viewport');
viewport.style.display = "";
navigator.camera.getPicture(dump_pic, fail, { quality: 50 });
}
function dump_pic(data)
{
var viewport = document.getElementById('viewport');
console.log(data);
viewport.style.display = "";
viewport.style.position = "absolute";
viewport.style.top = "10px";
viewport.style.left = "10px";
document.getElementById("test_img").src = "data:image/jpeg;base64," + data;
}
function close()
{
var viewport = document.getElementById('viewport');
viewport.style.position = "relative";
viewport.style.display = "none";
}
function fail(fail)
{
alert(fail);
}
// This is just to do this.
function readFile()
{
navigator.file.read('/sdcard/phonegap.txt', fail , fail);
}
function writeFile()
{
navigator.file.write('foo.txt', "This is a test of writing to a file", fail, fail);
}
function get_contacts()
{
var obj = new ContactFindOptions();
obj.filter="";
obj.multiple=true;
obj.limit=5;
navigator.service.contacts.find(["displayName", "phoneNumbers", "emails"], count_contacts, fail, obj);
}
function count_contacts(contacts)
{
alert(contacts.length);
}
function init(){
document.addEventListener("touchmove", preventBehavior, false);
document.addEventListener("deviceready", deviceInfo, true);
}
</script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Welcome to PhoneGap!</h1>
<h2>this file is located at assets/index.html</h2>
<h2>this file is located at assets/www/index.html</h2>
<div id="info">
<h4>Platform: <span id="platform">&nbsp;</span></h4>
<h4>Version: <span id="version">&nbsp;</span></h4>
<h4>UUID: <span id="uuid">&nbsp;</span></h4>
</div>
<h4>Platform: <span id="platform"> &nbsp;</span>, Version: <span id="version">&nbsp;</span></h4>
<h4>UUID: <span id="uuid"> &nbsp;</span>, Name: <span id="name">&nbsp;</span></h4>
<h4>Width: <span id="width"> &nbsp;</span>, Height: <span id="height">&nbsp;
</span>, Color Depth: <span id="colorDepth"></span></h4>
</div>
<dl id="accel-data">
<dt>X:</dt><dd id="x">&nbsp;</dd>
<dt>Y:</dt><dd id="y">&nbsp;</dd>
<dt>Z:</dt><dd id="z">&nbsp;</dd>
</dl>
<a href="#" class="btn large" onclick="watchAccel();">Watch Accelerometer</a>
<a href="#" class="btn large" onclick="toggleAccel();">Toggle Accelerometer</a>
<a href="#" class="btn large" onclick="getLocation();">Get Location</a>
<a href="tel://411" class="btn large">Call 411</a>
<a href="#" class="btn large" onclick="beep();">Beep</a>
<a href="#" class="btn large" onclick="vibrate();">Vibrate</a>
<a href="#" class="btn large" onclick="show_pic();">Get a Picture</a>
<a href="#" class="btn large" onclick="get_contacts();">Get phone's contacts</a>
<a href="#" class="btn large" onclick="get_contacts();">Get Phone's Contacts</a>
<a href="#" class="btn large" onclick="check_network();">Check Network</a>
<div id="viewport" class="viewport" style="display: none;">
<img style="width:60px;height:60px" id="test_img" src="" />
</div>

140
example/main.js Normal file
View File

@@ -0,0 +1,140 @@
var deviceInfo = function() {
document.getElementById("platform").innerHTML = device.platform;
document.getElementById("version").innerHTML = device.version;
document.getElementById("uuid").innerHTML = device.uuid;
document.getElementById("name").innerHTML = device.name;
document.getElementById("width").innerHTML = screen.width;
document.getElementById("height").innerHTML = screen.height;
document.getElementById("colorDepth").innerHTML = screen.colorDepth;
};
var getLocation = function() {
var suc = function(p) {
alert(p.coords.latitude + " " + p.coords.longitude);
};
var locFail = function() {
};
navigator.geolocation.getCurrentPosition(suc, locFail);
};
var beep = function() {
navigator.notification.beep(2);
};
var vibrate = function() {
navigator.notification.vibrate(0);
};
function roundNumber(num) {
var dec = 3;
var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
return result;
}
var accelerationWatch = null;
function updateAcceleration(a) {
document.getElementById('x').innerHTML = roundNumber(a.x);
document.getElementById('y').innerHTML = roundNumber(a.y);
document.getElementById('z').innerHTML = roundNumber(a.z);
}
var toggleAccel = function() {
if (accelerationWatch !== null) {
navigator.accelerometer.clearWatch(accelerationWatch);
updateAcceleration({
x : "",
y : "",
z : ""
});
accelerationWatch = null;
} else {
var options = {};
options.frequency = 1000;
accelerationWatch = navigator.accelerometer.watchAcceleration(
updateAcceleration, function(ex) {
alert("accel fail (" + ex.name + ": " + ex.message + ")");
}, options);
}
};
var preventBehavior = function(e) {
e.preventDefault();
};
function dump_pic(data) {
var viewport = document.getElementById('viewport');
console.log(data);
viewport.style.display = "";
viewport.style.position = "absolute";
viewport.style.top = "10px";
viewport.style.left = "10px";
document.getElementById("test_img").src = "data:image/jpeg;base64," + data;
}
function fail(msg) {
alert(msg);
}
function show_pic() {
navigator.camera.getPicture(dump_pic, fail, {
quality : 50
});
}
function close() {
var viewport = document.getElementById('viewport');
viewport.style.position = "relative";
viewport.style.display = "none";
}
// This is just to do this.
function readFile() {
navigator.file.read('/sdcard/phonegap.txt', fail, fail);
}
function writeFile() {
navigator.file.write('foo.txt', "This is a test of writing to a file",
fail, fail);
}
function contacts_success(contacts) {
alert(contacts.length
+ ' contacts returned.'
+ (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted)
: ''));
}
function get_contacts() {
var obj = new ContactFindOptions();
obj.filter = "";
obj.multiple = true;
obj.limit = 5;
navigator.service.contacts.find(
[ "displayName", "name" ], contacts_success,
fail, obj);
}
var networkReachableCallback = function(reachability) {
// There is no consistency on the format of reachability
var networkState = reachability.code || reachability;
var currentState = {};
currentState[NetworkStatus.NOT_REACHABLE] = 'No network connection';
currentState[NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK] = 'Carrier data connection';
currentState[NetworkStatus.REACHABLE_VIA_WIFI_NETWORK] = 'WiFi connection';
confirm("Connection type:\n" + currentState[networkState]);
};
function check_network() {
navigator.network.isReachable("www.mobiledevelopersolutions.com",
networkReachableCallback, {});
}
function init() {
// the next line makes it impossible to see Contacts on the HTC Evo since it
// doesn't have a scroll button
// document.addEventListener("touchmove", preventBehavior, false);
document.addEventListener("deviceready", deviceInfo, true);
}

View File

@@ -11,7 +11,7 @@ function Acceleration(x, y, z) {
this.y = y;
this.z = z;
this.timestamp = new Date().getTime();
};
}
/**
* This class provides access to device accelerometer data.
@@ -28,7 +28,7 @@ function Accelerometer() {
* List of accelerometer watch timers
*/
this.timers = {};
};
}
Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"];
@@ -42,13 +42,13 @@ Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"];
Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) {
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Accelerometer Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Accelerometer Error: errorCallback is not a function");
return;
}
@@ -68,16 +68,16 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error
Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) {
// Default interval (10 sec)
var frequency = (options != undefined)? options.frequency : 10000;
var frequency = (options !== undefined)? options.frequency : 10000;
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Accelerometer Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Accelerometer Error: errorCallback is not a function");
return;
}
@@ -108,12 +108,14 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb
Accelerometer.prototype.clearWatch = function(id) {
// Stop javascript timer & remove from timer list
if (id && navigator.accelerometer.timers[id] != undefined) {
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();
if (typeof navigator.accelerometer === "undefined") {
navigator.accelerometer = new Accelerometer();
}
});

89
framework/assets/js/app.js Executable file
View File

@@ -0,0 +1,89 @@
/*
* 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-2011, 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]);
};
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* Note: The user should not have to call this method. Instead, when the user
* registers for the "backbutton" event, this is automatically done.
*
* @param override T=override, F=cancel override
*/
App.prototype.overrideBackbutton = function(override) {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]);
};
/**
* Exit and terminate the application.
*/
App.prototype.exitApp = function() {
return PhoneGap.exec(null, null, "App", "exitApp", []);
};
PhoneGap.addConstructor(function() {
navigator.app = window.app = new App();
});

View File

@@ -59,13 +59,13 @@ Camera.prototype.PictureSourceType = Camera.PictureSourceType;
Camera.prototype.getPicture = function(successCallback, errorCallback, options) {
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Camera Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Camera Error: errorCallback is not a function");
return;
}
@@ -80,12 +80,14 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options)
destinationType = this.options.destinationType;
}
var sourceType = Camera.PictureSourceType.CAMERA;
if (typeof this.options.sourceType == "number") {
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();
if (typeof navigator.camera === "undefined") {
navigator.camera = new Camera();
}
});

View File

@@ -20,7 +20,7 @@ function Compass() {
* List of compass watch timers
*/
this.timers = {};
};
}
Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"];
@@ -34,13 +34,13 @@ Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"];
Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) {
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Compass Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Compass Error: errorCallback is not a function");
return;
}
@@ -60,16 +60,16 @@ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, o
Compass.prototype.watchHeading= function(successCallback, errorCallback, options) {
// Default interval (100 msec)
var frequency = (options != undefined) ? options.frequency : 100;
var frequency = (options !== undefined) ? options.frequency : 100;
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Compass Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Compass Error: errorCallback is not a function");
return;
}
@@ -109,5 +109,7 @@ Compass.prototype.clearWatch = function(id) {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.compass == "undefined") navigator.compass = new Compass();
if (typeof navigator.compass === "undefined") {
navigator.compass = new Compass();
}
});

View File

@@ -17,25 +17,19 @@
* @param {ContactAddress[]} addresses array of addresses
* @param {ContactField[]} ims instant messaging user ids
* @param {ContactOrganization[]} organizations
* @param {DOMString} published date contact was first created
* @param {DOMString} updated date contact was last updated
* @param {DOMString} revision date contact was last updated
* @param {DOMString} birthday contact's birthday
* @param (DOMString} anniversary contact's anniversary
* @param {DOMString} gender contact's gender
* @param {DOMString} note user notes about contact
* @param {DOMString} preferredUsername
* @param {ContactField[]} photos
* @param {ContactField[]} tags
* @param {ContactField[]} relationships
* @param {ContactField[]} categories
* @param {ContactField[]} urls contact's web sites
* @param {ContactAccounts[]} accounts contact's online accounts
* @param {DOMString} utcOffset UTC time zone offset
* @param {DOMString} connected
* @param {DOMString} timezone the contacts time zone
*/
var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, addresses,
ims, organizations, published, updated, birthday, anniversary, gender, note,
preferredUsername, photos, tags, relationships, urls, accounts, utcOffset, connected) {
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;
@@ -44,184 +38,14 @@ var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, ad
this.addresses = addresses || null; // ContactAddress[]
this.ims = ims || null; // ContactField[]
this.organizations = organizations || null; // ContactOrganization[]
this.published = published || null;
this.updated = updated || null;
this.revision = revision || null;
this.birthday = birthday || null;
this.anniversary = anniversary || null;
this.gender = gender || null;
this.note = note || null;
this.preferredUsername = preferredUsername || null;
this.photos = photos || null; // ContactField[]
this.tags = tags || null; // ContactField[]
this.relationships = relationships || null; // ContactField[]
this.categories = categories || null; // ContactField[]
this.urls = urls || null; // ContactField[]
this.accounts = accounts || null; // ContactAccount[]
this.utcOffset = utcOffset || null;
this.connected = connected || 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);
}
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;
return clonedContact;
};
/**
* Persists contact to device storage.
* @param successCB success callback
* @param errorCB error callback
*/
Contact.prototype.save = function(successCB, errorCB) {
};
/**
* Contact name.
* @param formatted
* @param familyName
* @param givenName
* @param middle
* @param prefix
* @param suffix
*/
var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) {
this.formatted = formatted || null;
this.familyName = familyName || null;
this.givenName = givenName || null;
this.middleName = middle || null;
this.honorificPrefix = prefix || null;
this.honorificSuffix = suffix || null;
};
/**
* Generic contact field.
* @param type
* @param value
* @param primary
*/
var ContactField = function(type, value, primary) {
this.type = type || null;
this.value = value || null;
this.primary = primary || null;
};
/**
* Contact address.
* @param formatted
* @param streetAddress
* @param locality
* @param region
* @param postalCode
* @param country
*/
var ContactAddress = function(formatted, streetAddress, locality, region, postalCode, country) {
this.formatted = formatted || null;
this.streetAddress = streetAddress || null;
this.locality = locality || null;
this.region = region || null;
this.postalCode = postalCode || null;
this.country = country || null;
};
/**
* Contact organization.
* @param name
* @param dept
* @param title
* @param startDate
* @param endDate
* @param location
* @param desc
*/
var ContactOrganization = function(name, dept, title, startDate, endDate, location, desc) {
this.name = name || null;
this.department = dept || null;
this.title = title || null;
this.startDate = startDate || null;
this.endDate = endDate || null;
this.location = location || null;
this.description = desc || null;
};
/**
* Contact account.
* @param domain
* @param username
* @param userid
*/
var ContactAccount = function(domain, username, userid) {
this.domain = domain || null;
this.username = username || null;
this.userid = userid || null;
}
/**
* Represents a group of Contacts.
*/
var Contacts = function() {
this.inProgress = false;
this.records = new Array();
}
/**
* Returns an array of Contacts matching the search criteria.
* @param fields that should be searched
* @param successCB success callback
* @param errorCB error callback
* @param {ContactFindOptions} options that can be applied to contact searching
* @return array of Contacts matching search criteria
*/
Contacts.prototype.find = function(fields, successCB, errorCB, options) {
PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]);
};
/**
* This function creates a new contact, but it does not persist the contact
* to device storage. To persist the contact to device storage, invoke
* contact.save().
* @param properties an object who's properties will be examined to create a new Contact
* @returns new Contact object
*/
Contacts.prototype.create = function(properties) {
var contact = new Contact();
for (i in properties) {
if (contact[i]!='undefined') {
contact[i]=properties[i];
}
}
return contact;
};
/**
* ContactFindOptions.
* @param filter used to match contacts against
* @param multiple boolean used to determine if more than one contact should be returned
* @param limit maximum number of results to return from the contacts search
* @param updatedSince return only contact records that have been updated on or after the given time
*/
var ContactFindOptions = function(filter, multiple, limit, updatedSince) {
this.filter = filter || '';
this.multiple = multiple || false;
this.limit = limit || 1;
this.updatedSince = updatedSince || '';
this.timezone = timezone || null;
};
/**
@@ -244,10 +68,230 @@ ContactError.IO_ERROR = 5;
ContactError.NOT_SUPPORTED_ERROR = 6;
ContactError.PERMISSION_DENIED_ERROR = 20;
/**
* 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);
var i;
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 < clonedContact.phoneNumbers.length; i++) {
clonedContact.phoneNumbers[i].id = null;
}
}
if (clonedContact.emails) {
for (i = 0; i < clonedContact.emails.length; i++) {
clonedContact.emails[i].id = null;
}
}
if (clonedContact.addresses) {
for (i = 0; i < clonedContact.addresses.length; i++) {
clonedContact.addresses[i].id = null;
}
}
if (clonedContact.ims) {
for (i = 0; i < clonedContact.ims.length; i++) {
clonedContact.ims[i].id = null;
}
}
if (clonedContact.organizations) {
for (i = 0; i < clonedContact.organizations.length; i++) {
clonedContact.organizations[i].id = null;
}
}
if (clonedContact.tags) {
for (i = 0; i < clonedContact.tags.length; i++) {
clonedContact.tags[i].id = null;
}
}
if (clonedContact.photos) {
for (i = 0; i < clonedContact.photos.length; i++) {
clonedContact.photos[i].id = null;
}
}
if (clonedContact.urls) {
for (i = 0; i < clonedContact.urls.length; i++) {
clonedContact.urls[i].id = null;
}
}
return clonedContact;
};
/**
* Persists contact to device storage.
* @param successCB success callback
* @param errorCB error callback
*/
Contact.prototype.save = function(successCB, errorCB) {
PhoneGap.exec(successCB, errorCB, "Contacts", "save", [this]);
};
/**
* Contact name.
* @param formatted
* @param familyName
* @param givenName
* @param middle
* @param prefix
* @param suffix
*/
var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) {
this.formatted = formatted || null;
this.familyName = familyName || null;
this.givenName = givenName || null;
this.middleName = middle || null;
this.honorificPrefix = prefix || null;
this.honorificSuffix = suffix || null;
};
/**
* Generic contact field.
* @param {DOMString} id unique identifier, should only be set by native code
* @param type
* @param value
* @param pref
*/
var ContactField = function(type, value, pref) {
this.id = null;
this.type = type || null;
this.value = value || null;
this.pref = pref || null;
};
/**
* Contact address.
* @param {DOMString} id unique identifier, should only be set by native code
* @param formatted
* @param streetAddress
* @param locality
* @param region
* @param postalCode
* @param country
*/
var ContactAddress = function(formatted, streetAddress, locality, region, postalCode, country) {
this.id = null;
this.formatted = formatted || null;
this.streetAddress = streetAddress || null;
this.locality = locality || null;
this.region = region || null;
this.postalCode = postalCode || null;
this.country = country || null;
};
/**
* Contact organization.
* @param {DOMString} id unique identifier, should only be set by native code
* @param name
* @param dept
* @param title
* @param startDate
* @param endDate
* @param location
* @param desc
*/
var ContactOrganization = function(name, dept, title) {
this.id = null;
this.name = name || null;
this.department = dept || null;
this.title = title || null;
};
/**
* Represents a group of Contacts.
*/
var Contacts = function() {
this.inProgress = false;
this.records = [];
};
/**
* Returns an array of Contacts matching the search criteria.
* @param fields that should be searched
* @param successCB success callback
* @param errorCB error callback
* @param {ContactFindOptions} options that can be applied to contact searching
* @return array of Contacts matching search criteria
*/
Contacts.prototype.find = function(fields, successCB, errorCB, options) {
PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]);
};
/**
* This function creates a new contact, but it does not persist the contact
* to device storage. To persist the contact to device storage, invoke
* contact.save().
* @param properties an object who's properties will be examined to create a new Contact
* @returns new Contact object
*/
Contacts.prototype.create = function(properties) {
var i;
var contact = new Contact();
for (i in properties) {
if (contact[i] !== 'undefined') {
contact[i] = properties[i];
}
}
return contact;
};
/**
* This function returns and array of contacts. It is required as we need to convert raw
* JSON objects into concrete Contact objects. Currently this method is called after
* navigator.service.contacts.find but before the find methods success call back.
*
* @param jsonArray an array of JSON Objects that need to be converted to Contact objects.
* @returns an array of Contact objects
*/
Contacts.prototype.cast = function(pluginResult) {
var contacts = [];
var i;
for (i=0; i<pluginResult.message.length; i++) {
contacts.push(navigator.service.contacts.create(pluginResult.message[i]));
}
pluginResult.message = contacts;
return pluginResult;
};
/**
* ContactFindOptions.
* @param filter used to match contacts against
* @param multiple boolean used to determine if more than one contact should be returned
* @param updatedSince return only contact records that have been updated on or after the given time
*/
var ContactFindOptions = function(filter, multiple, updatedSince) {
this.filter = filter || '';
this.multiple = multiple || true;
this.updatedSince = updatedSince || '';
};
/**
* Add the contact interface into the browser.
*/
PhoneGap.addConstructor(function() {
if(typeof navigator.service == "undefined") navigator.service = new Object();
if(typeof navigator.service.contacts == "undefined") navigator.service.contacts = new Contacts();
if(typeof navigator.service === "undefined") {
navigator.service = {};
}
if(typeof navigator.service.contacts === "undefined") {
navigator.service.contacts = new Contacts();
}
});

View File

@@ -30,6 +30,8 @@ Crypto.prototype.getPlainString = function(string) {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.Crypto == "undefined") navigator.Crypto = new Crypto();
if (typeof navigator.Crypto === "undefined") {
navigator.Crypto = new Crypto();
}
});

View File

@@ -3,7 +3,7 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
/**
@@ -25,6 +25,7 @@ function Device() {
me.available = true;
me.platform = info.platform;
me.version = info.version;
me.name = info.name;
me.uuid = info.uuid;
me.phonegap = info.phonegap;
PhoneGap.onPhoneGapInfoReady.fire();
@@ -45,13 +46,13 @@ function Device() {
Device.prototype.getInfo = function(successCallback, errorCallback) {
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Device Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Device Error: errorCallback is not a function");
return;
}
@@ -61,31 +62,37 @@ Device.prototype.getInfo = function(successCallback, errorCallback) {
};
/*
* DEPRECATED
* This is only for Android.
*
* You must explicitly override the back button.
*/
Device.prototype.overrideBackButton = function() {
BackButton.override();
}
console.log("Device.overrideBackButton() is deprecated. Use App.overrideBackbutton(true).");
app.overrideBackbutton(true);
};
/*
* DEPRECATED
* This is only for Android.
*
* This resets the back button to the default behaviour
*/
Device.prototype.resetBackButton = function() {
BackButton.reset();
}
console.log("Device.resetBackButton() is deprecated. Use App.overrideBackbutton(false).");
app.overrideBackbutton(false);
};
/*
* DEPRECATED
* This is only for Android.
*
* This terminates the activity!
*/
Device.prototype.exitApp = function() {
BackButton.exitApp();
}
console.log("Device.exitApp() is deprecated. Use App.exitApp().");
app.exitApp();
};
PhoneGap.addConstructor(function() {
navigator.device = window.device = new Device();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
/*
* 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;
}

View File

@@ -17,7 +17,7 @@ function Geolocation() {
// Geolocation listeners
this.listeners = {};
};
}
/**
* Position error object
@@ -28,7 +28,7 @@ function Geolocation() {
function PositionError(code, message) {
this.code = code;
this.message = message;
};
}
PositionError.PERMISSION_DENIED = 1;
PositionError.POSITION_UNAVAILABLE = 2;
@@ -42,7 +42,7 @@ PositionError.TIMEOUT = 3;
* @param {PositionOptions} options The options for getting the position data. (OPTIONAL)
*/
Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) {
if (navigator._geo.listeners["global"]) {
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."));
@@ -53,20 +53,20 @@ Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallba
var maximumAge = 10000;
var enableHighAccuracy = false;
var timeout = 10000;
if (typeof options != "undefined") {
if (typeof options.maximumAge != "undefined") {
if (typeof options !== "undefined") {
if (typeof options.maximumAge !== "undefined") {
maximumAge = options.maximumAge;
}
if (typeof options.enableHighAccuracy != "undefined") {
if (typeof options.enableHighAccuracy !== "undefined") {
enableHighAccuracy = options.enableHighAccuracy;
}
if (typeof options.timeout != "undefined") {
if (typeof options.timeout !== "undefined") {
timeout = options.timeout;
}
}
navigator._geo.listeners["global"] = {"success" : successCallback, "fail" : errorCallback };
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,
@@ -81,17 +81,17 @@ Geolocation.prototype.watchPosition = function(successCallback, errorCallback, o
var maximumAge = 10000;
var enableHighAccuracy = false;
var timeout = 10000;
if (typeof options != "undefined") {
if (typeof options.frequency != "undefined") {
if (typeof options !== "undefined") {
if (typeof options.frequency !== "undefined") {
maximumAge = options.frequency;
}
if (typeof options.maximumAge != "undefined") {
if (typeof options.maximumAge !== "undefined") {
maximumAge = options.maximumAge;
}
if (typeof options.enableHighAccuracy != "undefined") {
if (typeof options.enableHighAccuracy !== "undefined") {
enableHighAccuracy = options.enableHighAccuracy;
}
if (typeof options.timeout != "undefined") {
if (typeof options.timeout !== "undefined") {
timeout = options.timeout;
}
}
@@ -118,7 +118,7 @@ Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, s
var coords = new Coordinates(lat, lng, alt, altacc, head, vel);
var loc = new Position(coords, stamp);
try {
if (lat == "undefined" || lng == "undefined") {
if (lat === "undefined" || lng === "undefined") {
navigator._geo.listeners[id].fail(new PositionError(PositionError.POSITION_UNAVAILABLE, "Lat/Lng are undefined."));
}
else {
@@ -130,8 +130,8 @@ Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, s
console.log("Geolocation Error: Error calling success callback function.");
}
if (id == "global") {
delete navigator._geo.listeners["global"];
if (id === "global") {
delete navigator._geo.listeners.global;
}
};
@@ -186,7 +186,7 @@ PhoneGap.addConstructor(function() {
navigator._geo = new Geolocation();
// No native geolocation object for Android 1.x, so use PhoneGap geolocation
if (typeof navigator.geolocation == 'undefined') {
if (typeof navigator.geolocation === 'undefined') {
navigator.geolocation = navigator._geo;
Geolocation.usingPhoneGap = true;
}

View File

@@ -5,9 +5,3 @@
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
PhoneGap.addConstructor(function() {
if (typeof navigator.splashScreen == "undefined") {
navigator.splashScreen = SplashScreen; // SplashScreen object come from native side through addJavaScriptInterface
}
});

View File

@@ -1,23 +0,0 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
function KeyEvent()
{
}
KeyEvent.prototype.backTrigger = function()
{
var e = document.createEvent('Events');
e.initEvent('backKeyDown');
document.dispatchEvent(e);
}
if (document.keyEvent == null || typeof document.keyEvent == 'undefined')
{
window.keyEvent = document.keyEvent = new KeyEvent();
}

View File

@@ -40,8 +40,8 @@ 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 (msg === Media.MEDIA_STATE) {
if (value === Media.MEDIA_STOPPED) {
if (media.successCallback) {
media.successCallback();
}
@@ -50,10 +50,10 @@ PhoneGap.Media.onStatus = function(id, msg, value) {
media.statusCallback(value);
}
}
else if (msg == Media.MEDIA_DURATION) {
else if (msg === Media.MEDIA_DURATION) {
media._duration = value;
}
else if (msg == Media.MEDIA_ERROR) {
else if (msg === Media.MEDIA_ERROR) {
if (media.errorCallback) {
media.errorCallback(value);
}
@@ -76,25 +76,25 @@ PhoneGap.Media.onStatus = function(id, msg, value) {
Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) {
// successCallback optional
if (successCallback && (typeof successCallback != "function")) {
if (successCallback && (typeof successCallback !== "function")) {
console.log("Media Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Media Error: errorCallback is not a function");
return;
}
// statusCallback optional
if (statusCallback && (typeof statusCallback != "function")) {
if (statusCallback && (typeof statusCallback !== "function")) {
console.log("Media Error: statusCallback is not a function");
return;
}
// statusCallback optional
if (positionCallback && (typeof positionCallback != "function")) {
if (positionCallback && (typeof positionCallback !== "function")) {
console.log("Media Error: positionCallback is not a function");
return;
}
@@ -129,9 +129,9 @@ Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"];
* @constructor
*/
function MediaError() {
this.code = null,
this.code = null;
this.message = "";
};
}
MediaError.MEDIA_ERR_ABORTED = 1;
MediaError.MEDIA_ERR_NETWORK = 2;
@@ -192,3 +192,10 @@ 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]);
};

View File

@@ -13,7 +13,7 @@
function NetworkStatus() {
//this.code = null;
//this.message = "";
};
}
NetworkStatus.NOT_REACHABLE = 0;
NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1;
@@ -30,7 +30,7 @@ function Network() {
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.
@@ -57,6 +57,8 @@ Network.prototype.isReachable = function(uri, callback, options) {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.network == "undefined") navigator.network = new Network();
if (typeof navigator.network === "undefined") {
navigator.network = new Network();
}
});

View File

@@ -111,6 +111,8 @@ Notification.prototype.beep = function(count) {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.notification == "undefined") navigator.notification = new Notification();
if (typeof navigator.notification === "undefined") {
navigator.notification = new Notification();
}
});

View File

@@ -3,7 +3,7 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
@@ -28,8 +28,9 @@
* document.addEventListener("resume", myResumeListener, false);
*/
if (typeof(DeviceInfo) != 'object')
if (typeof(DeviceInfo) !== 'object') {
DeviceInfo = {};
}
/**
* This represents the PhoneGap API itself, and provides a global namespace for accessing
@@ -48,7 +49,7 @@ var PhoneGap = {
/**
* Custom pub-sub channel that can have functions subscribed to it
*/
PhoneGap.Channel = function(type)
PhoneGap.Channel = function (type)
{
this.type = type;
this.handlers = {};
@@ -66,10 +67,10 @@ PhoneGap.Channel = function(type)
*/
PhoneGap.Channel.prototype.subscribe = function(f, c, g) {
// need a function to call
if (f == null) { return; }
if (f === null) { return; }
var func = f;
if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, 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;
@@ -88,9 +89,9 @@ PhoneGap.Channel.prototype.subscribeOnce = function(f, c) {
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); }
if (typeof c === "object" && f instanceof Function) { f = PhoneGap.close(c, f); }
f.apply(this, this.fireArgs);
} else {
g = this.subscribe(m);
@@ -113,11 +114,14 @@ PhoneGap.Channel.prototype.unsubscribe = function(g) {
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;
var item, handler, rv;
for (item in this.handlers) {
if (this.handlers.hasOwnProperty(item)) {
handler = this.handlers[item];
if (handler instanceof Function) {
rv = (handler.apply(this, arguments) === false);
fail = fail || rv;
}
}
}
this.fired = true;
@@ -134,18 +138,29 @@ PhoneGap.Channel.prototype.fire = function(e) {
PhoneGap.Channel.join = function(h, c) {
var i = c.length;
var f = function() {
if (!(--i)) h();
if (!(--i)) {
h();
}
};
var len = i;
var j;
for (j=0; j<len; j++) {
if (!c[j].fired) {
c[j].subscribeOnce(f);
}
else {
i--;
}
}
for (var j=0; j<i; j++) {
(!c[j].fired?c[j].subscribeOnce(f):i--);
if (!i) {
h();
}
if (!i) h();
};
/**
* Boolean flag indicating if the PhoneGap API is available and initialized.
*/ // TODO: Remove this, it is unused here ... -jm
PhoneGap.available = DeviceInfo.uuid != undefined;
PhoneGap.available = DeviceInfo.uuid !== undefined;
/**
* Add an initialization function to a queue that ensures it will run and initialize
@@ -157,27 +172,33 @@ PhoneGap.addConstructor = function(func) {
try {
func();
} catch(e) {
if (typeof(debug['log']) == 'function') {
debug.log("Failed to run constructor: " + debug.processMessage(e));
} else {
alert("Failed to run constructor: " + e.message);
}
console.log("Failed to run constructor: " + e);
}
});
};
/**
* Adds a plugin object to window.plugins
* Plugins object
*/
if (!window.plugins) {
window.plugins = {};
}
/**
* Adds a plugin object to window.plugins.
* The plugin is accessed using window.plugins.<name>
*
* @param name The plugin name
* @param obj The plugin object
*/
PhoneGap.addPlugin = function(name, obj) {
if ( !window.plugins ) {
window.plugins = {};
}
if ( !window.plugins[name] ) {
window.plugins[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
@@ -232,6 +253,39 @@ if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); }
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.
*/
@@ -239,11 +293,17 @@ PhoneGap.Channel.join(function() {
// Start listening for XHR callbacks
setTimeout(function() {
if (CallbackServer.usePolling()) {
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.JSCallback();
var polling = prompt("usePolling", "gap_callbackServer:");
if (polling == "true") {
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.JSCallback();
}
}
}, 1);
@@ -253,22 +313,21 @@ PhoneGap.Channel.join(function() {
// Fire event to notify that all objects are created
PhoneGap.onPhoneGapReady.fire();
// Fire onDeviceReady event once all constructors have run and PhoneGap info has been
// received from native side, and any user defined initialization channels.
PhoneGap.Channel.join(function() {
// Turn off app loading dialog
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();
@@ -279,17 +338,50 @@ PhoneGap.m_document_addEventListener = document.addEventListener;
document.addEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (e == 'deviceready') {
if (e === 'deviceready') {
PhoneGap.onDeviceReady.subscribeOnce(handler);
} else if (e == 'resume') {
} else if (e === 'resume') {
PhoneGap.onResume.subscribe(handler);
} else if (e == 'pause') {
if (PhoneGap.onDeviceReady.fired) {
PhoneGap.onResume.fire();
}
} else if (e === 'pause') {
PhoneGap.onPause.subscribe(handler);
} else {
PhoneGap.m_document_addEventListener.call(document, evt, handler);
}
else {
// If subscribing to Android backbutton
if (e === 'backbutton') {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]);
}
PhoneGap.m_document_addEventListener.call(document, evt, handler, capture);
}
};
// Intercept calls to document.removeEventListener and watch for events that
// are generated by PhoneGap native code
PhoneGap.m_document_removeEventListener = document.removeEventListener;
document.removeEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
// If unsubscribing to Android backbutton
if (e === 'backbutton') {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]);
}
PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture);
};
/**
* Method to fire event from native code
*/
PhoneGap.fireEvent = function(type) {
var e = document.createEvent('Events');
e.initEvent(type);
document.dispatchEvent(e);
};
/**
* If JSON not included, use our own stringify. (Android 1.6)
* The restriction on ours is that it must be an array of simple types.
@@ -298,48 +390,53 @@ document.addEventListener = function(evt, handler, capture) {
* @return
*/
PhoneGap.stringify = function(args) {
if (typeof JSON == "undefined") {
if (typeof JSON === "undefined") {
var s = "[";
for (var i=0; i<args.length; i++) {
if (i > 0) {
s = s + ",";
}
var type = typeof args[i];
if ((type == "number") || (type == "boolean")) {
s = s + args[i];
}
else if (args[i] instanceof Array) {
s = s + "[" + args[i] + "]";
}
else if (args[i] instanceof Object) {
var start = true;
s = s + '{';
for (var name in args[i]) {
if (!start) {
s = s + ',';
}
s = s + '"' + name + '":';
var nameType = typeof args[i][name];
if ((nameType == "number") || (nameType == "boolean")) {
s = s + args[i][name];
}
else {
s = s + '"' + args[i][name] + '"';
}
start=false;
}
s = s + '}';
}
else {
var a = args[i].replace(/\\/g, '\\\\');
a = a.replace(/"/g, '\\"');
s = s + '"' + a + '"';
var i, type, start, name, nameType, a;
for (i = 0; i < args.length; i++) {
if (args[i] != null) {
if (i > 0) {
s = s + ",";
}
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) {
start = true;
s = s + '{';
for (name in args[i]) {
if (args[i][name] !== null) {
if (!start) {
s = s + ',';
}
s = s + '"' + name + '":';
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 {
a = args[i].replace(/\\/g, '\\\\');
a = a.replace(/"/g, '\\"');
s = s + '"' + a + '"';
}
}
}
s = s + "]";
return s;
}
else {
} else {
return JSON.stringify(args);
}
};
@@ -351,13 +448,14 @@ PhoneGap.stringify = function(args) {
* @return
*/
PhoneGap.clone = function(obj) {
var i, retVal;
if(!obj) {
return obj;
}
if(obj instanceof Array){
var retVal = new Array();
for(var i = 0; i < obj.length; ++i){
retVal = [];
for(i = 0; i < obj.length; ++i){
retVal.push(PhoneGap.clone(obj[i]));
}
return retVal;
@@ -370,10 +468,14 @@ PhoneGap.clone = function(obj) {
if(!(obj instanceof Object)){
return obj;
}
retVal = new Object();
if (obj instanceof Date) {
return obj;
}
retVal = {};
for(i in obj){
if(!(i in retVal) || retVal[i] != obj[i]) {
if(!(i in retVal) || retVal[i] !== obj[i]) {
retVal[i] = PhoneGap.clone(obj[i]);
}
}
@@ -417,23 +519,22 @@ PhoneGap.exec = function(success, fail, service, action, args) {
PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
}
// Note: Device returns string, but for some reason emulator returns object - so convert to string.
var r = ""+PluginManager.exec(service, action, callbackId, this.stringify(args), true);
var r = prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
// If a result was returned
if (r.length > 0) {
eval("var v="+r+";");
// If status is OK, then return value back to caller
if (v.status == PhoneGap.callbackStatus.OK) {
if (v.status === PhoneGap.callbackStatus.OK) {
// If there is a success callback, then call it now with returned value
// 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);
} catch (e) {
console.log("Error in success callback: " + callbackId + " = " + e);
}
// Clear callback if not expecting any more results
@@ -445,7 +546,7 @@ PhoneGap.exec = function(success, fail, service, action, args) {
}
// If no result
else if (v.status == PhoneGap.callbackStatus.NO_RESULT) {
else if (v.status === PhoneGap.callbackStatus.NO_RESULT) {
// Clear callback if not expecting any more results
if (!v.keepCallback) {
@@ -455,15 +556,15 @@ PhoneGap.exec = function(success, fail, service, action, args) {
// If error, then display error
else {
console.log("Error: Status="+r.status+" Message="+v.message);
console.log("Error: Status="+v.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);
catch (e1) {
console.log("Error in error callback: "+callbackId+" = "+e1);
}
// Clear callback if not expecting any more results
@@ -474,8 +575,8 @@ PhoneGap.exec = function(success, fail, service, action, args) {
return null;
}
}
} catch (e) {
console.log("Error: "+e);
} catch (e2) {
console.log("Error: "+e2);
}
};
@@ -489,7 +590,7 @@ PhoneGap.callbackSuccess = function(callbackId, args) {
if (PhoneGap.callbacks[callbackId]) {
// If result is to be sent to callback
if (args.status == PhoneGap.callbackStatus.OK) {
if (args.status === PhoneGap.callbackStatus.OK) {
try {
if (PhoneGap.callbacks[callbackId].success) {
PhoneGap.callbacks[callbackId].success(args.message);
@@ -541,45 +642,50 @@ PhoneGap.callbackError = function(callbackId, args) {
*/
// TODO: Is this used?
PhoneGap.run_command = function() {
if (!PhoneGap.available || !PhoneGap.queue.ready)
if (!PhoneGap.available || !PhoneGap.queue.ready) {
return;
}
PhoneGap.queue.ready = false;
var args = PhoneGap.queue.commands.shift();
if (PhoneGap.queue.commands.length == 0) {
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 i;
for (i = 1; i < args.length; i++) {
var arg = args[i];
if (arg == undefined || arg == null)
if (arg === undefined || arg === null) {
arg = '';
if (typeof(arg) == 'object')
}
if (typeof(arg) === 'object') {
dict = arg;
else
} else {
uri.push(encodeURIComponent(arg));
}
}
var url = "gap://" + args[0] + "/" + uri.join("/");
if (dict != null) {
if (dict !== null) {
var name;
var query_args = [];
for (var name in dict) {
if (typeof(name) != 'string')
continue;
query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name]));
for (name in dict) {
if (dict.hasOwnProperty(name) && (typeof (name) === 'string')) {
query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name]));
}
}
if (query_args.length > 0)
if (query_args.length > 0) {
url += "?" + query_args.join("&");
}
}
document.location = url;
};
PhoneGap.JSCallbackPort = CallbackServer.getPort();
PhoneGap.JSCallbackToken = CallbackServer.getToken();
PhoneGap.JSCallbackPort = null;
PhoneGap.JSCallbackToken = null;
/**
* This is only for Android.
@@ -589,14 +695,21 @@ PhoneGap.JSCallbackToken = CallbackServer.getToken();
* Java to JavaScript.
*/
PhoneGap.JSCallback = function() {
// If polling flag was changed, start using polling from now on
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
return;
}
var xmlhttp = new XMLHttpRequest();
// Callback function when XMLHttpRequest is ready
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState == 4){
if(xmlhttp.readyState === 4){
// If callback has JavaScript statement to execute
if (xmlhttp.status == 200) {
if (xmlhttp.status === 200) {
var msg = xmlhttp.responseText;
setTimeout(function() {
@@ -605,7 +718,7 @@ PhoneGap.JSCallback = function() {
}
catch (e) {
// If we're getting an error here, seeing the message will help in debugging
console.log("Message from Server: " + msg);
console.log("JSCallback: Message from Server: " + msg);
console.log("JSCallback Error: "+e);
}
}, 1);
@@ -613,19 +726,42 @@ PhoneGap.JSCallback = function() {
}
// If callback ping (used to keep XHR request from timing out)
else if (xmlhttp.status == 404) {
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();
prompt("restartServer", "gap_callbackServer:");
PhoneGap.JSCallbackPort = null;
PhoneGap.JSCallbackToken = null;
setTimeout(PhoneGap.JSCallback, 100);
}
}
}
};
if (PhoneGap.JSCallbackPort === null) {
PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:");
}
if (PhoneGap.JSCallbackToken === null) {
PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:");
}
xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true);
xmlhttp.send();
};
@@ -636,6 +772,11 @@ PhoneGap.JSCallback = function() {
*/
PhoneGap.JSCallbackPollingPeriod = 50;
/**
* Flag that can be set by the user to force polling to be used or force XHR to be used.
*/
PhoneGap.UsePolling = false; // T=use polling, F=use XHR
/**
* This is only for Android.
*
@@ -644,13 +785,21 @@ PhoneGap.JSCallbackPollingPeriod = 50;
* Java to JavaScript.
*/
PhoneGap.JSCallbackPolling = function() {
var msg = CallbackServer.getJavascript();
// If polling flag was changed, stop using polling from now on
if (!PhoneGap.UsePolling) {
PhoneGap.JSCallback();
return;
}
var msg = prompt("", "gap_poll:");
if (msg) {
setTimeout(function() {
try {
var t = eval(""+msg);
}
catch (e) {
console.log("JSCallbackPolling: Message from Server: " + msg);
console.log("JSCallbackPolling Error: "+e);
}
}, 1);
@@ -676,9 +825,10 @@ PhoneGap.createUUID = function() {
PhoneGap.UUIDcreatePart = function(length) {
var uuidpart = "";
for (var i=0; i<length; i++) {
var uuidchar = parseInt((Math.random() * 256)).toString(16);
if (uuidchar.length == 1) {
var i, uuidchar;
for (i=0; i<length; i++) {
uuidchar = parseInt((Math.random() * 256),0).toString(16);
if (uuidchar.length === 1) {
uuidchar = "0" + uuidchar;
}
uuidpart += uuidchar;
@@ -690,11 +840,27 @@ PhoneGap.close = function(context, func, params) {
if (typeof params === 'undefined') {
return function() {
return func.apply(context, arguments);
}
};
} else {
return function() {
return func.apply(context, params);
}
};
}
};
/**
* Load a JavaScript file after page has loaded.
*
* @param {String} jsfile The url of the JavaScript file to load.
* @param {Function} successCallback The callback to call when the file has been loaded.
*/
PhoneGap.includeJavascript = function(jsfile, successCallback) {
var id = document.getElementsByTagName("head")[0];
var el = document.createElement('script');
el.type = 'text/javascript';
if (typeof successCallback === 'function') {
el.onload = successCallback;
}
el.src = jsfile;
id.appendChild(el);
};

View File

@@ -19,7 +19,7 @@
*/
function Position(coords, timestamp) {
this.coords = coords;
this.timestamp = (timestamp != 'undefined') ? timestamp : new Date().getTime();
this.timestamp = (timestamp !== 'undefined') ? timestamp : new Date().getTime();
}
function Coordinates(lat, lng, alt, acc, head, vel, altacc) {
@@ -50,7 +50,7 @@ function Coordinates(lat, lng, alt, acc, head, vel, altacc) {
/**
* The altitude accuracy of the position.
*/
this.altitudeAccuracy = (altacc != 'undefined') ? altacc : null;
this.altitudeAccuracy = (altacc !== 'undefined') ? altacc : null;
}
/**

View File

@@ -20,30 +20,13 @@ var DroidDB = function() {
this.queryQueue = {};
};
/**
* Callback from native code when result from a query is available.
* PRIVATE METHOD
*
* @param rawdata JSON string of the row data
* @param id Query id
*/
DroidDB.prototype.addResult = function(rawdata, id) {
try {
eval("var data = " + rawdata + ";");
var query = this.queryQueue[id];
query.resultSet.push(data);
} catch (e) {
console.log("DroidDB.addResult(): Error="+e);
}
};
/**
* Callback from native code when query is complete.
* PRIVATE METHOD
*
* @param id Query id
*/
DroidDB.prototype.completeQuery = function(id) {
DroidDB.prototype.completeQuery = function(id, data) {
var query = this.queryQueue[id];
if (query) {
try {
@@ -59,10 +42,10 @@ DroidDB.prototype.completeQuery = function(id) {
// Save query results
var r = new DroidDB_Result();
r.rows.resultSet = query.resultSet;
r.rows.length = query.resultSet.length;
r.rows.resultSet = data;
r.rows.length = data.length;
try {
if (typeof query.successCallback == 'function') {
if (typeof query.successCallback === 'function') {
query.successCallback(query.tx, r);
}
} catch (ex) {
@@ -100,7 +83,7 @@ DroidDB.prototype.fail = function(reason, id) {
tx.queryList = {};
try {
if (typeof query.errorCallback == 'function') {
if (typeof query.errorCallback === 'function') {
query.errorCallback(query.tx, reason);
}
} catch (ex) {
@@ -116,35 +99,6 @@ DroidDB.prototype.fail = function(reason, id) {
}
};
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, successCallback, errorCallback) {
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
@@ -162,6 +116,37 @@ var DroidDB_Tx = function() {
this.queryList = {};
};
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);
}
}
}
};
/**
* Mark query in transaction as complete.
* If all queries are complete, call the user's transaction success callback.
@@ -174,10 +159,13 @@ DroidDB_Tx.prototype.queryComplete = function(id) {
// If no more outstanding queries, then fire transaction success
if (this.successCallback) {
var count = 0;
for (var i in this.queryList) {
count++;
var i;
for (i in this.queryList) {
if (this.queryList.hasOwnProperty(i)) {
count++;
}
}
if (count == 0) {
if (count === 0) {
try {
this.successCallback();
} catch(e) {
@@ -237,7 +225,7 @@ var DroidDB_Query = function(tx) {
this.successCallback = null;
this.errorCallback = null;
}
};
/**
* Execute SQL statement
@@ -250,7 +238,7 @@ var DroidDB_Query = function(tx) {
DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) {
// Init params array
if (typeof params == 'undefined') {
if (typeof params === 'undefined') {
params = [];
}
@@ -308,9 +296,82 @@ DroidDB_openDatabase = function(name, version, display_name, size) {
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) {
var i;
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(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]);
}
);
};
this.clear = function() {
storage = {};
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', []);
}
);
};
} catch(e) {
alert("Database error "+e+".");
return;
}
};
PhoneGap.addConstructor(function() {
if (typeof window.openDatabase == "undefined") {
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");
}
});

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles)
*
* 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.
*
*/
(function() {
// window object
var global = window;
// WebSocket Object. All listener methods are cleaned up!
var WebSocket = global.WebSocket = function(url) {
// get a new websocket object from factory (check com.strumsoft.websocket.WebSocketFactory.java)
this.socket = WebSocketFactory.getInstance(url);
// store in registry
if(this.socket) {
WebSocket.store[this.socket.getId()] = this;
} else {
throw new Error('websocket instantiation failed! Address could be wrong.');
}
};
// storage to hold websocket object for later invokation of event methods
WebSocket.store = {};
// static event methods to call event methods on target websocket objects
WebSocket.onmessage = function (evt) {
WebSocket.store[evt._target]['onmessage'].call(global, evt._data);
}
WebSocket.onopen = function (evt) {
WebSocket.store[evt._target]['onopen'].call(global, evt._data);
}
WebSocket.onclose = function (evt) {
WebSocket.store[evt._target]['onclose'].call(global, evt._data);
}
WebSocket.onerror = function (evt) {
WebSocket.store[evt._target]['onerror'].call(global, evt._data);
}
// instance event methods
WebSocket.prototype.send = function(data) {
this.socket.send(data);
}
WebSocket.prototype.close = function() {
this.socket.close();
}
WebSocket.prototype.getReadyState = function() {
this.socket.getReadyState();
}
///////////// Must be overloaded
WebSocket.prototype.onopen = function(){
throw new Error('onopen not implemented.');
};
// alerts message pushed from server
WebSocket.prototype.onmessage = function(msg){
throw new Error('onmessage not implemented.');
};
// alerts message pushed from server
WebSocket.prototype.onerror = function(msg){
throw new Error('onerror not implemented.');
};
// alert close event
WebSocket.prototype.onclose = function(){
throw new Error('onclose not implemented.');
};
})();

View File

@@ -0,0 +1,9 @@
<html>
<head>
<title></title>
<script src="phonegap.0.9.4.min.js"></script>
</head>
<body>
</body>
</html>

73
framework/build.xml Normal file → Executable file
View File

@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="PhoneGap" default="help">
<!-- LOAD VERSION -->
<loadfile property="version" srcFile="../VERSION">
<filterchain>
<striplinebreaks/>
</filterchain>
</loadfile>
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked in in Version
Control Systems. -->
@@ -73,7 +80,7 @@
<include name="lint.js" />
</fileset>
<fileset dir="assets/www">
<include name="phonegap.js" />
<include name="phonegap.${version}.js" />
</fileset>
</concat>
@@ -95,16 +102,72 @@
</exec>
</target>
<target name="build-javascript">
<delete file="assets/www/phonegap.js"/>
<concat destfile="assets/www/phonegap.js">
<!-- Combine JavaScript files into one phonegap.js file.
This task does not create a compressed JavaScript file. -->
<target name="build-uncompressed-javascript">
<!-- Clean up existing files -->
<delete file="assets/www/phonegap.${version}.min.js"/>
<delete file="assets/www/phonegap-tmp.js"/>
<delete file="assets/www/phonegap.${version}.js"/>
<!-- Create uncompressed JS file -->
<concat destfile="assets/www/phonegap.${version}.js">
<fileset dir="assets/js" includes="phonegap.js.base" />
<fileset dir="assets/js" includes="*.js" />
</concat>
<!-- update project files to reference phonegap-x.x.x.js -->
<replaceregexp match="phonegap(.*)\.js" replace="phonegap.${version}.js" byline="true">
<fileset file="assets/www/index.html" />
<fileset file="../example/index.html" />
</replaceregexp>
</target>
<!-- Combine JavaScript files into one phonegap-uncompressed.js file.
Compress this file using yuicompressor to create phonegap.js. -->
<target name="build-javascript">
<!-- Clean up existing files -->
<delete file="assets/www/phonegap_${version}.min.js"/>
<delete file="assets/www/phonegap-tmp.js"/>
<delete file="assets/www/phonegap_${version}.js"/>
<!-- Create uncompressed JS file -->
<concat destfile="assets/www/phonegap.${version}.js">
<fileset dir="assets/js" includes="phonegap.js.base" />
<fileset dir="assets/js" includes="*.js" />
</concat>
<!-- Compress JS file -->
<java jar="${basedir}/../util/yuicompressor/yuicompressor-2.4.2.jar" fork="true" failonerror="true">
<arg line="--nomunge -o assets/www/phonegap-tmp.js assets/www/phonegap.${version}.js"/>
</java>
<concat destfile="assets/www/phonegap.${version}.min.js">
<fileset dir="assets/js" includes="header.txt" />
<fileset dir="assets/www" includes="phonegap-tmp.js" />
</concat>
<!-- update project files to reference phonegap-x.x.x.min.js -->
<replaceregexp match="phonegap(.*)\.js" replace="phonegap.${version}.min.js" byline="true">
<fileset file="assets/www/index.html" />
<fileset file="../example/index.html" />
</replaceregexp>
<!-- Delete temp file -->
<delete file="assets/www/phonegap-tmp.js"/>
</target>
<!-- Build PhoneGap jar file that includes all native code, and PhoneGap JS file
that includes all JavaScript code.
The default is to compress the JavaScript code using yuicompressor.
If you want uncompressed JavaScript, change
"build-javascript" => "build-uncompressed-javascript".
-->
<target name="jar" depends="build-javascript, compile">
<jar jarfile="phonegap.jar" basedir="bin/classes" excludes="**/R*.class" />
<jar jarfile="phonegap.${version}.jar" basedir="bin/classes" excludes="com/phonegap/R.class,com/phonegap/R$*.class"/>
</target>
<target name="phonegap_debug" depends="build-javascript, debug">

View File

@@ -13,6 +13,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
@@ -60,7 +61,7 @@ public class AccelListener extends Plugin implements SensorEventListener {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
super.setContext(ctx);
this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
}

View File

@@ -0,0 +1,173 @@
/*
* 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-2011, IBM Corporation
*/
package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
/**
* This class exposes methods in DroidGap that can be called from JavaScript.
*/
public class App extends Plugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
try {
if (action.equals("clearCache")) {
this.clearCache();
}
else if (action.equals("loadUrl")) {
this.loadUrl(args.getString(0), args.optJSONObject(1));
}
else if (action.equals("cancelLoadUrl")) {
this.cancelLoadUrl();
}
else if (action.equals("clearHistory")) {
this.clearHistory();
}
else if (action.equals("addService")) {
this.addService(args.getString(0), args.getString(1));
}
else if (action.equals("overrideBackbutton")) {
this.overrideBackbutton(args.getBoolean(0));
}
else if (action.equals("isBackbuttonOverridden")) {
boolean b = this.isBackbuttonOverridden();
return new PluginResult(status, b);
}
else if (action.equals("exitApp")) {
this.exitApp();
}
return new PluginResult(status, result);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
/**
* Clear the resource cache.
*/
public void clearCache() {
((DroidGap)this.ctx).clearCache();
}
/**
* Load the url into the webview.
*
* @param url
* @param props Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...)
* @throws JSONException
*/
public void loadUrl(String url, JSONObject props) throws JSONException {
System.out.println("App.loadUrl("+url+","+props+")");
int wait = 0;
// If there are properties, then set them on the Activity
if (props != null) {
JSONArray keys = props.names();
for (int i=0; i<keys.length(); i++) {
String key = keys.getString(i);
if (key.equals("wait")) {
wait = props.getInt(key);
}
else {
Object value = props.get(key);
if (value == null) {
}
else if (value.getClass().equals(String.class)) {
this.ctx.getIntent().putExtra(key, (String)value);
}
else if (value.getClass().equals(Boolean.class)) {
this.ctx.getIntent().putExtra(key, (Boolean)value);
}
else if (value.getClass().equals(Integer.class)) {
this.ctx.getIntent().putExtra(key, (Integer)value);
}
}
}
}
// If wait property, then delay loading
if (wait > 0) {
((DroidGap)this.ctx).loadUrl(url, wait);
}
else {
((DroidGap)this.ctx).loadUrl(url);
}
}
/**
* Cancel loadUrl before it has been loaded.
*/
public void cancelLoadUrl() {
((DroidGap)this.ctx).cancelLoadUrl();
}
/**
* Clear web history in this web view.
*/
public void clearHistory() {
((DroidGap)this.ctx).clearHistory();
}
/**
* Add a class that implements a service.
*
* @param serviceType
* @param className
*/
public void addService(String serviceType, String className) {
this.ctx.addService(serviceType, className);
}
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* @param override T=override, F=cancel override
*/
public void overrideBackbutton(boolean override) {
System.out.println("WARNING: Back Button Default Behaviour will be overridden. The backbutton event will be fired!");
((DroidGap)this.ctx).bound = override;
}
/**
* Return whether the Android back button is overridden by the user.
*
* @return boolean
*/
public boolean isBackbuttonOverridden() {
return ((DroidGap)this.ctx).bound;
}
/**
* Exit the Android application.
*/
public void exitApp() {
((DroidGap)this.ctx).finish();
}
}

View File

@@ -20,7 +20,7 @@ import android.content.Context;
import android.media.AudioManager;
/**
* This class called by DroidGap to play and record audio.
* This class called by PhonegapActivity to play and record audio.
* The file can be local or over a network using http.
*
* Audio formats supported (tested):
@@ -77,13 +77,17 @@ public class AudioHandler extends Plugin {
long l = this.getDurationAudio(args.getString(0), args.getString(1));
return new PluginResult(status, l);
}
else if (action.equals("release")) {
boolean b = this.release(args.getString(0));
return new PluginResult(status, b);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
@@ -117,6 +121,21 @@ public class AudioHandler extends Plugin {
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
/**
* Release the audio player instance to save memory.
*
* @param id The id of the audio player
*/
private boolean release(String id) {
if (!this.players.containsKey(id)) {
return false;
}
AudioPlayer audio = this.players.get(id);
this.players.remove(id);
audio.destroy();
return true;
}
/**
* Start recording and save the specified file.

View File

@@ -1,51 +0,0 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
*/
package com.phonegap;
import android.app.Activity;
import android.util.Log;
import android.webkit.WebView;
/*
* This class literally exists to protect DroidGap from Javascript directly.
*
*
*/
public class BrowserKey {
DroidGap mAction;
boolean bound;
WebView mView;
BrowserKey(WebView view, DroidGap action)
{
bound = false;
mAction = action;
}
public void override()
{
Log.d("PhoneGap", "WARNING: Back Button Default Behaviour will be overridden. The backKeyDown event will be fired!");
bound = true;
}
public boolean isBound()
{
return bound;
}
public void reset()
{
bound = false;
}
public void exitApp()
{
mAction.finish();
}
}

View File

@@ -69,7 +69,7 @@ public class CallbackServer implements Runnable {
/**
* Indicates that polling should be used instead of XHR.
*/
private boolean usePolling;
private boolean usePolling = true;
/**
* Security token to prevent other apps from accessing this callback server via XHR
@@ -84,10 +84,28 @@ public class CallbackServer implements Runnable {
this.active = false;
this.empty = true;
this.port = 0;
this.javascript = new LinkedList<String>();
if (android.net.Proxy.getDefaultHost() != null) {
this.javascript = new LinkedList<String>();
}
/**
* Init callback server and start XHR if running local app.
*
* If PhoneGap app is loaded from file://, then we can use XHR
* otherwise we have to use polling due to cross-domain security restrictions.
*
* @param url The URL of the PhoneGap app being loaded
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
this.usePolling = true;
this.stopServer();
}
else if (android.net.Proxy.getDefaultHost() != null) {
this.usePolling = true;
this.stopServer();
}
else {
this.usePolling = false;
@@ -96,7 +114,7 @@ public class CallbackServer implements Runnable {
}
/**
* Determine if polling should be used instead of XHR.
* Return if polling is being used instead of XHR.
*
* @return
*/
@@ -158,9 +176,9 @@ public class CallbackServer implements Runnable {
String request;
ServerSocket waitSocket = new ServerSocket(0);
this.port = waitSocket.getLocalPort();
//System.out.println(" -- using port " +this.port);
//System.out.println("CallbackServer -- using port " +this.port);
this.token = java.util.UUID.randomUUID().toString();
//System.out.println(" -- using token "+this.token);
//System.out.println("CallbackServer -- using token "+this.token);
while (this.active) {
//System.out.println("CallbackServer: Waiting for data on socket");
@@ -168,43 +186,62 @@ public class CallbackServer implements Runnable {
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()),40);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
request = xhrReader.readLine();
//System.out.println("Request="+request);
if (request.contains("GET")) {
String response = "";
//System.out.println("CallbackServerRequest="+request);
if (this.active && (request != null)) {
if (request.contains("GET")) {
// Get requested file
String[] requestParts = request.split(" ");
// Must have security token
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
//System.out.println("CallbackServer -- Processing GET request");
// Must have security token
if (request.substring(5,41).equals(this.token)) {
//System.out.println(" -- Processing GET request");
// Wait until there is some data to send, or send empty data every 30 sec
// to prevent XHR timeout on the client
synchronized (this) {
while (this.empty) {
try {
this.wait(30000); // prevent timeout from happening
//System.out.println(">>> break <<<");
break;
// Wait until there is some data to send, or send empty data every 10 sec
// to prevent XHR timeout on the client
synchronized (this) {
while (this.empty) {
try {
this.wait(10000); // prevent timeout from happening
//System.out.println("CallbackServer>>> break <<<");
break;
}
catch (Exception e) { }
}
catch (Exception e) { }
}
}
}
// If server is still running
if (this.active) {
// If server is still running
if (this.active) {
// If no data, then send 404 back to client before it times out
if (this.empty) {
//System.out.println(" -- sending data 0");
output.writeBytes("HTTP/1.1 404 NO DATA\r\n\r\n");
// If no data, then send 404 back to client before it times out
if (this.empty) {
//System.out.println("CallbackServer -- sending data 0");
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
}
else {
//System.out.println("CallbackServer -- sending item");
response = "HTTP/1.1 200 OK\r\n\r\n"+this.getJavascript();
}
}
else {
//System.out.println(" -- sending item");
output.writeBytes("HTTP/1.1 200 OK\r\n\r\n"+this.getJavascript());
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
}
//System.out.println("CallbackServer: response="+response);
//System.out.println("CallbackServer: closing output");
output.writeBytes(response);
output.flush();
}
//System.out.println("CallbackServer: closing output");
output.close();
output.close();
xhrReader.close();
}
} catch (IOException e) {
e.printStackTrace();
@@ -219,11 +256,13 @@ public class CallbackServer implements Runnable {
*/
public void stopServer() {
//System.out.println("CallbackServer.stopServer()");
this.active = false;
if (this.active) {
this.active = false;
// Break out of server wait
synchronized (this) {
this.notify();
// Break out of server wait
synchronized (this) {
this.notify();
}
}
}

View File

@@ -80,7 +80,7 @@ public class CameraLauncher extends Plugin {
this.takePicture(args.getInt(0), destType);
}
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
this.getImage(srcType, destType);
this.getImage(args.getInt(0), srcType, destType);
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
@@ -100,7 +100,7 @@ public class CameraLauncher extends Plugin {
/**
* Take a picture with the camera.
* When an image is captured or the camera view is cancelled, the result is returned
* in DroidGap.onActivityResult, which forwards the result to this.onActivityResult.
* in PhonegapActivity.onActivityResult, which forwards the result to this.onActivityResult.
*
* The image can either be returned as a base64 string or a URI that points to the file.
* To display base64 string in an img tag, set the source to:
@@ -129,10 +129,14 @@ public class CameraLauncher extends Plugin {
/**
* Get image from photo library.
*
* @param returnType
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param srcType The album to get image from.
* @param returnType Set the type of image to return.
*/
// TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
public void getImage(int srcType, int returnType) {
public void getImage(int quality, int srcType, int returnType) {
this.mQuality = quality;
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
@@ -197,6 +201,9 @@ public class CameraLauncher extends Plugin {
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
bitmap.recycle();
bitmap = null;
System.gc();
} catch (IOException e) {
e.printStackTrace();
this.failPicture("Error capturing image.");
@@ -224,6 +231,9 @@ public class CameraLauncher extends Plugin {
try {
Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri));
this.processPicture(bitmap);
bitmap.recycle();
bitmap = null;
System.gc();
} catch (FileNotFoundException e) {
e.printStackTrace();
this.failPicture("Error retrieving image.");
@@ -257,11 +267,15 @@ public class CameraLauncher extends Plugin {
byte[] output = Base64.encodeBase64(code);
String js_out = new String(output);
this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId);
js_out = null;
output = null;
code = null;
}
}
catch(Exception e) {
this.failPicture("Error compressing image.");
}
jpeg_data = null;
}
/**

View File

@@ -12,6 +12,7 @@ import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
@@ -55,7 +56,7 @@ public class CompassListener extends Plugin implements SensorEventListener {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
super.setContext(ctx);
this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
}

View File

@@ -155,6 +155,9 @@ public abstract class ContactAccessor {
else if (key.startsWith("urls")) {
map.put("urls", true);
}
else if (key.startsWith("photos")) {
map.put("photos", true);
}
}
}
catch (JSONException e) {
@@ -162,11 +165,36 @@ public abstract class ContactAccessor {
}
return map;
}
/**
* Convenience method to get a string from a JSON object. Saves a
* lot of try/catch writing.
* If the property is not found in the object null will be returned.
*
* @param obj contact object to search
* @param property to be looked up
* @return The value of the property
*/
protected String getJsonString(JSONObject obj, String property) {
String value = null;
try {
value = obj.getString(property);
if (value.equals("null")) {
Log.d(LOG_TAG, property + " is string called 'null'");
value = null;
}
}
catch (JSONException e) {
Log.d(LOG_TAG, "Could not get = " + e.getMessage());
}
return value;
}
/**
* Handles adding a JSON Contact object into the database.
* @return TODO
*/
public abstract void save(JSONObject contact);
public abstract boolean save(JSONObject contact);
/**
* Handles searching through SDK-specific contacts API.

View File

@@ -36,6 +36,7 @@ import org.json.JSONObject;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Contacts;
@@ -62,6 +63,7 @@ import android.webkit.WebView;
*/
@SuppressWarnings("deprecation")
public class ContactAccessorSdk3_4 extends ContactAccessor {
private static final String PEOPLE_ID_EQUALS = "people._id = ?";
/**
* A static map that converts the JavaScript property name to Android database column name.
*/
@@ -102,22 +104,28 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
*/
public JSONArray search(JSONArray fields, JSONObject options) {
String searchTerm = "";
int limit = 1;
boolean multiple = false;
try {
searchTerm = options.getString("filter");
int limit = Integer.MAX_VALUE;
boolean multiple = true;
if (options != null) {
searchTerm = options.optString("filter");
if (searchTerm.length()==0) {
searchTerm = "%";
}
else {
searchTerm = "%" + searchTerm + "%";
}
multiple = options.getBoolean("multiple");
if (multiple) {
limit = options.getInt("limit");
try {
multiple = options.getBoolean("multiple");
if (!multiple) {
limit = 1;
}
} catch (JSONException e) {
// Multiple was not specified so we assume the default is true.
}
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
else {
searchTerm = "%";
}
ContentResolver cr = mApp.getContentResolver();
@@ -140,7 +148,7 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
// Do query for name and note
Cursor cur = cr.query(People.CONTENT_URI,
new String[] {People.DISPLAY_NAME, People.NOTES},
"people._id = ?",
PEOPLE_ID_EQUALS,
new String[] {contactId},
null);
cur.moveToFirst();
@@ -305,11 +313,13 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
while (cursor.moveToNext()) {
im = new JSONObject();
try{
im.put("primary", false);
im.put("id", cursor.getString(
cursor.getColumnIndex(ContactMethods._ID)));
im.put("perf", false);
im.put("value", cursor.getString(
cursor.getColumnIndex(ContactMethodsColumns.DATA)));
im.put("type", cursor.getString(
cursor.getColumnIndex(ContactMethodsColumns.TYPE)));
im.put("type", getContactType(cursor.getInt(
cursor.getColumnIndex(ContactMethodsColumns.TYPE))));
ims.put(im);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
@@ -335,13 +345,10 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
while (cursor.moveToNext()) {
organization = new JSONObject();
try{
organization.put("id", cursor.getString(cursor.getColumnIndex(Organizations._ID)));
organization.put("name", cursor.getString(cursor.getColumnIndex(Organizations.COMPANY)));
organization.put("title", cursor.getString(cursor.getColumnIndex(Organizations.TITLE)));
// organization.put("department", cursor.getString(cursor.getColumnIndex(Organizations)));
// organization.put("description", cursor.getString(cursor.getColumnIndex(Organizations)));
// organization.put("endDate", cursor.getString(cursor.getColumnIndex(Organizations)));
// organization.put("location", cursor.getString(cursor.getColumnIndex(Organizations)));
// organization.put("startDate", cursor.getString(cursor.getColumnIndex(Organizations)));
organizations.put(organization);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
@@ -368,6 +375,7 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
while (cursor.moveToNext()) {
address = new JSONObject();
try{
address.put("id", cursor.getString(cursor.getColumnIndex(ContactMethods._ID)));
address.put("formatted", cursor.getString(cursor.getColumnIndex(ContactMethodsColumns.DATA)));
addresses.put(address);
} catch (JSONException e) {
@@ -394,9 +402,10 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
while (cursor.moveToNext()) {
phone = new JSONObject();
try{
phone.put("primary", false);
phone.put("id", cursor.getString(cursor.getColumnIndex(Phones._ID)));
phone.put("perf", false);
phone.put("value", cursor.getString(cursor.getColumnIndex(Phones.NUMBER)));
phone.put("type", cursor.getString(cursor.getColumnIndex(Phones.TYPE)));
phone.put("type", getPhoneType(cursor.getInt(cursor.getColumnIndex(Phones.TYPE))));
phones.put(phone);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
@@ -422,7 +431,8 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
while (cursor.moveToNext()) {
email = new JSONObject();
try{
email.put("primary", false);
email.put("id", cursor.getString(cursor.getColumnIndex(ContactMethods._ID)));
email.put("perf", false);
email.put("value", cursor.getString(cursor.getColumnIndex(ContactMethods.DATA)));
// TODO Find out why adding an email type throws and exception
//email.put("type", cursor.getString(cursor.getColumnIndex(ContactMethods.TYPE)));
@@ -434,10 +444,372 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
return emails;
}
/**
* This method will save a contact object into the devices contacts database.
*
* @param contact the contact to be saved.
* @returns true if the contact is successfully saved, false otherwise.
*/
@Override
public void save(JSONObject contact) {
// TODO Auto-generated method stub
public boolean save(JSONObject contact) {
ContentValues personValues = new ContentValues();
String id = getJsonString(contact, "id");
String name = getJsonString(contact, "displayName");
if (name != null) {
personValues.put(Contacts.People.NAME, name);
}
String note = getJsonString(contact, "note");
if (note != null) {
personValues.put(Contacts.People.NOTES, note);
}
/* STARRED 0 = Contacts, 1 = Favorites */
personValues.put(Contacts.People.STARRED, 0);
Uri newPersonUri;
// Add new contact
if (id == null) {
newPersonUri = Contacts.People.createPersonInMyContactsGroup(mApp.getContentResolver(), personValues);
}
// modify existing contact
else {
newPersonUri = Uri.withAppendedPath(Contacts.People.CONTENT_URI, id);
mApp.getContentResolver().update(newPersonUri, personValues, PEOPLE_ID_EQUALS, new String[]{id});
}
if (newPersonUri != null) {
// phoneNumbers
savePhoneNumbers(contact, newPersonUri);
// emails
saveEntries(contact, newPersonUri, "emails", Contacts.KIND_EMAIL);
// addresses
saveAddresses(contact, newPersonUri);
// organizations
saveOrganizations(contact, newPersonUri);
// ims
saveEntries(contact, newPersonUri, "ims", Contacts.KIND_IM);
// Successfully create a Contact
return true;
}
return false;
}
/**
* Takes a JSON contact object and loops through the available organizations. If the
* organization has an id that is not equal to null the organization will be updated in the database.
* If the id is null then we treat it as a new organization.
*
* @param contact the contact to extract the organizations from
* @param uri the base URI for this contact.
*/
private void saveOrganizations(JSONObject contact, Uri newPersonUri) {
ContentValues values = new ContentValues();
Uri orgUri = Uri.withAppendedPath(newPersonUri,
Contacts.Organizations.CONTENT_DIRECTORY);
String id = null;
try {
JSONArray orgs = contact.getJSONArray("organizations");
if (orgs != null && orgs.length() > 0) {
JSONObject org;
for (int i=0; i<orgs.length(); i++) {
org = orgs.getJSONObject(i);
id = getJsonString(org, "id");
values.put(Contacts.Organizations.COMPANY, getJsonString(org, "name"));
values.put(Contacts.Organizations.TITLE, getJsonString(org, "title"));
if (id == null) {
Uri contactUpdate = mApp.getContentResolver().insert(orgUri, values);
}
else {
Uri tempUri = Uri.withAppendedPath(orgUri, id);
mApp.getContentResolver().update(tempUri, values, null, null);
}
}
}
}
catch (JSONException e) {
Log.d(LOG_TAG, "Could not save organizations = " + e.getMessage());
}
}
/**
* Takes a JSON contact object and loops through the available addresses. If the
* address has an id that is not equal to null the address will be updated in the database.
* If the id is null then we treat it as a new address.
*
* @param contact the contact to extract the addresses from
* @param uri the base URI for this contact.
*/
private void saveAddresses(JSONObject contact, Uri uri) {
ContentValues values = new ContentValues();
Uri newUri = Uri.withAppendedPath(uri,
Contacts.People.ContactMethods.CONTENT_DIRECTORY);
String id = null;
try {
JSONArray entries = contact.getJSONArray("addresses");
if (entries != null && entries.length() > 0) {
JSONObject entry;
values.put(Contacts.ContactMethods.KIND, Contacts.KIND_POSTAL);
for (int i=0; i<entries.length(); i++) {
entry = entries.getJSONObject(i);
id = getJsonString(entry, "id");
String address = getJsonString(entry, "formatted");
if (address != null) {
values.put(Contacts.ContactMethods.DATA, address);
}
else {
values.put(Contacts.ContactMethods.DATA, createAddressString(entry));
}
if (id == null) {
Uri contactUpdate = mApp.getContentResolver().insert(newUri, values);
}
else {
Uri tempUri = Uri.withAppendedPath(newUri, id);
mApp.getContentResolver().update(tempUri, values, null, null);
}
}
}
}
catch (JSONException e) {
Log.d(LOG_TAG, "Could not save address = " + e.getMessage());
}
}
/**
* Takes a ContactAddress JSON object and creates a fully
* formatted address string.
*
* @param entry the full address object
* @return a formatted address string
*/
private String createAddressString(JSONObject entry) {
StringBuffer buffer = new StringBuffer("");
if (getJsonString(entry, "locality") != null ) {
buffer.append(getJsonString(entry, "locality"));
}
if (getJsonString(entry, "region") != null ) {
if (buffer.length() > 0 ) {
buffer.append(", ");
}
buffer.append(getJsonString(entry, "region"));
}
if (getJsonString(entry, "postalCode") != null ) {
if (buffer.length() > 0 ) {
buffer.append(", ");
}
buffer.append(getJsonString(entry, "postalCode"));
}
if (getJsonString(entry, "country") != null ) {
if (buffer.length() > 0 ) {
buffer.append(", ");
}
buffer.append(getJsonString(entry, "country"));
}
return buffer.toString();
}
/**
* Takes a JSON contact object and loops through the available entries (Emails/IM's). If the
* entry has an id that is not equal to null the entry will be updated in the database.
* If the id is null then we treat it as a new entry.
*
* @param contact the contact to extract the entries from
* @param uri the base URI for this contact.
*/
private void saveEntries(JSONObject contact, Uri uri, String dataType, int contactKind) {
ContentValues values = new ContentValues();
Uri newUri = Uri.withAppendedPath(uri,
Contacts.People.ContactMethods.CONTENT_DIRECTORY);
String id = null;
try {
JSONArray entries = contact.getJSONArray(dataType);
if (entries != null && entries.length() > 0) {
JSONObject entry;
values.put(Contacts.ContactMethods.KIND, contactKind);
for (int i=0; i<entries.length(); i++) {
entry = entries.getJSONObject(i);
id = getJsonString(entry, "id");
values.put(Contacts.ContactMethods.DATA, getJsonString(entry, "value"));
values.put(Contacts.ContactMethods.TYPE, getContactType(getJsonString(entry, "type")));
if (id==null) {
Uri contactUpdate = mApp.getContentResolver().insert(newUri, values);
}
else {
Uri tempUri = Uri.withAppendedPath(newUri, id);
mApp.getContentResolver().update(tempUri, values, null, null);
}
}
}
}
catch (JSONException e) {
Log.d(LOG_TAG, "Could not save " + dataType + " = " + e.getMessage());
}
}
/**
* Converts a string from the W3C Contact API to it's Android int value.
* @param string
* @return Android int value
*/
private int getContactType(String string) {
int type = Contacts.ContactMethods.TYPE_OTHER;
if (string!=null) {
if ("home".equals(string.toLowerCase())) {
return Contacts.ContactMethods.TYPE_HOME;
}
else if ("work".equals(string.toLowerCase())) {
return Contacts.ContactMethods.TYPE_WORK;
}
else if ("other".equals(string.toLowerCase())) {
return Contacts.ContactMethods.TYPE_OTHER;
}
else if ("custom".equals(string.toLowerCase())) {
return Contacts.ContactMethods.TYPE_CUSTOM;
}
}
return type;
}
/**
* getPhoneType converts an Android phone type into a string
* @param type
* @return phone type as string.
*/
private String getContactType(int type) {
String stringType;
switch (type) {
case Contacts.ContactMethods.TYPE_CUSTOM:
stringType = "custom";
break;
case Contacts.ContactMethods.TYPE_HOME:
stringType = "home";
break;
case Contacts.ContactMethods.TYPE_WORK:
stringType = "work";
break;
case Contacts.ContactMethods.TYPE_OTHER:
default:
stringType = "other";
break;
}
return stringType;
}
/**
* Takes a JSON contact object and loops through the available phone numbers. If the phone
* number has an id that is not equal to null the phone number will be updated in the database.
* If the id is null then we treat it as a new phone number.
*
* @param contact the contact to extract the phone numbers from
* @param uri the base URI for this contact.
*/
private void savePhoneNumbers(JSONObject contact, Uri uri) {
ContentValues values = new ContentValues();
Uri phonesUri = Uri.withAppendedPath(uri,
Contacts.People.Phones.CONTENT_DIRECTORY);
String id = null;
try {
JSONArray phones = contact.getJSONArray("phoneNumbers");
if (phones != null && phones.length() > 0) {
JSONObject phone;
for (int i=0; i<phones.length(); i++) {
phone = phones.getJSONObject(i);
id = getJsonString(phone, "id");
values.put(Contacts.Phones.NUMBER, getJsonString(phone, "value"));
values.put(Contacts.Phones.TYPE, getPhoneType(getJsonString(phone, "type")));
if (id==null) {
Uri phoneUpdate = mApp.getContentResolver().insert(phonesUri, values);
}
else {
Uri newUri = Uri.withAppendedPath(phonesUri, id);
mApp.getContentResolver().update(newUri, values, null, null);
}
}
}
}
catch (JSONException e) {
Log.d(LOG_TAG, "Could not save phones = " + e.getMessage());
}
}
/**
* Converts a string from the W3C Contact API to it's Android int value.
* @param string
* @return Android int value
*/
private int getPhoneType(String string) {
int type = Contacts.Phones.TYPE_OTHER;
if ("home".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_HOME;
}
else if ("mobile".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_MOBILE;
}
else if ("work".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_WORK;
}
else if ("work fax".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_FAX_WORK;
}
else if ("home fax".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_FAX_HOME;
}
else if ("fax".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_FAX_WORK;
}
else if ("pager".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_PAGER;
}
else if ("other".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_OTHER;
}
else if ("custom".equals(string.toLowerCase())) {
return Contacts.Phones.TYPE_CUSTOM;
}
return type;
}
/**
* getPhoneType converts an Android phone type into a string
* @param type
* @return phone type as string.
*/
private String getPhoneType(int type) {
String stringType;
switch (type) {
case Contacts.Phones.TYPE_CUSTOM:
stringType = "custom";
break;
case Contacts.Phones.TYPE_FAX_HOME:
stringType = "home fax";
break;
case Contacts.Phones.TYPE_FAX_WORK:
stringType = "work fax";
break;
case Contacts.Phones.TYPE_HOME:
stringType = "home";
break;
case Contacts.Phones.TYPE_MOBILE:
stringType = "mobile";
break;
case Contacts.Phones.TYPE_PAGER:
stringType = "pager";
break;
case Contacts.Phones.TYPE_WORK:
stringType = "work";
break;
case Contacts.Phones.TYPE_OTHER:
default:
stringType = "custom";
break;
}
return stringType;
}
@Override
@@ -447,7 +819,7 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
*/
public boolean remove(String id) {
int result = mApp.getContentResolver().delete(People.CONTENT_URI,
"people._id = ?",
PEOPLE_ID_EQUALS,
new String[] {id});
return (result > 0) ? true : false;

File diff suppressed because it is too large Load Diff

View File

@@ -42,11 +42,11 @@ public class ContactManager extends Plugin {
try {
if (action.equals("search")) {
JSONArray res = contactAccessor.search(args.getJSONArray(0), args.getJSONObject(1));
return new PluginResult(status, res);
JSONArray res = contactAccessor.search(args.getJSONArray(0), args.optJSONObject(1));
return new PluginResult(status, res, "navigator.service.contacts.cast");
}
else if (action.equals("save")) {
// TODO Coming soon!
return new PluginResult(status, contactAccessor.save(args.getJSONObject(0)));
}
else if (action.equals("remove")) {
if (contactAccessor.remove(args.getString(0))) {

View File

@@ -11,6 +11,7 @@ import java.util.TimeZone;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
import android.content.Context;
@@ -19,7 +20,7 @@ import android.telephony.TelephonyManager;
public class Device extends Plugin {
public static String phonegapVersion = "0.9.2"; // PhoneGap version
public static String phonegapVersion = "0.9.4"; // PhoneGap version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID
@@ -35,7 +36,7 @@ public class Device extends Plugin {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
super.setContext(ctx);
Device.uuid = getUuid();
}

View File

@@ -11,7 +11,6 @@ import java.io.File;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
/**
* This class provides file directory utilities.
@@ -21,6 +20,8 @@ import android.util.Log;
*/
public class DirectoryManager {
private static final String LOG_TAG = "DirectoryManager";
/**
* Determine if a file or directory exists.
*
@@ -36,7 +37,6 @@ public class DirectoryManager {
File newPath = constructFilePaths(path.toString(), name);
status = newPath.exists();
}
// If no SD card
else{
status = false;
@@ -72,29 +72,6 @@ public class DirectoryManager {
return (freeSpace);
}
/**
* Create directory on SD card.
*
* @param directoryName The name of the directory to create.
* @return T=successful, F=failed
*/
protected static boolean createDirectory(String directoryName) {
boolean status;
// Make sure SD card exists
if ((testSaveLocationExists()) && (!directoryName.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), directoryName);
status = newPath.mkdir();
status = true;
}
// If no SD card or invalid dir name
else {
status = false;
}
return status;
}
/**
* Determine if SD card exists.
@@ -117,95 +94,6 @@ public class DirectoryManager {
return status;
}
/**
* Delete directory.
*
* @param fileName The name of the directory to delete
* @return T=deleted, F=could not delete
*/
protected static boolean deleteDirectory(String fileName) {
boolean status;
SecurityManager checker = new SecurityManager();
// Make sure SD card exists
if ((testSaveLocationExists()) && (!fileName.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), fileName);
checker.checkDelete(newPath.toString());
// If dir to delete is really a directory
if (newPath.isDirectory()) {
String[] listfile = newPath.list();
// Delete all files within the specified directory and then delete the directory
try{
for (int i=0; i < listfile.length; i++){
File deletedFile = new File (newPath.toString()+"/"+listfile[i].toString());
deletedFile.delete();
}
newPath.delete();
Log.i("DirectoryManager deleteDirectory", fileName);
status = true;
}
catch (Exception e){
e.printStackTrace();
status = false;
}
}
// If dir not a directory, then error
else {
status = false;
}
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Delete file.
*
* @param fileName The name of the file to delete
* @return T=deleted, F=not deleted
*/
protected static boolean deleteFile(String fileName) {
boolean status;
SecurityManager checker = new SecurityManager();
// Make sure SD card exists
if ((testSaveLocationExists()) && (!fileName.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), fileName);
checker.checkDelete(newPath.toString());
// If file to delete is really a file
if (newPath.isFile()){
try {
Log.i("DirectoryManager deleteFile", fileName);
newPath.delete();
status = true;
}catch (SecurityException se){
se.printStackTrace();
status = false;
}
}
// If not a file, then error
else {
status = false;
}
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Create a new file object from two file paths.
*
@@ -215,8 +103,12 @@ public class DirectoryManager {
*/
private static File constructFilePaths (String file1, String file2) {
File newPath;
newPath = new File(file1+"/"+file2);
if (file2.startsWith(file1)) {
newPath = new File(file2);
}
else {
newPath = new File(file1+"/"+file2);
}
return newPath;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,359 @@
/*
* 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
*/
package com.phonegap;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.util.Log;
import android.webkit.CookieManager;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
public class FileTransfer extends Plugin {
private static final String LOG_TAG = "FileUploader";
private static final String LINE_START = "--";
private static final String LINE_END = "\r\n";
private static final String BOUNDRY = "*****";
public static int FILE_NOT_FOUND_ERR = 1;
public static int INVALID_URL_ERR = 2;
public static int CONNECTION_ERR = 3;
private SSLSocketFactory defaultSSLSocketFactory = null;
private HostnameVerifier defaultHostnameVerifier = null;
/* (non-Javadoc)
* @see com.phonegap.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String)
*/
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
String file = null;
String server = null;
try {
file = args.getString(0);
server = args.getString(1);
}
catch (JSONException e) {
Log.d(LOG_TAG, "Missing filename or server name");
return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing filename or server name");
}
// Setup the options
String fileKey = null;
String fileName = null;
String mimeType = null;
fileKey = getArgument(args, 2, "file");
fileName = getArgument(args, 3, "image.jpg");
mimeType = getArgument(args, 4, "image/jpeg");
try {
JSONObject params = args.optJSONObject(5);
boolean trustEveryone = args.optBoolean(6);
if (action.equals("upload")) {
FileUploadResult r = upload(file, server, fileKey, fileName, mimeType, params, trustEveryone);
Log.d(LOG_TAG, "****** About to return a result from upload");
return new PluginResult(PluginResult.Status.OK, r.toJSONObject());
} else {
return new PluginResult(PluginResult.Status.INVALID_ACTION);
}
} catch (FileNotFoundException e) {
Log.e(LOG_TAG, e.getMessage(), e);
JSONObject error = createFileUploadError(FILE_NOT_FOUND_ERR);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, e.getMessage(), e);
JSONObject error = createFileUploadError(INVALID_URL_ERR);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (SSLException e) {
Log.e(LOG_TAG, e.getMessage(), e);
Log.d(LOG_TAG, "Got my ssl exception!!!");
JSONObject error = createFileUploadError(CONNECTION_ERR);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (IOException e) {
Log.e(LOG_TAG, e.getMessage(), e);
JSONObject error = createFileUploadError(CONNECTION_ERR);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
// always verify the host - don't check for certificate
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
/**
* This function will install a trust manager that will blindly trust all SSL
* certificates. The reason this code is being added is to enable developers
* to do development using self signed SSL certificates on their web server.
*
* The standard HttpsURLConnection class will throw an exception on self
* signed certificates if this code is not run.
*/
private void trustAllHosts() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
} };
// Install the all-trusting trust manager
try {
// Backup the current SSL socket factory
defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
// Install our all trusting manager
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
}
/**
* Create an error object based on the passed in errorCode
* @param errorCode the error
* @return JSONObject containing the error
*/
private JSONObject createFileUploadError(int errorCode) {
JSONObject error = null;
try {
error = new JSONObject();
error.put("code", errorCode);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
return error;
}
/**
* Convenience method to read a parameter from the list of JSON args.
* @param args the args passed to the Plugin
* @param position the position to retrieve the arg from
* @param defaultString the default to be used if the arg does not exist
* @return String with the retrieved value
*/
private String getArgument(JSONArray args, int position, String defaultString) {
String arg = defaultString;
if(args.length() >= position) {
arg = args.optString(position);
if (arg == null || "null".equals(arg)) {
arg = defaultString;
}
}
return arg;
}
/**
* Uploads the specified file to the server URL provided using an HTTP
* multipart request.
* @param file Full path of the file on the file system
* @param server URL of the server to receive the file
* @param fileKey Name of file request parameter
* @param fileName File name to be used on server
* @param mimeType Describes file content type
* @param params key:value pairs of user-defined parameters
* @return FileUploadResult containing result of upload request
*/
public FileUploadResult upload(String file, String server, final String fileKey, final String fileName,
final String mimeType, JSONObject params, boolean trustEveryone) throws IOException, SSLException {
// Create return object
FileUploadResult result = new FileUploadResult();
// Get a input stream of the file on the phone
InputStream fileInputStream = getPathFromUri(file);
HttpURLConnection conn = null;
DataOutputStream dos = null;
int bytesRead, bytesAvailable, bufferSize;
long totalBytes;
byte[] buffer;
int maxBufferSize = 8096;
//------------------ CLIENT REQUEST
// open a URL connection to the server
URL url = new URL(server);
// Open a HTTP connection to the URL based on protocol
if (url.getProtocol().toLowerCase().equals("https")) {
// Using standard HTTPS connection. Will not allow self signed certificate
if (!trustEveryone) {
conn = (HttpsURLConnection) url.openConnection();
}
// Use our HTTPS connection that blindly trusts everyone.
// This should only be used in debug environments
else {
// Setup the HTTPS connection class to trust everyone
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
// Save the current hostnameVerifier
defaultHostnameVerifier = https.getHostnameVerifier();
// Setup the connection not to verify hostnames
https.setHostnameVerifier(DO_NOT_VERIFY);
conn = https;
}
}
// Return a standard HTTP conneciton
else {
conn = (HttpURLConnection) url.openConnection();
}
// Allow Inputs
conn.setDoInput(true);
// Allow Outputs
conn.setDoOutput(true);
// Don't use a cached copy.
conn.setUseCaches(false);
// Use a post method.
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+BOUNDRY);
// Set the cookies on the response
String cookie = CookieManager.getInstance().getCookie(server);
if (cookie != null) {
conn.setRequestProperty("Cookie", cookie);
}
dos = new DataOutputStream( conn.getOutputStream() );
// Send any extra parameters
try {
for (Iterator iter = params.keys(); iter.hasNext();) {
Object key = iter.next();
dos.writeBytes(LINE_START + BOUNDRY + LINE_END);
dos.writeBytes("Content-Disposition: form-data; name=\"" + key.toString() + "\"; ");
dos.writeBytes(LINE_END + LINE_END);
dos.writeBytes(params.getString(key.toString()));
dos.writeBytes(LINE_END);
}
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
dos.writeBytes(LINE_START + BOUNDRY + LINE_END);
dos.writeBytes("Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"" + fileName +"\"" + LINE_END);
dos.writeBytes("Content-Type: " + mimeType + LINE_END);
dos.writeBytes(LINE_END);
// create a buffer of maximum size
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
totalBytes = 0;
while (bytesRead > 0) {
totalBytes += bytesRead;
result.setBytesSent(totalBytes);
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
// send multipart form data necesssary after file data...
dos.writeBytes(LINE_END);
dos.writeBytes(LINE_START + BOUNDRY + LINE_START + LINE_END);
// close streams
fileInputStream.close();
dos.flush();
dos.close();
//------------------ read the SERVER RESPONSE
StringBuffer responseString = new StringBuffer("");
DataInputStream inStream = new DataInputStream ( conn.getInputStream() );
String line;
while (( line = inStream.readLine()) != null) {
responseString.append(line);
}
Log.d(LOG_TAG, "got response from server");
Log.d(LOG_TAG, responseString.toString());
// send request and retrieve response
result.setResponseCode(conn.getResponseCode());
result.setResponse(responseString.toString());
inStream.close();
conn.disconnect();
// Revert back to the proper verifier and socket factories
if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) {
((HttpsURLConnection)conn).setHostnameVerifier(defaultHostnameVerifier);
HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
}
return result;
}
/**
* Get an input stream based on file path or content:// uri
*
* @param path
* @return an input stream
* @throws FileNotFoundException
*/
private InputStream getPathFromUri(String path) throws FileNotFoundException {
if (path.startsWith("content:")) {
Uri uri = Uri.parse(path);
return ctx.getContentResolver().openInputStream(uri);
}
else {
return new FileInputStream(path);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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
* Copyright (c) 2010, IBM Corporation
*/
package com.phonegap;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Encapsulates the result and/or status of uploading a file to a remote server.
*/
public class FileUploadResult {
private long bytesSent = 0; // bytes sent
private int responseCode = -1; // HTTP response code
private String response = null; // HTTP response
public long getBytesSent() {
return bytesSent;
}
public void setBytesSent(long bytes) {
this.bytesSent = bytes;
}
public int getResponseCode() {
return responseCode;
}
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
public JSONObject toJSONObject() throws JSONException {
return new JSONObject(
"{bytesSent:" + bytesSent +
",responseCode:" + responseCode +
",response:" + JSONObject.quote(response) + "}");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -86,7 +86,7 @@ public class GeoListener {
* @param msg The error message
*/
void fail(int code, String msg) {
this.broker.sendJavascript("navigator._geo.fail('" + this.id + "', " + ", " + code + ", '" + msg + "');");
this.broker.sendJavascript("navigator._geo.fail('" + this.id + "', '" + code + "', '" + msg + "');");
this.stop();
}

View File

@@ -7,6 +7,8 @@
*/
package com.phonegap;
import com.phonegap.api.PhonegapActivity;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
@@ -19,7 +21,7 @@ import android.os.Bundle;
*/
public class GpsListener implements LocationListener {
private DroidGap mCtx; // DroidGap object
private PhonegapActivity mCtx; // PhonegapActivity object
private LocationManager mLocMan; // Location manager object
private GeoListener owner; // Geolistener object (parent)
@@ -35,7 +37,7 @@ public class GpsListener implements LocationListener {
* @param interval
* @param m
*/
public GpsListener(DroidGap ctx, int interval, GeoListener m) {
public GpsListener(PhonegapActivity ctx, int interval, GeoListener m) {
this.owner = m;
this.mCtx = ctx;
this.mLocMan = (LocationManager) this.mCtx.getSystemService(Context.LOCATION_SERVICE);

View File

@@ -7,6 +7,8 @@
*/
package com.phonegap;
import com.phonegap.api.PhonegapActivity;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
@@ -15,7 +17,7 @@ import android.os.Bundle;
public class NetworkListener implements LocationListener {
private DroidGap mCtx; // DroidGap object
private PhonegapActivity mCtx; // PhonegapActivity object
private LocationManager mLocMan; // Location manager object
private GeoListener owner; // Geolistener object (parent)
@@ -31,7 +33,7 @@ public class NetworkListener implements LocationListener {
* @param interval
* @param m
*/
public NetworkListener(DroidGap ctx, int interval, GeoListener m) {
public NetworkListener(PhonegapActivity ctx, int interval, GeoListener m) {
this.owner = m;
this.mCtx = ctx;
this.mLocMan = (LocationManager) this.mCtx.getSystemService(Context.LOCATION_SERVICE);

View File

@@ -12,6 +12,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
@@ -38,7 +39,7 @@ public class NetworkManager extends Plugin {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
super.setContext(ctx);
this.sockMan = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
}

View File

@@ -10,6 +10,7 @@ package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import com.phonegap.api.Plugin;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.PluginResult;
import android.app.AlertDialog;
import android.app.ProgressDialog;
@@ -172,7 +173,7 @@ public class Notification extends Plugin {
*/
public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) {
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
final Notification notification = this;
Runnable runnable = new Runnable() {
@@ -208,7 +209,7 @@ public class Notification extends Plugin {
*/
public synchronized void confirm(final String message, final String title, String buttonLabels, final String callbackId) {
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
final Notification notification = this;
final String[] fButtons = buttonLabels.split(",");
@@ -272,7 +273,7 @@ public class Notification extends Plugin {
this.spinnerDialog = null;
}
final Notification notification = this;
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
Runnable runnable = new Runnable() {
public void run() {
notification.spinnerDialog = ProgressDialog.show(ctx, title , message, true, true,
@@ -308,7 +309,7 @@ public class Notification extends Plugin {
this.progressDialog = null;
}
final Notification notification = this;
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
Runnable runnable = new Runnable() {
public void run() {
notification.progressDialog = new ProgressDialog(ctx);

View File

@@ -1,21 +0,0 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
*/
package com.phonegap;
public class SplashScreen {
private final DroidGap gap;
public SplashScreen(DroidGap gap) {
this.gap = gap;
}
public void hide() {
gap.runOnUiThread(new Runnable() {
public void run() {
gap.hideSplashScreen();
}
});
}
}

View File

@@ -160,24 +160,25 @@ public class Storage extends Plugin {
*/
public void processResults(Cursor cur, String tx_id) {
String result = "[]";
// If query result has rows
if (cur.moveToFirst()) {
JSONArray fullresult = new JSONArray();
String key = "";
String value = "";
int colCount = cur.getColumnCount();
// Build up JSON result object for each row
do {
JSONObject result = new JSONObject();
JSONObject row = new JSONObject();
try {
for (int i = 0; i < colCount; ++i) {
key = cur.getColumnName(i);
value = cur.getString(i).replace("\"", "\\\""); // must escape " with \" for JavaScript
result.put(key, value);
value = cur.getString(i);
row.put(key, value);
}
// Send row back to JavaScript
this.sendJavascript("droiddb.addResult('" + result.toString() + "','" + tx_id + "');");
fullresult.put(row);
} catch (JSONException e) {
e.printStackTrace();
@@ -185,9 +186,11 @@ public class Storage extends Plugin {
} while (cur.moveToNext());
result = fullresult.toString();
}
// Let JavaScript know that there are no more rows
this.sendJavascript("droiddb.completeQuery('" + tx_id + "');");
this.sendJavascript("droiddb.completeQuery('" + tx_id + "', "+result+");");
}

3
framework/src/com/phonegap/TempListener.java Normal file → Executable file
View File

@@ -11,6 +11,7 @@ import java.util.List;
import org.json.JSONArray;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
@@ -37,7 +38,7 @@ public class TempListener extends Plugin implements SensorEventListener {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
super.setContext(ctx);
this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
}

View File

@@ -8,7 +8,6 @@
package com.phonegap.api;
import org.json.JSONArray;
import com.phonegap.DroidGap;
import android.content.Intent;
import android.webkit.WebView;
@@ -43,7 +42,7 @@ public interface IPlugin {
*
* @param ctx The context of the main Activity.
*/
void setContext(DroidGap ctx);
void setContext(PhonegapActivity ctx);
/**
* Sets the main View of the application, this is the WebView within which

View File

@@ -0,0 +1,43 @@
/*
* 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
*/
package com.phonegap.api;
import android.app.Activity;
import android.content.Intent;
/**
* The Phonegap activity abstract class that is extended by DroidGap.
* It is used to isolate plugin development, and remove dependency on entire Phonegap library.
*/
public abstract class PhonegapActivity extends Activity {
/**
* Add a class that implements a service.
*
* @param serviceType
* @param className
*/
abstract public void addService(String serviceType, String className);
/**
* Send JavaScript statement back to JavaScript.
*
* @param message
*/
abstract public void sendJavascript(String statement);
/**
* Launch an activity for which you would like a result when it finished. When this activity exits,
* your onActivityResult() method will be called.
*
* @param command The command object
* @param intent The intent to start
* @param requestCode The request code that is passed to callback to identify the activity
*/
abstract public void startActivityForResult(Plugin command, Intent intent, int requestCode);
}

View File

@@ -8,7 +8,7 @@
package com.phonegap.api;
import org.json.JSONArray;
import com.phonegap.DroidGap;
import android.content.Intent;
import android.webkit.WebView;
@@ -20,7 +20,7 @@ import android.webkit.WebView;
public abstract class Plugin implements IPlugin {
public WebView webView; // WebView object
public DroidGap ctx; // DroidGap object
public PhonegapActivity ctx; // PhonegapActivity object
/**
* Executes the request and returns PluginResult.
@@ -48,7 +48,7 @@ public abstract class Plugin implements IPlugin {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
this.ctx = ctx;
}

View File

@@ -14,7 +14,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import android.webkit.WebView;
import com.phonegap.DroidGap;
/**
* PluginManager is exposed to JavaScript in the PhoneGap WebView.
@@ -27,7 +26,7 @@ public final class PluginManager {
private HashMap<String, Plugin> plugins = new HashMap<String,Plugin>();
private HashMap<String, String> services = new HashMap<String,String>();
private final DroidGap ctx;
private final PhonegapActivity ctx;
private final WebView app;
/**
@@ -36,7 +35,7 @@ public final class PluginManager {
* @param app
* @param ctx
*/
public PluginManager(WebView app, DroidGap ctx) {
public PluginManager(WebView app, PhonegapActivity ctx) {
this.ctx = ctx;
this.app = app;
}
@@ -76,7 +75,7 @@ public final class PluginManager {
}
if (isPhoneGapPlugin(c)) {
final Plugin plugin = this.addPlugin(clazz, c);
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
runAsync = async && !plugin.isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
@@ -195,7 +194,7 @@ public final class PluginManager {
try {
Plugin plugin = (Plugin)clazz.newInstance();
this.plugins.put(className, plugin);
plugin.setContext((DroidGap)this.ctx);
plugin.setContext(this.ctx);
plugin.setView(this.app);
return plugin;
}

View File

@@ -10,10 +10,13 @@ package com.phonegap.api;
import org.json.JSONArray;
import org.json.JSONObject;
import android.util.Log;
public class PluginResult {
private final int status;
private final String message;
private boolean keepCallback = false;
private String cast = null;
public PluginResult(Status status) {
this.status = status.ordinal();
@@ -22,7 +25,19 @@ public class PluginResult {
public PluginResult(Status status, String message) {
this.status = status.ordinal();
this.message = "'" + message + "'";
this.message = JSONObject.quote(message);
}
public PluginResult(Status status, JSONArray message, String cast) {
this.status = status.ordinal();
this.message = message.toString();
this.cast = cast;
}
public PluginResult(Status status, JSONObject message, String cast) {
this.status = status.ordinal();
this.message = message.toString();
this.cast = cast;
}
public PluginResult(Status status, JSONArray message) {
@@ -71,7 +86,15 @@ public class PluginResult {
}
public String toSuccessCallbackString(String callbackId) {
return "PhoneGap.callbackSuccess('"+callbackId+"', " + this.getJSONString() + " );";
StringBuffer buf = new StringBuffer("");
if (cast != null) {
buf.append("var temp = "+cast+"("+this.getJSONString() + ");\n");
buf.append("PhoneGap.callbackSuccess('"+callbackId+"',temp);");
}
else {
buf.append("PhoneGap.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");");
}
return buf.toString();
}
public String toErrorCallbackString(String callbackId) {

View File

@@ -0,0 +1,9 @@
package com.phonegap.file;
public class EncodingException extends Exception {
public EncodingException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,9 @@
package com.phonegap.file;
public class FileExistsException extends Exception {
public FileExistsException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,9 @@
package com.phonegap.file;
public class InvalidModificationException extends Exception {
public InvalidModificationException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,9 @@
package com.phonegap.file;
public class NoModificationAllowedException extends Exception {
public NoModificationAllowedException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,9 @@
package com.phonegap.file;
public class TypeMismatchException extends Exception {
public TypeMismatchException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,658 @@
/*
* Copyright (c) 2010 Nathan Rajlich (https://github.com/TooTallNate)
* Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles)
*
* 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.
*
*/
package com.phonegap.websocket;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import android.util.Log;
import android.webkit.WebView;
/**
* The <tt>WebSocket</tt> is an implementation of WebSocket Client API, and
* expects a valid "ws://" URI to connect to. When connected, an instance
* recieves important events related to the life of the connection, like
* <var>onOpen</var>, <var>onClose</var>, <var>onError</var> and
* <var>onMessage</var>. An instance can send messages to the server via the
* <var>send</var> method.
*
* @author Animesh Kumar
*/
public class WebSocket implements Runnable {
/**
* Enum for WebSocket Draft
*/
public enum Draft {
DRAFT75, DRAFT76
}
// //////////////// CONSTANT
/**
* The connection has not yet been established.
*/
public final static int WEBSOCKET_STATE_CONNECTING = 0;
/**
* The WebSocket connection is established and communication is possible.
*/
public final static int WEBSOCKET_STATE_OPEN = 1;
/**
* The connection is going through the closing handshake.
*/
public final static int WEBSOCKET_STATE_CLOSING = 2;
/**
* The connection has been closed or could not be opened.
*/
public final static int WEBSOCKET_STATE_CLOSED = 3;
/**
* An empty string
*/
private static String BLANK_MESSAGE = "";
/**
* The javascript method name for onOpen event.
*/
private static String EVENT_ON_OPEN = "onopen";
/**
* The javascript method name for onMessage event.
*/
private static String EVENT_ON_MESSAGE = "onmessage";
/**
* The javascript method name for onClose event.
*/
private static String EVENT_ON_CLOSE = "onclose";
/**
* The javascript method name for onError event.
*/
private static String EVENT_ON_ERROR = "onerror";
/**
* The default port of WebSockets, as defined in the spec.
*/
public static final int DEFAULT_PORT = 80;
/**
* The WebSocket protocol expects UTF-8 encoded bytes.
*/
public static final String UTF8_CHARSET = "UTF-8";
/**
* The byte representing Carriage Return, or \r
*/
public static final byte DATA_CR = (byte) 0x0D;
/**
* The byte representing Line Feed, or \n
*/
public static final byte DATA_LF = (byte) 0x0A;
/**
* The byte representing the beginning of a WebSocket text frame.
*/
public static final byte DATA_START_OF_FRAME = (byte) 0x00;
/**
* The byte representing the end of a WebSocket text frame.
*/
public static final byte DATA_END_OF_FRAME = (byte) 0xFF;
// //////////////// INSTANCE Variables
/**
* The WebView instance from Phonegap DroidGap
*/
private WebView appView;
/**
* The unique id for this instance (helps to bind this to javascript events)
*/
private String id;
/**
* The URI this client is supposed to connect to.
*/
private URI uri;
/**
* The port of the websocket server
*/
private int port;
/**
* The Draft of the WebSocket protocol the Client is adhering to.
*/
private Draft draft;
/**
* The <tt>SocketChannel</tt> instance to use for this server connection.
* This is used to read and write data to.
*/
private SocketChannel socketChannel;
/**
* The 'Selector' used to get event keys from the underlying socket.
*/
private Selector selector;
/**
* Keeps track of whether or not the client thread should continue running.
*/
private boolean running;
/**
* Internally used to determine whether to recieve data as part of the
* remote handshake, or as part of a text frame.
*/
private boolean handshakeComplete;
/**
* The 1-byte buffer reused throughout the WebSocket connection to read
* data.
*/
private ByteBuffer buffer;
/**
* The bytes that make up the remote handshake.
*/
private ByteBuffer remoteHandshake;
/**
* The bytes that make up the current text frame being read.
*/
private ByteBuffer currentFrame;
/**
* Queue of buffers that need to be sent to the client.
*/
private BlockingQueue<ByteBuffer> bufferQueue;
/**
* Lock object to ensure that data is sent from the bufferQueue in the
* proper order
*/
private Object bufferQueueMutex = new Object();
/**
* Number 1 used in handshake
*/
private int number1 = 0;
/**
* Number 2 used in handshake
*/
private int number2 = 0;
/**
* Key3 used in handshake
*/
private byte[] key3 = null;
/**
* The readyState attribute represents the state of the connection.
*/
private int readyState = WEBSOCKET_STATE_CONNECTING;
/**
* Constructor.
*
* Note: this is protected because it's supposed to be instantiated from
* {@link WebSocketFactory} only.
*
* @param appView
* {@link android.webkit.WebView}
* @param uri
* websocket server {@link URI}
* @param draft
* websocket server {@link Draft} implementation (75/76)
* @param id
* unique id for this instance
*/
protected WebSocket(WebView appView, URI uri, Draft draft, String id) {
this.appView = appView;
this.uri = uri;
this.draft = draft;
// port
port = uri.getPort();
if (port == -1) {
port = DEFAULT_PORT;
}
// Id
this.id = id;
this.bufferQueue = new LinkedBlockingQueue<ByteBuffer>();
this.handshakeComplete = false;
this.remoteHandshake = this.currentFrame = null;
this.buffer = ByteBuffer.allocate(1);
}
// //////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////// WEB SOCKET API Methods
// ///////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////
/**
* Starts a new Thread and connects to server
*
* @throws IOException
*/
public Thread connect() throws IOException {
this.running = true;
this.readyState = WEBSOCKET_STATE_CONNECTING;
// open socket
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
// set address
socketChannel.connect(new InetSocketAddress(uri.getHost(), port));
// start a thread to make connection
// More info:
// http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82
// http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
Log.v("websocket", "Starting a new thread to manage data reading/writing");
Thread th = new Thread(this);
th.start();
// return thread object for explicit closing, if needed
return th;
}
public void run() {
while (this.running) {
try {
_connect();
} catch (IOException e) {
this.onError(e);
}
}
}
/**
* Closes connection with server
*/
public void close() {
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSING;
// close socket channel
try {
this.socketChannel.close();
} catch (IOException e) {
this.onError(e);
}
this.running = false;
selector.wakeup();
// fire onClose method
this.onClose();
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSED;
}
/**
* Sends <var>text</var> to server
*
* @param text
* String to send to server
*/
public void send(String text) {
try {
if (this.readyState == WEBSOCKET_STATE_OPEN) {
_send(text);
} else {
this.onError(new NotYetConnectedException());
}
} catch (IOException e) {
this.onError(e);
}
}
/**
* Called when an entire text frame has been recieved.
*
* @param msg
* Message from websocket server
*/
public void onMessage(String msg) {
appView.loadUrl(buildJavaScriptData(EVENT_ON_MESSAGE, msg));
}
public void onOpen() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_OPEN, BLANK_MESSAGE));
}
public void onClose() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_CLOSE, BLANK_MESSAGE));
}
public void onError(Throwable t) {
String msg = t.getMessage();
appView.loadUrl(buildJavaScriptData(EVENT_ON_ERROR, msg));
}
public String getId() {
return id;
}
/**
* @return the readyState
*/
public int getReadyState() {
return readyState;
}
/**
* Builds text for javascript engine to invoke proper event method with
* proper data.
*
* @param event
* websocket event (onOpen, onMessage etc.)
* @param msg
* Text message received from websocket server
* @return
*/
private String buildJavaScriptData(String event, String msg) {
String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\","
+ "\"_data\":'" + msg + "'" + "}" + ")";
return _d;
}
// //////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////// WEB SOCKET Internal Methods
// //////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////
private boolean _send(String text) throws IOException {
if (!this.handshakeComplete) {
throw new NotYetConnectedException();
}
if (text == null) {
throw new NullPointerException("Cannot send 'null' data to a WebSocket.");
}
// Get 'text' into a WebSocket "frame" of bytes
byte[] textBytes = text.getBytes(UTF8_CHARSET.toString());
ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2);
b.put(DATA_START_OF_FRAME);
b.put(textBytes);
b.put(DATA_END_OF_FRAME);
b.rewind();
// See if we have any backlog that needs to be sent first
if (_write()) {
// Write the ByteBuffer to the socket
this.socketChannel.write(b);
}
// If we didn't get it all sent, add it to the buffer of buffers
if (b.remaining() > 0) {
if (!this.bufferQueue.offer(b)) {
throw new IOException("Buffers are full, message could not be sent to"
+ this.socketChannel.socket().getRemoteSocketAddress());
}
return false;
}
return true;
}
// actual connection logic
private void _connect() throws IOException {
// Continuous loop that is only supposed to end when "close" is called.
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
while (i.hasNext()) {
SelectionKey key = i.next();
i.remove();
if (key.isConnectable()) {
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
socketChannel.register(selector, SelectionKey.OP_READ);
_writeHandshake();
}
if (key.isReadable()) {
try {
_read();
} catch (NoSuchAlgorithmException nsa) {
this.onError(nsa);
}
}
}
}
private void _writeHandshake() throws IOException {
String path = this.uri.getPath();
if (path.indexOf("/") != 0) {
path = "/" + path;
}
String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : "");
String origin = "*"; // TODO: Make 'origin' configurable
String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n" + "Host: " + host + "\r\n" + "Origin: " + origin
+ "\r\n";
// Add randon keys for Draft76
if (this.draft == Draft.DRAFT76) {
request += "Sec-WebSocket-Key1: " + this._randomKey() + "\r\n";
request += "Sec-WebSocket-Key2: " + this._randomKey() + "\r\n";
this.key3 = new byte[8];
(new Random()).nextBytes(this.key3);
}
request += "\r\n";
_write(request.getBytes(UTF8_CHARSET));
if (this.key3 != null) {
_write(this.key3);
}
}
private boolean _write() throws IOException {
synchronized (this.bufferQueueMutex) {
ByteBuffer buffer = this.bufferQueue.peek();
while (buffer != null) {
this.socketChannel.write(buffer);
if (buffer.remaining() > 0) {
return false; // Didn't finish this buffer. There's more to
// send.
} else {
this.bufferQueue.poll(); // Buffer finished. Remove it.
buffer = this.bufferQueue.peek();
}
}
return true;
}
}
private void _write(byte[] bytes) throws IOException {
this.socketChannel.write(ByteBuffer.wrap(bytes));
}
private void _read() throws IOException, NoSuchAlgorithmException {
this.buffer.rewind();
int bytesRead = -1;
try {
bytesRead = this.socketChannel.read(this.buffer);
} catch (Exception ex) {
}
if (bytesRead == -1) {
close();
} else if (bytesRead > 0) {
this.buffer.rewind();
if (!this.handshakeComplete) {
_readHandshake();
} else {
_readFrame();
}
}
}
private void _readFrame() throws UnsupportedEncodingException {
byte newestByte = this.buffer.get();
if (newestByte == DATA_START_OF_FRAME) { // Beginning of Frame
this.currentFrame = null;
} else if (newestByte == DATA_END_OF_FRAME) { // End of Frame
String textFrame = null;
// currentFrame will be null if END_OF_FRAME was send directly after
// START_OF_FRAME, thus we will send 'null' as the sent message.
if (this.currentFrame != null) {
textFrame = new String(this.currentFrame.array(), UTF8_CHARSET.toString());
}
// fire onMessage method
this.onMessage(textFrame);
} else { // Regular frame data, add to current frame buffer
ByteBuffer frame = ByteBuffer.allocate((this.currentFrame != null ? this.currentFrame
.capacity() : 0)
+ this.buffer.capacity());
if (this.currentFrame != null) {
this.currentFrame.rewind();
frame.put(this.currentFrame);
}
frame.put(newestByte);
this.currentFrame = frame;
}
}
private void _readHandshake() throws IOException, NoSuchAlgorithmException {
ByteBuffer ch = ByteBuffer.allocate((this.remoteHandshake != null ? this.remoteHandshake
.capacity() : 0)
+ this.buffer.capacity());
if (this.remoteHandshake != null) {
this.remoteHandshake.rewind();
ch.put(this.remoteHandshake);
}
ch.put(this.buffer);
this.remoteHandshake = ch;
byte[] h = this.remoteHandshake.array();
// If the ByteBuffer contains 16 random bytes, and ends with
// 0x0D 0x0A 0x0D 0x0A (or two CRLFs), then the client
// handshake is complete for Draft 76 Client.
if ((h.length >= 20 && h[h.length - 20] == DATA_CR && h[h.length - 19] == DATA_LF
&& h[h.length - 18] == DATA_CR && h[h.length - 17] == DATA_LF)) {
_readHandshake(new byte[] { h[h.length - 16], h[h.length - 15], h[h.length - 14],
h[h.length - 13], h[h.length - 12], h[h.length - 11], h[h.length - 10],
h[h.length - 9], h[h.length - 8], h[h.length - 7], h[h.length - 6],
h[h.length - 5], h[h.length - 4], h[h.length - 3], h[h.length - 2],
h[h.length - 1] });
// If the ByteBuffer contains 8 random bytes,ends with
// 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response
// contains Sec-WebSocket-Key1 then the client
// handshake is complete for Draft 76 Server.
} else if ((h.length >= 12 && h[h.length - 12] == DATA_CR && h[h.length - 11] == DATA_LF
&& h[h.length - 10] == DATA_CR && h[h.length - 9] == DATA_LF)
&& new String(this.remoteHandshake.array(), UTF8_CHARSET)
.contains("Sec-WebSocket-Key1")) {// ************************
_readHandshake(new byte[] { h[h.length - 8], h[h.length - 7], h[h.length - 6],
h[h.length - 5], h[h.length - 4], h[h.length - 3], h[h.length - 2],
h[h.length - 1] });
// Consider Draft 75, and the Flash Security Policy
// Request edge-case.
} else if ((h.length >= 4 && h[h.length - 4] == DATA_CR && h[h.length - 3] == DATA_LF
&& h[h.length - 2] == DATA_CR && h[h.length - 1] == DATA_LF)
&& !(new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec"))
|| (h.length == 23 && h[h.length - 1] == 0)) {
_readHandshake(null);
}
}
private void _readHandshake(byte[] handShakeBody) throws IOException, NoSuchAlgorithmException {
// byte[] handshakeBytes = this.remoteHandshake.array();
// String handshake = new String(handshakeBytes, UTF8_CHARSET);
// TODO: Do some parsing of the returned handshake, and close connection
// in received anything unexpected!
this.handshakeComplete = true;
boolean isConnectionReady = true;
if (this.draft == WebSocket.Draft.DRAFT76) {
if (handShakeBody == null) {
isConnectionReady = true;
}
byte[] challenge = new byte[] { (byte) (this.number1 >> 24),
(byte) ((this.number1 << 8) >> 24), (byte) ((this.number1 << 16) >> 24),
(byte) ((this.number1 << 24) >> 24), (byte) (this.number2 >> 24),
(byte) ((this.number2 << 8) >> 24), (byte) ((this.number2 << 16) >> 24),
(byte) ((this.number2 << 24) >> 24), this.key3[0], this.key3[1], this.key3[2],
this.key3[3], this.key3[4], this.key3[5], this.key3[6], this.key3[7] };
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] expected = md5.digest(challenge);
for (int i = 0; i < handShakeBody.length; i++) {
if (expected[i] != handShakeBody[i]) {
isConnectionReady = true;
}
}
}
if (isConnectionReady) {
this.readyState = WEBSOCKET_STATE_OPEN;
// fire onOpen method
this.onOpen();
} else {
close();
}
}
private String _randomKey() {
Random r = new Random();
long maxNumber = 4294967295L;
long spaces = r.nextInt(12) + 1;
int max = new Long(maxNumber / spaces).intValue();
max = Math.abs(max);
int number = r.nextInt(max) + 1;
if (this.number1 == 0) {
this.number1 = number;
} else {
this.number2 = number;
}
long product = number * spaces;
String key = Long.toString(product);
int numChars = r.nextInt(12);
for (int i = 0; i < numChars; i++) {
int position = r.nextInt(key.length());
position = Math.abs(position);
char randChar = (char) (r.nextInt(95) + 33);
// exclude numbers here
if (randChar >= 48 && randChar <= 57) {
randChar -= 15;
}
key = new StringBuilder(key).insert(position, randChar).toString();
}
for (int i = 0; i < spaces; i++) {
int position = r.nextInt(key.length() - 1) + 1;
position = Math.abs(position);
key = new StringBuilder(key).insert(position, "\u0020").toString();
}
return key;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles)
*
* 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.
*
*/
package com.phonegap.websocket;
import java.net.URI;
import java.util.Random;
import android.webkit.WebView;
/**
* The <tt>WebSocketFactory</tt> is like a helper class to instantiate new
* WebSocket instaces especially from Javascript side. It expects a valid
* "ws://" URI.
*
* @author Animesh Kumar
*/
public class WebSocketFactory {
/** The app view. */
WebView appView;
/**
* Instantiates a new web socket factory.
*
* @param appView
* the app view
*/
public WebSocketFactory(WebView appView) {
this.appView = appView;
}
public WebSocket getInstance(String url) {
// use Draft75 by default
return getInstance(url, WebSocket.Draft.DRAFT75);
}
public WebSocket getInstance(String url, WebSocket.Draft draft) {
WebSocket socket = null;
Thread th = null;
try {
socket = new WebSocket(appView, new URI(url), draft, getRandonUniqueId());
th = socket.connect();
return socket;
} catch (Exception e) {
//Log.v("websocket", e.toString());
if(th != null) {
th.interrupt();
}
}
return null;
}
/**
* Generates random unique ids for WebSocket instances
*
* @return String
*/
private String getRandonUniqueId() {
return "WEBSOCKET." + new Random().nextInt(100);
}
}

32
lib/classic.rb Normal file → Executable file
View File

@@ -19,9 +19,15 @@ class Classic
end
def setup
@android_dir = File.expand_path(File.dirname(__FILE__).gsub('lib',''))
@android_dir = File.expand_path(File.dirname(__FILE__).gsub(/lib$/,''))
@framework_dir = File.join(@android_dir, "framework")
@icon = File.join(@www, 'icon.png')
@icon = File.join(@www, 'icon.png') unless File.exists?(@icon)
# Hash that stores the location of icons for each resolution type. Uses the default icon for all resolutions as a baseline.
@icons = {
:"drawable-ldpi" => @icon,
:"drawable-mdpi" => @icon,
:"drawable-hdpi" => @icon
} if @icons.nil?
@app_js_dir = ''
@content = 'index.html'
end
@@ -82,11 +88,12 @@ class Classic
# copies stuff from src directory into the android project directory (@path)
def copy_libs
version = IO.read(File.join(@framework_dir, '../VERSION'))
framework_res_dir = File.join(@framework_dir, "res")
app_res_dir = File.join(@path, "res")
# copies in the jar
FileUtils.mkdir_p File.join(@path, "libs")
FileUtils.cp File.join(@framework_dir, "phonegap.jar"), File.join(@path, "libs")
FileUtils.cp File.join(@framework_dir, "phonegap.#{ version }.jar"), File.join(@path, "libs")
# copies in the strings.xml
FileUtils.mkdir_p File.join(app_res_dir, "values")
FileUtils.cp File.join(framework_res_dir, "values","strings.xml"), File.join(app_res_dir, "values", "strings.xml")
@@ -96,11 +103,19 @@ class Classic
FileUtils.cp File.join(framework_res_dir, "layout", f), File.join(app_res_dir, "layout", f)
end
# icon file copy
# if it is not in the www directory use the default one in the src dir
@icon = File.join(framework_res_dir, "drawable", "icon.png") unless File.exists?(@icon)
%w(drawable-hdpi drawable-ldpi drawable-mdpi).each do |e|
# if specific resolution icons are specified, use those. if not, see if a general purpose icon was defined.
# finally, fall back to using the default PhoneGap one.
currentIcon = ""
if !@icons[e.to_sym].nil? && File.exists?(File.join(@www, @icons[e.to_sym]))
currentIcon = File.join(@www, @icons[e.to_sym])
elsif File.exists?(@icon)
currentIcon = @icon
else
currentIcon = File.join(framework_res_dir, "drawable", "icon.png")
end
FileUtils.mkdir_p(File.join(app_res_dir, e))
FileUtils.cp(@icon, File.join(app_res_dir, e, "icon.png"))
FileUtils.cp(currentIcon, File.join(app_res_dir, e, "icon.png"))
end
# concat JS and put into www folder. this can be overridden in the config.xml via @app_js_dir
js_dir = File.join(@framework_dir, "assets", "js")
@@ -110,7 +125,7 @@ class Classic
phonegapjs << IO.read(File.join(js_dir, script))
phonegapjs << "\n\n"
end
File.open(File.join(@path, "assets", "www", @app_js_dir, "phonegap.js"), 'w') {|f| f.write(phonegapjs) }
File.open(File.join(@path, "assets", "www", @app_js_dir, "phonegap.#{ version }.js"), 'w') {|f| f.write(phonegapjs) }
end
# puts app name in strings
@@ -126,8 +141,7 @@ class Classic
end
end
# this is so fucking unholy yet oddly beautiful
# not sure if I should thank Ruby or apologize for this abusive use of string interpolation
# create java source file
def write_java
j = "
package #{ @pkg };

View File

@@ -29,7 +29,7 @@ class Create < Classic
@content = 'index.html'
# stop executation on errors
raise 'Expected index.html in the following folder #{ path }.\nThe path is expected to be the directory droidgap create is run from or specified as a command line arg like droidgap create my_path.' unless File.exists? File.join(path, 'index.html')
raise "Expected index.html in the following folder #{ path }.\nThe path is expected to be the directory droidgap create is run from or specified as a command line arg like droidgap create my_path." unless File.exists? File.join(path, 'index.html')
raise 'Could not find android in your PATH!' if @android_sdk_path.empty?
end
@@ -40,16 +40,40 @@ class Create < Classic
if File.exists?(config_file)
require 'rexml/document'
f = File.new config_file
doc = REXML::Document.new(f)
@config = {}
doc = REXML::Document.new(f)
@config = {}
@config[:id] = doc.root.attributes["id"]
@config[:version] = doc.root.attributes["version"]
@config[:icons] = {}
defaultIconSize = 0
doc.root.elements.each do |n|
@config[:name] = n.text.gsub('-','').gsub(' ','') if n.name == 'name'
@config[:description] = n.text if n.name == 'description'
@config[:icon] = n.attributes["src"] if n.name == 'icon'
@config[:content] = n.attributes["src"] if n.name == 'content'
@config[:content] = n.attributes["src"] if n.name == 'content'
if n.name == 'icon'
if n.attributes["width"] == '72' && n.attributes["height"] == '72'
@config[:icons]["drawable-hdpi".to_sym] = n.attributes["src"]
if 72 > defaultIconSize
@config[:icon] = n.attributes["src"]
defaultIconSize = 72
end
elsif n.attributes["width"] == '48' && n.attributes["height"] == '48'
@config[:icons]["drawable-mdpi".to_sym] = n.attributes["src"]
if 48 > defaultIconSize
@config[:icon] = n.attributes["src"]
defaultIconSize = 48
end
elsif n.attributes["width"] == '36' && n.attributes["height"] == '36'
@config[:icons]["drawable-ldpi".to_sym] = n.attributes["src"]
if 36 > defaultIconSize
@config[:icon] = n.attributes["src"]
defaultIconSize = 36
end
else
@config[:icon] = n.attributes["src"]
end
end
if n.name == "preference" && n.attributes["name"] == 'javascript_folder'
@config[:js_dir] = n.attributes["value"]
@@ -62,7 +86,8 @@ class Create < Classic
# will change the name from the directory to the name element text
@name = @config[:name] if @config[:name]
# set the icon from the config
@icon = File.join(@www, @config[:icon])
@icon = File.join(@www, @config[:icon]) if @config[:icon]
@icons = @config[:icons] if @config[:icons].length > 0
# sets the app js dir where phonegap.js gets copied
@app_js_dir = @config[:js_dir] ? @config[:js_dir] : ''
# sets the start page

20
lib/update.rb Normal file → Executable file
View File

@@ -15,9 +15,9 @@ class Update
end
# removes local.properties and recreates based on android_sdk_path
# then generates framework/phonegap.jar
# then generates framework/phonegap.jar & framework/assets/www/phonegap.js
def build_jar
puts "Building the JAR..."
puts "Building the JAR and combining JS files..."
%w(local.properties phonegap.js phonegap.jar).each do |f|
FileUtils.rm File.join(@framework_dir, f) if File.exists? File.join(@framework_dir, f)
end
@@ -32,23 +32,13 @@ class Update
# copies stuff from framework into the project
# TODO need to allow for www import inc icon
def copy_libs
puts "Copying over libraries and assets and creating phonegap.js..."
puts "Copying over libraries and assets..."
FileUtils.mkdir_p File.join(@path, "libs")
FileUtils.cp File.join(@framework_dir, "phonegap.jar"), File.join(@path, "libs")
# concat JS and put into www folder.
js_dir = File.join(@framework_dir, "assets", "js")
phonegapjs = IO.read(File.join(js_dir, 'phonegap.js.base'))
Dir.new(js_dir).entries.each do |script|
next if script[0].chr == "." or script == "phonegap.js.base"
phonegapjs << IO.read(File.join(js_dir, script))
phonegapjs << "\n\n"
end
File.open(File.join(@path, "assets", "www", "phonegap.js"), 'w') {|f| f.write(phonegapjs) }
FileUtils.mkdir_p File.join(@path, "assets", "www")
FileUtils.cp File.join(@framework_dir, "assets", "www", "phonegap.js"), File.join(@path, "assets", "www")
end
#
end

31
util/yuicompressor/LICENSE Executable file
View File

@@ -0,0 +1,31 @@
YUI is issued by Yahoo! under the BSD License below.
Copyright (c) 2010, Yahoo! Inc.
All rights reserved.
Redistribution and use of this software in source and binary forms, with or
without modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Yahoo! Inc. nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission of Yahoo! Inc.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
http://developer.yahoo.com/yui/license.html

140
util/yuicompressor/README Executable file
View File

@@ -0,0 +1,140 @@
==============================================================================
YUI Compressor
==============================================================================
NAME
YUI Compressor - The Yahoo! JavaScript and CSS Compressor
SYNOPSIS
Usage: java -jar yuicompressor-x.y.z.jar [options] [input file]
Global Options
-h, --help Displays this information
--type <js|css> Specifies the type of the input file
--charset <charset> Read the input file using <charset>
--line-break <column> Insert a line break after the specified column number
-v, --verbose Display informational messages and warnings
-o <file> Place the output into <file>. Defaults to stdout.
JavaScript Options
--nomunge Minify only, do not obfuscate
--preserve-semi Preserve all semicolons
--disable-optimizations Disable all micro optimizations
DESCRIPTION
The YUI Compressor is a JavaScript compressor which, in addition to removing
comments and white-spaces, obfuscates local variables using the smallest
possible variable name. This obfuscation is safe, even when using constructs
such as 'eval' or 'with' (although the compression is not optimal is those
cases) Compared to jsmin, the average savings is around 20%.
The YUI Compressor is also able to safely compress CSS files. The decision
on which compressor is being used is made on the file extension (js or css)
GLOBAL OPTIONS
-h, --help
Prints help on how to use the YUI Compressor
--line-break
Some source control tools don't like files containing lines longer than,
say 8000 characters. The linebreak option is used in that case to split
long lines after a specific column. It can also be used to make the code
more readable, easier to debug (especially with the MS Script Debugger)
Specify 0 to get a line break after each semi-colon in JavaScript, and
after each rule in CSS.
--type js|css
The type of compressor (JavaScript or CSS) is chosen based on the
extension of the input file name (.js or .css) This option is required
if no input file has been specified. Otherwise, this option is only
required if the input file extension is neither 'js' nor 'css'.
--charset character-set
If a supported character set is specified, the YUI Compressor will use it
to read the input file. Otherwise, it will assume that the platform's
default character set is being used. The output file is encoded using
the same character set.
-o outfile
Place output in file outfile. If not specified, the YUI Compressor will
default to the standard output, which you can redirect to a file.
-v, --verbose
Display informational messages and warnings.
JAVASCRIPT ONLY OPTIONS
--nomunge
Minify only. Do not obfuscate local symbols.
--preserve-semi
Preserve unnecessary semicolons (such as right before a '}') This option
is useful when compressed code has to be run through JSLint (which is the
case of YUI for example)
--disable-optimizations
Disable all the built-in micro optimizations.
NOTES
+ If no input file is specified, it defaults to stdin.
+ The YUI Compressor requires Java version >= 1.4.
+ It is possible to prevent a local variable, nested function or function
argument from being obfuscated by using "hints". A hint is a string that
is located at the very beginning of a function body like so:
function fn (arg1, arg2, arg3) {
"arg2:nomunge, localVar:nomunge, nestedFn:nomunge";
...
var localVar;
...
function nestedFn () {
....
}
...
}
The hint itself disappears from the compressed file.
+ C-style comments starting with /*! are preserved. This is useful with
comments containing copyright/license information. For example:
/*!
* TERMS OF USE - EASING EQUATIONS
* Open source under the BSD License.
* Copyright 2001 Robert Penner All rights reserved.
*/
becomes:
/*
* TERMS OF USE - EASING EQUATIONS
* Open source under the BSD License.
* Copyright 2001 Robert Penner All rights reserved.
*/
AUTHOR
The YUI Compressor was written and is maintained by:
Julien Lecomte <jlecomte@yahoo-inc.com>
The CSS portion is a port of Isaac Schlueter's cssmin utility.
COPYRIGHT
Copyright (c) 2007-2009, Yahoo! Inc. All rights reserved.
LICENSE
All code specific to YUI Compressor is issued under a BSD license.
YUI Compressor extends and implements code from Mozilla's Rhino project.
Rhino is issued under the Mozilla Public License (MPL), and MPL applies
to the Rhino source and binaries that are distributed with YUI Compressor.

Binary file not shown.