diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js
index ee853986..aa1c43e9 100755
--- a/framework/assets/js/file.js
+++ b/framework/assets/js/file.js
@@ -22,6 +22,23 @@ function FileProperties(filePath) {
     this.lastModifiedDate = null;
 }
 
+/**
+ * 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 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.
  *
@@ -54,6 +71,10 @@ FileError.ENCODING_ERR = 5;
 FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
 FileError.INVALID_STATE_ERR = 7;
 FileError.SYNTAX_ERR = 8;
+FileError.INVALID_MODIFICATION_ERR = 9;
+FileError.QUOTA_EXCEEDED_ERR = 10;
+FileError.TYPE_MISMATCH_ERR = 11;
+FileError.PATH_EXISTS_ERR = 12;
 
 //-----------------------------------------------------------------------------
 // File manager
@@ -63,16 +84,12 @@ function FileMgr() {
 }
 
 FileMgr.prototype.getFileProperties = function(filePath) {
-    return PhoneGap.exec(null, null, "File", "getFile", [filePath]);
+    return PhoneGap.exec(null, null, "File", "getFileProperties", [filePath]);
 };
 
 FileMgr.prototype.getFileBasePaths = function() {
 };
 
-FileMgr.prototype.getRootPaths = function() {
-    return PhoneGap.exec(null, null, "File", "getRootPaths", []);
-};
-
 FileMgr.prototype.testSaveLocationExists = function(successCallback, errorCallback) {
     return PhoneGap.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []);
 };
@@ -85,18 +102,6 @@ FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, error
     return PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]);
 };
 
-FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) {
-    return PhoneGap.exec(successCallback, errorCallback, "File", "createDirectory", [dirName]);
-};
-
-FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) {
-    return PhoneGap.exec(successCallback, errorCallback, "File", "deleteDirectory", [dirName]);
-};
-
-FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) {
-    return PhoneGap.exec(successCallback, errorCallback, "File", "deleteFile", [fileName]);
-};
-
 FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) {
     return PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []);
 };
@@ -198,11 +203,16 @@ FileReader.prototype.abort = function() {
 /**
  * Read text file.
  *
- * @param file          The name of the file
+ * @param file          {File} File object containing file properties
  * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
  */
 FileReader.prototype.readAsText = function(file, encoding) {
-    this.fileName = file;
+    this.fileName = "";
+	if (typeof file.fullPath === "undefined") {
+		this.fileName = file;
+	} else {
+		this.fileName = file.fullPath;
+	}
 
     // LOADING state
     this.readyState = FileReader.LOADING;
@@ -219,7 +229,7 @@ FileReader.prototype.readAsText = function(file, encoding) {
     var me = this;
 
     // Read file
-    navigator.fileMgr.readAsText(file, enc,
+    navigator.fileMgr.readAsText(this.fileName, enc,
 
         // Success callback
         function(r) {
@@ -284,10 +294,15 @@ FileReader.prototype.readAsText = function(file, encoding) {
  * A data url is of the form:
  *      data:[<mediatype>][;base64],<data>
  *
- * @param file          The name of the file
+ * @param file          {File} File object containing file properties
  */
 FileReader.prototype.readAsDataURL = function(file) {
-    this.fileName = file;
+	this.fileName = "";
+    if (typeof file.fullPath === "undefined") {
+        this.fileName = file;
+    } else {
+        this.fileName = file.fullPath;
+    }
 
     // LOADING state
     this.readyState = FileReader.LOADING;
@@ -301,7 +316,7 @@ FileReader.prototype.readAsDataURL = function(file) {
     var me = this;
 
     // Read file
-    navigator.fileMgr.readAsDataURL(file,
+    navigator.fileMgr.readAsDataURL(this.fileName,
 
         // Success callback
         function(r) {
@@ -363,7 +378,7 @@ FileReader.prototype.readAsDataURL = function(file) {
 /**
  * Read file and return data as a binary data.
  *
- * @param file          The name of the file
+ * @param file          {File} File object containing file properties
  */
 FileReader.prototype.readAsBinaryString = function(file) {
     // TODO - Can't return binary data to browser.
@@ -373,7 +388,7 @@ FileReader.prototype.readAsBinaryString = function(file) {
 /**
  * Read file and return data as a binary data.
  *
- * @param file          The name of the file
+ * @param file          {File} File object containing file properties
  */
 FileReader.prototype.readAsArrayBuffer = function(file) {
     // TODO - Can't return binary data to browser.
@@ -391,19 +406,18 @@ FileReader.prototype.readAsArrayBuffer = function(file) {
  *      The root directory is the root of the file system.
  *      To write to the SD card, the file name is "sdcard/my_file.txt"
  *      
- * @param filePath the file to write to
+ * @param file {File} File object containing file properties
  * @param append if true write to the end of the file, otherwise overwrite the file
  */
-function FileWriter(filePath, append) {
+function FileWriter(file) {
     this.fileName = "";
     this.length = 0;
-	if (filePath) {
-		var f = navigator.fileMgr.getFileProperties(filePath);
-	    this.fileName = f.name;
-	    this.length = f.size;
+	if (file) {
+	    this.fileName = file.fullPath || file;
+	    this.length = file.size || 0;
 	}
     // default is to write at the beginning of the file
-    this.position = (append !== true) ? 0 : this.length;
+    this.position = 0;
 
     this.readyState = 0; // EMPTY
 
@@ -753,3 +767,418 @@ FileWriter.prototype.truncate = function(size) {
     );
 };
 
+function LocalFileSystem() {
+};
+
+// File error codes
+LocalFileSystem.TEMPORARY = 0;
+LocalFileSystem.PERSISTENT = 1;
+LocalFileSystem.RESOURCE = 2;
+LocalFileSystem.APPLICATION = 3;
+
+/**
+ * Requests a filesystem in which to store application data.
+ * 
+ * @param {int} type of file system being requested
+ * @param {Function} successCallback is called with the new FileSystem
+ * @param {Function} errorCallback is called with a FileError
+ */
+LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) {
+	if (type < 0 || type > 3) {
+		if (typeof errorCallback == "function") {
+			errorCallback({
+				"code": FileError.SYNTAX_ERR
+			});
+		}
+	}
+	else {
+		PhoneGap.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]);
+	}
+};
+
+/**
+ * 
+ * @param {DOMString} uri referring to a local file in a filesystem
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]);
+};
+
+/**
+* This function returns and array of contacts.  It is required as we need to convert raw 
+* JSON objects into concrete Contact objects.  Currently this method is called after 
+* navigator.service.contacts.find but before the find methods success call back.
+* 
+* @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects.
+* @returns an entry 
+*/
+LocalFileSystem.prototype._castFS = function(pluginResult) {
+    var entry = null;
+    entry = new DirectoryEntry();
+    entry.isDirectory = pluginResult.message.root.isDirectory;
+    entry.isFile = pluginResult.message.root.isFile;
+    entry.name = pluginResult.message.root.name;
+    entry.fullPath = pluginResult.message.root.fullPath;
+    pluginResult.message.root = entry;
+    return pluginResult;    
+}
+
+LocalFileSystem.prototype._castEntry = function(pluginResult) {
+    var entry = null;
+    if (pluginResult.message.isDirectory) {
+        console.log("This is a dir");
+        entry = new DirectoryEntry();
+    }
+    else if (pluginResult.message.isFile) {
+        console.log("This is a file");
+        entry = new FileEntry();
+    }
+    entry.isDirectory = pluginResult.message.isDirectory;
+    entry.isFile = pluginResult.message.isFile;
+    entry.name = pluginResult.message.name;
+    entry.fullPath = pluginResult.message.fullPath;
+    pluginResult.message = entry;
+    return pluginResult;    
+}
+
+LocalFileSystem.prototype._castEntries = function(pluginResult) {
+    var entries = pluginResult.message;
+	var retVal = []; 
+	for (i=0; i<entries.length; i++) {
+		retVal.push(window.localFileSystem._createEntry(entries[i]));
+	}
+    pluginResult.message = retVal;
+    return pluginResult;    
+}
+
+LocalFileSystem.prototype._createEntry = function(castMe) {
+	var entry = null;
+    if (castMe.isDirectory) {
+        console.log("This is a dir");
+        entry = new DirectoryEntry();
+    }
+    else if (castMe.isFile) {
+        console.log("This is a file");
+        entry = new FileEntry();
+    }
+    entry.isDirectory = castMe.isDirectory;
+    entry.isFile = castMe.isFile;
+    entry.name = castMe.name;
+    entry.fullPath = castMe.fullPath;
+    return entry;    
+	
+}
+
+LocalFileSystem.prototype._castDate = function(pluginResult) {
+	if (pluginResult.message.modificationTime) {
+	    var modTime = new Date(pluginResult.message.modificationTime);
+	    pluginResult.message.modificationTime = modTime;
+	}
+	else if (pluginResult.message.lastModifiedDate) {
+		var file = new File();
+        file.size = pluginResult.message.size;
+        file.type = pluginResult.message.type;
+        file.name = pluginResult.message.name;
+        file.fullPath = pluginResult.message.fullPath;
+		file.lastModifedDate = new Date(pluginResult.message.lastModifiedDate);
+	    pluginResult.message = file;		
+	}
+	
+    return pluginResult;	
+}
+
+/**
+ * Information about the state of the file or directory
+ * 
+ * {Date} modificationTime (readonly)
+ */
+function Metadata() {
+    this.modificationTime=null;
+};
+
+/**
+ * Supplies arguments to methods that lookup or create files and directories
+ * 
+ * @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
+ */
+function Flags(create, exclusive) {
+    this.create = create || false;
+    this.exclusive = exclusive || false;
+};
+
+/**
+ * An interface representing a file system
+ * 
+ * {DOMString} name the unique name of the file system (readonly)
+ * {DirectoryEntry} root directory of the file system (readonly)
+ */
+function FileSystem() {
+    this.name = null;
+    this.root = null;
+};
+
+/**
+ * An interface representing a directory on the file system.
+ * 
+ * {boolean} isFile always false (readonly)
+ * {boolean} isDirectory always true (readonly)
+ * {DOMString} name of the directory, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the directory (readonly)
+ * {FileSystem} filesystem on which the directory resides (readonly)
+ */
+function DirectoryEntry() {
+    this.isFile = false;
+    this.isDirectory = true;
+    this.name = null;
+    this.fullPath = null;
+    this.filesystem = null;
+};
+
+/**
+ * Copies a directory to a new location
+ * 
+ * @param {DirectoryEntry} parent the directory to which to copy the entry
+ * @param {DOMString} newName the new name of the entry, defaults to the current name
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]);
+};
+
+/**
+ * Looks up the metadata of the entry
+ * 
+ * @param {Function} successCallback is called with a Metadata object
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getMetadata = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]);
+};
+
+/**
+ * Gets the parent of the entry
+ * 
+ * @param {Function} successCallback is called with a parent entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getParent = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]);
+};
+
+/**
+ * Moves a directory to a new location
+ * 
+ * @param {DirectoryEntry} parent the directory to which to move the entry
+ * @param {DOMString} newName the new name of the entry, defaults to the current name
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]);
+};
+
+/**
+ * Removes the entry
+ * 
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.remove = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]);
+};
+
+/**
+ * Returns a URI that can be used to identify this entry.
+ * 
+ * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
+ * @return uri
+ */
+DirectoryEntry.prototype.toURI = function(mimeType) {
+    return "file://" + this.fullPath;
+};
+
+/**
+ * Creates a new DirectoryReader to read entries from this directory
+ */
+DirectoryEntry.prototype.createReader = function(successCallback, errorCallback) {
+    return new DirectoryReader(this.fullPath);
+};
+
+/**
+ * Creates or looks up a directory
+ * 
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
+ * @param {Flags} options to create or excluively create the directory
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getDirectory", [this.fullPath, path, options]);
+};
+
+/**
+ * Creates or looks up a file
+ * 
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
+ * @param {Flags} options to create or excluively create the file
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getFile", [this.fullPath, path, options]);
+};
+
+/**
+ * Deletes a directory and all of it's contents
+ * 
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "removeRecursively", [this.fullPath]);
+};
+
+/**
+ * An interface that lists the files and directories in a directory.
+ */
+function DirectoryReader(fullPath){
+	this.fullPath = fullPath || null;    
+};
+
+/**
+ * Returns a list of entries from a directory.
+ * 
+ * @param {Function} successCallback is called with a list of entries
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "readEntries", [this.fullPath]);
+}
+ 
+/**
+ * An interface representing a directory on the file system.
+ * 
+ * {boolean} isFile always true (readonly)
+ * {boolean} isDirectory always false (readonly)
+ * {DOMString} name of the file, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the file (readonly)
+ * {FileSystem} filesystem on which the directory resides (readonly)
+ */
+function FileEntry() {
+    this.isFile = true;
+    this.isDirectory = false;
+    this.name = null;
+    this.fullPath = null;
+    this.filesystem = null;
+};
+
+/**
+ * Copies a file to a new location
+ * 
+ * @param {DirectoryEntry} parent the directory to which to copy the entry
+ * @param {DOMString} newName the new name of the entry, defaults to the current name
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]);
+};
+
+/**
+ * Looks up the metadata of the entry
+ * 
+ * @param {Function} successCallback is called with a Metadata object
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.getMetadata = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]);
+};
+
+/**
+ * Gets the parent of the entry
+ * 
+ * @param {Function} successCallback is called with a parent entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.getParent = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]);
+};
+
+/**
+ * Moves a directory to a new location
+ * 
+ * @param {DirectoryEntry} parent the directory to which to move the entry
+ * @param {DOMString} newName the new name of the entry, defaults to the current name
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]);
+};
+
+/**
+ * Removes the entry
+ * 
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.remove = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]);
+};
+
+/**
+ * Returns a URI that can be used to identify this entry.
+ * 
+ * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
+ * @return uri
+ */
+FileEntry.prototype.toURI = function(mimeType) {
+    return "file://" + this.fullPath;
+};
+
+/**
+ * Creates a new FileWriter associated with the file that this FileEntry represents.
+ * 
+ * @param {Function} successCallback is called with the new FileWriter
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.createWriter = function(successCallback, errorCallback) {
+	var writer = new FileWriter(this.fullPath);
+
+    if (writer.fileName == null || writer.fileName == "") {
+		if (typeof errorCallback == "function") {
+			errorCallback({
+				"code": FileError.INVALID_STATE_ERR
+			});
+		}
+	}
+	
+    if (typeof successCallback == "function") {
+        successCallback(writer);
+    }
+};
+
+/**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ * 
+ * @param {Function} successCallback is called with the new File object
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.file = function(successCallback, errorCallback) {
+    PhoneGap.exec(successCallback, errorCallback, "File", "getFileMetadata", [this.fullPath]);
+};
+
+/**
+ * Add the FileSystem interface into the browser.
+ */
+PhoneGap.addConstructor(function() {
+	var pgLocalFileSystem = new LocalFileSystem();
+	// Needed for cast methods
+    if(typeof window.localFileSystem == "undefined") window.localFileSystem  = pgLocalFileSystem;
+    if(typeof window.requestFileSystem == "undefined") window.requestFileSystem  = pgLocalFileSystem.requestFileSystem;
+    if(typeof window.resolveLocalFileSystemURI == "undefined") window.resolveLocalFileSystemURI = pgLocalFileSystem.resolveLocalFileSystemURI;
+});
diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base
index 8866a377..e98a3726 100755
--- a/framework/assets/js/phonegap.js.base
+++ b/framework/assets/js/phonegap.js.base
@@ -394,42 +394,44 @@ PhoneGap.stringify = function(args) {
         var s = "[";
         var i, type, start, name, nameType, a;
         for (i = 0; i < args.length; i++) {
-            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;
-                    }
+            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 + '}';
-            } else {
-                a = args[i].replace(/\\/g, '\\\\');
-                a = a.replace(/"/g, '\\"');
-                s = s + '"' + a + '"';
             }
         }
         s = s + "]";
diff --git a/framework/src/com/phonegap/DirectoryManager.java b/framework/src/com/phonegap/DirectoryManager.java
index b452c2b3..a659527d 100644
--- a/framework/src/com/phonegap/DirectoryManager.java
+++ b/framework/src/com/phonegap/DirectoryManager.java
@@ -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;
-	}
-
 }
\ No newline at end of file
diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java
index 37fa0fea..b88fc962 100755
--- a/framework/src/com/phonegap/FileUtils.java
+++ b/framework/src/com/phonegap/FileUtils.java
@@ -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 {
     	}
     }  
 }
+
diff --git a/framework/src/com/phonegap/api/PluginResult.java b/framework/src/com/phonegap/api/PluginResult.java
index d042d64c..41371702 100755
--- a/framework/src/com/phonegap/api/PluginResult.java
+++ b/framework/src/com/phonegap/api/PluginResult.java
@@ -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();
diff --git a/framework/src/com/phonegap/file/EncodingException.java b/framework/src/com/phonegap/file/EncodingException.java
new file mode 100644
index 00000000..7dcf7af0
--- /dev/null
+++ b/framework/src/com/phonegap/file/EncodingException.java
@@ -0,0 +1,9 @@
+package com.phonegap.file;
+
+public class EncodingException extends Exception {
+
+	public EncodingException(String message) {
+		super(message);
+	}
+	
+}
diff --git a/framework/src/com/phonegap/file/FileExistsException.java b/framework/src/com/phonegap/file/FileExistsException.java
new file mode 100644
index 00000000..22c40ab2
--- /dev/null
+++ b/framework/src/com/phonegap/file/FileExistsException.java
@@ -0,0 +1,9 @@
+package com.phonegap.file;
+
+public class FileExistsException extends Exception {
+
+	public FileExistsException(String msg) {
+		super(msg);
+	}
+
+}
diff --git a/framework/src/com/phonegap/file/InvalidModificationException.java b/framework/src/com/phonegap/file/InvalidModificationException.java
new file mode 100644
index 00000000..bd706299
--- /dev/null
+++ b/framework/src/com/phonegap/file/InvalidModificationException.java
@@ -0,0 +1,9 @@
+package com.phonegap.file;
+
+public class InvalidModificationException extends Exception {
+
+	public InvalidModificationException(String message) {
+		super(message);
+	}
+
+}
diff --git a/framework/src/com/phonegap/file/NoModificationAllowedException.java b/framework/src/com/phonegap/file/NoModificationAllowedException.java
new file mode 100644
index 00000000..d120fb8f
--- /dev/null
+++ b/framework/src/com/phonegap/file/NoModificationAllowedException.java
@@ -0,0 +1,9 @@
+package com.phonegap.file;
+
+public class NoModificationAllowedException extends Exception {
+
+	public NoModificationAllowedException(String message) {
+		super(message);
+	}
+
+}
diff --git a/framework/src/com/phonegap/file/TypeMismatchException.java b/framework/src/com/phonegap/file/TypeMismatchException.java
new file mode 100644
index 00000000..7af86698
--- /dev/null
+++ b/framework/src/com/phonegap/file/TypeMismatchException.java
@@ -0,0 +1,9 @@
+package com.phonegap.file;
+
+public class TypeMismatchException extends Exception {
+
+	public TypeMismatchException(String message) {
+		super(message);
+	}
+
+}