Merge github.com:phonegap/phonegap-android

Conflicts:
	framework/assets/www/phonegap.js
	framework/src/com/phonegap/DroidGap.java
This commit is contained in:
Anis Kadri 2011-03-13 15:07:23 -07:00
commit 1e4dc1bf5d
67 changed files with 7440 additions and 1870 deletions

View File

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

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.9.4

View File

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

View File

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

140
example/main.js Normal file
View File

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

View File

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

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

@ -0,0 +1,89 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010-2011, IBM Corporation
*/
/**
* Constructor
*/
function App() {}
/**
* Clear the resource cache.
*/
App.prototype.clearCache = function() {
PhoneGap.exec(null, null, "App", "clearCache", []);
};
/**
* Load the url into the webview.
*
* @param url The URL to load
* @param props Properties that can be passed in to the activity:
* wait: int => wait msec before loading URL
* loadingDialog: "Title,Message" => display a native loading dialog
* hideLoadingDialogOnPage: boolean => hide loadingDialog when page loaded instead of when deviceready event occurs.
* loadInWebView: boolean => cause all links on web page to be loaded into existing web view, instead of being loaded into new browser.
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error
* errorUrl: URL => URL to load if there's an error loading specified URL with loadUrl(). Should be a local URL such as file:///android_asset/www/error.html");
* keepRunning: boolean => enable app to keep running in background
*
* Example:
* App app = new App();
* app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
*/
App.prototype.loadUrl = function(url, props) {
PhoneGap.exec(null, null, "App", "loadUrl", [url, props]);
};
/**
* Cancel loadUrl that is waiting to be loaded.
*/
App.prototype.cancelLoadUrl = function() {
PhoneGap.exec(null, null, "App", "cancelLoadUrl", []);
};
/**
* Clear web history in this web view.
* Instead of BACK button loading the previous web page, it will exit the app.
*/
App.prototype.clearHistory = function() {
PhoneGap.exec(null, null, "App", "clearHistory", []);
};
/**
* Add a class that implements a service.
*
* @param serviceType
* @param className
*/
App.prototype.addService = function(serviceType, className) {
PhoneGap.exec(null, null, "App", "addService", [serviceType, className]);
};
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* Note: The user should not have to call this method. Instead, when the user
* registers for the "backbutton" event, this is automatically done.
*
* @param override T=override, F=cancel override
*/
App.prototype.overrideBackbutton = function(override) {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]);
};
/**
* Exit and terminate the application.
*/
App.prototype.exitApp = function() {
return PhoneGap.exec(null, null, "App", "exitApp", []);
};
PhoneGap.addConstructor(function() {
navigator.app = window.app = new App();
});

View File

@ -59,19 +59,17 @@ 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;
}
this.successCallback = successCallback;
this.errorCallback = errorCallback;
this.options = options;
var quality = 80;
if (options.quality) {
@ -82,45 +80,14 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options)
destinationType = this.options.destinationType;
}
var sourceType = Camera.PictureSourceType.CAMERA;
if (typeof this.options.sourceType == "number") {
if (typeof this.options.sourceType === "number") {
sourceType = this.options.sourceType;
}
PhoneGap.exec(null, null, "Camera", "takePicture", [quality, destinationType, sourceType]);
};
/**
* Callback function from native code that is called when image has been captured.
*
* @param picture The base64 encoded string of the image
*/
Camera.prototype.success = function(picture) {
if (this.successCallback) {
try {
this.successCallback(picture);
}
catch (e) {
console.log("Camera error calling user's success callback: " + e);
}
}
};
/**
* Callback function from native code that is called when there is an error
* capturing an image, or the capture is cancelled.
*
* @param err The error message
*/
Camera.prototype.error = function(err) {
if (this.errorCallback) {
try {
this.errorCallback(err);
}
catch (e) {
console.log("Camera error calling user's error callback: " + e);
}
}
PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]);
};
PhoneGap.addConstructor(function() {
if (typeof navigator.camera == "undefined") navigator.camera = new Camera();
if (typeof navigator.camera === "undefined") {
navigator.camera = new Camera();
}
});

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
/**
* FileTransfer uploads a file to a remote server.
*/
function FileTransfer() {}
/**
* FileUploadResult
*/
function FileUploadResult() {
this.bytesSent = 0;
this.responseCode = null;
this.response = null;
}
/**
* FileTransferError
*/
function FileTransferError() {
this.code = null;
}
FileTransferError.FILE_NOT_FOUND_ERR = 1;
FileTransferError.INVALID_URL_ERR = 2;
FileTransferError.CONNECTION_ERR = 3;
/**
* Given an absolute file path, uploads a file on the device to a remote server
* using a multipart HTTP request.
* @param filePath {String} Full path of the file on the device
* @param server {String} URL of the server to receive the file
* @param successCallback (Function} Callback to be invoked when upload has completed
* @param errorCallback {Function} Callback to be invoked upon error
* @param options {FileUploadOptions} Optional parameters such as file name and mimetype
*/
FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, debug) {
// check for options
var fileKey = null;
var fileName = null;
var mimeType = null;
var params = null;
if (options) {
fileKey = options.fileKey;
fileName = options.fileName;
mimeType = options.mimeType;
if (options.params) {
params = options.params;
}
else {
params = {};
}
}
PhoneGap.exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, debug]);
};
/**
* Options to customize the HTTP request used to upload files.
* @param fileKey {String} Name of file request parameter.
* @param fileName {String} Filename to be used by the server. Defaults to image.jpg.
* @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg.
* @param params {Object} Object with key: value params to send to the server.
*/
function FileUploadOptions(fileKey, fileName, mimeType, params) {
this.fileKey = fileKey || null;
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
}

View File

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

7
framework/assets/js/header.txt Executable file
View File

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

View File

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

View File

