Compare commits

..

86 Commits
0.9.3 ... 0.9.5

Author SHA1 Message Date
Bryce Curtis
0aacfbdd50 Update version to 0.9.5 2011-04-27 14:10:13 -05:00
macdonst
2cd116e4e7 Issue 60: Contact search unicode problem
Contact search was not working for unicode letters.  The CallbackServer was changed so that it returned url encode strings.  On the JavaScript side the PhoneGap callback handler decodes the returned string.
2011-04-25 22:22:12 +08:00
Bryce Curtis
673a8871df Ticket 136: window.openDatabase() in Android 3.0 throws SECURITY_ERR (most code written by Simon MacDonald - I just tested and checked in)
When you call window.openDatabase() on an Android 3.0 device you get and error something like this:

E/Web Console( 1791): SECURITY_ERR: DOM Exception 18: An attempt was made to break through the security policy of the user agent.

Simon worked with Pat for a bit and they think this is a WebKit or Android/WebKit interaction bug. In the meantime this fix determines if you are on Android 3.0 and uses Droid_DB if so.
2011-04-19 16:54:16 -05:00
Fil Maj
44945f9d5e Partial resolution for ticket 57: some issues with camera functionality not firing callbacks properly. 2011-04-18 18:12:01 -07:00
Roman
887f754014 Hidden NPE fixed, which appeared when someone pass null as arguments
(for such SQL as e.g. CREATE TABLE).

It is especially important when work with dome 3d party persistemce
libraries, like e.g. http://github.com/zefhemel/persistencejs which
passes these nulls.
2011-04-18 17:02:55 -07:00
macdonst
674015460f Fixing file commands so that they run async 2011-04-13 03:13:10 +08:00
macdonst
40084293c3 Ticket 127: Android FileReader/FileWriter methods should return FileError object on error. 2011-04-07 07:17:41 +08:00
Fil Maj
ed4c57e791 Woops, finger slipped. 2011-04-06 11:01:36 -07:00
Fil Maj
bf164f4161 Fix for ticket 121: Checking for null return on native openDatabase call not enough as only allowed one DB per PhoneGap app. Have to proxy openDatabase and check at runtime. 2011-04-06 10:50:24 -07:00
Bryce Curtis
626119ae3b Bug 126: NullPointerException in onDestroy() 2011-04-05 15:42:25 -05:00
macdonst
e766188689 W3C Media Capture API
An implementation of the W3C Media Capture spec:
http://dev.w3.org/2009/dap/camera/Overview-API

Capture operations are supported for audio, video, and images.  Each
capture operation launches the native audio recorder, video recorder,
or camera application, respectively.
2011-04-01 15:52:53 -04:00
macdonst
d74569ffa7 Read As Text missing load event call
FileReader.readAsText didn't call the onload callback on success.
2011-04-01 23:04:14 +08:00
macdonst
d424af03e4 Ticket 124: File Transfer multipart badly formed trips mod_security
A standard from has no trailing whitespace after a content-disposition line like so: "Content-Disposition: form-data; name="data";" however when using the extra params of Android FileTransfer a space is added on the end "Content-Disposition: form-data; name="data"; "

This fix simply removes the trailing whitespace.
2011-04-01 22:43:38 +08:00
Bryce Curtis
f6f80537c3 Merge branch 'master' of https://github.com/jos3000/phonegap-android into jos3000-master 2011-03-30 13:48:51 -05:00
Bryce Curtis
908485751b Add check to only init and run JS code once - even if included multiple times. 2011-03-30 13:29:24 -05:00
macdonst
b850d225f4 Support old way of adding service in PhoneGap 0.9.5
PhoneGap 0.9.4 replaced PluginManager.addService() with navigator.app.addService().  This is problematic with the older plugin as they are not being maintained.  I'm adding in a PluginManger JavaScript class which will implement the addService method and call navigator.app.addService() method under the hood.  This way we won't break old code.
2011-03-30 21:04:03 +08:00
Jos Shepherd
010c774988 Added native prompt() dialog support 2011-03-25 16:31:27 +00:00
macdonst
969f0c87d7 PhoneGap Android Ticket 113:
FileTransfer returns FILE_NOT_FOUND_ERR on http 500 error

For some reason on Android if you do a getInputStream() on a HTTP Connection and the server returns a 500 error it will report a FileNotFoundException.  Catching this exception and throwing an IOException so that we can report a more accurate error in JavaScript.
2011-03-24 23:31:12 +08:00
Fil Maj
b3e9794189 Fix for lighthouse ticket 115: certain versions of Android 2.2 return "null" for window.openDatabase. Hook in PhoneGap fallback for storage in this case. 2011-03-23 11:07:45 -07:00
Bryce Curtis
935295c9b8 Bug 110 - When you close an app on Android you see a JS error in logcat. 2011-03-18 17:27:36 -05:00
Fil Maj
04de2052fd As best a fix as can be made for issue 95: on HTC devices, if text input is in bottom half of page, it does not get scrolled up to top half of page when you tap it and virtual keyboard comes up. 2011-03-15 12:46:05 -07:00
Fil Maj
60eb60b4f5 Merge branch 'master' of github.com:phonegap/phonegap-android 2011-03-14 16:15:28 -07:00
Fil Maj
ec307fdda8 Null check in droidgap classic (build script). 2011-03-14 16:15:19 -07:00
Bryce Curtis
7344964c05 Add support for setting sms body using uri "sms:#?body=text". 2011-03-13 22:36:09 -05:00
macdonst
1fc56921aa Ticket #90: Move _createEvent from File to PhoneGap
Got rid of _createEvent from file.js as it is redundant code.
2011-03-10 04:26:11 +08:00
Bryce Curtis
21a34a8980 Ticket 106 - Simplify splash screen logic based upon idea from vadim. 2011-03-08 22:00:33 -06:00
Fil Maj
8d73b364f2 Issue 107: always send resume event to JS. 2011-03-07 16:50:10 -08:00
Fil Maj
fb2c25c6c6 Issue 107: Always send pause event to JS. 2011-03-07 16:48:23 -08:00
Mark Darbyshire
47ca081f36 Implement localStorage.key() and localStorage.length
This brings PhoneGap's implementation in line with the spec at http://dev.w3.org/html5/webstorage/
It makes the following demo work when you include PhoneGap: http://people.w3.org/mike/localstorage.html
I was hopeful it would make my app, which makes use of LawnChair, work, but I've had no such luck as of yet.
2011-03-07 15:55:14 -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
51 changed files with 4120 additions and 1701 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.5

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,13 +5,13 @@
<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" src="phonegap.0.9.5.min.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></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>, Version: <span id="version">&nbsp;</span></h4>
<h4>UUID: <span id="uuid"> &nbsp;</span>, Name: <span id="name">&nbsp;</span></h4>
@@ -29,7 +29,8 @@
<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>

View File

@@ -1,127 +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 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 getLocation = function() {
var suc = function(p){
alert(p.coords.latitude + " " + p.coords.longitude);
};
var fail = function(){};
navigator.geolocation.getCurrentPosition(suc,fail);
var locFail = function() {
};
var beep = function(){
navigator.notification.beep(2);
};
var vibrate = function(){
navigator.notification.vibrate(0);
};
navigator.geolocation.getCurrentPosition(suc, locFail);
};
function roundNumber(num) {
var dec = 3;
var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
return result;
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 accelerationWatch = false;
var toggleAccel = function() {
if (accelerationWatch) {
navigator.accelerometer.clearWatch(accelerationWatch);
updateAcceleration( {
x : "",
y : "",
z : ""
});
accelerationWatch = false;
} else {
accelerationWatch = true;
var options = new Object();
options.frequency = 1000;
accelerationWatch = navigator.accelerometer.watchAcceleration(
updateAcceleration, function(ex) {
navigator.accelerometer.clearWatch(accel_watch_id);
alert("accel fail (" + ex.name + ": " + ex.message + ")");
}, options);
}
};
};
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 preventBehavior = function(e) {
e.preventDefault();
};
var preventBehavior = function(e) {
e.preventDefault();
};
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 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 close()
{
var viewport = document.getElementById('viewport');
viewport.style.position = "relative";
viewport.style.display = "none";
}
function show_pic() {
navigator.camera.getPicture(dump_pic, fail, {
quality : 50
});
}
function fail(fail)
{
alert(fail);
}
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);
}
// 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 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"], contacts_success, fail, obj);
}
function contacts_success(contacts) {
alert(contacts.length
+ ' contacts returned.'
+ (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted)
: ''));
}
function contacts_success(contacts)
{
alert(contacts.length + ' contacts returned.' +
(contacts[2] ? (' Third contact is ' + contacts[2].displayName) : ''));
}
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);
}
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

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
package="com.phonegap" android:versionName="1.1" android:versionCode="5">
<supports-screens
android:largeScreens="true"
@@ -8,6 +8,7 @@
android:resizeable="true"
android:anyDensity="true"
/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
@@ -23,6 +24,9 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
<activity android:name=".StandAlone"
@@ -33,6 +37,7 @@
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="2" />
<uses-sdk android:minSdkVersion="2" />
</manifest>

View File

@@ -6,18 +6,21 @@
* Copyright (c) 2010, IBM Corporation
*/
function Acceleration(x, y, z) {
if (!PhoneGap.hasResource("accelerometer")) {
PhoneGap.addResource("accelerometer");
Acceleration = function(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
this.timestamp = new Date().getTime();
};
}
/**
* This class provides access to device accelerometer data.
* @constructor
*/
function Accelerometer() {
Accelerometer = function() {
/**
* The last known acceleration. type=Acceleration()
@@ -28,7 +31,7 @@ function Accelerometer() {
* List of accelerometer watch timers
*/
this.timers = {};
};
}
Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"];
@@ -42,13 +45,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 +71,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 +111,15 @@ 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();
}
});
};

View File

@@ -3,14 +3,16 @@
* 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
*/
if (!PhoneGap.hasResource("app")) {
PhoneGap.addResource("app");
/**
* Constructor
*/
function App() {
}
App = function() {};
/**
* Clear the resource cache.
@@ -22,19 +24,19 @@ App.prototype.clearCache = function() {
/**
* 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
* @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 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]);
@@ -64,3 +66,28 @@ App.prototype.clearHistory = function() {
App.prototype.addService = function(serviceType, className) {
PhoneGap.exec(null, null, "App", "addService", [serviceType, className]);
};
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* Note: The user should not have to call this method. Instead, when the user
* registers for the "backbutton" event, this is automatically done.
*
* @param override T=override, F=cancel override
*/
App.prototype.overrideBackbutton = function(override) {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]);
};
/**
* Exit and terminate the application.
*/
App.prototype.exitApp = function() {
return PhoneGap.exec(null, null, "App", "exitApp", []);
};
PhoneGap.addConstructor(function() {
navigator.app = window.app = new App();
});
};

View File

