cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ste...@apache.org
Subject [15/53] Moving them temporarily to core in this repo, we're going to move this again before the release, because change is scary
Date Tue, 30 Apr 2013 21:35:31 GMT
http://git-wip-us.apache.org/repos/asf/cordova-android/blob/5cc3852c/framework/src/org/apache/cordova/core/FileUtils.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/core/FileUtils.java
index 394feb0,0000000..88b667b
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/core/FileUtils.java
+++ b/framework/src/org/apache/cordova/core/FileUtils.java
@@@ -1,1186 -1,0 +1,1061 @@@
 +/*
 +       Licensed to the Apache Software Foundation (ASF) under one
 +       or more contributor license agreements.  See the NOTICE file
 +       distributed with this work for additional information
 +       regarding copyright ownership.  The ASF licenses this file
 +       to you under the Apache License, Version 2.0 (the
 +       "License"); you may not use this file except in compliance
 +       with the License.  You may obtain a copy of the License at
 +
 +         http://www.apache.org/licenses/LICENSE-2.0
 +
 +       Unless required by applicable law or agreed to in writing,
 +       software distributed under the License is distributed on an
 +       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +       KIND, either express or implied.  See the License for the
 +       specific language governing permissions and limitations
 +       under the License.
 +*/
 +package org.apache.cordova.core;
 +
- import java.io.*;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.net.URLDecoder;
- import java.nio.channels.FileChannel;
++import android.database.Cursor;
++import android.net.Uri;
++import android.os.Environment;
++import android.provider.MediaStore;
++import android.util.Log;
 +
 +import org.apache.commons.codec.binary.Base64;
 +import org.apache.cordova.api.CallbackContext;
- import org.apache.cordova.api.CordovaInterface;
 +import org.apache.cordova.api.CordovaPlugin;
 +import org.apache.cordova.api.PluginResult;
 +import org.apache.cordova.file.EncodingException;
 +import org.apache.cordova.file.FileExistsException;
 +import org.apache.cordova.file.InvalidModificationException;
 +import org.apache.cordova.file.NoModificationAllowedException;
 +import org.apache.cordova.file.TypeMismatchException;
 +import org.json.JSONArray;
 +import org.json.JSONException;
 +import org.json.JSONObject;
 +