@ -40,8 +40,8 @@ PhoneGap.Media.onStatus = function(id, msg, value) {
var media = PhoneGap.mediaObjects[id];
// If state update
if (msg == Media.MEDIA_STATE) {
if (value == Media.MEDIA_STOPPED) {
if (msg === Media.MEDIA_STATE) {
if (value === Media.MEDIA_STOPPED) {
if (media.successCallback) {
media.successCallback();
}
@ -50,10 +50,10 @@ PhoneGap.Media.onStatus = function(id, msg, value) {
media.statusCallback(value);
}
}
else if (msg == Media.MEDIA_DURATION) {
else if (msg === Media.MEDIA_DURATION) {
media._duration = value;
}
else if (msg == Media.MEDIA_ERROR) {
else if (msg === Media.MEDIA_ERROR) {
if (media.errorCallback) {
media.errorCallback(value);
}
@ -76,25 +76,25 @@ PhoneGap.Media.onStatus = function(id, msg, value) {
Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) {
// successCallback optional
if (successCallback && (typeof successCallback != "function")) {
if (successCallback && (typeof successCallback !== "function")) {
console.log("Media Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Media Error: errorCallback is not a function");
return;
}
// statusCallback optional
if (statusCallback && (typeof statusCallback != "function")) {
if (statusCallback && (typeof statusCallback !== "function")) {
console.log("Media Error: statusCallback is not a function");
return;
}
// statusCallback optional
if (positionCallback && (typeof positionCallback != "function")) {
if (positionCallback && (typeof positionCallback !== "function")) {
console.log("Media Error: positionCallback is not a function");
return;
}
@ -129,9 +129,9 @@ Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"];
* @constructor
*/
function MediaError() {
this.code = null,
this.code = null;
this.message = "";
};
}
MediaError.MEDIA_ERR_ABORTED = 1;
MediaError.MEDIA_ERR_NETWORK = 2;
@ -192,3 +192,10 @@ Media.prototype.stopRecord = function() {
PhoneGap.exec(null, null, "Media", "stopRecordingAudio", [this.id]);
};
/**
* Release the resources.
*/
Media.prototype.release = function() {
PhoneGap.exec(null, null, "Media", "release", [this.id]);
};

View File

@ -13,7 +13,7 @@
function NetworkStatus() {
//this.code = null;
//this.message = "";
};
}
NetworkStatus.NOT_REACHABLE = 0;
NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1;
@ -30,7 +30,7 @@ function Network() {
remoteHostStatus: int(0/1/2), internetConnectionStatus: int(0/1/2), localWiFiConnectionStatus: int (0/2) }
*/
this.lastReachability = null;
};
}
/**
* Called by the geolocation framework when the reachability status has changed.
@ -57,6 +57,8 @@ Network.prototype.isReachable = function(uri, callback, options) {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.network == "undefined") navigator.network = new Network();
if (typeof navigator.network === "undefined") {
navigator.network = new Network();
}
});

View File

@ -16,27 +16,29 @@ function Notification() {
* Open a native alert dialog, with a customizable title and button text.
*
* @param {String} message Message to print in the body of the alert
* @param {Function} completeCallback The callback that is called when user clicks on a button.
* @param {String} title Title of the alert dialog (default: Alert)
* @param {String} buttonLabel Label of the close button (default: OK)
*/
Notification.prototype.alert = function(message, title, buttonLabel) {
Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) {
var _title = (title || "Alert");
var _buttonLabel = (buttonLabel || "OK");
PhoneGap.exec(null, null, "Notification", "alert", [message,_title,_buttonLabel]);
PhoneGap.exec(completeCallback, null, "Notification", "alert", [message,_title,_buttonLabel]);
};
/**
* Open a native confirm dialog, with a customizable title and button text.
* The result that the user selects is returned to the result callback.
*
* @param {String} message Message to print in the body of the alert
* @param {Function} resultCallback The callback that is called when user clicks on a button.
* @param {String} title Title of the alert dialog (default: Confirm)
* @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel')
* @return {Number} The index of the button clicked
*/
Notification.prototype.confirm = function(message, title, buttonLabels) {
Notification.prototype.confirm = function(message, resultCallback, title, buttonLabels) {
var _title = (title || "Confirm");
var _buttonLabels = (buttonLabels || "OK,Cancel");
return PhoneGap.exec(null, null, "Notification", "confirm", [message,_title,_buttonLabels]);
PhoneGap.exec(resultCallback, null, "Notification", "confirm", [message,_title,_buttonLabels]);
};
/**
@ -109,6 +111,8 @@ Notification.prototype.beep = function(count) {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.notification == "undefined") navigator.notification = new Notification();
if (typeof navigator.notification === "undefined") {
navigator.notification = new Notification();
}
});

View File

@ -3,7 +3,7 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
@ -28,8 +28,9 @@
* document.addEventListener("resume", myResumeListener, false);
*/
if (typeof(DeviceInfo) != 'object')
if (typeof(DeviceInfo) !== 'object') {
DeviceInfo = {};
}
/**
* This represents the PhoneGap API itself, and provides a global namespace for accessing
@ -66,10 +67,10 @@ PhoneGap.Channel = function(type)
*/
PhoneGap.Channel.prototype.subscribe = function(f, c, g) {
// need a function to call
if (f == null) { return; }
if (f === null) { return; }
var func = f;
if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, f); }
if (typeof c === "object" && f instanceof Function) { func = PhoneGap.close(c, f); }
g = g || func.observer_guid || f.observer_guid || this.guid++;
func.observer_guid = g;
@ -88,9 +89,9 @@ PhoneGap.Channel.prototype.subscribeOnce = function(f, c) {
var m = function() {
f.apply(c || null, arguments);
_this.unsubscribe(g);
}
};
if (this.fired) {
if (typeof c == "object" && f instanceof Function) { f = PhoneGap.close(c, f); }
if (typeof c === "object" && f instanceof Function) { f = PhoneGap.close(c, f); }
f.apply(this, this.fireArgs);
} else {
g = this.subscribe(m);
@ -113,13 +114,16 @@ 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];
var item, handler, rv;
for (item in this.handlers) {
if (this.handlers.hasOwnProperty(item)) {
handler = this.handlers[item];
if (handler instanceof Function) {
var rv = (handler.apply(this, arguments)==false);
rv = (handler.apply(this, arguments) === false);
fail = fail || rv;
}
}
}
this.fired = true;
this.fireArgs = arguments;
return !fail;
@ -134,18 +138,29 @@ PhoneGap.Channel.prototype.fire = function(e) {
PhoneGap.Channel.join = function(h, c) {
var i = c.length;
var f = function() {
if (!(--i)) h();
if (!(--i)) {
h();
}
for (var j=0; j<i; j++) {
(!c[j].fired?c[j].subscribeOnce(f):i--);
};
var len = i;
var j;
for (j=0; j<len; j++) {
if (!c[j].fired) {
c[j].subscribeOnce(f);
}
else {
i--;
}
}
if (!i) {
h();
}
if (!i) h();
};
/**
* Boolean flag indicating if the PhoneGap API is available and initialized.
*/ // TODO: Remove this, it is unused here ... -jm
PhoneGap.available = DeviceInfo.uuid != undefined;
PhoneGap.available = DeviceInfo.uuid !== undefined;
/**
* Add an initialization function to a queue that ensures it will run and initialize
@ -157,27 +172,33 @@ PhoneGap.addConstructor = function(func) {
try {
func();
} catch(e) {
if (typeof(debug['log']) == 'function') {
debug.log("Failed to run constructor: " + debug.processMessage(e));
} else {
alert("Failed to run constructor: " + e.message);
}
console.log("Failed to run constructor: " + e);
}
});
};
/**
* Adds a plugin object to window.plugins
* Plugins object
*/
PhoneGap.addPlugin = function(name, obj) {
if (!window.plugins) {
window.plugins = {};
}
/**
* Adds a plugin object to window.plugins.
* The plugin is accessed using window.plugins.<name>
*
* @param name The plugin name
* @param obj The plugin object
*/
PhoneGap.addPlugin = function(name, obj) {
if (!window.plugins[name]) {
window.plugins[name] = obj;
}
else {
console.log("Error: Plugin "+name+" already exists.");
}
};
/**
* onDOMContentLoaded channel is fired when the DOM content
@ -232,13 +253,59 @@ 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.
*/
PhoneGap.Channel.join(function() {
// Start listening for XHR callbacks
setTimeout(function() {
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
}
else {
var polling = prompt("usePolling", "gap_callbackServer:");
if (polling == "true") {
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.JSCallback();
}
}
}, 1);
// Run PhoneGap constructors
PhoneGap.onPhoneGapInit.fire();
@ -246,13 +313,10 @@ PhoneGap.Channel.join(function() {
// Fire event to notify that all objects are created
PhoneGap.onPhoneGapReady.fire();
}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]);
/**
* Fire onDeviceReady event once all constructors have run and PhoneGap info has been
* received from native side.
*/
// 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();
@ -260,7 +324,9 @@ PhoneGap.Channel.join(function() {
// Fire the onresume event, since first one happens before JavaScript is loaded
PhoneGap.onResume.fire();
}, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]);
}, PhoneGap.deviceReadyChannelsArray);
}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]);
// Listen for DOMContentLoaded and notify our channel subscribers
document.addEventListener('DOMContentLoaded', function() {
@ -272,15 +338,48 @@ 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') {
PhoneGap.onPause.subscribe(handler);
} else {
PhoneGap.m_document_addEventListener.call(document, evt, handler);
if (PhoneGap.onDeviceReady.fired) {
PhoneGap.onResume.fire();
}
} else if (e === 'pause') {
PhoneGap.onPause.subscribe(handler);
}
else {
// If subscribing to Android backbutton
if (e === 'backbutton') {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]);
}
PhoneGap.m_document_addEventListener.call(document, evt, handler, capture);
}
};
// Intercept calls to document.removeEventListener and watch for events that
// are generated by PhoneGap native code
PhoneGap.m_document_removeEventListener = document.removeEventListener;
document.removeEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
// If unsubscribing to Android backbutton
if (e === 'backbutton') {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]);
}
PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture);
};
/**
* Method to fire event from native code
*/
PhoneGap.fireEvent = function(type) {
var e = document.createEvent('Events');
e.initEvent(type);
document.dispatchEvent(e);
};
/**
@ -291,48 +390,53 @@ document.addEventListener = function(evt, handler, capture) {
* @return
*/
PhoneGap.stringify = function(args) {
if (typeof JSON == "undefined") {
if (typeof JSON === "undefined") {
var s = "[";
for (var i=0; i<args.length; i++) {
var i, type, start, name, nameType, a;
for (i = 0; i < args.length; i++) {
if (args[i] != null) {
if (i > 0) {
s = s + ",";
}
var type = typeof args[i];
if ((type == "number") || (type == "boolean")) {
type = typeof args[i];
if ((type === "number") || (type === "boolean")) {
s = s + args[i];
}
else if (args[i] instanceof Array) {
} else if (args[i] instanceof Array) {
s = s + "[" + args[i] + "]";
}
else if (args[i] instanceof Object) {
var start = true;
} else if (args[i] instanceof Object) {
start = true;
s = s + '{';
for (var name in args[i]) {
for (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")) {
nameType = typeof args[i][name];
if ((nameType === "number") || (nameType === "boolean")) {
s = s + args[i][name];
}
else {
} 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, '\\\\');
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);
}
};
@ -344,13 +448,14 @@ PhoneGap.stringify = function(args) {
* @return
*/
PhoneGap.clone = function(obj) {
var i, retVal;
if(!obj) {
return obj;
}
if(obj instanceof Array){
var retVal = new Array();
for(var i = 0; i < obj.length; ++i){
retVal = [];
for(i = 0; i < obj.length; ++i){
retVal.push(PhoneGap.clone(obj[i]));
}
return retVal;
@ -364,9 +469,13 @@ PhoneGap.clone = function(obj) {
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]);
}
}
@ -375,6 +484,19 @@ PhoneGap.clone = function(obj) {
PhoneGap.callbackId = 0;
PhoneGap.callbacks = {};
PhoneGap.callbackStatus = {
NO_RESULT: 0,
OK: 1,
CLASS_NOT_FOUND_EXCEPTION: 2,
ILLEGAL_ACCESS_EXCEPTION: 3,
INSTANTIATION_EXCEPTION: 4,
MALFORMED_URL_EXCEPTION: 5,
IO_EXCEPTION: 6,
INVALID_ACTION: 7,
JSON_EXCEPTION: 8,
ERROR: 9
};
/**
* Execute a PhoneGap command. It is up to the native side whether this action is synch or async.
@ -397,38 +519,64 @@ 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 == 0) {
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);
}
// Clear callback if not expecting any more results
if (!v.keepCallback) {
delete PhoneGap.callbacks[callbackId];
}
}
return v.message;
}
// If no result
else if (v.status === PhoneGap.callbackStatus.NO_RESULT) {
// Clear callback if not expecting any more results
if (!v.keepCallback) {
delete PhoneGap.callbacks[callbackId];
}
}
// If error, then display error
else {
console.log("Error: Status="+r.status+" Message="+v.message);
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 (e1) {
console.log("Error in error callback: "+callbackId+" = "+e1);
}
// Clear callback if not expecting any more results
if (!v.keepCallback) {
delete PhoneGap.callbacks[callbackId];
}
}
return null;
}
}
} catch (e) {
console.log("Error: "+e);
} catch (e2) {
console.log("Error: "+e2);
}
};
@ -440,6 +588,9 @@ PhoneGap.exec = function(success, fail, service, action, args) {
*/
PhoneGap.callbackSuccess = function(callbackId, args) {
if (PhoneGap.callbacks[callbackId]) {
// If result is to be sent to callback
if (args.status === PhoneGap.callbackStatus.OK) {
try {
if (PhoneGap.callbacks[callbackId].success) {
PhoneGap.callbacks[callbackId].success(args.message);
@ -448,8 +599,13 @@ PhoneGap.callbackSuccess = function(callbackId, args) {
catch (e) {
console.log("Error in success callback: "+callbackId+" = "+e);
}
}
// Clear callback if not expecting any more results
if (!args.keepCallback) {
delete PhoneGap.callbacks[callbackId];
}
}
};
/**
@ -468,8 +624,12 @@ PhoneGap.callbackError = function(callbackId, args) {
catch (e) {
console.log("Error in error callback: "+callbackId+" = "+e);
}
// Clear callback if not expecting any more results
if (!args.keepCallback) {
delete PhoneGap.callbacks[callbackId];
}
}
};
@ -482,43 +642,51 @@ 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;
for (name in dict) {
if (dict.hasOwnProperty(name) && (typeof (name) === 'string')) {
query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name]));
}
if (query_args.length > 0)
}
if (query_args.length > 0) {
url += "?" + query_args.join("&");
}
}
document.location = url;
};
PhoneGap.JSCallbackPort = null;
PhoneGap.JSCallbackToken = null;
/**
* This is only for Android.
*
@ -527,14 +695,21 @@ PhoneGap.run_command = function() {
* Java to JavaScript.
*/
PhoneGap.JSCallback = function() {
// If polling flag was changed, start using polling from now on
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
return;
}
var xmlhttp = new XMLHttpRequest();
// Callback function when XMLHttpRequest is ready
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState == 4){
if(xmlhttp.readyState === 4){
// If callback has JavaScript statement to execute
if (xmlhttp.status == 200) {
if (xmlhttp.status === 200) {
var msg = xmlhttp.responseText;
setTimeout(function() {
@ -542,6 +717,8 @@ PhoneGap.JSCallback = function() {
var t = eval(msg);
}
catch (e) {
// If we're getting an error here, seeing the message will help in debugging
console.log("JSCallback: Message from Server: " + msg);
console.log("JSCallback Error: "+e);
}
}, 1);
@ -549,21 +726,88 @@ PhoneGap.JSCallback = function() {
}
// If callback ping (used to keep XHR request from timing out)
else if (xmlhttp.status == 404) {
else if (xmlhttp.status === 404) {
setTimeout(PhoneGap.JSCallback, 10);
}
// If security error
else if (xmlhttp.status === 403) {
console.log("JSCallback Error: Invalid token. Stopping callbacks.");
}
// If server is stopping
else if (xmlhttp.status === 503) {
console.log("JSCallback Error: Service unavailable. Stopping callbacks.");
}
// If request wasn't GET
else if (xmlhttp.status === 400) {
console.log("JSCallback Error: Bad request. Stopping callbacks.");
}
// If error, restart callback server
else {
console.log("JSCallback Error: Request failed.");
CallbackServer.restartServer();
prompt("restartServer", "gap_callbackServer:");
PhoneGap.JSCallbackPort = null;
PhoneGap.JSCallbackToken = null;
setTimeout(PhoneGap.JSCallback, 100);
}
}
};
if (PhoneGap.JSCallbackPort === null) {
PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:");
}
if (PhoneGap.JSCallbackToken === null) {
PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:");
}
xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true);
xmlhttp.send();
};
/**
* The polling period to use with JSCallbackPolling.
* This can be changed by the application. The default is 50ms.
*/
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.
*
* Internal function that uses polling to call into PhoneGap Java code and retrieve
* any JavaScript code that needs to be run. This is used for callbacks from
* Java to JavaScript.
*/
PhoneGap.JSCallbackPolling = function() {
// If polling flag was changed, stop using polling from now on
if (!PhoneGap.UsePolling) {
PhoneGap.JSCallback();
return;
}
xmlhttp.open("GET", "http://127.0.0.1:"+CallbackServer.getPort()+"/" , true);
xmlhttp.send();
var msg = prompt("", "gap_poll:");
if (msg) {
setTimeout(function() {
try {
var t = eval(""+msg);
}
catch (e) {
console.log("JSCallbackPolling: Message from Server: " + msg);
console.log("JSCallbackPolling Error: "+e);
}
}, 1);
setTimeout(PhoneGap.JSCallbackPolling, 1);
}
else {
setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod);
}
};
/**
@ -581,9 +825,10 @@ PhoneGap.createUUID = function() {
PhoneGap.UUIDcreatePart = function(length) {
var uuidpart = "";
for (var i=0; i<length; i++) {
var uuidchar = parseInt((Math.random() * 256)).toString(16);
if (uuidchar.length == 1) {
var i, uuidchar;
for (i=0; i<length; i++) {
uuidchar = parseInt((Math.random() * 256),0).toString(16);
if (uuidchar.length === 1) {
uuidchar = "0" + uuidchar;
}
uuidpart += uuidchar;
@ -595,11 +840,27 @@ PhoneGap.close = function(context, func, params) {
if (typeof params === 'undefined') {
return function() {
return func.apply(context, arguments);
}
};
} else {
return function() {
return func.apply(context, params);
}
};
}
};
/**
* Load a JavaScript file after page has loaded.
*
* @param {String} jsfile The url of the JavaScript file to load.
* @param {Function} successCallback The callback to call when the file has been loaded.
*/
PhoneGap.includeJavascript = function(jsfile, successCallback) {
var id = document.getElementsByTagName("head")[0];
var el = document.createElement('script');
el.type = 'text/javascript';
if (typeof successCallback === 'function') {
el.onload = successCallback;
}
el.src = jsfile;
id.appendChild(el);
};

View File

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

View File

@ -20,30 +20,13 @@ var DroidDB = function() {
this.queryQueue = {};
};
/**
* Callback from native code when result from a query is available.
* PRIVATE METHOD
*
* @param rawdata JSON string of the row data
* @param id Query id
*/
DroidDB.prototype.addResult = function(rawdata, id) {
try {
eval("var data = " + rawdata + ";");
var query = this.queryQueue[id];
query.resultSet.push(data);
} catch (e) {
console.log("DroidDB.addResult(): Error="+e);
}
};
/**
* Callback from native code when query is complete.
* PRIVATE METHOD
*
* @param id Query id
*/
DroidDB.prototype.completeQuery = function(id) {
DroidDB.prototype.completeQuery = function(id, data) {
var query = this.queryQueue[id];
if (query) {
try {
@ -59,10 +42,10 @@ DroidDB.prototype.completeQuery = function(id) {
// Save query results
var r = new DroidDB_Result();
r.rows.resultSet = query.resultSet;
r.rows.length = query.resultSet.length;
r.rows.resultSet = data;
r.rows.length = data.length;
try {
if (typeof query.successCallback == 'function') {
if (typeof query.successCallback === 'function') {
query.successCallback(query.tx, r);
}
} catch (ex) {
@ -100,7 +83,7 @@ DroidDB.prototype.fail = function(reason, id) {
tx.queryList = {};
try {
if (typeof query.errorCallback == 'function') {
if (typeof query.errorCallback === 'function') {
query.errorCallback(query.tx, reason);
}
} catch (ex) {
@ -116,35 +99,6 @@ DroidDB.prototype.fail = function(reason, id) {
}
};
var DatabaseShell = function() {
};
/**
* Start a transaction.
* Does not support rollback in event of failure.
*
* @param process {Function} The transaction function
* @param successCallback {Function}
* @param errorCallback {Function}
*/
DatabaseShell.prototype.transaction = function(process, successCallback, errorCallback) {
var tx = new DroidDB_Tx();
tx.successCallback = successCallback;
tx.errorCallback = errorCallback;
try {
process(tx);
} catch (e) {
console.log("Transaction error: "+e);
if (tx.errorCallback) {
try {
tx.errorCallback(e);
} catch (ex) {
console.log("Transaction error calling user error callback: "+e);
}
}
}
};
/**
* Transaction object
* PRIVATE METHOD
@ -162,6 +116,37 @@ var DroidDB_Tx = function() {
this.queryList = {};
};
var DatabaseShell = function() {
};
/**
* Start a transaction.
* Does not support rollback in event of failure.
*
* @param process {Function} The transaction function
* @param successCallback {Function}
* @param errorCallback {Function}
*/
DatabaseShell.prototype.transaction = function(process, errorCallback, successCallback) {
var tx = new DroidDB_Tx();
tx.successCallback = successCallback;
tx.errorCallback = errorCallback;
try {
process(tx);
} catch (e) {
console.log("Transaction error: "+e);
if (tx.errorCallback) {
try {
tx.errorCallback(e);
} catch (ex) {
console.log("Transaction error calling user error callback: "+e);
}
}
}
};
/**
* Mark query in transaction as complete.
* If all queries are complete, call the user's transaction success callback.
@ -174,10 +159,13 @@ DroidDB_Tx.prototype.queryComplete = function(id) {
// If no more outstanding queries, then fire transaction success
if (this.successCallback) {
var count = 0;
for (var i in this.queryList) {
var i;
for (i in this.queryList) {
if (this.queryList.hasOwnProperty(i)) {
count++;
}
if (count == 0) {
}
if (count === 0) {
try {
this.successCallback();
} catch(e) {
@ -237,7 +225,7 @@ var DroidDB_Query = function(tx) {
this.successCallback = null;
this.errorCallback = null;
}
};
/**
* Execute SQL statement
@ -250,7 +238,7 @@ var DroidDB_Query = function(tx) {
DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) {
// Init params array
if (typeof params == 'undefined') {
if (typeof params === 'undefined') {
params = [];
}
@ -308,9 +296,100 @@ DroidDB_openDatabase = function(name, version, display_name, size) {
return db;
};
/**
* For browsers with no localStorage we emulate it with SQLite. Follows the w3c api.
* TODO: Do similar for sessionStorage.
*/
var CupcakeLocalStorage = function() {
try {
this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440);
var storage = {};
this.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++) {
storage[result.rows.item(i)['id']] = result.rows.item(i)['body'];
}
setLength(result.rows.length);
PhoneGap.initializationComplete("cupcakeStorage");
});
},
function (err) {
alert(err.message);
}
);
this.setItem = function(key, val) {
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") {
if (typeof window.openDatabase === "undefined") {
navigator.openDatabase = window.openDatabase = DroidDB_openDatabase;
window.droiddb = new DroidDB();
}
if (typeof window.localStorage === "undefined") {
navigator.localStorage = window.localStorage = new CupcakeLocalStorage();
PhoneGap.waitForInitialization("cupcakeStorage");
}
});

View File

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

File diff suppressed because it is too large Load Diff

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,10 +17,10 @@ import java.util.LinkedList;
/**
* This class provides a way for Java to run JavaScript in the web page that has loaded PhoneGap.
* The CallbackServer class implements an XHR server and a list of JavaScript statements
* that are to be executed on the web page.
* The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
* statements that are to be executed on the web page.
*
* The process flow is:
* The process flow for XHR is:
* 1. JavaScript makes an async XHR call.
* 2. The server holds the connection open until data is available.
* 3. The server writes the data to the client and closes the connection.
@ -30,6 +30,14 @@ import java.util.LinkedList;
*
* The CallbackServer class requires the following permission in Android manifest file
* <uses-permission android:name="android.permission.INTERNET" />
*
* If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
* This can be determined by the client by calling CallbackServer.usePolling().
*
* The process flow for polling is:
* 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
* 2. If statement available, then client processes it.
* 3. The client repeats #1 in loop.
*/
public class CallbackServer implements Runnable {
@ -58,6 +66,16 @@ public class CallbackServer implements Runnable {
*/
private boolean empty;
/**
* Indicates that polling should be used instead of XHR.
*/
private boolean usePolling = true;
/**
* Security token to prevent other apps from accessing this callback server via XHR
*/
private String token;
/**
* Constructor.
*/
@ -67,8 +85,42 @@ public class CallbackServer implements Runnable {
this.empty = true;
this.port = 0;
this.javascript = new LinkedList<String>();
}
/**
* Init callback server and start XHR if running local app.
*
* If PhoneGap app is loaded from file://, then we can use XHR
* otherwise we have to use polling due to cross-domain security restrictions.
*
* @param url The URL of the PhoneGap app being loaded
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
this.usePolling = true;
this.stopServer();
}
else if (android.net.Proxy.getDefaultHost() != null) {
this.usePolling = true;
this.stopServer();
}
else {
this.usePolling = false;
this.startServer();
}
}
/**
* Return if polling is being used instead of XHR.
*
* @return
*/
public boolean usePolling() {
return this.usePolling;
}
/**
* Get the port that this server is running on.
@ -79,6 +131,15 @@ public class CallbackServer implements Runnable {
return this.port;
}
/**
* Get the security token that this server requires when calling getJavascript().
*
* @return
*/
public String getToken() {
return this.token;
}
/**
* Start the server on a new thread.
*/
@ -115,7 +176,9 @@ public class CallbackServer implements Runnable {
String request;
ServerSocket waitSocket = new ServerSocket(0);
this.port = waitSocket.getLocalPort();
//System.out.println(" -- using port " +this.port);
//System.out.println("CallbackServer -- using port " +this.port);
this.token = java.util.UUID.randomUUID().toString();
//System.out.println("CallbackServer -- using token "+this.token);
while (this.active) {
//System.out.println("CallbackServer: Waiting for data on socket");
@ -123,18 +186,25 @@ public class CallbackServer implements Runnable {
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()),40);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
request = xhrReader.readLine();
//System.out.println("Request="+request);
if(request.contains("GET"))
{
//System.out.println(" -- Processing GET request");
String response = "";
//System.out.println("CallbackServerRequest="+request);
if (this.active && (request != null)) {
if (request.contains("GET")) {
// Wait until there is some data to send, or send empty data every 30 sec
// 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");
// 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(30000); // prevent timeout from happening
//System.out.println(">>> break <<<");
this.wait(10000); // prevent timeout from happening
//System.out.println("CallbackServer>>> break <<<");
break;
}
catch (Exception e) { }
@ -146,17 +216,32 @@ public class CallbackServer implements Runnable {
// If no data, then send 404 back to client before it times out
if (this.empty) {
//System.out.println(" -- sending data 0");
output.writeBytes("HTTP/1.1 404 NO DATA\r\n\r\n");
//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(" -- sending item");
output.writeBytes("HTTP/1.1 200 OK\r\n\r\n"+this.getJavascript());
//System.out.println("CallbackServer -- sending item");
response = "HTTP/1.1 200 OK\r\n\r\n"+this.getJavascript();
}
}
else {
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
}
}
else {
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
}
//System.out.println("CallbackServer: response="+response);
//System.out.println("CallbackServer: closing output");
output.writeBytes(response);
output.flush();
}
output.close();
xhrReader.close();
}
} catch (IOException e) {
e.printStackTrace();
@ -171,6 +256,7 @@ public class CallbackServer implements Runnable {
*/
public void stopServer() {
//System.out.println("CallbackServer.stopServer()");
if (this.active) {
this.active = false;
// Break out of server wait
@ -178,6 +264,7 @@ public class CallbackServer implements Runnable {
this.notify();
}
}
}
/**
* Destroy

View File

@ -45,6 +45,7 @@ public class CameraLauncher extends Plugin {
private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
private Uri imageUri; // Uri of captured image
public String callbackId;
/**
* Constructor.
@ -63,6 +64,7 @@ public class CameraLauncher extends Plugin {
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
String result = "";
this.callbackId = callbackId;
try {
if (action.equals("takePicture")) {
@ -78,8 +80,11 @@ public class CameraLauncher extends Plugin {
this.takePicture(args.getInt(0), destType);
}
else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
this.getImage(srcType, destType);
this.getImage(args.getInt(0), srcType, destType);
}
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
return new PluginResult(status, result);
} catch (JSONException e) {
@ -95,7 +100,7 @@ public class CameraLauncher extends Plugin {
/**
* Take a picture with the camera.
* When an image is captured or the camera view is cancelled, the result is returned
* in DroidGap.onActivityResult, which forwards the result to this.onActivityResult.
* in PhonegapActivity.onActivityResult, which forwards the result to this.onActivityResult.
*
* The image can either be returned as a base64 string or a URI that points to the file.
* To display base64 string in an img tag, set the source to:
@ -124,10 +129,14 @@ public class CameraLauncher extends Plugin {
/**
* Get image from photo library.
*
* @param returnType
* @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
* @param srcType The album to get image from.
* @param returnType Set the type of image to return.
*/
// TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
public void getImage(int srcType, int returnType) {
public void getImage(int quality, int srcType, int returnType) {
this.mQuality = quality;
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
@ -190,8 +199,11 @@ public class CameraLauncher extends Plugin {
os.close();
// Send Uri back to JavaScript for viewing image
this.sendJavascript("navigator.camera.success('" + uri.toString() + "');");
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.");
@ -219,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.");
@ -227,7 +242,7 @@ public class CameraLauncher extends Plugin {
// If sending filename back
else if (destType == FILE_URI) {
this.sendJavascript("navigator.camera.success('" + uri + "');");
this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId);
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
@ -251,12 +266,16 @@ public class CameraLauncher extends Plugin {
byte[] code = jpeg_data.toByteArray();
byte[] output = Base64.encodeBase64(code);
String js_out = new String(output);
this.sendJavascript("navigator.camera.success('" + js_out + "');");
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;
}
/**
@ -265,6 +284,6 @@ public class CameraLauncher extends Plugin {
* @param err
*/
public void failPicture(String err) {
this.sendJavascript("navigator.camera.error('" + err + "');");
this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,52 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi
* Copyright (c) 2010, IBM Corporation
*/
package com.phonegap;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Encapsulates the result and/or status of uploading a file to a remote server.
*/
public class FileUploadResult {
private long bytesSent = 0; // bytes sent
private int responseCode = -1; // HTTP response code
private String response = null; // HTTP response
public long getBytesSent() {
return bytesSent;
}
public void setBytesSent(long bytes) {
this.bytesSent = bytes;
}
public int getResponseCode() {
return responseCode;
}
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
public JSONObject toJSONObject() throws JSONException {
return new JSONObject(
"{bytesSent:" + bytesSent +
",responseCode:" + responseCode +
",response:" + JSONObject.quote(response) + "}");
}
}

View File

@ -8,26 +8,53 @@
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;
import org.json.JSONArray;
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 = 8;
public static int SECURITY_ERR = 18;
public static int ABORT_ERR = 20;
public static int NOT_FOUND_ERR = 1;
public static int SECURITY_ERR = 2;
public static int ABORT_ERR = 3;
public static int NOT_READABLE_ERR = 24;
public static int ENCODING_ERR = 26;
public static int NOT_READABLE_ERR = 4;
public static int ENCODING_ERR = 5;
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;
@ -36,7 +63,6 @@ public class FileUtils extends Plugin {
* Constructor.
*/
public FileUtils() {
System.out.println("FileUtils()");
}
/**
@ -52,6 +78,7 @@ public class FileUtils extends Plugin {
String result = "";
//System.out.println("FileUtils.execute("+action+")");
try {
try {
if (action.equals("testSaveLocationExists")) {
boolean b = DirectoryManager.testSaveLocationExists();
@ -68,26 +95,11 @@ public class FileUtils extends Plugin {
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("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);
@ -97,9 +109,6 @@ public class FileUtils extends Plugin {
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);
@ -108,21 +117,718 @@ public class FileUtils extends Plugin {
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("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("resolveLocalFileSystemURI")) {
JSONObject obj = resolveLocalFileSystemURI(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castEntry");
}
else if (action.equals("getMetadata")) {
JSONObject obj = getMetadata(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castDate");
}
else if (action.equals("getFileMetadata")) {
JSONObject obj = getFileMetadata(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castDate");
}
else if (action.equals("getParent")) {
JSONObject obj = getParent(args.getString(0));
return new PluginResult(status, obj, "window.localFileSystem._castEntry");
}
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);
}
} 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.
*
@ -139,6 +845,39 @@ public class FileUtils extends Plugin {
else if (action.equals("writeAsText")) {
return false;
}
else if (action.equals("requestFileSystem")) {
return false;
}
else if (action.equals("getMetadata")) {
return false;
}
else if (action.equals("toURI")) {
return false;
}
else if (action.equals("getParent")) {
return false;
}
else if (action.equals("getFile")) {
return false;
}
else if (action.equals("getDirectory")) {
return false;
}
else if (action.equals("remove")) {
return false;
}
else if (action.equals("removeRecursively")) {
return false;
}
else if (action.equals("readEntries")) {
return false;
}
else if (action.equals("getFileMetadata")) {
return false;
}
else if (action.equals("resolveLocalFileSystemURI")) {
return false;
}
return true;
}
@ -156,15 +895,14 @@ public class FileUtils extends Plugin {
* @throws FileNotFoundException, IOException
*/
public String readAsText(String filename, String encoding) throws FileNotFoundException, IOException {
System.out.println("FileUtils.readAsText("+filename+", "+encoding+")");
StringBuilder data = new StringBuilder();
FileInputStream fis = new FileInputStream(filename);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis, encoding), 1024);
String line;
while ((line = reader.readLine()) != null) {
data.append(line);
byte[] bytes = new byte[1000];
BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numRead = 0;
while ((numRead = bis.read(bytes, 0, 1000)) >= 0) {
bos.write(bytes, 0, numRead);
}
return data.toString();
return new String(bos.toByteArray(), encoding);
}
/**
@ -176,7 +914,7 @@ public class FileUtils extends Plugin {
*/
public String readAsDataURL(String filename) throws FileNotFoundException, IOException {
byte[] bytes = new byte[1000];
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename), 1024);
BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int numRead = 0;
while ((numRead = bis.read(bytes, 0, 1000)) >= 0) {
@ -184,14 +922,31 @@ public class FileUtils extends Plugin {
}
// Determine content type from file name
// TODO
String contentType = "";
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
*/
private String getMimeType(String filename) {
MimeTypeMap map = MimeTypeMap.getSingleton();
return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename));
}
/**
* Write contents of file.
*
@ -212,5 +967,58 @@ public class FileUtils extends Plugin {
out.close();
}
/**
* Write contents of file.
*
* @param filename The name of the file.
* @param data The contents of the file.
* @param offset The position to begin writing the file.
* @throws FileNotFoundException, IOException
*/
public long write(String filename, String data, long offset) throws FileNotFoundException, IOException {
RandomAccessFile file = new RandomAccessFile(filename, "rw");
file.seek(offset);
file.writeBytes(data);
file.close();
return data.length();
}
/**
* Truncate the file to size
*
* @param filename
* @param size
* @throws FileNotFoundException, IOException
*/
private long truncateFile(String filename, long size) throws FileNotFoundException, IOException {
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
if (raf.length() >= size) {
FileChannel channel = raf.getChannel();
channel.truncate(size);
return size;
}
return raf.length();
}
/**
* 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

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

View File

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

1
framework/src/com/phonegap/HttpHandler.java Normal file → Executable file
View File

@ -62,7 +62,6 @@ public class HttpHandler {
if (numread <= 0)
break;
out.write(buff, 0, numread);
System.out.println("numread" + numread);
i++;
} while (true);
out.flush();

View File

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

View File

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

View File

@ -10,6 +10,7 @@ package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import com.phonegap.api.Plugin;
import com.phonegap.api.PhonegapActivity;
import com.phonegap.api.PluginResult;
import android.app.AlertDialog;
import android.app.ProgressDialog;
@ -55,11 +56,16 @@ public class Notification extends Plugin {
this.vibrate(args.getLong(0));
}
else if (action.equals("alert")) {
this.alert(args.getString(0),args.getString(1),args.getString(2));
this.alert(args.getString(0),args.getString(1),args.getString(2), callbackId);
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
else if (action.equals("confirm")) {
int i = this.confirm(args.getString(0),args.getString(1),args.getString(2));
return new PluginResult(status, i);
this.confirm(args.getString(0),args.getString(1),args.getString(2), callbackId);
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
else if (action.equals("activityStart")) {
this.activityStart(args.getString(0),args.getString(1));
@ -163,9 +169,17 @@ public class Notification extends Plugin {
* @param message The message the alert should display
* @param title The title of the alert
* @param buttonLabel The label of the button
* @param callbackId The callback id
*/
public synchronized void alert(String message,String title,String buttonLabel){
AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx);
public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) {
final PhonegapActivity ctx = this.ctx;
final Notification notification = this;
Runnable runnable = new Runnable() {
public void run() {
AlertDialog.Builder dlg = new AlertDialog.Builder(ctx);
dlg.setMessage(message);
dlg.setTitle(title);
dlg.setCancelable(false);
@ -173,27 +187,32 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
notification.success(new PluginResult(PluginResult.Status.OK, 0), callbackId);
}
});
dlg.create();
dlg.show();
};
};
this.ctx.runOnUiThread(runnable);
}
/**
* Builds and shows a native Android confirm dialog with given title, message, buttons.
* This dialog only shows up to 3 buttons. Any labels after that will be ignored.
* The index of the button pressed will be returned to the JavaScript callback identified by callbackId.
*
* @param message The message the dialog should display
* @param title The title of the dialog
* @param buttonLabels A comma separated list of button labels (Up to 3 buttons)
* @return The index of the button clicked (1,2 or 3)
* @param callbackId The callback id
*/
public synchronized int confirm(final String message, final String title, String buttonLabels) {
public synchronized void confirm(final String message, final String title, String buttonLabels, final String callbackId) {
// Create dialog on UI thread
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
final Notification notification = this;
final String[] fButtons = buttonLabels.split(",");
Runnable runnable = new Runnable() {
public void run() {
AlertDialog.Builder dlg = new AlertDialog.Builder(ctx);
@ -207,10 +226,7 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
synchronized(notification) {
notification.confirmResult = 1;
notification.notifyAll();
}
notification.success(new PluginResult(PluginResult.Status.OK, 1), callbackId);
}
});
}
@ -221,10 +237,7 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
synchronized(notification) {
notification.confirmResult = 2;
notification.notifyAll();
}
notification.success(new PluginResult(PluginResult.Status.OK, 2), callbackId);
}
});
}
@ -235,10 +248,7 @@ public class Notification extends Plugin {
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
synchronized(notification) {
notification.confirmResult = 3;
notification.notifyAll();
}
notification.success(new PluginResult(PluginResult.Status.OK, 3), callbackId);
}
}
);
@ -246,19 +256,9 @@ public class Notification extends Plugin {
dlg.create();
dlg.show();
}
};
};
this.ctx.runOnUiThread(runnable);
// Wait for dialog to close
synchronized(runnable) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return this.confirmResult;
}
/**
@ -273,7 +273,7 @@ public class Notification extends Plugin {
this.spinnerDialog = null;
}
final Notification notification = this;
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
Runnable runnable = new Runnable() {
public void run() {
notification.spinnerDialog = ProgressDialog.show(ctx, title , message, true, true,
@ -309,7 +309,7 @@ public class Notification extends Plugin {
this.progressDialog = null;
}
final Notification notification = this;
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
Runnable runnable = new Runnable() {
public void run() {
notification.progressDialog = new ProgressDialog(ctx);

View File

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

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

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

View File

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

View File

@ -0,0 +1,43 @@
/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
*/
package com.phonegap.api;
import android.app.Activity;
import android.content.Intent;
/**
* The Phonegap activity abstract class that is extended by DroidGap.
* It is used to isolate plugin development, and remove dependency on entire Phonegap library.
*/
public abstract class PhonegapActivity extends Activity {
/**
* Add a class that implements a service.
*
* @param serviceType
* @param className
*/
abstract public void addService(String serviceType, String className);
/**
* Send JavaScript statement back to JavaScript.
*
* @param message
*/
abstract public void sendJavascript(String statement);
/**
* Launch an activity for which you would like a result when it finished. When this activity exits,
* your onActivityResult() method will be called.
*
* @param command The command object
* @param intent The intent to start
* @param requestCode The request code that is passed to callback to identify the activity
*/
abstract public void startActivityForResult(Plugin command, Intent intent, int requestCode);
}

View File

@ -8,7 +8,7 @@
package com.phonegap.api;
import org.json.JSONArray;
import com.phonegap.DroidGap;
import android.content.Intent;
import android.webkit.WebView;
@ -20,7 +20,7 @@ import android.webkit.WebView;
public abstract class Plugin implements IPlugin {
public WebView webView; // WebView object
public DroidGap ctx; // DroidGap object
public PhonegapActivity ctx; // PhonegapActivity object
/**
* Executes the request and returns PluginResult.
@ -48,7 +48,7 @@ public abstract class Plugin implements IPlugin {
*
* @param ctx The context of the main Activity.
*/
public void setContext(DroidGap ctx) {
public void setContext(PhonegapActivity ctx) {
this.ctx = ctx;
}
@ -99,7 +99,7 @@ public abstract class Plugin implements IPlugin {
* @param statement
*/
public void sendJavascript(String statement) {
this.ctx.callbackServer.sendJavascript(statement);
this.ctx.sendJavascript(statement);
}
/**
@ -113,7 +113,7 @@ public abstract class Plugin implements IPlugin {
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void success(PluginResult pluginResult, String callbackId) {
this.ctx.callbackServer.sendJavascript(pluginResult.toSuccessCallbackString(callbackId));
this.ctx.sendJavascript(pluginResult.toSuccessCallbackString(callbackId));
}
/**
@ -123,6 +123,6 @@ public abstract class Plugin implements IPlugin {
* @param callbackId The callback id used when calling back into JavaScript.
*/
public void error(PluginResult pluginResult, String callbackId) {
this.ctx.callbackServer.sendJavascript(pluginResult.toErrorCallbackString(callbackId));
this.ctx.sendJavascript(pluginResult.toErrorCallbackString(callbackId));
}
}

View File

@ -14,7 +14,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import android.webkit.WebView;
import com.phonegap.DroidGap;
/**
* PluginManager is exposed to JavaScript in the PhoneGap WebView.
@ -27,7 +26,7 @@ public final class PluginManager {
private HashMap<String, Plugin> plugins = new HashMap<String,Plugin>();
private HashMap<String, String> services = new HashMap<String,String>();
private final DroidGap ctx;
private final PhonegapActivity ctx;
private final WebView app;
/**
@ -36,8 +35,7 @@ public final class PluginManager {
* @param app
* @param ctx
*/
public PluginManager(WebView app, DroidGap ctx) {
System.out.println("PluginManager()");
public PluginManager(WebView app, PhonegapActivity ctx) {
this.ctx = ctx;
this.app = app;
}
@ -66,7 +64,6 @@ public final class PluginManager {
*/
@SuppressWarnings("unchecked")
public String exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) {
System.out.println("PluginManager.exec("+service+", "+action+", "+callbackId+", "+jsonArgs+", "+async+")");
PluginResult cr = null;
boolean runAsync = async;
try {
@ -78,7 +75,7 @@ public final class PluginManager {
}
if (isPhoneGapPlugin(c)) {
final Plugin plugin = this.addPlugin(clazz, c);
final DroidGap ctx = this.ctx;
final PhonegapActivity ctx = this.ctx;
runAsync = async && !plugin.isSynch(action);
if (runAsync) {
// Run this on a different thread so that this one can return back to JS
@ -87,10 +84,19 @@ public final class PluginManager {
try {
// Call execute on the plugin so that it can do it's thing
PluginResult cr = plugin.execute(action, args, callbackId);
// Check the status for 0 (success) or otherwise
if (cr.getStatus() == 0) {
int status = cr.getStatus();
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
}
// Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
else if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
ctx.sendJavascript(cr.toSuccessCallbackString(callbackId));
} else {
}
// If error
else {
ctx.sendJavascript(cr.toErrorCallbackString(callbackId));
}
} catch (Exception e) {
@ -104,6 +110,11 @@ public final class PluginManager {
} else {
// Call execute on the plugin so that it can do it's thing
cr = plugin.execute(action, args, callbackId);
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) {
return "";
}
}
}
} catch (ClassNotFoundException e) {
@ -119,9 +130,6 @@ public final class PluginManager {
}
ctx.sendJavascript(cr.toErrorCallbackString(callbackId));
}
if (cr != null) {
System.out.println(" -- returning result: "+cr.getJSONString());
}
return ( cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }" );
}
@ -183,11 +191,10 @@ public final class PluginManager {
if (this.plugins.containsKey(className)) {
return this.getPlugin(className);
}
System.out.println("PluginManager.addPlugin("+className+")");
try {
Plugin plugin = (Plugin)clazz.newInstance();
this.plugins.put(className, plugin);
plugin.setContext((DroidGap)this.ctx);
plugin.setContext(this.ctx);
plugin.setView(this.app);
return plugin;
}

View File

@ -10,18 +10,34 @@ package com.phonegap.api;
import org.json.JSONArray;
import org.json.JSONObject;
import android.util.Log;
public class PluginResult {
private final int status;
private final String message;
private boolean keepCallback = false;
private String cast = null;
public PluginResult(Status status) {
this.status = status.ordinal();
this.message = PluginResult.StatusMessages[this.status];
this.message = "'" + PluginResult.StatusMessages[this.status] + "'";
}
public PluginResult(Status status, String message) {
this.status = status.ordinal();
this.message = "'" + message + "'";
this.message = JSONObject.quote(message);
}
public PluginResult(Status status, JSONArray message, String cast) {
this.status = status.ordinal();
this.message = message.toString();
this.cast = cast;
}
public PluginResult(Status status, JSONObject message, String cast) {
this.status = status.ordinal();
this.message = message.toString();
this.cast = cast;
}
public PluginResult(Status status, JSONArray message) {
@ -34,20 +50,25 @@ public class PluginResult {
this.message = message.toString();
}
// TODO: BC: Added
public PluginResult(Status status, int i) {
this.status = status.ordinal();
this.message = ""+i;
}
public PluginResult(Status status, float f) {
this.status = status.ordinal();
this.message = ""+f;
}
public PluginResult(Status status, boolean b) {
this.status = status.ordinal();
this.message = ""+b;
}
public void setKeepCallback(boolean b) {
this.keepCallback = b;
}
public int getStatus() {
return status;
}
@ -56,12 +77,24 @@ public class PluginResult {
return message;
}
public boolean getKeepCallback() {
return this.keepCallback;
}
public String getJSONString() {
return "{ status: " + this.getStatus() + ", message: " + this.getMessage() + " }";
return "{status:" + this.status + ",message:" + this.message + ",keepCallback:" + this.keepCallback + "}";
}
public String toSuccessCallbackString(String callbackId) {
return "PhoneGap.callbackSuccess('"+callbackId+"', " + this.getJSONString() + " );";
StringBuffer buf = new StringBuffer("");
if (cast != null) {
buf.append("var temp = "+cast+"("+this.getJSONString() + ");\n");
buf.append("PhoneGap.callbackSuccess('"+callbackId+"',temp);");
}
else {
buf.append("PhoneGap.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");");
}
return buf.toString();
}
public String toErrorCallbackString(String callbackId) {
@ -69,6 +102,7 @@ public class PluginResult {
}
public static String[] StatusMessages = new String[] {
"No result",
"OK",
"Class not found",
"Illegal access",
@ -81,6 +115,7 @@ public class PluginResult {
};
public enum Status {
NO_RESULT,
OK,
CLASS_NOT_FOUND_EXCEPTION,
ILLEGAL_ACCESS_EXCEPTION,

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

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

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

View File

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

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

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

31
util/yuicompressor/LICENSE Executable file
View File

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

140
util/yuicompressor/README Executable file
View File

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

Binary file not shown.