Compare commits

...

20 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

Clean up existing FileManager native code.  Move some functionality to
file utility class.
2011-03-05 04:26:31 +08:00
paulb777
64d4337d5f Update index.html to Add networking to example 2011-02-28 23:49:14 +08:00
paulb777
a9e1751812 Add networking to example and fix contacts 2011-02-28 23:46:26 +08:00
Bryce Curtis
2bc7bd6768 Worked around JavaScript bridge exception for Android 2.3. Use "prompt" instead of calling objects directly. There were several objects called from JavaScript, including BrowserKey, so key events had to be reworked. 2011-02-27 20:07:24 -06:00
Fil Maj
1711fb07d7 Small patch to build script: ruby needs double quotes to interpolate variables into it properly. 2011-02-16 16:26:01 -08:00
paulb777
6f4673f590 JSLint clean JavaScript sources. No fatal errors remain. Options can turn off rest of warnings 2011-02-15 16:10:09 -08:00
Vadim Voituk
5e858f8bc3 Added CupcakeLocalStorage.clear() method (in according to http://dev.w3.org/html5/webstorage/#the-storage-interface) 2011-02-04 12:08:22 -08:00
Fil Maj
691b093ccd Upped script version in assets. 2011-02-04 11:55:20 -08:00
Fil Maj
99002f9dce Fix for build: version needs to be included in .jar and .js generated files. 2011-02-04 11:35:05 -08:00
Fil Maj
b07072c12b Fix for ticket 86 (build fail if phonegap-android dir is located under a dir with "lib" in it). Also bug fix in build if config.xml didnt contain an <icon> element. 2011-02-04 11:20:22 -08:00
Bryce Curtis
36dd964ba4 Logging status from wrong object. 2011-02-03 21:11:06 -06:00
38 changed files with 2968 additions and 910 deletions

View File

@@ -1 +1 @@
0.9.4
0.9.4

View File

@@ -5,7 +5,7 @@
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>PhoneGap</title>
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8">
<script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8" src="phonegap.0.9.4.min.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
</head>
@@ -29,7 +29,8 @@
<a href="#" class="btn large" onclick="beep();">Beep</a>
<a href="#" class="btn large" onclick="vibrate();">Vibrate</a>
<a href="#" class="btn large" onclick="show_pic();">Get a Picture</a>
<a href="#" class="btn large" onclick="get_contacts();">Get phone's contacts</a>
<a href="#" class="btn large" onclick="get_contacts();">Get Phone's Contacts</a>
<a href="#" class="btn large" onclick="check_network();">Check Network</a>
<div id="viewport" class="viewport" style="display: none;">
<img style="width:60px;height:60px" id="test_img" src="" />
</div>

View File

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

View File

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

View File

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

View File

@@ -59,13 +59,13 @@ Camera.prototype.PictureSourceType = Camera.PictureSourceType;
Camera.prototype.getPicture = function(successCallback, errorCallback, options) {
// successCallback required
if (typeof successCallback != "function") {
if (typeof successCallback !== "function") {
console.log("Camera Error: successCallback is not a function");
return;
}
// errorCallback optional
if (errorCallback && (typeof errorCallback != "function")) {
if (errorCallback && (typeof errorCallback !== "function")) {
console.log("Camera Error: errorCallback is not a function");
return;
}
@@ -80,12 +80,14 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options)
destinationType = this.options.destinationType;
}
var sourceType = Camera.PictureSourceType.CAMERA;
if (typeof this.options.sourceType == "number") {
if (typeof this.options.sourceType === "number") {
sourceType = this.options.sourceType;
}
PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]);
};
PhoneGap.addConstructor(function() {
if (typeof navigator.camera == "undefined") navigator.camera = new Camera();
if (typeof navigator.camera === "undefined") {
navigator.camera = new Camera();
}
});

View File

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

View File