- //import android.content.Context;
- import android.database.Cursor;
- import android.net.Uri;
- import android.os.Environment;
- import android.provider.MediaStore;
- import android.webkit.MimeTypeMap;
- 
- //import android.app.Activity;
++import java.io.BufferedInputStream;
++import java.io.ByteArrayInputStream;
++import java.io.ByteArrayOutputStream;
++import java.io.File;
++import java.io.FileInputStream;
++import java.io.FileNotFoundException;
++import java.io.FileOutputStream;
++import java.io.IOException;
++import java.io.RandomAccessFile;
++import java.net.MalformedURLException;
++import java.net.URL;
++import java.net.URLDecoder;
++import java.nio.channels.FileChannel;
 +
 +/**
 + * This class provides SD card file and directory services to JavaScript.
 + * Only files on the SD card can be accessed.
 + */
 +public class FileUtils extends CordovaPlugin {
 +    @SuppressWarnings("unused")
-     private static final String LOG_TAG = "FileUtils";
-     private static final String _DATA = "_data";    // The column name where the file path is stored
++    private static final String LOG_TAG = "FilePlugin";
 +
 +    public static int NOT_FOUND_ERR = 1;
 +    public static int SECURITY_ERR = 2;
 +    public static int ABORT_ERR = 3;
 +
 +    public static int NOT_READABLE_ERR = 4;
 +    public static int ENCODING_ERR = 5;
 +    public static int NO_MODIFICATION_ALLOWED_ERR = 6;
 +    public static int INVALID_STATE_ERR = 7;
 +    public static int SYNTAX_ERR = 8;
 +    public static int INVALID_MODIFICATION_ERR = 9;
 +    public static int QUOTA_EXCEEDED_ERR = 10;
 +    public static int TYPE_MISMATCH_ERR = 11;
 +    public static int PATH_EXISTS_ERR = 12;
 +
 +    public static int TEMPORARY = 0;
 +    public static int PERSISTENT = 1;
 +    public static int RESOURCE = 2;
 +    public static int APPLICATION = 3;
 +
-     FileReader f_in;
-     FileWriter f_out;
- 
 +    /**
 +     * Constructor.
 +     */
 +    public FileUtils() {
 +    }
 +
 +    /**
 +     * Executes the request and returns whether the action was valid.
 +     *
 +     * @param action 		The action to execute.
 +     * @param args 		JSONArray of arguments for the plugin.
 +     * @param callbackContext	The callback context used when calling back into JavaScript.
 +     * @return 			True if the action was valid, false otherwise.
 +     */
 +    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
 +        try {
 +            if (action.equals("testSaveLocationExists")) {
 +                boolean b = DirectoryManager.testSaveLocationExists();
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
 +            }
 +            else if (action.equals("getFreeDiskSpace")) {
 +                long l = DirectoryManager.getFreeDiskSpace(false);
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
 +            }
 +            else if (action.equals("testFileExists")) {
 +                boolean b = DirectoryManager.testFileExists(args.getString(0));
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
 +            }
 +            else if (action.equals("testDirectoryExists")) {
 +                boolean b = DirectoryManager.testFileExists(args.getString(0));
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
 +            }
 +            else if (action.equals("readAsText")) {
-                 int start = 0;
-                 int end = Integer.MAX_VALUE;
-                 if (args.length() >= 3) {
-                     start = args.getInt(2);
-                 }
-                 if (args.length() >= 4) {
-                     end = args.getInt(3);
-                 }
++                String encoding = args.getString(1);
++                int start = args.getInt(2);
++                int end = args.getInt(3);
 +
-                 String s = this.readAsText(args.getString(0), args.getString(1), start, end);
-                 callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
++                this.readFileAs(args.getString(0), start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
 +            }
 +            else if (action.equals("readAsDataURL")) {
-                 int start = 0;
-                 int end = Integer.MAX_VALUE;
-                 if (args.length() >= 2) {
-                     start = args.getInt(1);
-                 }
-                 if (args.length() >= 3) {
-                     end = args.getInt(2);
-                 }
++                int start = args.getInt(1);
++                int end = args.getInt(2);
 +
-                 String s = this.readAsDataURL(args.getString(0), start, end);
-                 callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
++                this.readFileAs(args.getString(0), start, end, callbackContext, null, -1);
 +            }
 +            else if (action.equals("readAsArrayBuffer")) {
-                 int start = 0;
-                 int end = Integer.MAX_VALUE;
-                 if (args.length() >= 2) {
-                     start = args.getInt(1);
-                 }
-                 if (args.length() >= 3) {
-                     end = args.getInt(2);
-                 }
++                int start = args.getInt(1);
++                int end = args.getInt(2);
++
++                this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
++            }
++            else if (action.equals("readAsBinaryString")) {
++                int start = args.getInt(1);
++                int end = args.getInt(2);
 +
-                 byte[] s = this.readAsBinary(args.getString(0), start, end);
-                 callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
++                this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
 +            }
 +            else if (action.equals("write")) {
 +                long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2));
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
 +            }
 +            else if (action.equals("truncate")) {
 +                long fileSize = this.truncateFile(args.getString(0), args.getLong(1));
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
 +            }
 +            else if (action.equals("requestFileSystem")) {
 +                long size = args.optLong(1);
 +                if (size != 0 && size > (DirectoryManager.getFreeDiskSpace(true) * 1024)) {
 +                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
 +                } else {
 +                    JSONObject obj = requestFileSystem(args.getInt(0));
 +                    callbackContext.success(obj);
 +                }
 +            }
 +            else if (action.equals("resolveLocalFileSystemURI")) {
 +                JSONObject obj = resolveLocalFileSystemURI(args.getString(0));
 +                callbackContext.success(obj);
 +            }
 +            else if (action.equals("getMetadata")) {
 +                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getMetadata(args.getString(0))));
 +            }
 +            else if (action.equals("getFileMetadata")) {
 +                JSONObject obj = getFileMetadata(args.getString(0));
 +                callbackContext.success(obj);
 +            }
 +            else if (action.equals("getParent")) {
 +                JSONObject obj = getParent(args.getString(0));
 +                callbackContext.success(obj);
 +            }
 +            else if (action.equals("getDirectory")) {
 +                JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), true);
 +                callbackContext.success(obj);
 +            }
 +            else if (action.equals("getFile")) {
 +                JSONObject obj = getFile(args.getString(0), args.getString(1), args.optJSONObject(2), false);
 +                callbackContext.success(obj);
 +            }
 +            else if (action.equals("remove")) {
 +                boolean success;
 +
 +                success = remove(args.getString(0));
 +
 +                if (success) {
 +                    notifyDelete(args.getString(0));
 +                    callbackContext.success();
 +                } else {
 +                    callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
 +                }
 +            }
 +            else if (action.equals("removeRecursively")) {
 +                boolean success = removeRecursively(args.getString(0));
 +                if (success) {
 +                    callbackContext.success();
 +                } else {
 +                    callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
 +                }
 +            }
 +            else if (action.equals("moveTo")) {
 +                JSONObject entry = transferTo(args.getString(0), args.getString(1), args.getString(2), true);
 +                callbackContext.success(entry);
 +            }
 +            else if (action.equals("copyTo")) {
 +                JSONObject entry = transferTo(args.getString(0), args.getString(1), args.getString(2), false);
 +                callbackContext.success(entry);
 +            }
 +            else if (action.equals("readEntries")) {
 +                JSONArray entries = readEntries(args.getString(0));
 +                callbackContext.success(entries);
 +            }
 +            else {
 +                return false;
 +            }
 +        } catch (FileNotFoundException e) {
 +            callbackContext.error(FileUtils.NOT_FOUND_ERR);
 +        } catch (FileExistsException e) {
 +            callbackContext.error(FileUtils.PATH_EXISTS_ERR);
 +        } catch (NoModificationAllowedException e) {
 +            callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
 +        } catch (InvalidModificationException e) {
 +            callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
 +        } catch (MalformedURLException e) {
 +            callbackContext.error(FileUtils.ENCODING_ERR);
 +        } catch (IOException e) {
 +            callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
 +        } catch (EncodingException e) {
 +            callbackContext.error(FileUtils.ENCODING_ERR);
 +        } catch (TypeMismatchException e) {
 +            callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
 +        }
 +        return true;
 +    }
 +
 +    /**
 +     * Need to check to see if we need to clean up the content store
 +     *
 +     * @param filePath the path to check
 +     */
 +    private void notifyDelete(String filePath) {
-         String newFilePath = getRealPathFromURI(Uri.parse(filePath), cordova);
++        String newFilePath = FileHelper.getRealPath(filePath, cordova);
 +        try {
 +            this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                 MediaStore.Images.Media.DATA + " = ?",
-                 new String[] { newFilePath });
++                    MediaStore.Images.Media.DATA + " = ?",
++                    new String[] { newFilePath });
 +        } catch (UnsupportedOperationException t) {
 +            // Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
 +            // The ContentResolver applies only when the file was registered in the
 +            // first case, which is generally only the case with images.
 +        }
 +    }
 +
 +    /**
 +     * 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
 +     */
 +    @SuppressWarnings("deprecation")
 +    private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException {
 +        String decoded = URLDecoder.decode(url, "UTF-8");
 +
 +        File fp = null;
 +
 +        // Handle the special case where you get an Android content:// uri.
 +        if (decoded.startsWith("content:")) {
 +            Cursor cursor = this.cordova.getActivity().managedQuery(Uri.parse(decoded), new String[] { MediaStore.Images.Media.DATA }, null, null, null);
 +            // Note: MediaStore.Images/Audio/Video.Media.DATA is always "_data"
 +            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
 +            cursor.moveToFirst();
 +            fp = new File(cursor.getString(column_index));
 +        } else {
 +            // Test to see if this is a valid URL first
 +            @SuppressWarnings("unused")
 +            URL testUrl = new URL(decoded);
 +
 +            if (decoded.startsWith("file://")) {
 +                int questionMark = decoded.indexOf("?");
 +                if (questionMark < 0) {
 +                    fp = new File(decoded.substring(7, decoded.length()));
 +                } else {
 +                    fp = new File(decoded.substring(7, questionMark));
 +                }
 +            } else {
 +                fp = new File(decoded);
 +            }
 +        }
 +
 +        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 = createFileObject(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++) {
 +                if (files[i].canRead()) {
 +                    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 NoModificationAllowedException
 +     * @throws IOException
 +     * @throws InvalidModificationException
 +     * @throws EncodingException
 +     * @throws JSONException
 +     * @throws FileExistsException
 +     */
 +    private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
-         String newFileName = getRealPathFromURI(Uri.parse(fileName), cordova);
-         newParent = getRealPathFromURI(Uri.parse(newParent), cordova);
++        String newFileName = FileHelper.getRealPath(fileName, cordova);
++        newParent = FileHelper.getRealPath(newParent, cordova);
 +
 +        // Check for invalid file name
 +        if (newName != null && newName.contains(":")) {
 +            throw new EncodingException("Bad file name");
 +        }
 +
 +        File source = new File(newFileName);
 +
 +        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);
 +        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) {
-             	JSONObject newFileEntry = moveFile(source, destination);
++                JSONObject newFileEntry = moveFile(source, destination);
 +
-             	// If we've moved a file given its content URI, we need to clean up.
-             	if (fileName.startsWith("content://")) {
-             		notifyDelete(fileName);
-             	}
++                // If we've moved a file given its content URI, we need to clean up.
++                if (fileName.startsWith("content://")) {
++                    notifyDelete(fileName);
++                }
 +
-             	return newFileEntry;
++                return newFileEntry;
 +            } 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");
 +        }
 +
 +        copyAction(srcFile, destFile);
 +
 +        return getEntry(destFile);
 +    }
 +
 +    /**
 +     * Moved this code into it's own method so moveTo could use it when the move is across file systems
 +     */
 +    private void copyAction(File srcFile, File destFile)
 +            throws FileNotFoundException, IOException {
 +        FileInputStream istream = new FileInputStream(srcFile);
 +        FileOutputStream ostream = new FileOutputStream(destFile);
 +        FileChannel input = istream.getChannel();
 +        FileChannel output = ostream.getChannel();
 +
 +        try {
 +            input.transferTo(0, input.size(), output);
 +        } finally {
 +            istream.close();
 +            ostream.close();
 +            input.close();
 +            output.close();
 +        }
 +    }
 +
 +    /**
 +     * 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 (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.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 directory");
 +            }
 +        }
 +
 +        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);
 +    }
 +
 +    /**
 +     * Check to see if the user attempted to copy an entry into its parent without changing its name,
 +     * or attempted to copy a directory into a directory that it contains directly or indirectly.
 +     *
 +     * @param srcDir
 +     * @param destinationDir
 +     * @return
 +     */
 +    private boolean isCopyOnItself(String src, String dest) {
 +
 +        // This weird test is to determine if we are copying or moving a directory into itself.
 +        // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but
 +        // Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR
 +        if (dest.startsWith(src) && dest.indexOf(File.separator, src.length() - 1) != -1) {
 +            return true;
 +        }
 +
 +        return false;
 +    }
 +
 +    /**
 +     * 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 IOException, 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
 +            copyAction(srcFile, destFile);
 +            if (destFile.exists()) {
 +                srcFile.delete();
 +            } else {
 +                throw new IOException("moved failed");
 +            }
 +        }
 +
 +        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 InvalidModificationException
 +     * @throws NoModificationAllowedException
 +     * @throws FileExistsException
 +     */
 +    private JSONObject moveDirectory(File srcDir, File destinationDir) throws IOException, JSONException, InvalidModificationException, NoModificationAllowedException, FileExistsException {
 +        // 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 (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) {
 +            throw new InvalidModificationException("Can't move 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
 +            copyDirectory(srcDir, destinationDir);
 +            if (destinationDir.exists()) {
 +                removeDirRecursively(srcDir);
 +            } else {
 +                throw new IOException("moved failed");
 +            }
 +        }
 +
 +        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 = createFileObject(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 = createFileObject(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 {
-             dirPath = getRealPathFromURI(Uri.parse(dirPath), cordova);
++            dirPath = FileHelper.getRealPath(dirPath, cordova);
 +            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 {
-         filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
++        filePath = FileHelper.getRealPath(filePath, cordova);
 +
 +        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) {
-         filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
++        filePath = FileHelper.getRealPath(filePath, cordova);
 +
 +        if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") ||
 +                filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) ||
 +                filePath.equals("/data/data/" + cordova.getActivity().getPackageName())) {
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    /**
-      * This method removes the "file://" from the passed in filePath
-      *
-      * @param filePath to be checked.
-      * @return
-      */
-     public static String stripFileProtocol(String filePath) {
-         if (filePath.startsWith("file://")) {
-             filePath = filePath.substring(7);
-         }
-         return filePath;
-     }
- 
-     /**
 +     * Create a File object from the passed in path
 +     *
 +     * @param filePath
 +     * @return
 +     */
 +    private File createFileObject(String filePath) {
-     	filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
++        filePath = FileHelper.getRealPath(filePath, cordova);
 +
 +        File file = new File(filePath);
 +        return file;
 +    }
 +
 +    /**
 +     * Look up metadata about this entry.
 +     *
 +     * @param filePath to entry
 +     * @return a long
 +     * @throws FileNotFoundException
 +     */
 +    private long getMetadata(String filePath) throws FileNotFoundException {
 +        File file = createFileObject(filePath);
 +
 +        if (!file.exists()) {
 +            throw new FileNotFoundException("Failed to find file in getMetadata");
 +        }
 +
 +        return file.lastModified();
 +    }
 +
 +    /**
 +     * 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 = createFileObject(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("type", FileHelper.getMimeType(filePath, cordova));
 +        metadata.put("name", file.getName());
 +        metadata.put("fullPath", filePath);
 +        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) {
 +            File fp;
 +            fs.put("name", "temporary");
 +            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
 +                fp = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
 +                        "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/");
 +                // Create the cache dir if it doesn't exist.
 +                fp.mkdirs();
 +                fs.put("root", getEntry(Environment.getExternalStorageDirectory().getAbsolutePath() +
 +                        "/Android/data/" + cordova.getActivity().getPackageName() + "/cache/"));
 +            } else {
 +                fp = new File("/data/data/" + cordova.getActivity().getPackageName() + "/cache/");
 +                // Create the cache dir if it doesn't exist.
 +                fp.mkdirs();
 +                fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName() + "/cache/"));
 +            }
 +        }
 +        else if (type == PERSISTENT) {
 +            fs.put("name", "persistent");
 +            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
 +                fs.put("root", getEntry(Environment.getExternalStorageDirectory()));
 +            } else {
 +                fs.put("root", getEntry("/data/data/" + cordova.getActivity().getPackageName()));
 +            }
 +        }
 +        else {
 +            throw new IOException("No filesystem of type requested");
 +        }
 +
 +        return fs;
 +    }
 +
 +    /**
-      * Returns a JSON Object representing a directory on the device's file system
++     * Returns a JSON object representing the given File.
 +     *
-      * @param path to the directory
-      * @return
++     * @param file the File to convert
++     * @return a JSON representation of the given File
 +     * @throws JSONException
 +     */
-     public JSONObject getEntry(File file) throws JSONException {
++    public static 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://" + file.getAbsolutePath());
-         // I can't add the next thing it as it would be an infinite loop
-         //entry.put("filesystem", null);
++        // The file system can't be specified, as it would lead to an infinite loop.
++        // entry.put("filesystem", null);
 +
 +        return entry;
 +    }
 +
 +    /**
 +     * Returns a JSON Object representing a directory on the device's file system
 +     *
 +     * @param path to the directory
 +     * @return
 +     * @throws JSONException
 +     */
 +    private JSONObject getEntry(String path) throws JSONException {
 +        return getEntry(new File(path));
 +    }
 +
-     /**
-      * Identifies if action to be executed returns a value and should be run synchronously.
-      *
-      * @param action	The action to execute
-      * @return			T=returns value
-      */
-     public boolean isSynch(String action) {
-         if (action.equals("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;
-     }
 +
 +    //--------------------------------------------------------------------------
 +    // LOCAL METHODS
 +    //--------------------------------------------------------------------------
 +
 +    /**
-      * Read content of text file.
++     * Read the contents of a file as text.
++     * This is done in a background thread; the result is sent to the callback.
 +     *
-      * @param filename			The name of the file.
-      * @param encoding			The encoding to return contents as.  Typical value is UTF-8.
-      * 							(see http://www.iana.org/assignments/character-sets)
-      * @param start                     Start position in the file.
-      * @param end                       End position to stop at (exclusive).
-      * @return					Contents of file.
-      * @throws FileNotFoundException, IOException
++     * @param filename          The name of the file.
++     * @param encoding          The encoding to return contents as.  Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
++     * @param start             Start position in the file.
++     * @param end               End position to stop at (exclusive).
++     * @return                  Contents of file.
 +     */
-     public String readAsText(String filename, String encoding, int start, int end) throws FileNotFoundException, IOException {
-         int diff = end - start;
-         byte[] bytes = new byte[1000];
-         BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
-         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-         int numRead = 0;
- 
-         if (start > 0) {
-             bis.skip(start);
-         }
- 
-         while ( diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
-             diff -= numRead;
-             bos.write(bytes, 0, numRead);
-         }
- 
-         return new String(bos.toByteArray(), encoding);
++    public void readFileAs(final String filename, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) {
++        this.cordova.getThreadPool().execute(new Runnable() {
++            public void run() {
++                try {
++                    byte[] bytes = readAsBinaryHelper(filename, start, end);
++                    
++                    PluginResult result;
++                    switch (resultType) {
++                        case PluginResult.MESSAGE_TYPE_STRING:
++                            result = new PluginResult(PluginResult.Status.OK, new String(bytes, encoding));
++                            break;
++                        case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
++                            result = new PluginResult(PluginResult.Status.OK, bytes);
++                            break;
++                        case PluginResult.MESSAGE_TYPE_BINARYSTRING:
++                            result = new PluginResult(PluginResult.Status.OK, bytes, true);
++                            break;
++                        default: // Base64.
++                            String contentType = FileHelper.getMimeType(filename, cordova);
++                            byte[] base64 = Base64.encodeBase64(bytes);
++                            String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
++                            result = new PluginResult(PluginResult.Status.OK, s);
++                    }
++
++                    callbackContext.sendPluginResult(result);
++                } catch (FileNotFoundException e) {
++                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
++                } catch (IOException e) {
++                    Log.d(LOG_TAG, e.getLocalizedMessage());
++                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
++                }
++            }
++        });
 +    }
 +
 +    /**
-      * Helper method to read a text file and return its contents as a byte[].
++     * Read the contents of a file as binary.
++     * This is done synchronously; the result is returned.
++     *
 +     * @param filename          The name of the file.
 +     * @param start             Start position in the file.
 +     * @param end               End position to stop at (exclusive).
 +     * @return                  Contents of the file as a byte[].
-      * @throws FileNotFoundException, IOException
++     * @throws IOException
 +     */
-     public byte[] readAsBinary(String filename, int start, int end) throws FileNotFoundException, IOException {
++    private byte[] readAsBinaryHelper(String filename, int start, int end) throws IOException {
 +        int diff = end - start;
 +        byte[] bytes = new byte[1000];
-         BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
++        BufferedInputStream bis = new BufferedInputStream(FileHelper.getInputStreamFromUriString(filename, cordova), 1024);
 +        ByteArrayOutputStream bos = new ByteArrayOutputStream();
 +        int numRead = 0;
 +
 +        if (start > 0) {
 +            bis.skip(start);
 +        }
 +
 +        while (diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
 +            diff -= numRead;
 +            bos.write(bytes, 0, numRead);
 +        }
 +
 +        return bos.toByteArray();
 +    }
 +
 +    /**
-      * Read content of a file and return as base64 encoded data url.
-      *
-      * @param filename			The name of the file.
-      * @param start             Start position in the file.
-      * @param end               End position to stop at (exclusive).
-      * @return					Contents of file = data:<media type>;base64,<data>
-      * @throws FileNotFoundException, IOException
-      */
-     public String readAsDataURL(String filename, int start, int end) throws FileNotFoundException, IOException {
-     	// Determine content type from file name
-         String contentType = null;
-         if (filename.startsWith("content:")) {
-             Uri fileUri = Uri.parse(filename);
-             contentType = this.cordova.getActivity().getContentResolver().getType(fileUri);
-         }
-         else {
-             contentType = getMimeType(filename);
-         }
- 
-     	byte[] base64 = Base64.encodeBase64(readAsBinary(filename, start, end));
-         String data = "data:" + contentType + ";base64," + new String(base64);
-         return data;
-     }
- 
-     /**
-      * Looks up the mime type of a given file name.
-      *
-      * @param filename
-      * @return a mime type
-      */
-     public static String getMimeType(String filename) {
-         if (filename != null) {
-             // Stupid bug in getFileExtensionFromUrl when the file name has a space
-             // So we need to replace the space with a url encoded %20
- 
-             // CB-2185: Stupid bug not putting JPG extension in the mime-type map
-             String url = filename.replace(" ", "%20").toLowerCase();
-             MimeTypeMap map = MimeTypeMap.getSingleton();
-             String extension = MimeTypeMap.getFileExtensionFromUrl(url);
-             if (extension.toLowerCase().equals("3ga")) {
-                 return "audio/3gpp";
-             } else {
-                 return map.getMimeTypeFromExtension(extension);
-             }
-         } else {
-             return "";
-         }
-     }
- 
-     /**
 +     * Write contents of file.
 +     *
 +     * @param filename			The name of the file.
 +     * @param data				The contents of the file.
 +     * @param offset			The position to begin writing the file.
 +     * @throws FileNotFoundException, IOException
 +     * @throws NoModificationAllowedException
 +     */
 +    /**/
 +    public long write(String filename, String data, int offset) throws FileNotFoundException, IOException, NoModificationAllowedException {
-     	if (filename.startsWith("content://")) {
-     		throw new NoModificationAllowedException("Couldn't write to file given its content URI");
-     	}
++        if (filename.startsWith("content://")) {
++            throw new NoModificationAllowedException("Couldn't write to file given its content URI");
++        }
 +
-         filename = getRealPathFromURI(Uri.parse(filename), cordova);
++        filename = FileHelper.getRealPath(filename, cordova);
 +
 +        boolean append = false;
 +        if (offset > 0) {
 +            this.truncateFile(filename, offset);
 +            append = true;
 +        }
 +
 +        byte[] rawData = data.getBytes();
 +        ByteArrayInputStream in = new ByteArrayInputStream(rawData);
 +        FileOutputStream out = new FileOutputStream(filename, append);
 +        byte buff[] = new byte[rawData.length];
 +        in.read(buff, 0, buff.length);
 +        out.write(buff, 0, rawData.length);
 +        out.flush();
 +        out.close();
 +
 +        return rawData.length;
 +    }
 +
 +    /**
 +     * Truncate the file to size
 +     *
 +     * @param filename
 +     * @param size
 +     * @throws FileNotFoundException, IOException
 +     * @throws NoModificationAllowedException
 +     */
 +    private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
-     	if (filename.startsWith("content://")) {
-     		throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
-     	}
++        if (filename.startsWith("content://")) {
++            throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
++        }
 +
-         filename = getRealPathFromURI(Uri.parse(filename), cordova);
++        filename = FileHelper.getRealPath(filename, cordova);
 +
 +        RandomAccessFile raf = new RandomAccessFile(filename, "rw");
 +        try {
 +            if (raf.length() >= size) {
 +                FileChannel channel = raf.getChannel();
 +                channel.truncate(size);
 +                return size;
 +            }
 +
 +            return raf.length();
 +        } finally {
 +            raf.close();
 +        }
 +    }
- 
-     /**
-      * Get an input stream based on file path or content:// uri
-      *
-      * @param path
-      * @return an input stream
-      * @throws FileNotFoundException
-      */
-     private InputStream getPathFromUri(String path) throws FileNotFoundException {
-         if (path.startsWith("content")) {
-             Uri uri = Uri.parse(path);
-             return cordova.getActivity().getContentResolver().openInputStream(uri);
-         }
-         else {
-             path = getRealPathFromURI(Uri.parse(path), cordova);
-             return new FileInputStream(path);
-         }
-     }
- 
-     /**
-      * 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
-      * @param cordova the current application context
-      * @return the full path to the file
-      */
-     @SuppressWarnings("deprecation")
-     protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) {
-         final String scheme = contentUri.getScheme();
- 
-         if (scheme == null) {
-         	return contentUri.toString();
-     	} else if (scheme.compareTo("content") == 0) {
-             String[] proj = { _DATA };
-             Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null);
-             int column_index = cursor.getColumnIndexOrThrow(_DATA);
-             cursor.moveToFirst();
-             return cursor.getString(column_index);
-         } else if (scheme.compareTo("file") == 0) {
-             return contentUri.getPath();
-         } else {
-             return contentUri.toString();
-         }
-     }
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/5cc3852c/framework/src/org/apache/cordova/core/GeoBroker.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/core/GeoBroker.java
index 00b97ed,0000000..8bcfb32
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/core/GeoBroker.java
+++ b/framework/src/org/apache/cordova/core/GeoBroker.java
@@@ -1,204 -1,0 +1,204 @@@
 +/*
 +       Licensed to the Apache Software Foundation (ASF) under one
 +       or more contributor license agreements.  See the NOTICE file
 +       distributed with this work for additional information
 +       regarding copyright ownership.  The ASF licenses this file
 +       to you under the Apache License, Version 2.0 (the
 +       "License"); you may not use this file except in compliance
 +       with the License.  You may obtain a copy of the License at
 +
 +         http://www.apache.org/licenses/LICENSE-2.0
 +
 +       Unless required by applicable law or agreed to in writing,
 +       software distributed under the License is distributed on an
 +       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +       KIND, either express or implied.  See the License for the
 +       specific language governing permissions and limitations
 +       under the License.
 +*/
 +package org.apache.cordova.core;
 +
 +import org.apache.cordova.api.CallbackContext;
 +import org.apache.cordova.api.CordovaPlugin;
 +import org.apache.cordova.api.PluginResult;
 +import org.json.JSONArray;
 +import org.json.JSONException;
 +import org.json.JSONObject;
 +
 +import android.content.Context;
 +import android.location.Location;
 +import android.location.LocationManager;
 +
 +/*
 + * This class is the interface to the Geolocation.  It's bound to the geo object.
 + *
 + * This class only starts and stops various GeoListeners, which consist of a GPS and a Network Listener
 + */
 +
 +public class GeoBroker extends CordovaPlugin {
 +    private GPSListener gpsListener;
 +    private NetworkListener networkListener;
 +    private LocationManager locationManager;
 +
 +    /**
 +     * Constructor.
 +     */
 +    public GeoBroker() {
 +    }
 +
 +    /**
 +     * Executes the request and returns PluginResult.
 +     *
 +     * @param action 		The action to execute.
 +     * @param args 		JSONArry of arguments for the plugin.
 +     * @param callbackContext	The callback id used when calling back into JavaScript.
 +     * @return 			True if the action was valid, or false if not.
 +     */
 +    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
 +        if (this.locationManager == null) {
 +            this.locationManager = (LocationManager) this.cordova.getActivity().getSystemService(Context.LOCATION_SERVICE);
 +            this.networkListener = new NetworkListener(this.locationManager, this);
 +            this.gpsListener = new GPSListener(this.locationManager, this);
 +        }
 +
 +        if ( locationManager.isProviderEnabled( LocationManager.GPS_PROVIDER ) ||
 +                locationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
 +
 +            if (action.equals("getLocation")) {
 +                boolean enableHighAccuracy = args.getBoolean(0);
 +                int maximumAge = args.getInt(1);
 +                Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER));
 +                // Check if we can use lastKnownLocation to get a quick reading and use less battery
 +                if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) {
 +                    PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
 +                    callbackContext.sendPluginResult(result);
 +                } else {
 +                    this.getCurrentLocation(callbackContext, enableHighAccuracy);
 +                }
 +            }
 +            else if (action.equals("addWatch")) {
 +                String id = args.getString(0);
 +                boolean enableHighAccuracy = args.getBoolean(1);
 +                this.addWatch(id, callbackContext, enableHighAccuracy);
 +            }
 +            else if (action.equals("clearWatch")) {
 +                String id = args.getString(0);
 +                this.clearWatch(id);
 +            }
 +            else {
 +                return false;
 +            }
 +        } else {
 +            PluginResult.Status status = PluginResult.Status.NO_RESULT;
 +            String message = "Location API is not available for this device.";
 +            PluginResult result = new PluginResult(status, message);
 +            callbackContext.sendPluginResult(result);
 +        }
 +        return true;
 +    }
 +
 +    private void clearWatch(String id) {
 +        this.gpsListener.clearWatch(id);
 +        this.networkListener.clearWatch(id);
 +    }
 +
 +    private void getCurrentLocation(CallbackContext callbackContext, boolean enableHighAccuracy) {
 +        if (enableHighAccuracy) {
 +            this.gpsListener.addCallback(callbackContext);
 +        } else {
 +            this.networkListener.addCallback(callbackContext);
 +        }
 +    }
 +
 +    private void addWatch(String timerId, CallbackContext callbackContext, boolean enableHighAccuracy) {
 +        if (enableHighAccuracy) {
 +            this.gpsListener.addWatch(timerId, callbackContext);
 +        } else {
 +            this.networkListener.addWatch(timerId, callbackContext);
 +        }
 +    }
 +
 +    /**
 +     * Called when the activity is to be shut down.
 +     * Stop listener.
 +     */
 +    public void onDestroy() {
 +        if (this.networkListener != null) {
 +            this.networkListener.destroy();
 +            this.networkListener = null;
 +        }
 +        if (this.gpsListener != null) {
 +            this.gpsListener.destroy();
 +            this.gpsListener = null;
 +        }
 +    }
 +
 +    /**
 +     * Called when the view navigates.
 +     * Stop the listeners.
 +     */
 +    public void onReset() {
 +        this.onDestroy();
 +    }
 +
 +    public JSONObject returnLocationJSON(Location loc) {
 +        JSONObject o = new JSONObject();
 +
 +        try {
 +            o.put("latitude", loc.getLatitude());
 +            o.put("longitude", loc.getLongitude());
 +            o.put("altitude", (loc.hasAltitude() ? loc.getAltitude() : null));
 +            o.put("accuracy", loc.getAccuracy());
 +            o.put("heading", (loc.hasBearing() ? (loc.hasSpeed() ? loc.getBearing() : null) : null));
-             o.put("speed", loc.getSpeed());
++            o.put("velocity", loc.getSpeed());
 +            o.put("timestamp", loc.getTime());
 +        } catch (JSONException e) {
 +            // TODO Auto-generated catch block
 +            e.printStackTrace();
 +        }
 +
 +        return o;
 +    }
 +
 +    public void win(Location loc, CallbackContext callbackContext) {
 +        PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc));
 +        callbackContext.sendPluginResult(result);
 +    }
 +
 +    /**
 +     * Location failed.  Send error back to JavaScript.
 +     * 
 +     * @param code			The error code
 +     * @param msg			The error message
 +     * @throws JSONException 
 +     */
 +    public void fail(int code, String msg, CallbackContext callbackContext) {
 +        JSONObject obj = new JSONObject();
 +        String backup = null;
 +        try {
 +            obj.put("code", code);
 +            obj.put("message", msg);
 +        } catch (JSONException e) {
 +            obj = null;
 +            backup = "{'code':" + code + ",'message':'" + msg.replaceAll("'", "\'") + "'}";
 +        }
 +        PluginResult result;
 +        if (obj != null) {
 +            result = new PluginResult(PluginResult.Status.ERROR, obj);
 +        } else {
 +            result = new PluginResult(PluginResult.Status.ERROR, backup);
 +        }
 +
 +        callbackContext.sendPluginResult(result);
 +    }
 +
 +    public boolean isGlobalListener(CordovaLocationListener listener)
 +    {
 +    	if (gpsListener != null && networkListener != null)
 +    	{
 +    		return gpsListener.equals(listener) || networkListener.equals(listener);
 +    	}
 +    	else
 +    		return false;
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/5cc3852c/framework/src/org/apache/cordova/core/Notification.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/core/Notification.java
index 58bfc0d,0000000..14f27f6
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/core/Notification.java
+++ b/framework/src/org/apache/cordova/core/Notification.java
@@@ -1,341 -1,0 +1,346 @@@
 +/*
 +       Licensed to the Apache Software Foundation (ASF) under one
 +       or more contributor license agreements.  See the NOTICE file
 +       distributed with this work for additional information
 +       regarding copyright ownership.  The ASF licenses this file
 +       to you under the Apache License, Version 2.0 (the
 +       "License"); you may not use this file except in compliance
 +       with the License.  You may obtain a copy of the License at
 +
 +         http://www.apache.org/licenses/LICENSE-2.0
 +
 +       Unless required by applicable law or agreed to in writing,
 +       software distributed under the License is distributed on an
 +       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +       KIND, either express or implied.  See the License for the
 +       specific language governing permissions and limitations
 +       under the License.
 +*/
 +package org.apache.cordova.core;
 +
 +import org.apache.cordova.api.CallbackContext;
 +import org.apache.cordova.api.CordovaInterface;
 +import org.apache.cordova.api.CordovaPlugin;
 +import org.apache.cordova.api.PluginResult;
 +import org.json.JSONArray;
 +import org.json.JSONException;
 +import android.app.AlertDialog;
 +import android.app.ProgressDialog;
 +import android.content.Context;
 +import android.content.DialogInterface;
 +import android.media.Ringtone;
 +import android.media.RingtoneManager;
 +import android.net.Uri;
 +import android.os.Vibrator;
 +
 +/**
 + * This class provides access to notifications on the device.
 + */
 +public class Notification extends CordovaPlugin {
 +
 +    public int confirmResult = -1;
 +    public ProgressDialog spinnerDialog = null;
 +    public ProgressDialog progressDialog = null;
 +
 +    /**
 +     * Constructor.
 +     */
 +    public Notification() {
 +    }
 +
 +    /**
 +     * Executes the request and returns PluginResult.
 +     *
 +     * @param action            The action to execute.
 +     * @param args              JSONArray of arguments for the plugin.
 +     * @param callbackContext   The callback context used when calling back into JavaScript.
 +     * @return                  True when the action was valid, false otherwise.
 +     */
 +    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
 +        if (action.equals("beep")) {
 +            this.beep(args.getLong(0));
 +        }
 +        else if (action.equals("vibrate")) {
 +            this.vibrate(args.getLong(0));
 +        }
 +        else if (action.equals("alert")) {
 +            this.alert(args.getString(0), args.getString(1), args.getString(2), callbackContext);
 +            return true;
 +        }
 +        else if (action.equals("confirm")) {
-             this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackContext);
++            this.confirm(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext);
 +            return true;
 +        }
 +        else if (action.equals("activityStart")) {
 +            this.activityStart(args.getString(0), args.getString(1));
 +        }
 +        else if (action.equals("activityStop")) {
 +            this.activityStop();
 +        }
 +        else if (action.equals("progressStart")) {
 +            this.progressStart(args.getString(0), args.getString(1));
 +        }
 +        else if (action.equals("progressValue")) {
 +            this.progressValue(args.getInt(0));
 +        }
 +        else if (action.equals("progressStop")) {
 +            this.progressStop();
 +        }
 +        else {
 +            return false;
 +        }
 +
 +        // Only alert and confirm are async.
 +        callbackContext.success();
 +        return true;
 +    }
 +
 +    //--------------------------------------------------------------------------
 +    // LOCAL METHODS
 +    //--------------------------------------------------------------------------
 +
 +    /**
 +     * Beep plays the default notification ringtone.
 +     *
 +     * @param count     Number of times to play notification
 +     */
 +    public void beep(long count) {
 +        Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
 +        Ringtone notification = RingtoneManager.getRingtone(this.cordova.getActivity().getBaseContext(), ringtone);
 +
 +        // If phone is not set to silent mode
 +        if (notification != null) {
 +            for (long i = 0; i < count; ++i) {
 +                notification.play();
 +                long timeout = 5000;
 +                while (notification.isPlaying() && (timeout > 0)) {
 +                    timeout = timeout - 100;
 +                    try {
 +                        Thread.sleep(100);
 +                    } catch (InterruptedException e) {
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Vibrates the device for the specified amount of time.
 +     *
 +     * @param time      Time to vibrate in ms.
 +     */
 +    public void vibrate(long time) {
 +        // Start the vibration, 0 defaults to half a second.
 +        if (time == 0) {
 +            time = 500;
 +        }
 +        Vibrator vibrator = (Vibrator) this.cordova.getActivity().getSystemService(Context.VIBRATOR_SERVICE);
 +        vibrator.vibrate(time);
 +    }
 +
 +    /**
 +     * Builds and shows a native Android alert with given Strings
 +     * @param message           The message the alert should display
 +     * @param title             The title of the alert
 +     * @param buttonLabel       The label of the button
 +     * @param callbackContext   The callback context
 +     */
 +    public synchronized void alert(final String message, final String title, final String buttonLabel, final CallbackContext callbackContext) {
 +
 +        final CordovaInterface cordova = this.cordova;
 +
 +        Runnable runnable = new Runnable() {
 +            public void run() {
 +
 +                AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
 +                dlg.setMessage(message);
 +                dlg.setTitle(title);
 +                dlg.setCancelable(true);
 +                dlg.setPositiveButton(buttonLabel,
 +                        new AlertDialog.OnClickListener() {
 +                            public void onClick(DialogInterface dialog, int which) {
 +                                dialog.dismiss();
 +                                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
 +                            }
 +                        });
 +                dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
 +                    public void onCancel(DialogInterface dialog)
 +                    {
 +                        dialog.dismiss();
 +                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
 +                    }
 +                });
-                 
++
 +                dlg.create();
 +                dlg.show();
 +            };
 +        };
 +        this.cordova.getActivity().runOnUiThread(runnable);
 +    }
 +
 +    /**
 +     * Builds and shows a native Android confirm dialog with given title, message, buttons.
 +     * This dialog only shows up to 3 buttons.  Any labels after that will be ignored.
 +     * The index of the button pressed will be returned to the JavaScript callback identified by callbackId.
 +     *
 +     * @param message           The message the dialog should display
 +     * @param title             The title of the dialog
 +     * @param buttonLabels      A comma separated list of button labels (Up to 3 buttons)
 +     * @param callbackContext   The callback context.
 +     */
-     public synchronized void confirm(final String message, final String title, String buttonLabels, final CallbackContext callbackContext) {
++    public synchronized void confirm(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) {
 +
 +        final CordovaInterface cordova = this.cordova;
-         final String[] fButtons = buttonLabels.split(",");
 +
 +        Runnable runnable = new Runnable() {
 +            public void run() {
 +                AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
 +                dlg.setMessage(message);
 +                dlg.setTitle(title);
 +                dlg.setCancelable(true);
 +
 +                // First button
-                 if (fButtons.length > 0) {
-                     dlg.setNegativeButton(fButtons[0],
-                             new AlertDialog.OnClickListener() {
-                                 public void onClick(DialogInterface dialog, int which) {
-                                     dialog.dismiss();
-                                     callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
-                                 }
-                             });
++                if (buttonLabels.length() > 0) {
++                    try {
++						dlg.setNegativeButton(buttonLabels.getString(0),
++						        new AlertDialog.OnClickListener() {
++						            public void onClick(DialogInterface dialog, int which) {
++						                dialog.dismiss();
++						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
++						            }
++						        });
++					} catch (JSONException e) { }
 +                }
 +
 +                // Second button
-                 if (fButtons.length > 1) {
-                     dlg.setNeutralButton(fButtons[1],
-                             new AlertDialog.OnClickListener() {
-                                 public void onClick(DialogInterface dialog, int which) {
-                                     dialog.dismiss();
-                                     callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
-                                 }
-                             });
++                if (buttonLabels.length() > 1) {
++                    try {
++						dlg.setNeutralButton(buttonLabels.getString(1),
++						        new AlertDialog.OnClickListener() {
++						            public void onClick(DialogInterface dialog, int which) {
++						                dialog.dismiss();
++						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
++						            }
++						        });
++					} catch (JSONException e) { }
 +                }
 +
 +                // Third button
-                 if (fButtons.length > 2) {
-                     dlg.setPositiveButton(fButtons[2],
-                             new AlertDialog.OnClickListener() {
-                                 public void onClick(DialogInterface dialog, int which) {
-                                     dialog.dismiss();
-                                     callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
-                                 }
-                             }
-                             );
++                if (buttonLabels.length() > 2) {
++                    try {
++						dlg.setPositiveButton(buttonLabels.getString(2),
++						        new AlertDialog.OnClickListener() {
++						            public void onClick(DialogInterface dialog, int which) {
++						                dialog.dismiss();
++						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
++						            }
++						        }
++						        );
++					} catch (JSONException e) { }
 +                }
 +                dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
 +                    public void onCancel(DialogInterface dialog)
 +                    {
 +                        dialog.dismiss();
 +                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
 +                    }
 +                });
 +
 +                dlg.create();
 +                dlg.show();
 +            };
 +        };
 +        this.cordova.getActivity().runOnUiThread(runnable);
 +    }
 +
 +    /**
 +     * Show the spinner.
 +     *
 +     * @param title     Title of the dialog
 +     * @param message   The message of the dialog
 +     */
 +    public synchronized void activityStart(final String title, final String message) {
 +        if (this.spinnerDialog != null) {
 +            this.spinnerDialog.dismiss();
 +            this.spinnerDialog = null;
 +        }
 +        final CordovaInterface cordova = this.cordova;
 +        Runnable runnable = new Runnable() {
 +            public void run() {
 +                Notification.this.spinnerDialog = ProgressDialog.show(cordova.getActivity(), title, message, true, true,
 +                        new DialogInterface.OnCancelListener() {
 +                            public void onCancel(DialogInterface dialog) {
 +                                Notification.this.spinnerDialog = null;
 +                            }
 +                        });
 +            }
 +        };
 +        this.cordova.getActivity().runOnUiThread(runnable);
 +    }
 +
 +    /**
 +     * Stop spinner.
 +     */
 +    public synchronized void activityStop() {
 +        if (this.spinnerDialog != null) {
 +            this.spinnerDialog.dismiss();
 +            this.spinnerDialog = null;
 +        }
 +    }
 +
 +    /**
 +     * Show the progress dialog.
 +     *
 +     * @param title     Title of the dialog
 +     * @param message   The message of the dialog
 +     */
 +    public synchronized void progressStart(final String title, final String message) {
 +        if (this.progressDialog != null) {
 +            this.progressDialog.dismiss();
 +            this.progressDialog = null;
 +        }
 +        final Notification notification = this;
 +        final CordovaInterface cordova = this.cordova;
 +        Runnable runnable = new Runnable() {
 +            public void run() {
 +                notification.progressDialog = new ProgressDialog(cordova.getActivity());
 +                notification.progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
 +                notification.progressDialog.setTitle(title);
 +                notification.progressDialog.setMessage(message);
 +                notification.progressDialog.setCancelable(true);
 +                notification.progressDialog.setMax(100);
 +                notification.progressDialog.setProgress(0);
 +                notification.progressDialog.setOnCancelListener(
 +                        new DialogInterface.OnCancelListener() {
 +                            public void onCancel(DialogInterface dialog) {
 +                                notification.progressDialog = null;
 +                            }
 +                        });
 +                notification.progressDialog.show();
 +            }
 +        };
 +        this.cordova.getActivity().runOnUiThread(runnable);
 +    }
 +
 +    /**
 +     * Set value of progress bar.
 +     *
 +     * @param value     0-100
 +     */
 +    public synchronized void progressValue(int value) {
 +        if (this.progressDialog != null) {
 +            this.progressDialog.setProgress(value);
 +        }
 +    }
 +
 +    /**
 +     * Stop progress dialog.
 +     */
 +    public synchronized void progressStop() {
 +        if (this.progressDialog != null) {
 +            this.progressDialog.dismiss();
 +            this.progressDialog = null;
 +        }
 +    }
 +
 +}


Mime
View raw message