@@ -6,6 +6,9 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("camera")) {
PhoneGap.addResource("camera");
/**
* This class provides access to the device camera.
*
@@ -59,13 +62,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 +83,15 @@ 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

@@ -0,0 +1,187 @@
/*
* 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
*/
/**
* The CaptureError interface encapsulates all errors in the Capture API.
*/
function CaptureError() {
this.code = null;
};
// Capture error codes
CaptureError.CAPTURE_INTERNAL_ERR = 0;
CaptureError.CAPTURE_APPLICATION_BUSY = 1;
CaptureError.CAPTURE_INVALID_ARGUMENT = 2;
CaptureError.CAPTURE_NO_MEDIA_FILES = 3;
CaptureError.CAPTURE_NOT_SUPPORTED = 20;
/**
* The Capture interface exposes an interface to the camera and microphone of the hosting device.
*/
function Capture() {
this.supportedAudioFormats = [];
this.supportedImageFormats = [];
this.supportedVideoFormats = [];
};
/**
* Launch audio recorder application for recording audio clip(s).
*
* @param {Function} successCB
* @param {Function} errorCB
* @param {CaptureAudioOptions} options
*/
Capture.prototype.captureAudio = function(successCallback, errorCallback, options) {
PhoneGap.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]);
};
/**
* Launch camera application for taking image(s).
*
* @param {Function} successCB
* @param {Function} errorCB
* @param {CaptureImageOptions} options
*/
Capture.prototype.captureImage = function(successCallback, errorCallback, options) {
PhoneGap.exec(successCallback, errorCallback, "Capture", "captureImage", [options]);
};
/**
* Launch camera application for taking image(s).
*
* @param {Function} successCB
* @param {Function} errorCB
* @param {CaptureImageOptions} options
*/
Capture.prototype._castMediaFile = function(pluginResult) {
var mediaFiles = [];
var i;
for (i=0; i<pluginResult.message.length; i++) {
var mediaFile = new MediaFile();
mediaFile.name = pluginResult.message[i].name;
mediaFile.fullPath = pluginResult.message[i].fullPath;
mediaFile.type = pluginResult.message[i].type;
mediaFile.lastModifiedDate = pluginResult.message[i].lastModifiedDate;
mediaFile.size = pluginResult.message[i].size;
mediaFiles.push(mediaFile);
}
pluginResult.message = mediaFiles;
return pluginResult;
};
/**
* Launch device camera application for recording video(s).
*
* @param {Function} successCB
* @param {Function} errorCB
* @param {CaptureVideoOptions} options
*/
Capture.prototype.captureVideo = function(successCallback, errorCallback, options) {
PhoneGap.exec(successCallback, errorCallback, "Capture", "captureVideo", [options]);
};
/**
* Encapsulates a set of parameters that the capture device supports.
*/
function ConfigurationData() {
// The ASCII-encoded string in lower case representing the media type.
this.type;
// The height attribute represents height of the image or video in pixels.
// In the case of a sound clip this attribute has value 0.
this.height = 0;
// The width attribute represents width of the image or video in pixels.
// In the case of a sound clip this attribute has value 0
this.width = 0;
};
/**
* Encapsulates all image capture operation configuration options.
*/
function CaptureImageOptions() {
// Upper limit of images user can take. Value must be equal or greater than 1.
this.limit = 1;
// The selected image mode. Must match with one of the elements in supportedImageModes array.
this.mode;
};
/**
* Encapsulates all video capture operation configuration options.
*/
function CaptureVideoOptions() {
// Upper limit of videos user can record. Value must be equal or greater than 1.
this.limit;
// Maximum duration of a single video clip in seconds.
this.duration;
// The selected video mode. Must match with one of the elements in supportedVideoModes array.
this.mode;
};
/**
* Encapsulates all audio capture operation configuration options.
*/
function CaptureAudioOptions() {
// Upper limit of sound clips user can record. Value must be equal or greater than 1.
this.limit;
// Maximum duration of a single sound clip in seconds.
this.duration;
// The selected audio mode. Must match with one of the elements in supportedAudioModes array.
this.mode;
};
/**
* Represents a single file.
*
* name {DOMString} name of the file, without path information
* fullPath {DOMString} the full path of the file, including the name
* type {DOMString} mime type
* lastModifiedDate {Date} last modified date
* size {Number} size of the file in bytes
*/
function MediaFile(name, fullPath, type, lastModifiedDate, size) {
this.name = name || null;
this.fullPath = fullPath || null;
this.type = type || null;
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
}
/**
* Launch device camera application for recording video(s).
*
* @param {Function} successCB
* @param {Function} errorCB
*/
MediaFile.prototype.getFormatData = function(successCallback, errorCallback) {
PhoneGap.exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]);
};
/**
* MediaFileData encapsulates format information of a media file.
*
* @param {DOMString} codecs
* @param {long} bitrate
* @param {long} height
* @param {long} width
* @param {float} duration
*/
function MediaFileData(codecs, bitrate, height, width, duration) {
this.codecs = codecs || null;
this.bitrate = bitrate || 0;
this.height = height || 0;
this.width = width || 0;
this.duration = duration || 0;
}
PhoneGap.addConstructor(function() {
if (typeof navigator.device === "undefined") {
navigator.device = window.device = new Device();
}
if (typeof navigator.device.capture === "undefined") {
navigator.device.capture = window.device.capture = new Capture();
}
});

View File

