Compare commits

..

5 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
32 changed files with 1235 additions and 1036 deletions

View File

@@ -1 +1 @@
0.9.5
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.0.9.5.min.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>

View File

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

View File

@@ -6,10 +6,7 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("accelerometer")) {
PhoneGap.addResource("accelerometer");
Acceleration = function(x, y, z) {
function Acceleration(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
@@ -20,7 +17,7 @@ Acceleration = function(x, y, z) {
* This class provides access to device accelerometer data.
* @constructor
*/
Accelerometer = function() {
function Accelerometer() {
/**
* The last known acceleration. type=Acceleration()
@@ -122,4 +119,3 @@ PhoneGap.addConstructor(function() {
navigator.accelerometer = new Accelerometer();
}
});
};

View File

@@ -6,13 +6,10 @@
* Copyright (c) 2010-2011, IBM Corporation
*/
if (!PhoneGap.hasResource("app")) {
PhoneGap.addResource("app");
/**
* Constructor
*/
App = function() {};
function App() {}
/**
* Clear the resource cache.
@@ -90,4 +87,3 @@ App.prototype.exitApp = function() {
PhoneGap.addConstructor(function() {
navigator.app = window.app = new App();
});
};

View File

@@ -6,9 +6,6 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("camera")) {
PhoneGap.addResource("camera");
/**
* This class provides access to the device camera.
*
@@ -94,4 +91,3 @@ PhoneGap.addConstructor(function() {
navigator.camera = new Camera();
}
});
};

View File

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

View File

@@ -6,14 +6,11 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("compass")) {
PhoneGap.addResource("compass");
/**
* This class provides access to device Compass data.
* @constructor
*/
Compass = function() {
function Compass() {
/**
* The last known Compass position.
*/
@@ -23,7 +20,7 @@ Compass = function() {
* List of compass watch timers
*/
this.timers = {};
};
}
Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"];
@@ -116,4 +113,3 @@ PhoneGap.addConstructor(function() {
navigator.compass = new Compass();
}
});
};

View File

@@ -6,9 +6,6 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("contact")) {
PhoneGap.addResource("contact");
/**
* Contains information about a single contact.
* @param {DOMString} id unique identifier
@@ -298,4 +295,3 @@ PhoneGap.addConstructor(function() {
navigator.service.contacts = new Contacts();
}
});
};

View File

@@ -8,9 +8,6 @@
// TODO: Needs to be commented
if (!PhoneGap.hasResource("crypto")) {
PhoneGap.addResource("crypto");
var Crypto = function() {
};
@@ -37,4 +34,4 @@ PhoneGap.addConstructor(function() {
navigator.Crypto = new Crypto();
}
});
};

View File

@@ -6,15 +6,12 @@
* Copyright (c) 2010-2011, IBM Corporation
*/
if (!PhoneGap.hasResource("device")) {
PhoneGap.addResource("device");
/**
* This represents the mobile device, and provides properties for inspecting the model, version, UUID of the
* phone, etc.
* @constructor
*/
Device = function() {
function Device() {
this.available = PhoneGap.available;
this.platform = null;
this.version = null;
@@ -38,7 +35,7 @@ Device = function() {
console.log("Error initializing PhoneGap: " + e);
alert("Error initializing PhoneGap: "+e);
});
};
}
/**
* Get device info
@@ -98,8 +95,5 @@ Device.prototype.exitApp = function() {
};
PhoneGap.addConstructor(function() {
if (typeof navigator.device === "undefined") {
navigator.device = window.device = new Device();
}
navigator.device = window.device = new Device();
});
};

View File

@@ -6,19 +6,21 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("file")) {
PhoneGap.addResource("file");
/**
* This class provides generic read and write access to the mobile device file system.
* They are not used to read files from a server.
*/
/**
* This class provides some useful information about a file.
* This is the fields returned when navigator.fileMgr.getFileProperties()
* is called.
*/
FileProperties = function(filePath) {
function FileProperties(filePath) {
this.filePath = filePath;
this.size = 0;
this.lastModifiedDate = null;
};
}
/**
* Represents a single file.
@@ -29,17 +31,33 @@ FileProperties = function(filePath) {
* lastModifiedDate {Date} last modified date
* size {Number} size of the file in bytes
*/
File = function(name, fullPath, type, lastModifiedDate, size) {
function File(name, fullPath, type, lastModifiedDate, size) {
this.name = name || null;
this.fullPath = fullPath || null;
this.type = type || null;
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
}
/**
* Create an event object since we can't set target on DOM event.
*
* @param type
* @param target
*
*/
File._createEvent = function(type, target) {
// Can't create event object, since we can't set target (its readonly)
//var evt = document.createEvent('Events');
//evt.initEvent("onload", false, false);
var evt = {"type": type};
evt.target = target;
return evt;
};
FileError = function() {
function FileError() {
this.code = null;
};
}
// File error codes
// Found in DOMException
@@ -62,8 +80,8 @@ FileError.PATH_EXISTS_ERR = 12;
// File manager
//-----------------------------------------------------------------------------
FileMgr = function() {
};
function FileMgr() {
}
FileMgr.prototype.getFileProperties = function(filePath) {
return PhoneGap.exec(null, null, "File", "getFileProperties", [filePath]);
@@ -127,7 +145,7 @@ PhoneGap.addConstructor(function() {
* The root directory is the root of the file system.
* To read from the SD card, the file name is "sdcard/my_file.txt"
*/
FileReader = function() {
function FileReader() {
this.fileName = "";
this.readyState = 0;
@@ -145,7 +163,7 @@ FileReader = function() {
this.onerror = null; // When the read has failed (see errors).
this.onloadend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the read has been aborted. For instance, by invoking the abort() method.
};
}
// States
FileReader.EMPTY = 0;
@@ -167,15 +185,18 @@ FileReader.prototype.abort = function() {
// If error callback
if (typeof this.onerror === "function") {
this.onerror({"type":"error", "target":this});
evt = File._createEvent("error", this);
this.onerror(evt);
}
// If abort callback
if (typeof this.onabort === "function") {
this.oneabort({"type":"abort", "target":this});
evt = File._createEvent("abort", this);
this.onabort(evt);
}
// If load end callback
if (typeof this.onloadend === "function") {
this.onloadend({"type":"loadend", "target":this});
evt = File._createEvent("loadend", this);
this.onloadend(evt);
}
};
@@ -198,7 +219,8 @@ FileReader.prototype.readAsText = function(file, encoding) {
// If loadstart callback
if (typeof this.onloadstart === "function") {
this.onloadstart({"type":"loadstart", "target":this});
var evt = File._createEvent("loadstart", this);
this.onloadstart(evt);
}
// Default encoding is UTF-8
@@ -223,7 +245,8 @@ FileReader.prototype.readAsText = function(file, encoding) {
// If onload callback
if (typeof me.onload === "function") {
me.onload({"type":"load", "target":me});
evt = File._createEvent("load", me);
me.onload(evt);
}
// DONE state
@@ -231,7 +254,8 @@ FileReader.prototype.readAsText = function(file, encoding) {
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
evt = File._createEvent("loadend", me);
me.onloadend(evt);
}
},
@@ -244,13 +268,12 @@ FileReader.prototype.readAsText = function(file, encoding) {
}
// Save error
var fileError = new FileError();
fileError.code = e;
me.error = fileError;
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror({"type":"error", "target":me});
evt = File._createEvent("error", me);
me.onerror(evt);
}
// DONE state
@@ -258,7 +281,8 @@ FileReader.prototype.readAsText = function(file, encoding) {
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
evt = File._createEvent("loadend", me);
me.onloadend(evt);
}
}
);
@@ -285,7 +309,8 @@ FileReader.prototype.readAsDataURL = function(file) {
// If loadstart callback
if (typeof this.onloadstart === "function") {
this.onloadstart({"type":"loadstart", "target":this});
var evt = File._createEvent("loadstart", this);
this.onloadstart(evt);
}
var me = this;
@@ -307,7 +332,8 @@ FileReader.prototype.readAsDataURL = function(file) {
// If onload callback
if (typeof me.onload === "function") {
me.onload({"type":"load", "target":me});
evt = File._createEvent("load", me);
me.onload(evt);
}
// DONE state
@@ -315,7 +341,8 @@ FileReader.prototype.readAsDataURL = function(file) {
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
evt = File._createEvent("loadend", me);
me.onloadend(evt);
}
},
@@ -328,13 +355,12 @@ FileReader.prototype.readAsDataURL = function(file) {
}
// Save error
var fileError = new FileError();
fileError.code = e;
me.error = fileError;
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror({"type":"error", "target":me});
evt = File._createEvent("error", me);
me.onerror(evt);
}
// DONE state
@@ -342,7 +368,8 @@ FileReader.prototype.readAsDataURL = function(file) {
// If onloadend callback
if (typeof me.onloadend === "function") {
me.onloadend({"type":"loadend", "target":me});
evt = File._createEvent("loadend", me);
me.onloadend(evt);
}
}
);
@@ -382,7 +409,7 @@ FileReader.prototype.readAsArrayBuffer = function(file) {
* @param file {File} File object containing file properties
* @param append if true write to the end of the file, otherwise overwrite the file
*/
FileWriter = function(file) {
function FileWriter(file) {
this.fileName = "";
this.length = 0;
if (file) {
@@ -406,7 +433,7 @@ FileWriter = function(file) {
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
};
}
// States
FileWriter.INIT = 0;
@@ -429,21 +456,115 @@ FileWriter.prototype.abort = function() {
// If error callback
if (typeof this.onerror === "function") {
this.onerror({"type":"error", "target":this});
evt = File._createEvent("error", this);
this.onerror(evt);
}
// If abort callback
if (typeof this.onabort === "function") {
this.oneabort({"type":"abort", "target":this});
evt = File._createEvent("abort", this);
this.onabort(evt);
}
this.readyState = FileWriter.DONE;
// If write end callback
if (typeof this.onwriteend == "function") {
this.onwriteend({"type":"writeend", "target":this});
evt = File._createEvent("writeend", this);
this.onwriteend(evt);
}
};
/**
* @Deprecated: use write instead
*
* @param file to write the data to
* @param text to be written
* @param bAppend if true write to end of file, otherwise overwrite the file
*/
FileWriter.prototype.writeAsText = function(file, text, bAppend) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw FileError.INVALID_STATE_ERR;
}
if (bAppend !== true) {
bAppend = false; // for null values
}
this.fileName = file;
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
var evt = File._createEvent("writestart", me);
me.onwritestart(evt);
}
// Write file
navigator.fileMgr.writeAsText(file, text, bAppend,
// Success callback
function(r) {
var evt;
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// Save result
me.result = r;
// If onwrite callback
if (typeof me.onwrite === "function") {
evt = File._createEvent("write", me);
me.onwrite(evt);
}
// DONE state
me.readyState = FileWriter.DONE;
// If onwriteend callback
if (typeof me.onwriteend === "function") {
evt = File._createEvent("writeend", me);
me.onwriteend(evt);
}
},
// Error callback
function(e) {
var evt;
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// Save error
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
evt = File._createEvent("error", me);
me.onerror(evt);
}
// DONE state
me.readyState = FileWriter.DONE;
// If onwriteend callback
if (typeof me.onwriteend === "function") {
evt = File._createEvent("writeend", me);
me.onwriteend(evt);
}
}
);
};
/**
* Writes data to the file
*
@@ -462,7 +583,8 @@ FileWriter.prototype.write = function(text) {
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart({"type":"writestart", "target":me});
var evt = File._createEvent("writestart", me);
me.onwritestart(evt);
}
// Write file
@@ -483,7 +605,8 @@ FileWriter.prototype.write = function(text) {
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite({"type":"write", "target":me});
evt = File._createEvent("write", me);
me.onwrite(evt);
}
// DONE state
@@ -491,7 +614,8 @@ FileWriter.prototype.write = function(text) {
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend({"type":"writeend", "target":me});
evt = File._createEvent("writeend", me);
me.onwriteend(evt);
}
},
@@ -505,13 +629,12 @@ FileWriter.prototype.write = function(text) {
}
// Save error
var fileError = new FileError();
fileError.code = e;
me.error = fileError;
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror({"type":"error", "target":me});
evt = File._createEvent("error", me);
me.onerror(evt);
}
// DONE state
@@ -519,7 +642,8 @@ FileWriter.prototype.write = function(text) {
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend({"type":"writeend", "target":me});
evt = File._createEvent("writeend", me);
me.onwriteend(evt);
}
}
);
@@ -579,7 +703,8 @@ FileWriter.prototype.truncate = function(size) {
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart({"type":"writestart", "target":this});
var evt = File._createEvent("writestart", me);
me.onwritestart(evt);
}
// Write file
@@ -599,7 +724,8 @@ FileWriter.prototype.truncate = function(size) {
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite({"type":"write", "target":me});
evt = File._createEvent("write", me);
me.onwrite(evt);
}
// DONE state
@@ -607,7 +733,8 @@ FileWriter.prototype.truncate = function(size) {
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend({"type":"writeend", "target":me});
evt = File._createEvent("writeend", me);
me.onwriteend(evt);
}
},
@@ -620,13 +747,12 @@ FileWriter.prototype.truncate = function(size) {
}
// Save error
var fileError = new FileError();
fileError.code = e;
me.error = fileError;
me.error = e;
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror({"type":"error", "target":me});
evt = File._createEvent("error", me);
me.onerror(evt);
}
// DONE state
@@ -634,13 +760,14 @@ FileWriter.prototype.truncate = function(size) {
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend({"type":"writeend", "target":me});
evt = File._createEvent("writeend", me);
me.onwriteend(evt);
}
}
);
};
LocalFileSystem = function() {
function LocalFileSystem() {
};
// File error codes
@@ -767,7 +894,7 @@ LocalFileSystem.prototype._castDate = function(pluginResult) {
*
* {Date} modificationTime (readonly)
*/
Metadata = function() {
function Metadata() {
this.modificationTime=null;
};
@@ -777,7 +904,7 @@ Metadata = function() {
* @param {boolean} create file or directory if it doesn't exist
* @param {boolean} exclusive if true the command will fail if the file or directory exists
*/
Flags = function(create, exclusive) {
function Flags(create, exclusive) {
this.create = create || false;
this.exclusive = exclusive || false;
};
@@ -788,7 +915,7 @@ Flags = function(create, exclusive) {
* {DOMString} name the unique name of the file system (readonly)
* {DirectoryEntry} root directory of the file system (readonly)
*/
FileSystem = function() {
function FileSystem() {
this.name = null;
this.root = null;
};
@@ -802,7 +929,7 @@ FileSystem = function() {
* {DOMString} fullPath the absolute full path to the directory (readonly)
* {FileSystem} filesystem on which the directory resides (readonly)
*/
DirectoryEntry = function() {
function DirectoryEntry() {
this.isFile = false;
this.isDirectory = true;
this.name = null;
@@ -918,7 +1045,7 @@ DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCall
/**
* An interface that lists the files and directories in a directory.
*/
DirectoryReader = function(fullPath){
function DirectoryReader(fullPath){
this.fullPath = fullPath || null;
};
@@ -941,7 +1068,7 @@ DirectoryReader.prototype.readEntries = function(successCallback, errorCallback)
* {DOMString} fullPath the absolute full path to the file (readonly)
* {FileSystem} filesystem on which the directory resides (readonly)
*/
FileEntry = function() {
function FileEntry() {
this.isFile = true;
this.isDirectory = false;
this.name = null;
@@ -1055,4 +1182,3 @@ PhoneGap.addConstructor(function() {
if(typeof window.requestFileSystem == "undefined") window.requestFileSystem = pgLocalFileSystem.requestFileSystem;
if(typeof window.resolveLocalFileSystemURI == "undefined") window.resolveLocalFileSystemURI = pgLocalFileSystem.resolveLocalFileSystemURI;
});
};

View File

@@ -6,29 +6,26 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("filetransfer")) {
PhoneGap.addResource("filetransfer");
/**
* FileTransfer uploads a file to a remote server.
*/
FileTransfer = function() {};
function FileTransfer() {}
/**
* FileUploadResult
*/
FileUploadResult = function() {
function FileUploadResult() {
this.bytesSent = 0;
this.responseCode = null;
this.response = null;
};
}
/**
* FileTransferError
*/
FileTransferError = function() {
function FileTransferError() {
this.code = null;
};
}
FileTransferError.FILE_NOT_FOUND_ERR = 1;
FileTransferError.INVALID_URL_ERR = 2;
@@ -72,10 +69,9 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro
* @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg.
* @param params {Object} Object with key: value params to send to the server.
*/
FileUploadOptions = function(fileKey, fileName, mimeType, params) {
function FileUploadOptions(fileKey, fileName, mimeType, params) {
this.fileKey = fileKey || null;
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
};
};
}

View File

@@ -6,21 +6,18 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("geolocation")) {
PhoneGap.addResource("geolocation");
/**
* This class provides access to device GPS data.
* @constructor
*/
Geolocation = function() {
function Geolocation() {
// The last known GPS position.
this.lastPosition = null;
// Geolocation listeners
this.listeners = {};
};
}
/**
* Position error object
@@ -28,10 +25,10 @@ Geolocation = function() {
* @param code
* @param message
*/
PositionError = function(code, message) {
function PositionError(code, message) {
this.code = code;
this.message = message;
};
}
PositionError.PERMISSION_DENIED = 1;
PositionError.POSITION_UNAVAILABLE = 2;
@@ -194,4 +191,4 @@ PhoneGap.addConstructor(function() {
Geolocation.usingPhoneGap = true;
}
});
};

View File

@@ -6,9 +6,6 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("media")) {
PhoneGap.addResource("media");
/**
* List of media objects.
* PRIVATE
@@ -131,10 +128,10 @@ Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"];
* This class contains information about any Media errors.
* @constructor
*/
MediaError = function() {
function MediaError() {
this.code = null;
this.message = "";
};
}
MediaError.MEDIA_ERR_ABORTED = 1;
MediaError.MEDIA_ERR_NETWORK = 2;
@@ -201,4 +198,4 @@ Media.prototype.stopRecord = function() {
Media.prototype.release = function() {
PhoneGap.exec(null, null, "Media", "release", [this.id]);
};
};

View File

@@ -6,17 +6,14 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("network")) {
PhoneGap.addResource("network");
/**
* This class contains information about any NetworkStatus.
* @constructor
*/
NetworkStatus = function() {
function NetworkStatus() {
//this.code = null;
//this.message = "";
};
}
NetworkStatus.NOT_REACHABLE = 0;
NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1;
@@ -26,14 +23,14 @@ NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2;
* This class provides access to device Network data (reachability).
* @constructor
*/
Network = function() {
function Network() {
/**
* The last known Network status.
* { hostName: string, ipAddress: string,
remoteHostStatus: int(0/1/2), internetConnectionStatus: int(0/1/2), localWiFiConnectionStatus: int (0/2) }
*/
this.lastReachability = null;
};
}
/**
* Called by the geolocation framework when the reachability status has changed.
@@ -64,4 +61,4 @@ PhoneGap.addConstructor(function() {
navigator.network = new Network();
}
});
};

View File

@@ -6,14 +6,11 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("notification")) {
PhoneGap.addResource("notification");
/**
* This class provides access to notifications on the device.
*/
Notification = function() {
};
function Notification() {
}
/**
* Open a native alert dialog, with a customizable title and button text.
@@ -118,4 +115,4 @@ PhoneGap.addConstructor(function() {
navigator.notification = new Notification();
}
});
};

View File

@@ -6,7 +6,6 @@
* Copyright (c) 2010-2011, IBM Corporation
*/
if (typeof PhoneGap === "undefined") {
/**
* The order of events during page load and PhoneGap startup is as follows:
@@ -19,8 +18,6 @@ if (typeof PhoneGap === "undefined") {
* onPhoneGapInfoReady Internal event fired when device properties are available
* onDeviceReady User event fired to indicate that PhoneGap is ready
* onResume User event fired to indicate a start/resume lifecycle event
* onPause User event fired to indicate a pause lifecycle event
* onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
*
* The only PhoneGap events that user code should register for are:
* onDeviceReady
@@ -29,7 +26,6 @@ if (typeof PhoneGap === "undefined") {
* Listeners can be registered as:
* document.addEventListener("deviceready", myDeviceReadyListener, false);
* document.addEventListener("resume", myResumeListener, false);
* document.addEventListener("pause", myPauseListener, false);
*/
if (typeof(DeviceInfo) !== 'object') {
@@ -49,30 +45,6 @@ var PhoneGap = {
}
};
/**
* List of resource files loaded by PhoneGap.
* This is used to ensure JS and other files are loaded only once.
*/
PhoneGap.resources = {base: true};
/**
* Determine if resource has been loaded by PhoneGap
*
* @param name
* @return
*/
PhoneGap.hasResource = function(name) {
return PhoneGap.resources[name];
};
/**
* Add a resource to list of loaded resources by PhoneGap
*
* @param name
*/
PhoneGap.addResource = function(name) {
PhoneGap.resources[name] = true;
};
/**
* Custom pub-sub channel that can have functions subscribed to it
@@ -269,17 +241,6 @@ PhoneGap.onResume = new PhoneGap.Channel('onResume');
*/
PhoneGap.onPause = new PhoneGap.Channel('onPause');
/**
* onDestroy channel is fired when the PhoneGap native code
* is destroyed. It is used internally.
* Window.onunload should be used by the user.
*/
PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy');
PhoneGap.onDestroy.subscribeOnce(function() {
PhoneGap.shuttingDown = true;
});
PhoneGap.shuttingDown = false;
// _nativeReady is global variable that the native side can set
// to signify that the native code is ready. It is a global since
// it may be called before any PhoneGap JS is ready.
@@ -735,11 +696,6 @@ PhoneGap.JSCallbackToken = null;
*/
PhoneGap.JSCallback = function() {
// Exit if shutting down app
if (PhoneGap.shuttingDown) {
return;
}
// If polling flag was changed, start using polling from now on
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
@@ -751,17 +707,11 @@ PhoneGap.JSCallback = function() {
// Callback function when XMLHttpRequest is ready
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState === 4){
// Exit if shutting down app
if (PhoneGap.shuttingDown) {
return;
}
// If callback has JavaScript statement to execute
if (xmlhttp.status === 200) {
// Need to url decode the response and replace %20 with a space
var msg = decodeURIComponent(xmlhttp.responseText.replace(/\+/g, '%20'));
var msg = xmlhttp.responseText;
setTimeout(function() {
try {
var t = eval(msg);
@@ -836,11 +786,6 @@ PhoneGap.UsePolling = false; // T=use polling, F=use XHR
*/
PhoneGap.JSCallbackPolling = function() {
// Exit if shutting down app
if (PhoneGap.shuttingDown) {
return;
}
// If polling flag was changed, stop using polling from now on
if (!PhoneGap.UsePolling) {
PhoneGap.JSCallback();
@@ -919,17 +864,3 @@ PhoneGap.includeJavascript = function(jsfile, successCallback) {
el.src = jsfile;
id.appendChild(el);
};
/**
* This class is provided to bridge the gap between the way plugins were setup in 0.9.3 and 0.9.4.
* Users should be calling navigator.add.addService() instead of PluginManager.addService().
* @class
* @deprecated
*/
var PluginManager = {
addService: function(serviceType, className) {
navigator.app.addService(serviceType, className);
}
};
};

View File

@@ -6,9 +6,6 @@
* Copyright (c) 2010, IBM Corporation
*/
if (!PhoneGap.hasResource("position")) {
PhoneGap.addResource("position");
/**
* This class contains position information.
* @param {Object} lat
@@ -20,12 +17,12 @@ PhoneGap.addResource("position");
* @param {Object} vel
* @constructor
*/
Position = function(coords, timestamp) {
function Position(coords, timestamp) {
this.coords = coords;
this.timestamp = (timestamp !== 'undefined') ? timestamp : new Date().getTime();
};
}
Coordinates = function(lat, lng, alt, acc, head, vel, altacc) {
function Coordinates(lat, lng, alt, acc, head, vel, altacc) {
/**
* The latitude of the position.
*/
@@ -54,13 +51,13 @@ Coordinates = function(lat, lng, alt, acc, head, vel, altacc) {
* The altitude accuracy of the position.
*/
this.altitudeAccuracy = (altacc !== 'undefined') ? altacc : null;
};
}
/**
* This class specifies the options for requesting position data.
* @constructor
*/
PositionOptions = function() {
function PositionOptions() {
/**
* Specifies the desired position accuracy.
*/
@@ -70,19 +67,18 @@ PositionOptions = function() {
* is called.
*/
this.timeout = 10000;
};
}
/**
* This class contains information about any GSP errors.
* @constructor
*/
PositionError = function() {
function PositionError() {
this.code = null;
this.message = "";
};
}
PositionError.UNKNOWN_ERROR = 0;
PositionError.PERMISSION_DENIED = 1;
PositionError.POSITION_UNAVAILABLE = 2;
PositionError.TIMEOUT = 3;
};

View File

@@ -12,9 +12,6 @@
* most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required
*/
if (!PhoneGap.hasResource("storage")) {
PhoneGap.addResource("storage");
/**
* Storage object that is called by native code when performing queries.
* PRIVATE METHOD
@@ -310,21 +307,15 @@ var CupcakeLocalStorage = function() {
this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440);
var storage = {};
this.length = 0;
function setLength (length) {
this.length = length;
localStorage.length = length;
}
this.db.transaction(
function (transaction) {
var i;
transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))');
transaction.executeSql('SELECT * FROM storage', [], function(tx, result) {
for(var i = 0; i < result.rows.length; i++) {
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;
}
setLength(result.rows.length);
PhoneGap.initializationComplete("cupcakeStorage");
PhoneGap.initializationComplete("cupcakeStorage");
});
},
@@ -333,13 +324,13 @@ var CupcakeLocalStorage = function() {
}
);
this.setItem = function(key, val) {
if (typeof(storage[key])=='undefined') {
this.length++;
}
//console.log('set');
storage[key] = val;
this.db.transaction(
function (transaction) {
transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))');
transaction.executeSql('REPLACE INTO storage (id, body) values(?,?)', [key,val]);
}
);
@@ -349,62 +340,38 @@ var CupcakeLocalStorage = function() {
};
this.removeItem = function(key) {
delete storage[key];
this.length--;
this.db.transaction(
function (transaction) {
transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))');
transaction.executeSql('DELETE FROM storage where id=?', [key]);
}
);
};
this.clear = function() {
storage = {};
this.length = 0;
this.db.transaction(
function (transaction) {
transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))');
transaction.executeSql('DELETE FROM storage', []);
}
);
};
this.key = function(index) {
var i = 0;
for (var j in storage) {
if (i==index) {
return j;
} else {
i++;
}
}
return null;
}
};
} catch(e) {
alert("Database error "+e+".");
return;
}
};
PhoneGap.addConstructor(function() {
var setupDroidDB = function() {
if (typeof window.openDatabase === "undefined") {
navigator.openDatabase = window.openDatabase = DroidDB_openDatabase;
window.droiddb = new DroidDB();
}
if ((typeof window.openDatabase === "undefined") || (navigator.userAgent.indexOf("Android 3.0") != -1)) {
setupDroidDB();
} else {
window.openDatabase_orig = window.openDatabase;
window.openDatabase = function(name, version, desc, size) {
var db = window.openDatabase_orig(name, version, desc, size);
if (db == null) {
setupDroidDB();
return DroidDB_openDatabase(name, version, desc, size);
} else return db;
}
}
if (typeof window.localStorage === "undefined") {
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.0.9.5.min.js"></script>
<script src="phonegap.0.9.4.min.js"></script>
</head>
<body>

View File

@@ -13,7 +13,6 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLEncoder;
import java.util.LinkedList;
/**
@@ -222,10 +221,7 @@ public class CallbackServer implements Runnable {
}
else {
//System.out.println("CallbackServer -- sending item");
response = "HTTP/1.1 200 OK\r\n\r\n";
String js = this.getJavascript();
if (js != null)
response += URLEncoder.encode(js, "UTF-8");
response = "HTTP/1.1 200 OK\r\n\r\n"+this.getJavascript();
}
}
else {

View File

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

View File

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

View File

@@ -10,19 +10,18 @@ package com.phonegap;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.AlertDialog;
import android.widget.EditText;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Picture;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
@@ -32,6 +31,7 @@ import android.webkit.JsPromptResult;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebView.PictureListener;
import android.webkit.WebViewClient;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.WebSettings.LayoutAlgorithm;
@@ -111,8 +111,8 @@ public class DroidGap extends PhonegapActivity {
protected WebView appView;
protected WebViewClient webViewClient;
protected LinearLayout root;
public boolean bound = false;
private LinearLayout root;
boolean bound = false;
public CallbackServer callbackServer;
protected PluginManager pluginManager;
protected boolean cancelLoadUrl = false;
@@ -235,8 +235,7 @@ public class DroidGap extends PhonegapActivity {
// Bind PhoneGap objects to JavaScript
this.bindBrowser(this.appView);
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
// Add web view
root.addView(this.appView);
setContentView(root);
@@ -287,7 +286,6 @@ public class DroidGap extends PhonegapActivity {
this.addService("Storage", "com.phonegap.Storage");
this.addService("Temperature", "com.phonegap.TempListener");
this.addService("FileTransfer", "com.phonegap.FileTransfer");
this.addService("Capture", "com.phonegap.Capture");
}
/**
@@ -304,7 +302,8 @@ public class DroidGap extends PhonegapActivity {
// If spashscreen
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
if (this.splashscreen != 0) {
root.setBackgroundResource(this.splashscreen);
this.appView.setBackgroundColor(0);
this.appView.setBackgroundResource(splashscreen);
}
// If hideLoadingDialogOnPageLoad
@@ -601,19 +600,16 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onPause() {
super.onPause();
if (this.appView == null) {
return;
}
// Send pause event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// If app doesn't want to run in background
if (!this.keepRunning) {
// Forward to plugins
this.pluginManager.onPause();
// Send pause event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// Pause JavaScript timers (including setInterval)
this.appView.pauseTimers();
}
@@ -625,12 +621,6 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onResume() {
super.onResume();
if (this.appView == null) {
return;
}
// Send resume event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};");
// If app doesn't want to run in background
if (!this.keepRunning || this.activityResultKeepRunning) {
@@ -644,6 +634,9 @@ public class DroidGap extends PhonegapActivity {
// Forward to plugins
this.pluginManager.onResume();
// Send resume event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};");
// Resume JavaScript timers (including setInterval)
this.appView.resumeTimers();
}
@@ -656,14 +649,9 @@ public class DroidGap extends PhonegapActivity {
public void onDestroy() {
super.onDestroy();
if (this.appView != null) {
// Make sure pause event is sent if onPause hasn't been called before onDestroy
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// Send destroy event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};");
// Load blank page so that JavaScript onunload is called
this.appView.loadUrl("about:blank");
@@ -673,7 +661,6 @@ public class DroidGap extends PhonegapActivity {
if (this.callbackServer != null) {
this.callbackServer.destroy();
}
}
}
/**
@@ -785,7 +772,7 @@ public class DroidGap extends PhonegapActivity {
// Calling PluginManager.exec() to call a native service using
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
if (defaultValue.substring(0, 4).equals("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
@@ -826,28 +813,9 @@ public class DroidGap extends PhonegapActivity {
// Show dialog
else {
final JsPromptResult res = result;
AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx);
dlg.setMessage(message);
final EditText input = new EditText(this.ctx);
dlg.setView(input);
dlg.setCancelable(false);
dlg.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String usertext = input.getText().toString();
res.confirm(usertext);
}
});
dlg.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
res.cancel();
}
});
dlg.create();
dlg.show();
}
//@TODO:
result.confirm("");
}
return true;
}
@@ -980,38 +948,19 @@ public class DroidGap extends PhonegapActivity {
return true;
}
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
String address = null;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
}
else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
}
}
}
intent.setData(Uri.parse("sms:"+address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
System.out.println("Error sending sms "+url+":"+ e.toString());
}
return true;
}
// If sms:5551212
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
intent.putExtra("address", url.substring(4));
intent.setType("vnd.android-dir/mms-sms");
startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
System.out.println("Error sending sms "+url+":"+ e.toString());
}
return true;
}
// All else
else {
@@ -1061,8 +1010,16 @@ public class DroidGap extends PhonegapActivity {
// from the JS side when the JS gets to that code.
appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}");
// Make app view visible
appView.setVisibility(View.VISIBLE);
// If splash screen is showing, clear it
if (this.ctx.splashscreen != 0) {
this.ctx.splashscreen = 0;
appView.setPictureListener(new PictureListener(){
public void onNewPicture(WebView viewtwo, Picture picture) {
appView.setBackgroundResource(0);
appView.setPictureListener(null);
}
});
}
// Stop "app loading" spinner if showing
if (this.ctx.hideLoadingDialogOnPageLoad) {
@@ -1109,9 +1066,6 @@ public class DroidGap extends PhonegapActivity {
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (this.appView == null) {
return super.onKeyDown(keyCode, event);
}
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {

View File

@@ -272,7 +272,7 @@ public class FileTransfer extends Plugin {
for (Iterator iter = params.keys(); iter.hasNext();) {
Object key = iter.next();
dos.writeBytes(LINE_START + BOUNDRY + LINE_END);
dos.writeBytes("Content-Disposition: form-data; name=\"" + key.toString() + "\";");
dos.writeBytes("Content-Disposition: form-data; name=\"" + key.toString() + "\"; ");
dos.writeBytes(LINE_END + LINE_END);
dos.writeBytes(params.getString(key.toString()));
dos.writeBytes(LINE_END);
@@ -315,13 +315,7 @@ public class FileTransfer extends Plugin {
//------------------ read the SERVER RESPONSE
StringBuffer responseString = new StringBuffer("");
DataInputStream inStream;
try {
inStream = new DataInputStream ( conn.getInputStream() );
} catch(FileNotFoundException e) {
throw new IOException("Received error from server");
}
DataInputStream inStream = new DataInputStream ( conn.getInputStream() );
String line;
while (( line = inStream.readLine()) != null) {
responseString.append(line);

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-2011, IBM Corporation
* Copyright (c) 2010, IBM Corporation
*/
package com.phonegap;
@@ -836,19 +836,49 @@ public class FileUtils extends Plugin {
* @return T=returns value
*/
public boolean isSynch(String action) {
if (action.equals("testSaveLocationExists")) {
return true;
}
else if (action.equals("getFreeDiskSpace")) {
return true;
}
else if (action.equals("testFileExists")) {
return true;
}
else if (action.equals("testDirectoryExists")) {
return true;
}
return false;
if (action.equals("readAsText")) {
return false;
}
else if (action.equals("readAsDataURL")) {
return false;
}
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;
}
//--------------------------------------------------------------------------
@@ -912,7 +942,7 @@ public class FileUtils extends Plugin {
* @param filename
* @return a mime type
*/
public static String getMimeType(String filename) {
private String getMimeType(String filename) {
MimeTypeMap map = MimeTypeMap.getSingleton();
return map.getMimeTypeFromExtension(map.getFileExtensionFromUrl(filename));
}

View File

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

View File

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

@@ -21,7 +21,7 @@ class Classic
def setup
@android_dir = File.expand_path(File.dirname(__FILE__).gsub(/lib$/,''))
@framework_dir = File.join(@android_dir, "framework")
@icon = File.join(@www, 'icon.png') unless !@icon.nil? && File.exists?(@icon)
@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,