@@ -26,7 +26,7 @@
* @param {ContactField[]} urls contact's web sites
* @param {DOMString} timezone the contacts time zone
*/
var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, addresses,
var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses,
ims, organizations, revision, birthday, gender, note, photos, categories, urls, timezone) {
this.id = id || null;
this.rawId = null;
@@ -48,13 +48,33 @@ var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, ad
this.timezone = timezone || null;
};
/**
* ContactError.
* An error code assigned by an implementation when an error has occurred
*/
var ContactError = function() {
this.code=null;
};
/**
* Error codes
*/
ContactError.UNKNOWN_ERROR = 0;
ContactError.INVALID_ARGUMENT_ERROR = 1;
ContactError.NOT_FOUND_ERROR = 2;
ContactError.TIMEOUT_ERROR = 3;
ContactError.PENDING_OPERATION_ERROR = 4;
ContactError.IO_ERROR = 5;
ContactError.NOT_SUPPORTED_ERROR = 6;
ContactError.PERMISSION_DENIED_ERROR = 20;
/**
* Removes contact from device storage.
* @param successCB success callback
* @param errorCB error callback
*/
Contact.prototype.remove = function(successCB, errorCB) {
if (this.id == null) {
if (this.id === null) {
var errorObj = new ContactError();
errorObj.code = ContactError.NOT_FOUND_ERROR;
errorCB(errorObj);
@@ -71,48 +91,49 @@ Contact.prototype.remove = function(successCB, errorCB) {
*/
Contact.prototype.clone = function() {
var clonedContact = PhoneGap.clone(this);
var i;
clonedContact.id = null;
clonedContact.rawId = null;
// Loop through and clear out any id's in phones, emails, etc.
if (clonedContact.phoneNumbers) {
for (i=0; i<clonedContact.phoneNumbers.length; i++) {
clonedContact.phoneNumbers[i].id = null;
}
for (i = 0; i < clonedContact.phoneNumbers.length; i++) {
clonedContact.phoneNumbers[i].id = null;
}
}
if (clonedContact.emails) {
for (i=0; i<clonedContact.emails.length; i++) {
clonedContact.emails[i].id = null;
}
for (i = 0; i < clonedContact.emails.length; i++) {
clonedContact.emails[i].id = null;
}
}
if (clonedContact.addresses) {
for (i=0; i<clonedContact.addresses.length; i++) {
clonedContact.addresses[i].id = null;
}
for (i = 0; i < clonedContact.addresses.length; i++) {
clonedContact.addresses[i].id = null;
}
}
if (clonedContact.ims) {
for (i=0; i<clonedContact.ims.length; i++) {
clonedContact.ims[i].id = null;
}
for (i = 0; i < clonedContact.ims.length; i++) {
clonedContact.ims[i].id = null;
}
}
if (clonedContact.organizations) {
for (i=0; i<clonedContact.organizations.length; i++) {
clonedContact.organizations[i].id = null;
}
for (i = 0; i < clonedContact.organizations.length; i++) {
clonedContact.organizations[i].id = null;
}
}
if (clonedContact.tags) {
for (i=0; i<clonedContact.tags.length; i++) {
clonedContact.tags[i].id = null;
}
for (i = 0; i < clonedContact.tags.length; i++) {
clonedContact.tags[i].id = null;
}
}
if (clonedContact.photos) {
for (i=0; i<clonedContact.photos.length; i++) {
clonedContact.photos[i].id = null;
}
for (i = 0; i < clonedContact.photos.length; i++) {
clonedContact.photos[i].id = null;
}
}
if (clonedContact.urls) {
for (i=0; i<clonedContact.urls.length; i++) {
clonedContact.urls[i].id = null;
}
for (i = 0; i < clonedContact.urls.length; i++) {
clonedContact.urls[i].id = null;
}
}
return clonedContact;
};
@@ -201,8 +222,8 @@ var ContactOrganization = function(name, dept, title) {
*/
var Contacts = function() {
this.inProgress = false;
this.records = new Array();
}
this.records = [];
};
/**
* Returns an array of Contacts matching the search criteria.
* @param fields that should be searched
@@ -223,10 +244,11 @@ Contacts.prototype.find = function(fields, successCB, errorCB, options) {
* @returns new Contact object
*/
Contacts.prototype.create = function(properties) {
var i;
var contact = new Contact();
for (i in properties) {
if (contact[i]!='undefined') {
contact[i]=properties[i];
if (contact[i] !== 'undefined') {
contact[i] = properties[i];
}
}
return contact;
@@ -241,13 +263,14 @@ Contacts.prototype.create = function(properties) {
* @returns an array of Contact objects
*/
Contacts.prototype.cast = function(pluginResult) {
var contacts = new Array();
for (var i=0; i<pluginResult.message.length; i++) {
var contacts = [];
var i;
for (i=0; i<pluginResult.message.length; i++) {
contacts.push(navigator.service.contacts.create(pluginResult.message[i]));
}
pluginResult.message = contacts;
return pluginResult;
}
};
/**
* ContactFindOptions.
@@ -261,30 +284,14 @@ var ContactFindOptions = function(filter, multiple, updatedSince) {
this.updatedSince = updatedSince || '';
};
/**
* ContactError.
* An error code assigned by an implementation when an error has occurred
*/
var ContactError = function() {
this.code=null;
};
/**
* Error codes
*/
ContactError.UNKNOWN_ERROR = 0;
ContactError.INVALID_ARGUMENT_ERROR = 1;
ContactError.NOT_FOUND_ERROR = 2;
ContactError.TIMEOUT_ERROR = 3;
ContactError.PENDING_OPERATION_ERROR = 4;
ContactError.IO_ERROR = 5;
ContactError.NOT_SUPPORTED_ERROR = 6;
ContactError.PERMISSION_DENIED_ERROR = 20;
/**
* Add the contact interface into the browser.
*/
PhoneGap.addConstructor(function() {
if(typeof navigator.service == "undefined") navigator.service = new Object();
if(typeof navigator.service.contacts == "undefined") navigator.service.contacts = new Contacts();
if(typeof navigator.service === "undefined") {
navigator.service = {};
}
if(typeof navigator.service.contacts === "undefined") {
navigator.service.contacts = new Contacts();
}
});

View File

@@ -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
*/
/**
@@ -46,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;
}
@@ -62,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

@@ -9,7 +9,7 @@
/**
* FileTransfer uploads a file to a remote server.
*/
function FileTransfer() {};
function FileTransfer() {}
/**
* FileUploadResult
@@ -18,14 +18,14 @@ 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;
@@ -74,4 +74,4 @@ function FileUploadOptions(fileKey, fileName, mimeType, params) {
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
};
}

View File

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

View File

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

View File

@@ -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;

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
@@ -28,8 +28,9 @@
* document.addEventListener("resume", myResumeListener, false);
*/
if (typeof(DeviceInfo) != 'object')
if (typeof(DeviceInfo) !== 'object') {
DeviceInfo = {};
}
/**
* This represents the PhoneGap API itself, and provides a global namespace for accessing
@@ -48,7 +49,7 @@ var PhoneGap = {
/**
* Custom pub-sub channel that can have functions subscribed to it
*/
PhoneGap.Channel = function(type)
PhoneGap.Channel = function (type)
{
this.type = type;
this.handlers = {};
@@ -66,10 +67,10 @@ PhoneGap.Channel = function(type)
*/
PhoneGap.Channel.prototype.subscribe = function(f, c, g) {
// need a function to call
if (f == null) { return; }
if (f === null) { return; }
var func = f;
if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, f); }
if (typeof c === "object" && f instanceof Function) { func = PhoneGap.close(c, f); }
g = g || func.observer_guid || f.observer_guid || this.guid++;
func.observer_guid = g;
@@ -88,9 +89,9 @@ PhoneGap.Channel.prototype.subscribeOnce = function(f, c) {
var m = function() {
f.apply(c || null, arguments);
_this.unsubscribe(g);
}
};
if (this.fired) {
if (typeof c == "object" && f instanceof Function) { f = PhoneGap.close(c, f); }
if (typeof c === "object" && f instanceof Function) { f = PhoneGap.close(c, f); }
f.apply(this, this.fireArgs);
} else {
g = this.subscribe(m);
@@ -113,11 +114,14 @@ PhoneGap.Channel.prototype.unsubscribe = function(g) {
PhoneGap.Channel.prototype.fire = function(e) {
if (this.enabled) {
var fail = false;
for (var item in this.handlers) {
var handler = this.handlers[item];
if (handler instanceof Function) {
var rv = (handler.apply(this, arguments)==false);
fail = fail || rv;
var item, handler, rv;
for (item in this.handlers) {
if (this.handlers.hasOwnProperty(item)) {
handler = this.handlers[item];
if (handler instanceof Function) {
rv = (handler.apply(this, arguments) === false);
fail = fail || rv;
}
}
}
this.fired = true;
@@ -134,10 +138,13 @@ PhoneGap.Channel.prototype.fire = function(e) {
PhoneGap.Channel.join = function(h, c) {
var i = c.length;
var f = function() {
if (!(--i)) h();
}
if (!(--i)) {
h();
}
};
var len = i;
for (var j=0; j<len; j++) {
var j;
for (j=0; j<len; j++) {
if (!c[j].fired) {
c[j].subscribeOnce(f);
}
@@ -145,13 +152,15 @@ PhoneGap.Channel.join = function(h, c) {
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
@@ -189,7 +198,7 @@ PhoneGap.addPlugin = function(name, obj) {
else {
console.log("Error: Plugin "+name+" already exists.");
}
}
};
/**
* onDOMContentLoaded channel is fired when the DOM content
@@ -284,11 +293,17 @@ PhoneGap.Channel.join(function() {
// Start listening for XHR callbacks
setTimeout(function() {
if (CallbackServer.usePolling()) {
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.JSCallback();
var polling = prompt("usePolling", "gap_callbackServer:");
if (polling == "true") {
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.JSCallback();
}
}
}, 1);
@@ -298,6 +313,8 @@ PhoneGap.Channel.join(function() {
// Fire event to notify that all objects are created
PhoneGap.onPhoneGapReady.fire();
// Fire onDeviceReady event once all constructors have run and PhoneGap info has been
// received from native side, and any user defined initialization channels.
PhoneGap.Channel.join(function() {
// Turn off app loading dialog
@@ -311,22 +328,6 @@ PhoneGap.Channel.join(function() {
}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]);
/**
* Fire onDeviceReady event once all constructors have run and PhoneGap info has been
* received from native side.
*/
/*
PhoneGap.Channel.join(function() {
// Turn off app loading dialog
navigator.notification.activityStop();
PhoneGap.onDeviceReady.fire();
// Fire the onresume event, since first one happens before JavaScript is loaded
PhoneGap.onResume.fire();
}, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]);
*/
// Listen for DOMContentLoaded and notify our channel subscribers
document.addEventListener('DOMContentLoaded', function() {
PhoneGap.onDOMContentLoaded.fire();
@@ -337,20 +338,50 @@ PhoneGap.m_document_addEventListener = document.addEventListener;
document.addEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (e == 'deviceready') {
if (e === 'deviceready') {
PhoneGap.onDeviceReady.subscribeOnce(handler);
} else if (e == 'resume') {
} else if (e === 'resume') {
PhoneGap.onResume.subscribe(handler);
if (PhoneGap.onDeviceReady.fired) {
PhoneGap.onResume.fire();
}
} else if (e == 'pause') {
} else if (e === 'pause') {
PhoneGap.onPause.subscribe(handler);
} else {
}
else {
// If subscribing to Android backbutton
if (e === 'backbutton') {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]);
}
PhoneGap.m_document_addEventListener.call(document, evt, handler, capture);
}
};
// Intercept calls to document.removeEventListener and watch for events that
// are generated by PhoneGap native code
PhoneGap.m_document_removeEventListener = document.removeEventListener;
document.removeEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
// If unsubscribing to Android backbutton
if (e === 'backbutton') {
PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]);
}
PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture);
};
/**
* Method to fire event from native code
*/
PhoneGap.fireEvent = function(type) {
var e = document.createEvent('Events');
e.initEvent(type);
document.dispatchEvent(e);
};
/**
* If JSON not included, use our own stringify. (Android 1.6)
* The restriction on ours is that it must be an array of simple types.
@@ -359,57 +390,53 @@ document.addEventListener = function(evt, handler, capture) {
* @return
*/
PhoneGap.stringify = function(args) {
if (typeof JSON == "undefined") {
if (typeof JSON === "undefined") {
var s = "[";
for (var i=0; i<args.length; i++) {
if (i > 0) {
s = s + ",";
}
var type = typeof args[i];
if ((type == "number") || (type == "boolean")) {
s = s + args[i];
}
else if (args[i] instanceof Array) {
s = s + "[" + args[i] + "]";
}
else if (args[i] instanceof Object) {
var start = true;
s = s + '{';
for (var name in args[i]) {
if (args[i][name] != null) {
if (!start) {
s = s + ',';
}
s = s + '"' + name + '":';
var nameType = typeof args[i][name];
if ((nameType == "number") || (nameType == "boolean")) {
s = s + args[i][name];
}
else if ((typeof args[i][name]) == 'function') {
// don't copy the functions
s = s + '""';
}
else if (args[i][name] instanceof Object) {
s = s + this.stringify(args[i][name]);
}
else {
s = s + '"' + args[i][name] + '"';
}
start=false;
}
}
s = s + '}';
}
else {
var a = args[i].replace(/\\/g, '\\\\');
a = a.replace(/"/g, '\\"');
s = s + '"' + a + '"';
var i, type, start, name, nameType, a;
for (i = 0; i < args.length; i++) {
if (args[i] != null) {
if (i > 0) {
s = s + ",";
}
type = typeof args[i];
if ((type === "number") || (type === "boolean")) {
s = s + args[i];
} else if (args[i] instanceof Array) {
s = s + "[" + args[i] + "]";
} else if (args[i] instanceof Object) {
start = true;
s = s + '{';
for (name in args[i]) {
if (args[i][name] !== null) {
if (!start) {
s = s + ',';
}
s = s + '"' + name + '":';
nameType = typeof args[i][name];
if ((nameType === "number") || (nameType === "boolean")) {
s = s + args[i][name];
} else if ((typeof args[i][name]) === 'function') {
// don't copy the functions
s = s + '""';
} else if (args[i][name] instanceof Object) {
s = s + this.stringify(args[i][name]);
} else {
s = s + '"' + args[i][name] + '"';
}
start = false;
}
}
s = s + '}';
} else {
a = args[i].replace(/\\/g, '\\\\');
a = a.replace(/"/g, '\\"');
s = s + '"' + a + '"';
}
}
}
s = s + "]";
return s;
}
else {
} else {
return JSON.stringify(args);
}
};
@@ -421,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;
@@ -445,9 +473,9 @@ PhoneGap.clone = function(obj) {
return obj;
}
retVal = new Object();
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]);
}
}
@@ -491,23 +519,22 @@ PhoneGap.exec = function(success, fail, service, action, args) {
PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
}
// Note: Device returns string, but for some reason emulator returns object - so convert to string.
var r = ""+PluginManager.exec(service, action, callbackId, this.stringify(args), true);
var r = prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
// If a result was returned
if (r.length > 0) {
eval("var v="+r+";");
// If status is OK, then return value back to caller
if (v.status == PhoneGap.callbackStatus.OK) {
if (v.status === PhoneGap.callbackStatus.OK) {
// If there is a success callback, then call it now with returned value
// If there is a success callback, then call it now with
// returned value
if (success) {
try {
success(v.message);
}
catch (e) {
console.log("Error in success callback: "+callbackId+" = "+e);
success(v.message);
} catch (e) {
console.log("Error in success callback: " + callbackId + " = " + e);
}
// Clear callback if not expecting any more results
@@ -519,7 +546,7 @@ PhoneGap.exec = function(success, fail, service, action, args) {
}
// If no result
else if (v.status == PhoneGap.callbackStatus.NO_RESULT) {
else if (v.status === PhoneGap.callbackStatus.NO_RESULT) {
// Clear callback if not expecting any more results
if (!v.keepCallback) {
@@ -529,15 +556,15 @@ PhoneGap.exec = function(success, fail, service, action, args) {
// If error, then display error
else {
console.log("Error: Status="+r.status+" Message="+v.message);
console.log("Error: Status="+v.status+" Message="+v.message);
// If there is a fail callback, then call it now with returned value
if (fail) {
try {
fail(v.message);
}
catch (e) {
console.log("Error in error callback: "+callbackId+" = "+e);
catch (e1) {
console.log("Error in error callback: "+callbackId+" = "+e1);
}
// Clear callback if not expecting any more results
@@ -548,8 +575,8 @@ PhoneGap.exec = function(success, fail, service, action, args) {
return null;
}
}
} catch (e) {
console.log("Error: "+e);
} catch (e2) {
console.log("Error: "+e2);
}
};
@@ -563,10 +590,10 @@ PhoneGap.callbackSuccess = function(callbackId, args) {
if (PhoneGap.callbacks[callbackId]) {
// If result is to be sent to callback
if (args.status == PhoneGap.callbackStatus.OK) {
if (args.status === PhoneGap.callbackStatus.OK) {
try {
if (PhoneGap.callbacks[callbackId].success) {
PhoneGap.callbacks[callbackId].success(args.message);
PhoneGap.callbacks[callbackId].success(args.message);
}
}
catch (e) {
@@ -615,38 +642,43 @@ PhoneGap.callbackError = function(callbackId, args) {
*/
// TODO: Is this used?
PhoneGap.run_command = function() {
if (!PhoneGap.available || !PhoneGap.queue.ready)
if (!PhoneGap.available || !PhoneGap.queue.ready) {
return;
}
PhoneGap.queue.ready = false;
var args = PhoneGap.queue.commands.shift();
if (PhoneGap.queue.commands.length == 0) {
if (PhoneGap.queue.commands.length === 0) {
clearInterval(PhoneGap.queue.timer);
PhoneGap.queue.timer = null;
}
var uri = [];
var dict = null;
for (var i = 1; i < args.length; i++) {
var i;
for (i = 1; i < args.length; i++) {
var arg = args[i];
if (arg == undefined || arg == null)
if (arg === undefined || arg === null) {
arg = '';
if (typeof(arg) == 'object')
}
if (typeof(arg) === 'object') {
dict = arg;
else
} else {
uri.push(encodeURIComponent(arg));
}
}
var url = "gap://" + args[0] + "/" + uri.join("/");
if (dict != null) {
if (dict !== null) {
var name;
var query_args = [];
for (var name in dict) {
if (typeof(name) != 'string')
continue;
query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name]));
for (name in dict) {
if (dict.hasOwnProperty(name) && (typeof (name) === 'string')) {
query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name]));
}
}
if (query_args.length > 0)
if (query_args.length > 0) {
url += "?" + query_args.join("&");
}
}
document.location = url;
@@ -663,14 +695,21 @@ PhoneGap.JSCallbackToken = null;
* 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() {
@@ -687,41 +726,41 @@ PhoneGap.JSCallback = function() {
}
// If callback ping (used to keep XHR request from timing out)
else if (xmlhttp.status == 404) {
else if (xmlhttp.status === 404) {
setTimeout(PhoneGap.JSCallback, 10);
}
// If security error
else if (xmlhttp.status == 403) {
else if (xmlhttp.status === 403) {
console.log("JSCallback Error: Invalid token. Stopping callbacks.");
}
// If server is stopping
else if (xmlhttp.status == 503) {
else if (xmlhttp.status === 503) {
console.log("JSCallback Error: Service unavailable. Stopping callbacks.");
}
// If request wasn't GET
else if (xmlhttp.status == 400) {
else if (xmlhttp.status === 400) {
console.log("JSCallback Error: Bad request. Stopping callbacks.");
}
// If error, restart callback server
else {
console.log("JSCallback Error: Request failed.");
CallbackServer.restartServer();
prompt("restartServer", "gap_callbackServer:");
PhoneGap.JSCallbackPort = null;
PhoneGap.JSCallbackToken = null;
setTimeout(PhoneGap.JSCallback, 100);
}
}
}
};
if (PhoneGap.JSCallbackPort == null) {
PhoneGap.JSCallbackPort = CallbackServer.getPort();
if (PhoneGap.JSCallbackPort === null) {
PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:");
}
if (PhoneGap.JSCallbackToken == null) {
PhoneGap.JSCallbackToken = CallbackServer.getToken();
if (PhoneGap.JSCallbackToken === null) {
PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:");
}
xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true);
xmlhttp.send();
@@ -733,6 +772,11 @@ PhoneGap.JSCallback = function() {
*/
PhoneGap.JSCallbackPollingPeriod = 50;
/**
* Flag that can be set by the user to force polling to be used or force XHR to be used.
*/
PhoneGap.UsePolling = false; // T=use polling, F=use XHR
/**
* This is only for Android.
*
@@ -741,7 +785,14 @@ PhoneGap.JSCallbackPollingPeriod = 50;
* Java to JavaScript.
*/
PhoneGap.JSCallbackPolling = function() {
var msg = CallbackServer.getJavascript();
// If polling flag was changed, stop using polling from now on
if (!PhoneGap.UsePolling) {
PhoneGap.JSCallback();
return;
}
var msg = prompt("", "gap_poll:");
if (msg) {
setTimeout(function() {
try {
@@ -774,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;
@@ -788,11 +840,11 @@ PhoneGap.close = function(context, func, params) {
if (typeof params === 'undefined') {
return function() {
return func.apply(context, arguments);
}
};
} else {
return function() {
return func.apply(context, params);
}
};
}
};
@@ -806,7 +858,7 @@ PhoneGap.includeJavascript = function(jsfile, successCallback) {
var id = document.getElementsByTagName("head")[0];
var el = document.createElement('script');
el.type = 'text/javascript';
if (typeof successCallback == 'function') {
if (typeof successCallback === 'function') {
el.onload = successCallback;
}
el.src = jsfile;

View File

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

View File

@@ -45,7 +45,7 @@ DroidDB.prototype.completeQuery = function(id, data) {
r.rows.resultSet = data;
r.rows.length = data.length;
try {
if (typeof query.successCallback == 'function') {
if (typeof query.successCallback === 'function') {
query.successCallback(query.tx, r);
}
} catch (ex) {
@@ -83,7 +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) {
@@ -99,6 +99,24 @@ DroidDB.prototype.fail = function(reason, id) {
}
};
/**
* Transaction object
* PRIVATE METHOD
*/
var DroidDB_Tx = function() {
// Set the id of the transaction
this.id = PhoneGap.createUUID();
// Callbacks
this.successCallback = null;
this.errorCallback = null;
// Query list
this.queryList = {};
};
var DatabaseShell = function() {
};
@@ -128,22 +146,6 @@ DatabaseShell.prototype.transaction = function(process, errorCallback, successCa
}
};
/**
* Transaction object
* PRIVATE METHOD
*/
var DroidDB_Tx = function() {
// Set the id of the transaction
this.id = PhoneGap.createUUID();
// Callbacks
this.successCallback = null;
this.errorCallback = null;
// Query list
this.queryList = {};
};
/**
* Mark query in transaction as complete.
@@ -157,10 +159,13 @@ DroidDB_Tx.prototype.queryComplete = function(id) {
// If no more outstanding queries, then fire transaction success
if (this.successCallback) {
var count = 0;
for (var i in this.queryList) {
count++;
var i;
for (i in this.queryList) {
if (this.queryList.hasOwnProperty(i)) {
count++;
}
}
if (count == 0) {
if (count === 0) {
try {
this.successCallback();
} catch(e) {
@@ -220,7 +225,7 @@ var DroidDB_Query = function(tx) {
this.successCallback = null;
this.errorCallback = null;
}
};
/**
* Execute SQL statement
@@ -233,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 = [];
}
@@ -304,10 +309,11 @@ var CupcakeLocalStorage = function() {
var storage = {};
this.db.transaction(
function (transaction) {
var i;
transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))');
transaction.executeSql('SELECT * FROM storage', [], function(tx, result) {
for(var i = 0; i < result.rows.length; i++) {
storage[result.rows.item(i)['id']] = result.rows.item(i)['body'];
for(i = 0; i < result.rows.length; i++) {
storage[result.rows.item(i).id] = result.rows.item(i).body;
}
PhoneGap.initializationComplete("cupcakeStorage");
});
@@ -318,7 +324,7 @@ var CupcakeLocalStorage = function() {
}
);
this.setItem = function(key, val) {
console.log('set');
//console.log('set');
storage[key] = val;
this.db.transaction(
@@ -328,10 +334,10 @@ var CupcakeLocalStorage = function() {
transaction.executeSql('REPLACE INTO storage (id, body) values(?,?)', [key,val]);
}
);
}
};
this.getItem = function(key) {
return storage[key];
}
};
this.removeItem = function(key) {
delete storage[key];
this.db.transaction(
@@ -341,21 +347,29 @@ var CupcakeLocalStorage = function() {
transaction.executeSql('DELETE FROM storage where id=?', [key]);
}
);
}
};
this.clear = function() {
storage = {};
this.db.transaction(
function (transaction) {
transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))');
transaction.executeSql('DELETE FROM storage', []);
}
);
};
} catch(e) {
alert("Database error "+e+".");
return;
}
};
PhoneGap.addConstructor(function() {
if (typeof window.openDatabase == "undefined") {
if (typeof window.openDatabase === "undefined") {
navigator.openDatabase = window.openDatabase = DroidDB_openDatabase;
window.droiddb = new DroidDB();
}
if (typeof window.localStorage == "undefined") {
if (typeof window.localStorage === "undefined") {
navigator.localStorage = window.localStorage = new CupcakeLocalStorage();
PhoneGap.waitForInitialization("cupcakeStorage");
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles)
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
(function() {
// window object
var global = window;
// WebSocket Object. All listener methods are cleaned up!
var WebSocket = global.WebSocket = function(url) {
// get a new websocket object from factory (check com.strumsoft.websocket.WebSocketFactory.java)
this.socket = WebSocketFactory.getInstance(url);
// store in registry
if(this.socket) {
WebSocket.store[this.socket.getId()] = this;
} else {
throw new Error('websocket instantiation failed! Address could be wrong.');
}
};
// storage to hold websocket object for later invokation of event methods
WebSocket.store = {};
// static event methods to call event methods on target websocket objects
WebSocket.onmessage = function (evt) {
WebSocket.store[evt._target]['onmessage'].call(global, evt._data);
}
WebSocket.onopen = function (evt) {
WebSocket.store[evt._target]['onopen'].call(global, evt._data);
}
WebSocket.onclose = function (evt) {
WebSocket.store[evt._target]['onclose'].call(global, evt._data);
}
WebSocket.onerror = function (evt) {
WebSocket.store[evt._target]['onerror'].call(global, evt._data);
}
// instance event methods
WebSocket.prototype.send = function(data) {
this.socket.send(data);
}
WebSocket.prototype.close = function() {
this.socket.close();
}
WebSocket.prototype.getReadyState = function() {
this.socket.getReadyState();
}
///////////// Must be overloaded
WebSocket.prototype.onopen = function(){
throw new Error('onopen not implemented.');
};
// alerts message pushed from server
WebSocket.prototype.onmessage = function(msg){
throw new Error('onmessage not implemented.');
};
// alerts message pushed from server
WebSocket.prototype.onerror = function(msg){
throw new Error('onerror not implemented.');
};
// alert close event
WebSocket.prototype.onclose = function(){
throw new Error('onclose not implemented.');
};
})();

View File

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

View File

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

View File

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

View File

@@ -69,7 +69,7 @@ public class CallbackServer implements Runnable {
/**
* Indicates that polling should be used instead of XHR.
*/
private boolean usePolling;
private boolean usePolling = true;
/**
* Security token to prevent other apps from accessing this callback server via XHR

View File

@@ -8,15 +8,9 @@
package com.phonegap;
import java.io.File;
import java.util.Date;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
/**
* This class provides file directory utilities.
@@ -78,29 +72,6 @@ public class DirectoryManager {
return (freeSpace);
}
/**
* Create directory on SD card.
*
* @param directoryName The name of the directory to create.
* @return T=successful, F=failed
*/
protected static boolean createDirectory(String directoryName) {
boolean status;
// Make sure SD card exists
if ((testSaveLocationExists()) && (!directoryName.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), directoryName);
status = newPath.mkdir();
status = true;
}
// If no SD card or invalid dir name
else {
status = false;
}
return status;
}
/**
* Determine if SD card exists.
@@ -123,95 +94,6 @@ public class DirectoryManager {
return status;
}
/**
* Delete directory.
*
* @param fileName The name of the directory to delete
* @return T=deleted, F=could not delete
*/
protected static boolean deleteDirectory(String fileName) {
boolean status;
SecurityManager checker = new SecurityManager();
// Make sure SD card exists
if ((testSaveLocationExists()) && (!fileName.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), fileName);
checker.checkDelete(newPath.toString());
// If dir to delete is really a directory
if (newPath.isDirectory()) {
String[] listfile = newPath.list();
// Delete all files within the specified directory and then delete the directory
try{
for (int i=0; i < listfile.length; i++){
File deletedFile = new File (newPath.toString()+"/"+listfile[i].toString());
deletedFile.delete();
}
newPath.delete();
Log.i("DirectoryManager deleteDirectory", fileName);
status = true;
}
catch (Exception e){
e.printStackTrace();
status = false;
}
}
// If dir not a directory, then error
else {
status = false;
}
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Delete file.
*
* @param fileName The name of the file to delete
* @return T=deleted, F=not deleted
*/
protected static boolean deleteFile(String fileName) {
boolean status;
SecurityManager checker = new SecurityManager();
// Make sure SD card exists
if ((testSaveLocationExists()) && (!fileName.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), fileName);
checker.checkDelete(newPath.toString());
// If file to delete is really a file
if (newPath.isFile()){
try {
Log.i("DirectoryManager deleteFile", fileName);
newPath.delete();
status = true;
}catch (SecurityException se){
se.printStackTrace();
status = false;
}
}
// If not a file, then error
else {
status = false;
}
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Create a new file object from two file paths.
*
@@ -229,45 +111,4 @@ public class DirectoryManager {
}
return newPath;
}
/**
* This method will determine the file properties of the file specified
* by the filePath. Creates a JSONObject with name, lastModifiedDate and
* size properties.
*
* @param filePath the file to get the properties of
* @return a JSONObject with the files properties
*/
protected static JSONObject getFile(String filePath) {
File fp = new File(filePath);
JSONObject obj = new JSONObject();
try {
obj.put("name", fp.getAbsolutePath());
obj.put("lastModifiedDate", new Date(fp.lastModified()).toString());
obj.put("size", fp.length());
}
catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
return obj;
}
/**
* This method returns a JSONArray of file paths. Android's default
* location where files can be written is Environment.getExternalStorageDirectory().
* We are returning a array with one element so the interface can remain
* consistent with BlackBerry as they have two areas where files can be
* written.
*
* @return an array of file paths
*/
protected static JSONArray getRootPaths() {
JSONArray retVal = new JSONArray();
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";
retVal.put(path);
return retVal;
}
}

View File

@@ -3,11 +3,12 @@
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi Software Inc.
* Copyright (c) 2010, IBM Corporation
* Copyright (c) 2010-2011, IBM Corporation
*/
package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -26,6 +27,7 @@ import android.view.Window;
import android.view.WindowManager;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.JsPromptResult;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
@@ -110,7 +112,7 @@ public class DroidGap extends PhonegapActivity {
protected WebViewClient webViewClient;
private LinearLayout root;
private BrowserKey mKey;
boolean bound = false;
public CallbackServer callbackServer;
protected PluginManager pluginManager;
protected boolean cancelLoadUrl = false;
@@ -267,12 +269,6 @@ public class DroidGap extends PhonegapActivity {
private void bindBrowser(WebView appView) {
this.callbackServer = new CallbackServer();
this.pluginManager = new PluginManager(appView, this);
this.mKey = new BrowserKey(appView, this);
// This creates the new javascript interfaces for PhoneGap
appView.addJavascriptInterface(this.pluginManager, "PluginManager");
appView.addJavascriptInterface(this.mKey, "BackButton");
appView.addJavascriptInterface(this.callbackServer, "CallbackServer");
this.addService("App", "com.phonegap.App");
this.addService("Geolocation", "com.phonegap.GeoBroker");
@@ -659,10 +655,6 @@ public class DroidGap extends PhonegapActivity {
// Load blank page so that JavaScript onunload is called
this.appView.loadUrl("about:blank");
// Clean up objects
if (this.mKey != null) {
}
// Forward to plugins
this.pluginManager.onDestroy();
@@ -764,6 +756,69 @@ public class DroidGap extends PhonegapActivity {
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// Calling PluginManager.exec() to call a native service using
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
if (defaultValue.substring(0, 4).equals("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
boolean async = array.getBoolean(3);
String r = pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r);
} catch (JSONException e) {
e.printStackTrace();
}
}
// Polling for JavaScript messages
else if (defaultValue.equals("gap_poll:")) {
String r = callbackServer.getJavascript();
result.confirm(r);
}
// Calling into CallbackServer
else if (defaultValue.equals("gap_callbackServer:")) {
String r = "";
if (message.equals("usePolling")) {
r = ""+callbackServer.usePolling();
}
else if (message.equals("restartServer")) {
callbackServer.restartServer();
}
else if (message.equals("getPort")) {
r = Integer.toString(callbackServer.getPort());
}
else if (message.equals("getToken")) {
r = callbackServer.getToken();
}
result.confirm(r);
}
// Show dialog
else {
//@TODO:
result.confirm("");
}
return true;
}
}
/**
@@ -1016,8 +1071,8 @@ public class DroidGap extends PhonegapActivity {
if (keyCode == KeyEvent.KEYCODE_BACK) {
// If back key is bound, then send event to JavaScript
if (mKey.isBound()) {
this.appView.loadUrl("javascript:document.keyEvent.backTrigger()");
if (this.bound) {
this.appView.loadUrl("javascript:PhoneGap.fireEvent('backbutton');");
}
// If not bound
@@ -1037,12 +1092,12 @@ public class DroidGap extends PhonegapActivity {
// If menu key
else if (keyCode == KeyEvent.KEYCODE_MENU) {
appView.loadUrl("javascript:keyEvent.menuTrigger()");
this.appView.loadUrl("javascript:PhoneGap.fireEvent('menubutton');");
}
// If search key
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
appView.loadUrl("javascript:keyEvent.searchTrigger()");
this.appView.loadUrl("javascript:PhoneGap.fireEvent('searchbutton');");
}
return false;

View File

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

View File

@@ -10,6 +10,8 @@ package com.phonegap.api;
import org.json.JSONArray;
import org.json.JSONObject;
import android.util.Log;
public class PluginResult {
private final int status;
private final String message;
@@ -32,6 +34,12 @@ public class PluginResult {
this.cast = cast;
}
public PluginResult(Status status, JSONObject message, String cast) {
this.status = status.ordinal();
this.message = message.toString();
this.cast = cast;
}
public PluginResult(Status status, JSONArray message) {
this.status = status.ordinal();
this.message = message.toString();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles)
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.phonegap.websocket;
import java.net.URI;
import java.util.Random;
import android.webkit.WebView;
/**
* The <tt>WebSocketFactory</tt> is like a helper class to instantiate new
* WebSocket instaces especially from Javascript side. It expects a valid
* "ws://" URI.
*
* @author Animesh Kumar
*/
public class WebSocketFactory {
/** The app view. */
WebView appView;
/**
* Instantiates a new web socket factory.
*
* @param appView
* the app view
*/
public WebSocketFactory(WebView appView) {
this.appView = appView;
}
public WebSocket getInstance(String url) {
// use Draft75 by default
return getInstance(url, WebSocket.Draft.DRAFT75);
}
public WebSocket getInstance(String url, WebSocket.Draft draft) {
WebSocket socket = null;
Thread th = null;
try {
socket = new WebSocket(appView, new URI(url), draft, getRandonUniqueId());
th = socket.connect();
return socket;
} catch (Exception e) {
//Log.v("websocket", e.toString());
if(th != null) {
th.interrupt();
}
}
return null;
}
/**
* Generates random unique ids for WebSocket instances
*
* @return String
*/
private String getRandonUniqueId() {
return "WEBSOCKET." + new Random().nextInt(100);
}
}

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

View File

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