@@ -6,11 +6,14 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("compass")) {
PhoneGap.addResource("compass");
/**
* This class provides access to device Compass data.
* @constructor
*/
function Compass() {
Compass = function() {
/**
* The last known Compass position.
*/
@@ -34,13 +37,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 +63,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 +112,8 @@ 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

@@ -6,6 +6,9 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("contact")) {
PhoneGap.addResource("contact");
/**
* Contains information about a single contact.
* @param {DOMString} id unique identifier
@@ -17,24 +20,17 @@
* @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;
@@ -45,29 +41,43 @@ 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;
this.timezone = timezone || null;
};
/**
* ContactError.
* An error code assigned by an implementation when an error has occurred
*/
var ContactError = function() {
this.code=null;
};
/**
* Error codes
*/
ContactError.UNKNOWN_ERROR = 0;
ContactError.INVALID_ARGUMENT_ERROR = 1;
ContactError.NOT_FOUND_ERROR = 2;
ContactError.TIMEOUT_ERROR = 3;
ContactError.PENDING_OPERATION_ERROR = 4;
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) {
if (this.id === null) {
var errorObj = new ContactError();
errorObj.code = ContactError.NOT_FOUND_ERROR;
errorCB(errorObj);
@@ -84,48 +94,49 @@ Contact.prototype.remove = function(successCB, errorCB) {
*/
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
for (i = 0; i < clonedContact.tags.length; i++) {
clonedContact.tags[i].id = null;
}
}
if (clonedContact.relationships) {
for (i=0; i<clonedContact.relationships.length; i++) {
clonedContact.relationships[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;
}
for (i = 0; i < clonedContact.urls.length; i++) {
clonedContact.urls[i].id = null;
}
}
return clonedContact;
};
@@ -162,13 +173,13 @@ var ContactName = function(formatted, familyName, givenName, middle, prefix, suf
* @param {DOMString} id unique identifier, should only be set by native code
* @param type
* @param value
* @param primary
* @param pref
*/
var ContactField = function(type, value, primary) {
var ContactField = function(type, value, pref) {
this.id = null;
this.type = type || null;
this.value = value || null;
this.primary = primary || null;
this.pref = pref || null;
};
/**
@@ -202,38 +213,20 @@ var ContactAddress = function(formatted, streetAddress, locality, region, postal
* @param location
* @param desc
*/
var ContactOrganization = function(name, dept, title, startDate, endDate, location, desc) {
var ContactOrganization = function(name, dept, title) {
this.id = null;
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 {DOMString} id unique identifier, should only be set by native code
* @param domain
* @param username
* @param userid
*/
var ContactAccount = function(domain, username, userid) {
this.id = null;
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();
}
this.records = [];
};
/**
* Returns an array of Contacts matching the search criteria.
* @param fields that should be searched
@@ -254,10 +247,11 @@ Contacts.prototype.find = function(fields, successCB, errorCB, options) {
* @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];
if (contact[i] !== 'undefined') {
contact[i] = properties[i];
}
}
return contact;
@@ -272,52 +266,36 @@ Contacts.prototype.create = function(properties) {
* @returns an array of Contact objects
*/
Contacts.prototype.cast = function(pluginResult) {
var contacts = new Array();
for (var i=0; i<pluginResult.message.length; i++) {
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 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) {
var ContactFindOptions = function(filter, multiple, updatedSince) {
this.filter = filter || '';
this.multiple = multiple || false;
this.limit = limit || 1;
this.multiple = multiple || true;
this.updatedSince = updatedSince || '';
};
/**
* ContactError.
* An error code assigned by an implementation when an error has occurred
*/
var ContactError = function() {
this.code=null;
};
/**
* Error codes
*/
ContactError.UNKNOWN_ERROR = 0;
ContactError.INVALID_ARGUMENT_ERROR = 1;
ContactError.NOT_FOUND_ERROR = 2;
ContactError.TIMEOUT_ERROR = 3;
ContactError.PENDING_OPERATION_ERROR = 4;
ContactError.IO_ERROR = 5;
ContactError.NOT_SUPPORTED_ERROR = 6;
ContactError.PERMISSION_DENIED_ERROR = 20;
/**
* 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

@@ -8,6 +8,9 @@
// TODO: Needs to be commented
if (!PhoneGap.hasResource("crypto")) {
PhoneGap.addResource("crypto");
var Crypto = function() {
};
@@ -30,6 +33,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,15 +3,18 @@
* 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
*/
if (!PhoneGap.hasResource("device")) {
PhoneGap.addResource("device");
/**
* This represents the mobile device, and provides properties for inspecting the model, version, UUID of the
* phone, etc.
* @constructor
*/
function Device() {
Device = function() {
this.available = PhoneGap.available;
this.platform = null;
this.version = null;
@@ -35,7 +38,7 @@ function Device() {
console.log("Error initializing PhoneGap: " + e);
alert("Error initializing PhoneGap: "+e);
});
}
};
/**
* Get device info
@@ -46,13 +49,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;
}
@@ -62,32 +65,41 @@ 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();
if (typeof navigator.device === "undefined") {
navigator.device = window.device = new Device();
}
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
/*
* 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
*/
if (!PhoneGap.hasResource("filetransfer")) {
PhoneGap.addResource("filetransfer");
/**
* FileTransfer uploads a file to a remote server.
*/
FileTransfer = function() {};
/**
* FileUploadResult
*/
FileUploadResult = function() {
this.bytesSent = 0;
this.responseCode = null;
this.response = null;
};
/**
* FileTransferError
*/
FileTransferError = function() {
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.
*/
FileUploadOptions = function(fileKey, fileName, mimeType, params) {
this.fileKey = fileKey || null;
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
};
};

View File

@@ -6,11 +6,14 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("geolocation")) {
PhoneGap.addResource("geolocation");
/**
* This class provides access to device GPS data.
* @constructor
*/
function Geolocation() {
Geolocation = function() {
// The last known GPS position.
this.lastPosition = null;
@@ -25,7 +28,7 @@ function Geolocation() {
* @param code
* @param message
*/
function PositionError(code, message) {
PositionError = function(code, message) {
this.code = code;
this.message = message;
};
@@ -42,7 +45,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 +56,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 +84,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 +121,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 +133,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,9 +189,9 @@ 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

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

View File

@@ -6,6 +6,9 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("media")) {
PhoneGap.addResource("media");
/**
* List of media objects.
* PRIVATE
@@ -40,8 +43,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 +53,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 +79,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;
}
@@ -128,8 +131,8 @@ Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"];
* This class contains information about any Media errors.
* @constructor
*/
function MediaError() {
this.code = null,
MediaError = function() {
this.code = null;
this.message = "";
};
@@ -198,4 +201,4 @@ Media.prototype.stopRecord = function() {
Media.prototype.release = function() {
PhoneGap.exec(null, null, "Media", "release", [this.id]);
};
};

View File

@@ -6,11 +6,14 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("network")) {
PhoneGap.addResource("network");
/**
* This class contains information about any NetworkStatus.
* @constructor
*/
function NetworkStatus() {
NetworkStatus = function() {
//this.code = null;
//this.message = "";
};
@@ -23,7 +26,7 @@ NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2;
* This class provides access to device Network data (reachability).
* @constructor
*/
function Network() {
Network = function() {
/**
* The last known Network status.
* { hostName: string, ipAddress: string,
@@ -57,6 +60,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

@@ -6,11 +6,14 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("notification")) {
PhoneGap.addResource("notification");
/**
* This class provides access to notifications on the device.
*/
function Notification() {
}
Notification = function() {
};
/**
* Open a native alert dialog, with a customizable title and button text.
@@ -111,6 +114,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,9 +3,10 @@
* 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
*/
if (typeof PhoneGap === "undefined") {
/**
* The order of events during page load and PhoneGap startup is as follows:
@@ -18,6 +19,8 @@
* onPhoneGapInfoReady Internal event fired when device properties are available
* onDeviceReady User event fired to indicate that PhoneGap is ready
* onResume User event fired to indicate a start/resume lifecycle event
* onPause User event fired to indicate a pause lifecycle event
* onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
*
* The only PhoneGap events that user code should register for are:
* onDeviceReady
@@ -26,10 +29,12 @@
* Listeners can be registered as:
* document.addEventListener("deviceready", myDeviceReadyListener, false);
* document.addEventListener("resume", myResumeListener, false);
* document.addEventListener("pause", myPauseListener, false);
*/
if (typeof(DeviceInfo) != 'object')
if (typeof(DeviceInfo) !== 'object') {
DeviceInfo = {};
}
/**
* This represents the PhoneGap API itself, and provides a global namespace for accessing
@@ -44,11 +49,35 @@ var PhoneGap = {
}
};
/**
* List of resource files loaded by PhoneGap.
* This is used to ensure JS and other files are loaded only once.
*/
PhoneGap.resources = {base: true};
/**
* Determine if resource has been loaded by PhoneGap
*
* @param name
* @return
*/
PhoneGap.hasResource = function(name) {
return PhoneGap.resources[name];
};
/**
* Add a resource to list of loaded resources by PhoneGap
*
* @param name
*/
PhoneGap.addResource = function(name) {
PhoneGap.resources[name] = true;
};
/**
* 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 +95,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 +117,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 +142,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 +166,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
@@ -183,7 +226,7 @@ PhoneGap.addPlugin = function(name, obj) {
else {
console.log("Error: Plugin "+name+" already exists.");
}
}
};
/**
* onDOMContentLoaded channel is fired when the DOM content
@@ -226,6 +269,17 @@ PhoneGap.onResume = new PhoneGap.Channel('onResume');
*/
PhoneGap.onPause = new PhoneGap.Channel('onPause');
/**
* onDestroy channel is fired when the PhoneGap native code
* is destroyed. It is used internally.
* Window.onunload should be used by the user.
*/
PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy');
PhoneGap.onDestroy.subscribeOnce(function() {
PhoneGap.shuttingDown = true;
});
PhoneGap.shuttingDown = false;
// _nativeReady is global variable that the native side can set
// to signify that the native code is ready. It is a global since
// it may be called before any PhoneGap JS is ready.
@@ -238,6 +292,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.
*/
@@ -245,11 +332,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);
@@ -259,22 +352,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();
@@ -285,17 +377,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 {
}
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.
@@ -304,57 +429,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 (args[i][name] != null) {
if (!start) {
s = s + ',';
}
s = s + '"' + name + '":';
var nameType = typeof args[i][name];
if ((nameType == "number") || (nameType == "boolean")) {
s = s + args[i][name];
}
else if ((typeof args[i][name]) == 'function') {
// don't copy the functions
s = s + '""';
}
else if (args[i][name] instanceof Object) {
s = s + this.stringify(args[i][name]);
}
else {
s = s + '"' + args[i][name] + '"';
}
start=false;
}
}
s = s + '}';
}
else {
var a = args[i].replace(/\\/g, '\\\\');
a = a.replace(/"/g, '\\"');
s = s + '"' + a + '"';
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);
}
};
@@ -366,13 +487,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;
@@ -385,10 +507,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]);
}
}
@@ -432,23 +558,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);
success(v.message);
} catch (e) {
console.log("Error in success callback: " + callbackId + " = " + e);
}
// Clear callback if not expecting any more results
@@ -460,7 +585,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) {
@@ -470,15 +595,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
@@ -489,8 +614,8 @@ PhoneGap.exec = function(success, fail, service, action, args) {
return null;
}
}
} catch (e) {
console.log("Error: "+e);
} catch (e2) {
console.log("Error: "+e2);
}
};
@@ -504,10 +629,10 @@ 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);
PhoneGap.callbacks[callbackId].success(args.message);
}
}
catch (e) {
@@ -556,38 +681,43 @@ 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;
@@ -604,16 +734,34 @@ PhoneGap.JSCallbackToken = null;
* Java to JavaScript.
*/
PhoneGap.JSCallback = function() {
// Exit if shutting down app
if (PhoneGap.shuttingDown) {
return;
}
// 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){
// Exit if shutting down app
if (PhoneGap.shuttingDown) {
return;
}
// If callback has JavaScript statement to execute
if (xmlhttp.status == 200) {
if (xmlhttp.status === 200) {
var msg = xmlhttp.responseText;
// Need to url decode the response and replace %20 with a space
var msg = decodeURIComponent(xmlhttp.responseText.replace(/\+/g, '%20'));
setTimeout(function() {
try {
var t = eval(msg);
@@ -628,41 +776,41 @@ 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) {
else if (xmlhttp.status === 403) {
console.log("JSCallback Error: Invalid token. Stopping callbacks.");
}
// If server is stopping
else if (xmlhttp.status == 503) {
else if (xmlhttp.status === 503) {
console.log("JSCallback Error: Service unavailable. Stopping callbacks.");
}
// If request wasn't GET
else if (xmlhttp.status == 400) {
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 = CallbackServer.getPort();
if (PhoneGap.JSCallbackPort === null) {
PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:");
}
if (PhoneGap.JSCallbackToken == null) {
PhoneGap.JSCallbackToken = CallbackServer.getToken();
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();
@@ -674,6 +822,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.
*
@@ -682,7 +835,19 @@ PhoneGap.JSCallbackPollingPeriod = 50;
* Java to JavaScript.
*/
PhoneGap.JSCallbackPolling = function() {
var msg = CallbackServer.getJavascript();
// Exit if shutting down app
if (PhoneGap.shuttingDown) {
return;
}
// 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 {
@@ -715,9 +880,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;
@@ -729,11 +895,11 @@ 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);
}
};
}
};
@@ -747,9 +913,23 @@ PhoneGap.includeJavascript = function(jsfile, successCallback) {
var id = document.getElementsByTagName("head")[0];
var el = document.createElement('script');
el.type = 'text/javascript';
if (typeof successCallback == 'function') {
if (typeof successCallback === 'function') {
el.onload = successCallback;
}
el.src = jsfile;
id.appendChild(el);
};
/**
* This class is provided to bridge the gap between the way plugins were setup in 0.9.3 and 0.9.4.
* Users should be calling navigator.add.addService() instead of PluginManager.addService().
* @class
* @deprecated
*/
var PluginManager = {
addService: function(serviceType, className) {
navigator.app.addService(serviceType, className);
}
};
};

View File

@@ -6,6 +6,9 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("position")) {
PhoneGap.addResource("position");
/**
* This class contains position information.
* @param {Object} lat
@@ -17,12 +20,12 @@
* @param {Object} vel
* @constructor
*/
function Position(coords, timestamp) {
Position = function(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) {
Coordinates = function(lat, lng, alt, acc, head, vel, altacc) {
/**
* The latitude of the position.
*/
@@ -50,14 +53,14 @@ 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;
};
/**
* This class specifies the options for requesting position data.
* @constructor
*/
function PositionOptions() {
PositionOptions = function() {
/**
* Specifies the desired position accuracy.
*/
@@ -67,18 +70,19 @@ function PositionOptions() {
* is called.
*/
this.timeout = 10000;
}
};
/**
* This class contains information about any GSP errors.
* @constructor
*/
function PositionError() {
PositionError = function() {
this.code = null;
this.message = "";
}
};
PositionError.UNKNOWN_ERROR = 0;
PositionError.PERMISSION_DENIED = 1;
PositionError.POSITION_UNAVAILABLE = 2;
PositionError.TIMEOUT = 3;
};

View File

@@ -12,6 +12,9 @@
* most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required
*/
if (!PhoneGap.hasResource("storage")) {
PhoneGap.addResource("storage");
/**
* Storage object that is called by native code when performing queries.
* PRIVATE METHOD
@@ -45,7 +48,7 @@ DroidDB.prototype.completeQuery = function(id, data) {
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) {
@@ -83,7 +86,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) {
@@ -99,6 +102,24 @@ DroidDB.prototype.fail = function(reason, id) {
}
};
/**
* Transaction object
* PRIVATE METHOD
*/
var DroidDB_Tx = function() {
// Set the id of the transaction
this.id = PhoneGap.createUUID();
// Callbacks
this.successCallback = null;
this.errorCallback = null;
// Query list
this.queryList = {};
};
var DatabaseShell = function() {
};
@@ -128,22 +149,6 @@ DatabaseShell.prototype.transaction = function(process, errorCallback, successCa
}
};
/**
* Transaction object
* PRIVATE METHOD
*/
var DroidDB_Tx = function() {
// Set the id of the transaction
this.id = PhoneGap.createUUID();
// Callbacks
this.successCallback = null;
this.errorCallback = null;
// Query list
this.queryList = {};
};
/**
* Mark query in transaction as complete.
@@ -157,10 +162,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) {
@@ -220,7 +228,7 @@ var DroidDB_Query = function(tx) {
this.successCallback = null;
this.errorCallback = null;
}
};
/**
* Execute SQL statement
@@ -233,7 +241,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 = [];
}
@@ -302,13 +310,21 @@ var CupcakeLocalStorage = function() {
this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440);
var storage = {};
this.length = 0;
function setLength (length) {
this.length = length;
localStorage.length = length;
}
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(var i = 0; i < result.rows.length; i++) {
for(var i = 0; i < result.rows.length; i++) {
storage[result.rows.item(i)['id']] = result.rows.item(i)['body'];
}
setLength(result.rows.length);
PhoneGap.initializationComplete("cupcakeStorage");
});
},
@@ -317,45 +333,78 @@ var CupcakeLocalStorage = function() {
}
);
this.setItem = function(key, val) {
console.log('set');
if (typeof(storage[key])=='undefined') {
this.length++;
}
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.length--;
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.length = 0;
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', []);
}
);
};
this.key = function(index) {
var i = 0;
for (var j in storage) {
if (i==index) {
return j;
} else {
i++;
}
}
return null;
}
} catch(e) {
alert("Database error "+e+".");
return;
}
};
PhoneGap.addConstructor(function() {
if (typeof window.openDatabase == "undefined") {
var setupDroidDB = function() {
navigator.openDatabase = window.openDatabase = DroidDB_openDatabase;
window.droiddb = new DroidDB();
}
if ((typeof window.openDatabase === "undefined") || (navigator.userAgent.indexOf("Android 3.0") != -1)) {
setupDroidDB();
} else {
window.openDatabase_orig = window.openDatabase;
window.openDatabase = function(name, version, desc, size) {
var db = window.openDatabase_orig(name, version, desc, size);
if (db == null) {
setupDroidDB();
return DroidDB_openDatabase(name, version, desc, size);
} else return db;
}
}
if (typeof window.localStorage == "undefined") {
if (typeof window.localStorage === "undefined") {
navigator.localStorage = window.localStorage = new CupcakeLocalStorage();
PhoneGap.waitForInitialization("cupcakeStorage");
}
});
};

View File

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

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>
@@ -100,15 +107,21 @@
<target name="build-uncompressed-javascript">
<!-- Clean up existing files -->
<delete file="assets/www/phonegap.js"/>
<delete file="assets/www/phonegap.${version}.min.js"/>
<delete file="assets/www/phonegap-tmp.js"/>
<delete file="assets/www/phonegap-uncompressed.js"/>
<delete file="assets/www/phonegap.${version}.js"/>
<!-- Create uncompressed JS file -->
<concat destfile="assets/www/phonegap.js">
<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.
@@ -116,24 +129,30 @@
<target name="build-javascript">
<!-- Clean up existing files -->
<delete file="assets/www/phonegap.js"/>
<delete file="assets/www/phonegap_${version}.min.js"/>
<delete file="assets/www/phonegap-tmp.js"/>
<delete file="assets/www/phonegap-uncompressed.js"/>
<delete file="assets/www/phonegap_${version}.js"/>
<!-- Create uncompressed JS file -->
<concat destfile="assets/www/phonegap-uncompressed.js">
<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-uncompressed.js"/>
<arg line="--nomunge -o assets/www/phonegap-tmp.js assets/www/phonegap.${version}.js"/>
</java>
<concat destfile="assets/www/phonegap.js">
<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"/>
@@ -148,7 +167,7 @@
"build-javascript" => "build-uncompressed-javascript".
-->
<target name="jar" depends="build-javascript, compile">
<jar jarfile="phonegap.jar" basedir="bin/classes" excludes="com/phonegap/R.class,com/phonegap/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

@@ -3,7 +3,7 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
package com.phonegap;
@@ -17,7 +17,7 @@ import com.phonegap.api.PluginResult;
* This class exposes methods in DroidGap that can be called from JavaScript.
*/
public class App extends Plugin {
/**
* Executes the request and returns PluginResult.
*
@@ -45,6 +45,16 @@ public class App extends Plugin {
}
else if (action.equals("addService")) {
this.addService(args.getString(0), args.getString(1));
}
else if (action.equals("overrideBackbutton")) {
this.overrideBackbutton(args.getBoolean(0));
}
else if (action.equals("isBackbuttonOverridden")) {
boolean b = this.isBackbuttonOverridden();
return new PluginResult(status, b);
}
else if (action.equals("exitApp")) {
this.exitApp();
}
return new PluginResult(status, result);
} catch (JSONException e) {
@@ -132,4 +142,32 @@ public class App extends Plugin {
public void addService(String serviceType, String className) {
this.ctx.addService(serviceType, className);
}
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* @param override T=override, F=cancel override
*/
public void overrideBackbutton(boolean override) {
System.out.println("WARNING: Back Button Default Behaviour will be overridden. The backbutton event will be fired!");
((DroidGap)this.ctx).bound = override;
}
/**
* Return whether the Android back button is overridden by the user.
*
* @return boolean
*/
public boolean isBackbuttonOverridden() {
return ((DroidGap)this.ctx).bound;
}
/**
* Exit the Android application.
*/
public void exitApp() {
((DroidGap)this.ctx).finish();
}
}

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

@@ -13,6 +13,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLEncoder;
import java.util.LinkedList;
/**
@@ -69,7 +70,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
@@ -188,54 +189,63 @@ public class CallbackServer implements Runnable {
request = xhrReader.readLine();
String response = "";
//System.out.println("CallbackServerRequest="+request);
if (request.contains("GET")) {
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("CallbackServer -- Processing GET request");
// 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;
// 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("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
// 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";
String js = this.getJavascript();
if (js != null)
response += URLEncoder.encode(js, "UTF-8");
}
}
else {
//System.out.println("CallbackServer -- sending item");
response = "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 503 Service Unavailable\r\n\r\n ";
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
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();
}
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();
output.close();
output.close();
xhrReader.close();
}
} catch (IOException e) {
e.printStackTrace();

View File

@@ -201,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.");
@@ -228,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.");
@@ -261,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

@@ -0,0 +1,346 @@
package com.phonegap;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
public class Capture extends Plugin {
private static final String _DATA = "_data"; // The column name where the file path is stored
private static final int CAPTURE_AUDIO = 0; // Constant for capture audio
private static final int CAPTURE_IMAGE = 1; // Constant for capture image
private static final int CAPTURE_VIDEO = 2; // Constant for capture video
private static final String LOG_TAG = "Capture";
private String callbackId; // The ID of the callback to be invoked with our result
private long limit; // the number of pics/vids/clips to take
private double duration; // optional duration parameter for video recording
private JSONArray results; // The array of results to be returned to the user
private Uri imageUri; // Uri of captured image
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
this.callbackId = callbackId;
this.limit = 1;
this.duration = 0.0f;
this.results = new JSONArray();
JSONObject options = args.optJSONObject(0);
if (options != null) {
limit = options.optLong("limit", 1);
duration = options.optDouble("duration", 0.0f);
}
if (action.equals("getFormatData")) {
try {
JSONObject obj = getFormatData(args.getString(0), args.getString(1));
return new PluginResult(PluginResult.Status.OK, obj);
} catch (JSONException e) {
return new PluginResult(PluginResult.Status.ERROR);
}
}
else if (action.equals("captureAudio")) {
this.captureAudio();
}
else if (action.equals("captureImage")) {
this.captureImage();
}
else if (action.equals("captureVideo")) {
this.captureVideo(duration);
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
/**
* Provides the media data file data depending on it's mime type
*
* @param filePath path to the file
* @param mimeType of the file
* @return a MediaFileData object
*/
private JSONObject getFormatData(String filePath, String mimeType) {
JSONObject obj = new JSONObject();
try {
// setup defaults
obj.put("height", 0);
obj.put("width", 0);
obj.put("bitrate", 0);
obj.put("duration", 0);
obj.put("codecs", "");
if (mimeType.equals("image/jpeg")) {
obj = getImageData(filePath, obj);
}
else if (filePath.endsWith("audio/3gpp")) {
obj = getAudioVideoData(filePath, obj, false);
}
else if (mimeType.equals("video/3gpp")) {
obj = getAudioVideoData(filePath, obj, true);
}
}
catch (JSONException e) {
Log.d(LOG_TAG, "Error: setting media file data object");
}
return obj;
}
/**
* Get the Image specific attributes
*
* @param filePath path to the file
* @param obj represents the Media File Data
* @return a JSONObject that represents the Media File Data
* @throws JSONException
*/
private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException {
Bitmap bitmap = BitmapFactory.decodeFile(filePath);
obj.put("height", bitmap.getHeight());
obj.put("width", bitmap.getWidth());
return obj;
}
/**
* Get the Image specific attributes
*
* @param filePath path to the file
* @param obj represents the Media File Data
* @param video if true get video attributes as well
* @return a JSONObject that represents the Media File Data
* @throws JSONException
*/
private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException {
MediaPlayer player = new MediaPlayer();
try {
player.setDataSource(filePath);
player.prepare();
obj.put("duration", player.getDuration());
if (video) {
obj.put("height", player.getVideoHeight());
obj.put("width", player.getVideoWidth());
}
}
catch (IOException e) {
Log.d(LOG_TAG, "Error: loading video file");
}
return obj;
}
/**
* Sets up an intent to capture audio. Result handled by onActivityResult()
*/
private void captureAudio() {
Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION);
this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO);
}
/**
* Sets up an intent to capture images. Result handled by onActivityResult()
*/
private void captureImage() {
Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
// Specify file so that large image is captured and returned
File photo = new File(Environment.getExternalStorageDirectory(), "Capture.jpg");
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
this.imageUri = Uri.fromFile(photo);
this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE);
}
/**
* Sets up an intent to capture video. Result handled by onActivityResult()
*/
private void captureVideo(double duration) {
Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
// Introduced in API 8
//intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration);
this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO);
}
/**
* Called when the video view exits.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
* @throws JSONException
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
// Result received okay
if (resultCode == Activity.RESULT_OK) {
// An audio clip was requested
if (requestCode == CAPTURE_AUDIO) {
// Get the uri of the audio clip
Uri data = intent.getData();
// create a file object from the uri
results.put(createMediaFile(data));
if (results.length() >= limit) {
// Send Uri back to JavaScript for listening to audio
this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
} else {
// still need to capture more audio clips
captureAudio();
}
} else if (requestCode == CAPTURE_IMAGE) {
// For some reason if I try to do:
// Uri data = intent.getData();
// It crashes in the emulator and on my phone with a null pointer exception
// To work around it I had to grab the code from CameraLauncher.java
try {
// Read in bitmap of captured image
Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri);
// Create entry in media store for image
// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
ContentValues values = new ContentValues();
values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri uri = null;
try {
uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (UnsupportedOperationException e) {
System.out.println("Can't write to external media storage.");
try {
uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
} catch (UnsupportedOperationException ex) {
System.out.println("Can't write to internal media storage.");
this.fail("Error capturing image - no media storage found.");
return;
}
}
// Add compressed version of captured image to returned media store Uri
OutputStream os = this.ctx.getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
os.close();
bitmap.recycle();
bitmap = null;
System.gc();
// Add image to results
results.put(createMediaFile(uri));
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing image
this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
} else {
// still need to capture more images
captureImage();
}
} catch (IOException e) {
e.printStackTrace();
this.fail("Error capturing image.");
}
} else if (requestCode == CAPTURE_VIDEO) {
// Get the uri of the video clip
Uri data = intent.getData();
// create a file object from the uri
results.put(createMediaFile(data));
if (results.length() >= limit) {
// Send Uri back to JavaScript for viewing video
this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
} else {
// still need to capture more video clips
captureVideo(duration);
}
}
}
// If canceled
else if (resultCode == Activity.RESULT_CANCELED) {
// If we have partial results send them back to the user
if (results.length() > 0) {
this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
}
// user canceled the action
else {
this.fail("Canceled.");
}
}
// If something else
else {
// If we have partial results send them back to the user
if (results.length() > 0) {
this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
}
// something bad happened
else {
this.fail("Did not complete!");
}
}
}
/**
* Creates a JSONObject that represents a File from the Uri
*
* @param data the Uri of the audio/image/video
* @return a JSONObject that represents a File
*/
private JSONObject createMediaFile(Uri data) {
File fp = new File(getRealPathFromURI(data));
JSONObject obj = new JSONObject();
try {
// File properties
obj.put("name", fp.getName());
obj.put("fullPath", fp.getAbsolutePath());
obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath()));
obj.put("lastModifiedDate", fp.lastModified());
obj.put("size", fp.length());
} catch (JSONException e) {
// this will never happen
e.printStackTrace();
}
return obj;
}
/**
* Queries the media store to find out what the file path is for the Uri we supply
*
* @param contentUri the Uri of the audio/image/video
* @return the full path to the file
*/
private String getRealPathFromURI(Uri contentUri) {
String[] proj = { _DATA };
Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(_DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
/**
* Send error message to JavaScript.
*
* @param err
*/
public void fail(String err) {
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
}
}

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

View File

@@ -104,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();
@@ -309,7 +315,7 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
try{
im.put("id", cursor.getString(
cursor.getColumnIndex(ContactMethods._ID)));
im.put("primary", false);
im.put("perf", false);
im.put("value", cursor.getString(
cursor.getColumnIndex(ContactMethodsColumns.DATA)));
im.put("type", getContactType(cursor.getInt(
@@ -343,10 +349,6 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
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);
@@ -401,7 +403,7 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
phone = new JSONObject();
try{
phone.put("id", cursor.getString(cursor.getColumnIndex(Phones._ID)));
phone.put("primary", false);
phone.put("perf", false);
phone.put("value", cursor.getString(cursor.getColumnIndex(Phones.NUMBER)));
phone.put("type", getPhoneType(cursor.getInt(cursor.getColumnIndex(Phones.TYPE))));
phones.put(phone);
@@ -430,7 +432,7 @@ public class ContactAccessorSdk3_4 extends ContactAccessor {
email = new JSONObject();
try{
email.put("id", cursor.getString(cursor.getColumnIndex(ContactMethods._ID)));
email.put("primary", false);
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)));

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -20,7 +20,7 @@ import android.telephony.TelephonyManager;
public class Device extends Plugin {
public static String phonegapVersion = "0.9.3"; // PhoneGap version
public static String phonegapVersion = "0.9.5"; // PhoneGap version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID

View File

@@ -8,15 +8,9 @@
package com.phonegap;
import java.io.File;
import java.util.Date;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
/**
* This class provides file directory utilities.
@@ -78,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.
@@ -123,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.
*
@@ -229,45 +111,4 @@ public class DirectoryManager {
}
return newPath;
}
/**
* This method will determine the file properties of the file specified
* by the filePath. Creates a JSONObject with name, lastModifiedDate and
* size properties.
*
* @param filePath the file to get the properties of
* @return a JSONObject with the files properties
*/
protected static JSONObject getFile(String filePath) {
File fp = new File(filePath);
JSONObject obj = new JSONObject();
try {
obj.put("name", fp.getAbsolutePath());
obj.put("lastModifiedDate", new Date(fp.lastModified()).toString());
obj.put("size", fp.length());
}
catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
return obj;
}
/**
* This method returns a JSONArray of file paths. Android's default
* location where files can be written is Environment.getExternalStorageDirectory().
* We are returning a array with one element so the interface can remain
* consistent with BlackBerry as they have two areas where files can be
* written.
*
* @return an array of file paths
*/
protected static JSONArray getRootPaths() {
JSONArray retVal = new JSONArray();
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";
retVal.put(path);
return retVal;
}
}

View File

@@ -3,27 +3,32 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.AlertDialog;
import android.widget.EditText;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.JsPromptResult;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
@@ -53,8 +58,8 @@ import com.phonegap.api.PhonegapActivity;
* super.onCreate(savedInstanceState);
*
* // Set properties for activity
* super.setProperty("loadingDialog", "Title,Message"); // show loading dialog
* super.setProperty("errorUrl", "file:///android_asset/www/error.html"); // if error loading file in super.loadUrl().
* super.setStringProperty("loadingDialog", "Title,Message"); // show loading dialog
* super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); // if error loading file in super.loadUrl().
*
* // Initialize activity
* super.init();
@@ -66,7 +71,7 @@ import com.phonegap.api.PhonegapActivity;
* super.appView.clearCache(true);
*
* // Load your application
* super.setProperty("splashscreen", R.drawable.splash); // load splash.jpg image from the resource drawable directory
* super.setIntegerProperty("splashscreen", R.drawable.splash); // load splash.jpg image from the resource drawable directory
* super.loadUrl("file:///android_asset/www/index.html", 3000); // show splash screen 3 sec before loading app
* }
* }
@@ -75,30 +80,30 @@ import com.phonegap.api.PhonegapActivity;
*
* // Display a native loading dialog. Format for value = "Title,Message".
* // (String - default=null)
* super.setProperty("loadingDialog", "Wait,Loading Demo...");
* super.setStringProperty("loadingDialog", "Wait,Loading Demo...");
*
* // Hide loadingDialog when page loaded instead of when deviceready event
* // occurs. (Boolean - default=false)
* super.setProperty("hideLoadingDialogOnPage", true);
* super.setBooleanProperty("hideLoadingDialogOnPage", true);
*
* // Cause all links on web page to be loaded into existing web view,
* // instead of being loaded into new browser. (Boolean - default=false)
* super.setProperty("loadInWebView", true);
* super.setBooleanProperty("loadInWebView", true);
*
* // Load a splash screen image from the resource drawable directory.
* // (Integer - default=0)
* super.setProperty("splashscreen", R.drawable.splash);
* super.setIntegerProperty("splashscreen", R.drawable.splash);
*
* // Time in msec to wait before triggering a timeout error when loading
* // with super.loadUrl(). (Integer - default=20000)
* super.setProperty("loadUrlTimeoutValue", 60000);
* super.setIntegerProperty("loadUrlTimeoutValue", 60000);
*
* // URL to load if there's an error loading specified URL with loadUrl().
* // Should be a local URL starting with file://. (String - default=null)
* super.setProperty("errorUrl", "file:///android_asset/www/error.html");
* super.setStringProperty("errorUrl", "file:///android_asset/www/error.html");
*
* // Enable app to keep running in background. (Boolean - default=true)
* super.setProperty("keepRunning", false);
* super.setBooleanProperty("keepRunning", false);
*/
public class DroidGap extends PhonegapActivity {
@@ -106,8 +111,8 @@ public class DroidGap extends PhonegapActivity {
protected WebView appView;
protected WebViewClient webViewClient;
private LinearLayout root;
private BrowserKey mKey;
protected LinearLayout root;
public boolean bound = false;
public CallbackServer callbackServer;
protected PluginManager pluginManager;
protected boolean cancelLoadUrl = false;
@@ -177,6 +182,8 @@ public class DroidGap extends PhonegapActivity {
this.init();
}
}
// Setup the hardware volume controls to handle volume control
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
/**
@@ -228,7 +235,8 @@ public class DroidGap extends PhonegapActivity {
// Bind PhoneGap objects to JavaScript
this.bindBrowser(this.appView);
// Add web view
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
root.addView(this.appView);
setContentView(root);
@@ -262,12 +270,6 @@ public class DroidGap extends PhonegapActivity {
private void bindBrowser(WebView appView) {
this.callbackServer = new CallbackServer();
this.pluginManager = new PluginManager(appView, this);
this.mKey = new BrowserKey(appView, this);
// This creates the new javascript interfaces for PhoneGap
appView.addJavascriptInterface(this.pluginManager, "PluginManager");
appView.addJavascriptInterface(this.mKey, "BackButton");
appView.addJavascriptInterface(this.callbackServer, "CallbackServer");
this.addService("App", "com.phonegap.App");
this.addService("Geolocation", "com.phonegap.GeoBroker");
@@ -284,6 +286,8 @@ public class DroidGap extends PhonegapActivity {
this.addService("Notification", "com.phonegap.Notification");
this.addService("Storage", "com.phonegap.Storage");
this.addService("Temperature", "com.phonegap.TempListener");
this.addService("FileTransfer", "com.phonegap.FileTransfer");
this.addService("Capture", "com.phonegap.Capture");
}
/**
@@ -300,8 +304,7 @@ public class DroidGap extends PhonegapActivity {
// If spashscreen
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
if (this.splashscreen != 0) {
this.appView.setBackgroundColor(0);
this.appView.setBackgroundResource(splashscreen);
root.setBackgroundResource(this.splashscreen);
}
// If hideLoadingDialogOnPageLoad
@@ -598,16 +601,19 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onPause() {
super.onPause();
if (this.appView == null) {
return;
}
// Send pause event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// If app doesn't want to run in background
if (!this.keepRunning) {
// Forward to plugins
this.pluginManager.onPause();
// Send pause event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// Pause JavaScript timers (including setInterval)
this.appView.pauseTimers();
}
@@ -619,6 +625,12 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onResume() {
super.onResume();
if (this.appView == null) {
return;
}
// Send resume event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};");
// If app doesn't want to run in background
if (!this.keepRunning || this.activityResultKeepRunning) {
@@ -632,9 +644,6 @@ public class DroidGap extends PhonegapActivity {
// Forward to plugins
this.pluginManager.onResume();
// Send resume event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};");
// Resume JavaScript timers (including setInterval)
this.appView.resumeTimers();
}
@@ -647,22 +656,24 @@ public class DroidGap extends PhonegapActivity {
public void onDestroy() {
super.onDestroy();
if (this.appView != null) {
// Make sure pause event is sent if onPause hasn't been called before onDestroy
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// Send destroy event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};");
// Load blank page so that JavaScript onunload is called
this.appView.loadUrl("about:blank");
// Clean up objects
if (this.mKey != null) {
}
// Forward to plugins
this.pluginManager.onDestroy();
if (this.callbackServer != null) {
this.callbackServer.destroy();
}
}
}
/**
@@ -758,6 +769,88 @@ public class DroidGap extends PhonegapActivity {
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// Calling PluginManager.exec() to call a native service using
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
boolean async = array.getBoolean(3);
String r = pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r);
} catch (JSONException e) {
e.printStackTrace();
}
}
// Polling for JavaScript messages
else if (defaultValue.equals("gap_poll:")) {
String r = callbackServer.getJavascript();
result.confirm(r);
}
// Calling into CallbackServer
else if (defaultValue.equals("gap_callbackServer:")) {
String r = "";
if (message.equals("usePolling")) {
r = ""+callbackServer.usePolling();
}
else if (message.equals("restartServer")) {
callbackServer.restartServer();
}
else if (message.equals("getPort")) {
r = Integer.toString(callbackServer.getPort());
}
else if (message.equals("getToken")) {
r = callbackServer.getToken();
}
result.confirm(r);
}
// Show dialog
else {
final JsPromptResult res = result;
AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx);
dlg.setMessage(message);
final EditText input = new EditText(this.ctx);
dlg.setView(input);
dlg.setCancelable(false);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String usertext = input.getText().toString();
res.confirm(usertext);
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
res.cancel();
}
});
dlg.create();
dlg.show();
}
return true;
}
}
/**
@@ -887,22 +980,41 @@ public class DroidGap extends PhonegapActivity {
return true;
}
// If sms:5551212
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
intent.putExtra("address", url.substring(4));
intent.setType("vnd.android-dir/mms-sms");
startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
System.out.println("Error sending sms "+url+":"+ e.toString());
}
return true;
}
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// If http, https or file
else if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) {
// Get address
String address = null;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
}
else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
}
}
}
intent.setData(Uri.parse("sms:"+address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
System.out.println("Error sending sms "+url+":"+ e.toString());
}
return true;
}
// All else
else {
int i = url.lastIndexOf('/');
String newBaseUrl = url;
@@ -911,6 +1023,8 @@ public class DroidGap extends PhonegapActivity {
}
// If our app or file:, then load into our webview
// NOTE: This replaces our app with new URL. When BACK is pressed,
// our app is reloaded and restarted. All state is lost.
if (this.ctx.loadInWebView || url.startsWith("file://") || this.ctx.baseUrl.equals(newBaseUrl)) {
this.ctx.appView.loadUrl(url);
}
@@ -927,8 +1041,6 @@ public class DroidGap extends PhonegapActivity {
}
return true;
}
return false;
}
/**
@@ -949,11 +1061,8 @@ public class DroidGap extends PhonegapActivity {
// from the JS side when the JS gets to that code.
appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}");
// If splash screen is showing, clear it
if (this.ctx.splashscreen != 0) {
this.ctx.splashscreen = 0;
appView.setBackgroundResource(0);
}
// Make app view visible
appView.setVisibility(View.VISIBLE);
// Stop "app loading" spinner if showing
if (this.ctx.hideLoadingDialogOnPageLoad) {
@@ -1000,13 +1109,16 @@ public class DroidGap extends PhonegapActivity {
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (this.appView == null) {
return super.onKeyDown(keyCode, event);
}
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {
// If back key is bound, then send event to JavaScript
if (mKey.isBound()) {
this.appView.loadUrl("javascript:document.keyEvent.backTrigger()");
if (this.bound) {
this.appView.loadUrl("javascript:PhoneGap.fireEvent('backbutton');");
}
// If not bound
@@ -1026,12 +1138,12 @@ public class DroidGap extends PhonegapActivity {
// If menu key
else if (keyCode == KeyEvent.KEYCODE_MENU) {
appView.loadUrl("javascript:keyEvent.menuTrigger()");
this.appView.loadUrl("javascript:PhoneGap.fireEvent('menubutton');");
}
// If search key
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
appView.loadUrl("javascript:keyEvent.searchTrigger()");
this.appView.loadUrl("javascript:PhoneGap.fireEvent('searchbutton');");
}
return false;

View File

@@ -0,0 +1,365 @@
/*
* 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;
try {
inStream = new DataInputStream ( conn.getInputStream() );
} catch(FileNotFoundException e) {
throw new IOException("Received error from server");
}
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) + "}");
}
}

View File

@@ -3,11 +3,13 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
package com.phonegap;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.FileChannel;
import org.apache.commons.codec.binary.Base64;
@@ -16,16 +18,25 @@ import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
import com.phonegap.file.EncodingException;
import com.phonegap.file.FileExistsException;
import com.phonegap.file.InvalidModificationException;
import com.phonegap.file.NoModificationAllowedException;
import com.phonegap.file.TypeMismatchException;
/**
* This class provides SD card file and directory services to JavaScript.
* Only files on the SD card can be accessed.
*/
public class FileUtils extends Plugin {
private static final String LOG_TAG = "FileUtils";
public static int NOT_FOUND_ERR = 1;
public static int SECURITY_ERR = 2;
public static int ABORT_ERR = 3;
@@ -35,7 +46,16 @@ public class FileUtils extends Plugin {
public static int NO_MODIFICATION_ALLOWED_ERR = 6;
public static int INVALID_STATE_ERR = 7;
public static int SYNTAX_ERR = 8;
public static int INVALID_MODIFICATION_ERR = 9;
public static int QUOTA_EXCEEDED_ERR = 10;
public static int TYPE_MISMATCH_ERR = 11;
public static int PATH_EXISTS_ERR = 12;
public static int TEMPORARY = 0;
public static int PERSISTENT = 1;
public static int RESOURCE = 2;
public static int APPLICATION = 3;
FileReader f_in;
FileWriter f_out;
@@ -59,124 +79,776 @@ public class FileUtils extends Plugin {
//System.out.println("FileUtils.execute("+action+")");
try {
if (action.equals("testSaveLocationExists")) {
boolean b = DirectoryManager.testSaveLocationExists();
return new PluginResult(status, b);
}
else if (action.equals("getFreeDiskSpace")) {
long l = DirectoryManager.getFreeDiskSpace();
return new PluginResult(status, l);
}
else if (action.equals("testFileExists")) {
boolean b = DirectoryManager.testFileExists(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("testDirectoryExists")) {
boolean b = DirectoryManager.testFileExists(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("deleteDirectory")) {
boolean b = DirectoryManager.deleteDirectory(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("deleteFile")) {
boolean b = DirectoryManager.deleteFile(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("createDirectory")) {
boolean b = DirectoryManager.createDirectory(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("getRootPaths")) {
return new PluginResult(status, DirectoryManager.getRootPaths());
}
else if (action.equals("readAsText")) {
try {
String s = this.readAsText(args.getString(0), args.getString(1));
return new PluginResult(status, s);
} catch (FileNotFoundException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
try {
if (action.equals("testSaveLocationExists")) {
boolean b = DirectoryManager.testSaveLocationExists();
return new PluginResult(status, b);
}
else if (action.equals("getFreeDiskSpace")) {
long l = DirectoryManager.getFreeDiskSpace();
return new PluginResult(status, l);
}
else if (action.equals("testFileExists")) {
boolean b = DirectoryManager.testFileExists(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("testDirectoryExists")) {
boolean b = DirectoryManager.testFileExists(args.getString(0));
return new PluginResult(status, b);
}
else if (action.equals("readAsText")) {
try {
String s = this.readAsText(args.getString(0), args.getString(1));
return new PluginResult(status, s);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
}
}
else if (action.equals("readAsDataURL")) {
try {
String s = this.readAsDataURL(args.getString(0));
return new PluginResult(status, s);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
}
}
else if (action.equals("writeAsText")) {
try {
this.writeAsText(args.getString(0), args.getString(1), args.getBoolean(2));
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
}
}
else if (action.equals("write")) {
try {
long fileSize = this.write(args.getString(0), args.getString(1), args.getLong(2));
return new PluginResult(status, fileSize);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
}
}
else if (action.equals("truncate")) {
try {
long fileSize = this.truncateFile(args.getString(0), args.getLong(1));
return new PluginResult(status, fileSize);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
}
}
else if (action.equals("requestFileSystem")) {
long size = args.optLong(1);
if (size != 0) {
if (size > DirectoryManager.getFreeDiskSpace()) {
JSONObject error = new JSONObject().put("code", FileUtils.QUOTA_EXCEEDED_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
}
}
JSONObject obj = requestFileSystem(args.getInt(0));
return new PluginResult(status, obj, "window.localFileSystem._castFS");
}
}
else if (action.equals("readAsDataURL")) {
try {
String s = this.readAsDataURL(args.getString(0));
return new PluginResult(status, s);
} catch (FileNotFoundException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
else if (action.equals("resolveLocalFileSystemURI")) {
JSONObject obj = resolveLocalFileSystemURI(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castEntry");
}
}
else if (action.equals("writeAsText")) {
try {
this.writeAsText(args.getString(0), args.getString(1), args.getBoolean(2));
} catch (FileNotFoundException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
else if (action.equals("getMetadata")) {
JSONObject obj = getMetadata(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castDate");
}
}
else if (action.equals("write")) {
try {
long fileSize = this.write(args.getString(0), args.getString(1), args.getLong(2));
return new PluginResult(status, fileSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
else if (action.equals("getFileMetadata")) {
JSONObject obj = getFileMetadata(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castDate");
}
}
else if (action.equals("truncate")) {
try {
long fileSize = this.truncateFile(args.getString(0), args.getLong(1));
return new PluginResult(status, fileSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR);
} catch (IOException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR);
else if (action.equals("getParent")) {
JSONObject obj = getParent(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castEntry");
}
}
else if (action.equals("getFile")) {
JSONObject obj = DirectoryManager.getFile(args.getString(0));
return new PluginResult(status, obj);
else if (action.equals("getDirectory")) {
JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), true);
return new PluginResult(status, obj, "window.localFileSystem._castEntry");
}
else if (action.equals("getFile")) {
JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), false);
return new PluginResult(status, obj, "window.localFileSystem._castEntry");
}
else if (action.equals("remove")) {
boolean success;
success = remove(args.getString(0));
if (success) {
return new PluginResult(status);
} else {
JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
}
}
else if (action.equals("removeRecursively")) {
boolean success = removeRecursively(args.getString(0));
if (success) {
return new PluginResult(status);
} else {
JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
}
}
else if (action.equals("moveTo")) {
JSONObject entry = transferTo(args.getString(0), args.getJSONObject(1), args.optString(2), true);
return new PluginResult(status, entry, "window.localFileSystem._castEntry");
}
else if (action.equals("copyTo")) {
JSONObject entry = transferTo(args.getString(0), args.getJSONObject(1), args.optString(2), false);
return new PluginResult(status, entry, "window.localFileSystem._castEntry");
}
else if (action.equals("readEntries")) {
JSONArray entries = readEntries(args.getString(0));
return new PluginResult(status, entries, "window.localFileSystem._castEntries");
}
return new PluginResult(status, result);
} catch (FileNotFoundException e) {
JSONObject error = new JSONObject().put("code", FileUtils.NOT_FOUND_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (FileExistsException e) {
JSONObject error = new JSONObject().put("code", FileUtils.PATH_EXISTS_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (NoModificationAllowedException e) {
JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (JSONException e) {
JSONObject error = new JSONObject().put("code", FileUtils.NO_MODIFICATION_ALLOWED_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (InvalidModificationException e) {
JSONObject error = new JSONObject().put("code", FileUtils.INVALID_MODIFICATION_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (MalformedURLException e) {
JSONObject error = new JSONObject().put("code", FileUtils.ENCODING_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (IOException e) {
JSONObject error = new JSONObject().put("code", FileUtils.INVALID_MODIFICATION_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (EncodingException e) {
JSONObject error = new JSONObject().put("code", FileUtils.ENCODING_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
} catch (TypeMismatchException e) {
JSONObject error = new JSONObject().put("code", FileUtils.TYPE_MISMATCH_ERR);
return new PluginResult(PluginResult.Status.ERROR, error);
}
return new PluginResult(status, result);
} catch (JSONException e) {
e.printStackTrace();
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
}
/**
* Allows the user to look up the Entry for a file or directory referred to by a local URI.
*
* @param url of the file/directory to look up
* @return a JSONObject representing a Entry from the filesystem
* @throws MalformedURLException if the url is not valid
* @throws FileNotFoundException if the file does not exist
* @throws IOException if the user can't read the file
* @throws JSONException
*/
private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException {
// Test to see if this is a valid URL first
@SuppressWarnings("unused")
URL testUrl = new URL(url);
File fp = null;
if (url.startsWith("file://")) {
fp = new File(url.substring(7, url.length()));
} else {
fp = new File(url);
}
if (!fp.exists()) {
throw new FileNotFoundException();
}
if (!fp.canRead()) {
throw new IOException();
}
return getEntry(fp);
}
/**
* Read the list of files from this directory.
*
* @param fileName the directory to read from
* @return a JSONArray containing JSONObjects that represent Entry objects.
* @throws FileNotFoundException if the directory is not found.
* @throws JSONException
*/
private JSONArray readEntries(String fileName) throws FileNotFoundException, JSONException {
File fp = new File(fileName);
if (!fp.exists()) {
// The directory we are listing doesn't exist so we should fail.
throw new FileNotFoundException();
}
JSONArray entries = new JSONArray();
if (fp.isDirectory()) {
File[] files = fp.listFiles();
for (int i=0; i<files.length; i++) {
entries.put(getEntry(files[i]));
}
}
return entries;
}
/**
* A setup method that handles the move/copy of files/directories
*
* @param fileName to be copied/moved
* @param newParent is the location where the file will be copied/moved to
* @param newName for the file directory to be called, if null use existing file name
* @param move if false do a copy, if true do a move
* @return a Entry object
* @throws FileExistsException
* @throws NoModificationAllowedException
* @throws IOException
* @throws InvalidModificationException
* @throws EncodingException
* @throws JSONException
*/
private JSONObject transferTo(String fileName, JSONObject newParent, String newName, boolean move) throws JSONException, FileExistsException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException {
// Check for invalid file name
if (newName != null && newName.contains(":")) {
throw new EncodingException("Bad file name");
}
File source = new File(fileName);
if (!source.exists()) {
// The file/directory we are copying doesn't exist so we should fail.
throw new FileNotFoundException("The source does not exist");
}
File destinationDir = new File(newParent.getString("fullPath"));
if (!destinationDir.exists()) {
// The destination does not exist so we should fail.
throw new FileNotFoundException("The source does not exist");
}
// Figure out where we should be copying to
File destination = createDestination(newName, source, destinationDir);
//Log.d(LOG_TAG, "Source: " + source.getAbsolutePath());
//Log.d(LOG_TAG, "Destin: " + destination.getAbsolutePath());
// Check to see if source and destination are the same file
if (source.getAbsolutePath().equals(destination.getAbsolutePath())) {
throw new InvalidModificationException("Can't copy a file onto itself");
}
if (source.isDirectory()) {
if (move) {
return moveDirectory(source, destination);
} else {
return copyDirectory(source, destination);
}
} else {
if (move) {
return moveFile(source, destination);
} else {
return copyFile(source, destination);
}
}
}
/**
* Creates the destination File object based on name passed in
*
* @param newName for the file directory to be called, if null use existing file name
* @param fp represents the source file
* @param destination represents the destination file
* @return a File object that represents the destination
*/
private File createDestination(String newName, File fp, File destination) {
File destFile = null;
// I know this looks weird but it is to work around a JSON bug.
if ("null".equals(newName) || "".equals(newName) ) {
newName = null;
}
if (newName != null) {
destFile = new File(destination.getAbsolutePath() + File.separator + newName);
} else {
destFile = new File(destination.getAbsolutePath() + File.separator + fp.getName());
}
return destFile;
}
/**
* Copy a file
*
* @param srcFile file to be copied
* @param destFile destination to be copied to
* @return a FileEntry object
* @throws IOException
* @throws InvalidModificationException
* @throws JSONException
*/
private JSONObject copyFile(File srcFile, File destFile) throws IOException, InvalidModificationException, JSONException {
// Renaming a file to an existing directory should fail
if (destFile.exists() && destFile.isDirectory()) {
throw new InvalidModificationException("Can't rename a file to a directory");
}
FileChannel input = new FileInputStream(srcFile).getChannel();
FileChannel output = new FileOutputStream(destFile).getChannel();
input.transferTo(0, input.size(), output);
input.close();
output.close();
/*
if (srcFile.length() != destFile.length()) {
return false;
}
*/
return getEntry(destFile);
}
/**
* Copy a directory
*
* @param srcDir directory to be copied
* @param destinationDir destination to be copied to
* @return a DirectoryEntry object
* @throws JSONException
* @throws IOException
* @throws NoModificationAllowedException
* @throws InvalidModificationException
*/
private JSONObject copyDirectory(File srcDir, File destinationDir) throws JSONException, IOException, NoModificationAllowedException, InvalidModificationException {
// Renaming a file to an existing directory should fail
if (destinationDir.exists() && destinationDir.isFile()) {
throw new InvalidModificationException("Can't rename a file to a directory");
}
// Check to make sure we are not copying the directory into itself
if (destinationDir.getAbsolutePath().startsWith(srcDir.getAbsolutePath())) {
throw new InvalidModificationException("Can't copy itself into itself");
}
// See if the destination directory exists. If not create it.
if (!destinationDir.exists()) {
if (!destinationDir.mkdir()) {
// If we can't create the directory then fail
throw new NoModificationAllowedException("Couldn't create the destination direcotry");
}
}
for (File file : srcDir.listFiles()) {
if (file.isDirectory()) {
copyDirectory(file, destinationDir);
} else {
File destination = new File(destinationDir.getAbsoluteFile() + File.separator + file.getName());
copyFile(file, destination);
}
}
return getEntry(destinationDir);
}
/**
* Move a file
*
* @param srcFile file to be copied
* @param destFile destination to be copied to
* @return a FileEntry object
* @throws IOException
* @throws InvalidModificationException
* @throws JSONException
*/
private JSONObject moveFile(File srcFile, File destFile) throws JSONException, InvalidModificationException {
// Renaming a file to an existing directory should fail
if (destFile.exists() && destFile.isDirectory()) {
throw new InvalidModificationException("Can't rename a file to a directory");
}
// Try to rename the file
if (!srcFile.renameTo(destFile)) {
// Trying to rename the file failed. Possibly because we moved across file system on the device.
// Now we have to do things the hard way
// 1) Copy all the old file
// 2) delete the src file
}
return getEntry(destFile);
}
/**
* Move a directory
*
* @param srcDir directory to be copied
* @param destinationDir destination to be copied to
* @return a DirectoryEntry object
* @throws JSONException
* @throws IOException
* @throws NoModificationAllowedException
* @throws InvalidModificationException
*/
private JSONObject moveDirectory(File srcDir, File destinationDir) throws JSONException, FileExistsException, NoModificationAllowedException, InvalidModificationException {
// Renaming a file to an existing directory should fail
if (destinationDir.exists() && destinationDir.isFile()) {
throw new InvalidModificationException("Can't rename a file to a directory");
}
// Check to make sure we are not copying the directory into itself
if (destinationDir.getAbsolutePath().startsWith(srcDir.getAbsolutePath())) {
throw new InvalidModificationException("Can't copy itself into itself");
}
// If the destination directory already exists and is empty then delete it. This is according to spec.
if (destinationDir.exists()) {
if (destinationDir.list().length > 0) {
throw new InvalidModificationException("directory is not empty");
}
}
// Try to rename the directory
if (!srcDir.renameTo(destinationDir)) {
// Trying to rename the directory failed. Possibly because we moved across file system on the device.
// Now we have to do things the hard way
// 1) Copy all the old files
// 2) delete the src directory
}
return getEntry(destinationDir);
}
/**
* Deletes a directory and all of its contents, if any. In the event of an error
* [e.g. trying to delete a directory that contains a file that cannot be removed],
* some of the contents of the directory may be deleted.
* It is an error to attempt to delete the root directory of a filesystem.
*
* @param filePath the directory to be removed
* @return a boolean representing success of failure
* @throws FileExistsException
*/
private boolean removeRecursively(String filePath) throws FileExistsException {
File fp = new File(filePath);
// You can't delete the root directory.
if (atRootDirectory(filePath)) {
return false;
}
return removeDirRecursively(fp);
}
/**
* Loops through a directory deleting all the files.
*
* @param directory to be removed
* @return a boolean representing success of failure
* @throws FileExistsException
*/
private boolean removeDirRecursively(File directory) throws FileExistsException {
if (directory.isDirectory()) {
for (File file : directory.listFiles()) {
removeDirRecursively(file);
}
}
if (!directory.delete()) {
throw new FileExistsException("could not delete: " + directory.getName());
} else {
return true;
}
}
/**
* Deletes a file or directory. It is an error to attempt to delete a directory that is not empty.
* It is an error to attempt to delete the root directory of a filesystem.
*
* @param filePath file or directory to be removed
* @return a boolean representing success of failure
* @throws NoModificationAllowedException
* @throws InvalidModificationException
*/
private boolean remove(String filePath) throws NoModificationAllowedException, InvalidModificationException {
File fp = new File(filePath);
// You can't delete the root directory.
if (atRootDirectory(filePath)) {
throw new NoModificationAllowedException("You can't delete the root directory");
}
// You can't delete a directory that is not empty
if (fp.isDirectory() && fp.list().length > 0) {
throw new InvalidModificationException("You can't delete a directory that is not empty.");
}
return fp.delete();
}
/**
* Creates or looks up a file.
*
* @param dirPath base directory
* @param fileName file/directory to lookup or create
* @param options specify whether to create or not
* @param directory if true look up directory, if false look up file
* @return a Entry object
* @throws FileExistsException
* @throws IOException
* @throws TypeMismatchException
* @throws EncodingException
* @throws JSONException
*/
private JSONObject getFile(String dirPath, String fileName, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
boolean create = false;
boolean exclusive = false;
if (options != null) {
create = options.optBoolean("create");
if (create) {
exclusive = options.optBoolean("exclusive");
}
}
// Check for a ":" character in the file to line up with BB and iOS
if (fileName.contains(":")) {
throw new EncodingException("This file has a : in it's name");
}
File fp = createFileObject(dirPath, fileName);
if (create) {
if (exclusive && fp.exists()) {
throw new FileExistsException("create/exclusive fails");
}
if (directory) {
fp.mkdir();
} else {
fp.createNewFile();
}
if (!fp.exists()) {
throw new FileExistsException("create fails");
}
}
else {
if (!fp.exists()) {
throw new FileNotFoundException("path does not exist");
}
if (directory) {
if (fp.isFile()) {
throw new TypeMismatchException("path doesn't exist or is file");
}
} else {
if (fp.isDirectory()) {
throw new TypeMismatchException("path doesn't exist or is directory");
}
}
}
// Return the directory
return getEntry(fp);
}
/**
* If the path starts with a '/' just return that file object. If not construct the file
* object from the path passed in and the file name.
*
* @param dirPath root directory
* @param fileName new file name
* @return
*/
private File createFileObject(String dirPath, String fileName) {
File fp = null;
if (fileName.startsWith("/")) {
fp = new File(fileName);
} else {
fp = new File(dirPath + File.separator + fileName);
}
return fp;
}
/**
* Look up the parent DirectoryEntry containing this Entry.
* If this Entry is the root of its filesystem, its parent is itself.
*
* @param filePath
* @return
* @throws JSONException
*/
private JSONObject getParent(String filePath) throws JSONException {
if (atRootDirectory(filePath)) {
return getEntry(filePath);
}
return getEntry(new File(filePath).getParent());
}
/**
* Checks to see if we are at the root directory. Useful since we are
* not allow to delete this directory.
*
* @param filePath to directory
* @return true if we are at the root, false otherwise.
*/
private boolean atRootDirectory(String filePath) {
if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + ctx.getPackageName() + "/cache") ||
filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath())) {
return true;
}
return false;
}
/**
* Look up metadata about this entry.
*
* @param filePath to entry
* @return a Metadata object
* @throws FileNotFoundException
* @throws JSONException
*/
private JSONObject getMetadata(String filePath) throws FileNotFoundException, JSONException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException("Failed to find file in getMetadata");
}
JSONObject metadata = new JSONObject();
metadata.put("modificationTime", file.lastModified());
return metadata;
}
/**
* Returns a File that represents the current state of the file that this FileEntry represents.
*
* @param filePath to entry
* @return returns a JSONObject represent a W3C File object
* @throws FileNotFoundException
* @throws JSONException
*/
private JSONObject getFileMetadata(String filePath) throws FileNotFoundException, JSONException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException("File: " + filePath + " does not exist.");
}
JSONObject metadata = new JSONObject();
metadata.put("size", file.length());
metadata.put("type", getMimeType(filePath));
metadata.put("name", file.getName());
metadata.put("fullPath", file.getAbsolutePath());
metadata.put("lastModifiedDate", file.lastModified());
return metadata;
}
/**
* Requests a filesystem in which to store application data.
*
* @param type of file system requested
* @return a JSONObject representing the file system
* @throws IOException
* @throws JSONException
*/
private JSONObject requestFileSystem(int type) throws IOException, JSONException {
JSONObject fs = new JSONObject();
if (type == TEMPORARY) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
fs.put("name", "temporary");
fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() +
"/Android/data/" + ctx.getPackageName() + "/cache/"));
// Create the cache dir if it doesn't exist.
File fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
"/Android/data/" + ctx.getPackageName() + "/cache/");
fp.mkdirs();
} else {
throw new IOException("SD Card not mounted");
}
}
else if (type == PERSISTENT) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
fs.put("name", "persistent");
fs.put("root", getEntry(Environment.getExternalStorageDirectory()));
} else {
throw new IOException("SD Card not mounted");
}
}
else if (type == RESOURCE) {
fs.put("name", "resource");
}
else if (type == APPLICATION) {
fs.put("name", "application");
}
else {
throw new IOException("No filesystem of type requested");
}
return fs;
}
/**
* Returns a JSON Object representing a directory on the device's file system
*
* @param path to the directory
* @return
* @throws JSONException
*/
private JSONObject getEntry(File file) throws JSONException {
JSONObject entry = new JSONObject();
entry.put("isFile", file.isFile());
entry.put("isDirectory", file.isDirectory());
entry.put("name", file.getName());
entry.put("fullPath", file.getAbsolutePath());
// I can't add the next thing it as it would be an infinite loop
//entry.put("filesystem", null);
return entry;
}
/**
* Returns a JSON Object representing a directory on the device's file system
*
* @param path to the directory
* @return
* @throws JSONException
*/
private JSONObject getEntry(String path) throws JSONException {
return getEntry(new File(path));
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
*
* @param action The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("readAsText")) {
return false;
}
else if (action.equals("readAsDataURL")) {
return false;
}
else if (action.equals("writeAsText")) {
return false;
}
return true;
public boolean isSynch(String action) {
if (action.equals("testSaveLocationExists")) {
return true;
}
else if (action.equals("getFreeDiskSpace")) {
return true;
}
else if (action.equals("testFileExists")) {
return true;
}
else if (action.equals("testDirectoryExists")) {
return true;
}
return false;
}
//--------------------------------------------------------------------------
@@ -220,16 +892,30 @@ public class FileUtils extends Plugin {
}
// Determine content type from file name
MimeTypeMap map = MimeTypeMap.getSingleton();
String contentType = map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename));
if (contentType == null && filename.startsWith("content")) {
contentType = "image/jpeg";
String contentType = null;
if (filename.startsWith("content:")) {
Uri fileUri = Uri.parse(filename);
contentType = this.ctx.getContentResolver().getType(fileUri);
}
else {
contentType = getMimeType(filename);
}
byte[] base64 = Base64.encodeBase64(bos.toByteArray());
String data = "data:" + contentType + ";base64," + new String(base64);
return data;
}
/**
* Looks up the mime type of a given file name.
*
* @param filename
* @return a mime type
*/
public static String getMimeType(String filename) {
MimeTypeMap map = MimeTypeMap.getSingleton();
return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename));
}
/**
* Write contents of file.
@@ -305,3 +991,4 @@ public class FileUtils extends Plugin {
}
}
}

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

@@ -16,15 +16,21 @@ import android.database.Cursor;
import android.database.sqlite.*;
/**
* This class implements the HTML5 database support for Android 1.X devices.
* It is not used for Android 2.X, since HTML5 database is built in to the browser.
* This class implements the HTML5 database support for Android 1.X devices. It
* is not used for Android 2.X, since HTML5 database is built in to the browser.
*/
public class Storage extends Plugin {
// Data Definition Language
private static final String ALTER = "alter";
private static final String CREATE = "create";
private static final String DROP = "drop";
private static final String TRUNCATE = "truncate";
SQLiteDatabase myDb = null; // Database object
String path = null; // Database path
String dbName = null; // Database name
SQLiteDatabase myDb = null; // Database object
String path = null; // Database path
String dbName = null; // Database name
/**
* Constructor.
*/
@@ -34,29 +40,37 @@ public class Storage 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.
* @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 = "";
String result = "";
try {
// TODO: Do we want to allow a user to do this, since they could get to other app databases?
// TODO: Do we want to allow a user to do this, since they could get
// to other app databases?
if (action.equals("setStorage")) {
this.setStorage(args.getString(0));
}
else if (action.equals("openDatabase")) {
this.openDatabase(args.getString(0), args.getString(1), args.getString(2), args.getLong(3));
}
else if (action.equals("executeSql")) {
JSONArray a = args.getJSONArray(1);
int len = a.length();
String[] s = new String[len];
for (int i=0; i<len; i++) {
s[i] = a.getString(i);
} else if (action.equals("openDatabase")) {
this.openDatabase(args.getString(0), args.getString(1),
args.getString(2), args.getLong(3));
} else if (action.equals("executeSql")) {
String[] s = null;
if (args.isNull(1)) {
s = new String[0];
} else {
JSONArray a = args.getJSONArray(1);
int len = a.length();
s = new String[len];
for (int i = 0; i < len; i++) {
s[i] = a.getString(i);
}
}
this.executeSql(args.getString(0), s, args.getString(2));
}
@@ -67,15 +81,17 @@ public class Storage extends Plugin {
}
/**
* Identifies if action to be executed returns a value and should be run synchronously.
* Identifies if action to be executed returns a value and should be run
* synchronously.
*
* @param action The action to execute
* @return T=returns value
* @param action
* The action to execute
* @return T=returns value
*/
public boolean isSynch(String action) {
return false;
return true;
}
/**
* Clean up and close database.
*/
@@ -87,33 +103,40 @@ public class Storage extends Plugin {
}
}
//--------------------------------------------------------------------------
// LOCAL METHODS
//--------------------------------------------------------------------------
// --------------------------------------------------------------------------
// LOCAL METHODS
// --------------------------------------------------------------------------
/**
* Set the application package for the database. Each application saves its
* database files in a directory with the application package as part of the file name.
* Set the application package for the database. Each application saves its
* database files in a directory with the application package as part of the
* file name.
*
* For example, application "com.phonegap.demo.Demo" would save its database
* files in "/data/data/com.phonegap.demo/databases/" directory.
*
* @param appPackage The application package.
* @param appPackage
* The application package.
*/
public void setStorage(String appPackage) {
this.path = "/data/data/" + appPackage + "/databases/";
}
/**
* Open database.
*
* @param db The name of the database
* @param version The version
* @param display_name The display name
* @param size The size in bytes
* @param db
* The name of the database
* @param version
* The version
* @param display_name
* The display name
* @param size
* The size in bytes
*/
public void openDatabase(String db, String version, String display_name, long size) {
public void openDatabase(String db, String version, String display_name,
long size) {
// If database is open, then close it
if (this.myDb != null) {
this.myDb.close();
@@ -121,27 +144,36 @@ public class Storage extends Plugin {
// If no database path, generate from application package
if (this.path == null) {
Package pack = this.ctx.getClass().getPackage();
String appPackage = pack.getName();
this.setStorage(appPackage);
Package pack = this.ctx.getClass().getPackage();
String appPackage = pack.getName();
this.setStorage(appPackage);
}
this.dbName = this.path + db + ".db";
this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null);
}
/**
* Execute SQL statement.
*
* @param query The SQL query
* @param params Parameters for the query
* @param tx_id Transaction id
* @param query
* The SQL query
* @param params
* Parameters for the query
* @param tx_id
* Transaction id
*/
public void executeSql(String query, String[] params, String tx_id) {
try {
Cursor myCursor = this.myDb.rawQuery(query, params);
this.processResults(myCursor, tx_id);
myCursor.close();
if (isDDL(query)) {
this.myDb.execSQL(query);
this.sendJavascript("droiddb.completeQuery('" + tx_id + "', '');");
}
else {
Cursor myCursor = this.myDb.rawQuery(query, params);
this.processResults(myCursor, tx_id);
myCursor.close();
}
}
catch (SQLiteException ex) {
ex.printStackTrace();
@@ -151,24 +183,40 @@ public class Storage extends Plugin {
this.sendJavascript("droiddb.fail('" + ex.getMessage() + "','" + tx_id + "');");
}
}
/**
* Checks to see the the query is a Data Definintion command
*
* @param query to be executed
* @return true if it is a DDL command, false otherwise
*/
private boolean isDDL(String query) {
String cmd = query.toLowerCase();
if (cmd.startsWith(DROP) || cmd.startsWith(CREATE) || cmd.startsWith(ALTER) || cmd.startsWith(TRUNCATE)) {
return true;
}
return false;
}
/**
* Process query results.
*
* @param cur Cursor into query results
* @param tx_id Transaction id
* @param cur
* Cursor into query results
* @param tx_id
* Transaction id
*/
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 row = new JSONObject();
@@ -179,19 +227,20 @@ public class Storage extends Plugin {
row.put(key, value);
}
fullresult.put(row);
} catch (JSONException e) {
e.printStackTrace();
}
} while (cur.moveToNext());
result = fullresult.toString();
}
// Let JavaScript know that there are no more rows
this.sendJavascript("droiddb.completeQuery('" + tx_id + "', "+result+");");
this.sendJavascript("droiddb.completeQuery('" + tx_id + "', " + result
+ ");");
}
}

View File

@@ -10,6 +10,8 @@ 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;
@@ -32,6 +34,12 @@ public class PluginResult {
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) {
this.status = status.ordinal();
this.message = message.toString();

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

@@ -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 !@icon.nil? && 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

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