You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ma...@apache.org on 2012/02/03 16:43:10 UTC

[9/15] Rename to Cordova

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/com/phonegap/file/InvalidModificationException.java
----------------------------------------------------------------------
diff --git a/framework/src/com/phonegap/file/InvalidModificationException.java b/framework/src/com/phonegap/file/InvalidModificationException.java
deleted file mode 100644
index 505bd4d..0000000
--- a/framework/src/com/phonegap/file/InvalidModificationException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-       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 com.phonegap.file;
-
-public class InvalidModificationException extends Exception {
-
-	public InvalidModificationException(String message) {
-		super(message);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/com/phonegap/file/NoModificationAllowedException.java
----------------------------------------------------------------------
diff --git a/framework/src/com/phonegap/file/NoModificationAllowedException.java b/framework/src/com/phonegap/file/NoModificationAllowedException.java
deleted file mode 100644
index f62cf06..0000000
--- a/framework/src/com/phonegap/file/NoModificationAllowedException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
-       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 com.phonegap.file;
-
-public class NoModificationAllowedException extends Exception {
-
-	public NoModificationAllowedException(String message) {
-		super(message);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/com/phonegap/file/TypeMismatchException.java
----------------------------------------------------------------------
diff --git a/framework/src/com/phonegap/file/TypeMismatchException.java b/framework/src/com/phonegap/file/TypeMismatchException.java
deleted file mode 100644
index 99e82c6..0000000
--- a/framework/src/com/phonegap/file/TypeMismatchException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-       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 com.phonegap.file;
-
-public class TypeMismatchException extends Exception {
-
-	public TypeMismatchException(String message) {
-		super(message);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/AccelListener.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java
new file mode 100755
index 0000000..61d7acf
--- /dev/null
+++ b/framework/src/org/apache/cordova/AccelListener.java
@@ -0,0 +1,308 @@
+/*
+       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;
+
+import java.util.List;
+
+import org.apache.cordova.api.CordovaActivity;
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.content.Context;
+
+/**
+ * This class listens to the accelerometer sensor and stores the latest 
+ * acceleration values x,y,z.
+ */
+public class AccelListener extends Plugin implements SensorEventListener {
+
+	public static int STOPPED = 0;
+	public static int STARTING = 1;
+    public static int RUNNING = 2;
+    public static int ERROR_FAILED_TO_START = 3;
+    
+    public float TIMEOUT = 30000;		// Timeout in msec to shut off listener
+    
+    float x,y,z;						// most recent acceleration values
+    long timestamp;						// time of most recent value
+    int status;							// status of listener
+    long lastAccessTime;				// time the value was last retrieved
+
+    private SensorManager sensorManager;// Sensor manager
+    Sensor mSensor;						// Acceleration sensor returned by sensor manager
+
+    /**
+     * Create an accelerometer listener.
+     */
+    public AccelListener() {
+        this.x = 0;
+        this.y = 0;
+        this.z = 0;
+        this.timestamp = 0;
+        this.setStatus(AccelListener.STOPPED);
+     }
+    
+	/**
+	 * Sets the context of the Command. This can then be used to do things like
+	 * get file paths associated with the Activity.
+	 * 
+	 * @param ctx The context of the main Activity.
+	 */
+	public void setContext(CordovaActivity ctx) {
+		super.setContext(ctx);
+        this.sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
+	}
+
+	/**
+	 * Executes the request and returns PluginResult.
+	 * 
+	 * @param action 		The action to execute.
+	 * @param args 			JSONArry of arguments for the plugin.
+	 * @param callbackId	The callback id used when calling back into JavaScript.
+	 * @return 				A PluginResult object with a status and message.
+	 */
+	public PluginResult execute(String action, JSONArray args, String callbackId) {
+		PluginResult.Status status = PluginResult.Status.OK;
+		String result = "";		
+		
+		try {
+			if (action.equals("getStatus")) {
+				int i = this.getStatus();
+				return new PluginResult(status, i);
+			}
+			else if (action.equals("start")) {
+				int i = this.start();
+				return new PluginResult(status, i);
+			}
+			else if (action.equals("stop")) {
+				this.stop();
+				return new PluginResult(status, 0);
+			}
+			else if (action.equals("getAcceleration")) {
+				// If not running, then this is an async call, so don't worry about waiting
+				if (this.status != AccelListener.RUNNING) {
+					int r = this.start();
+					if (r == AccelListener.ERROR_FAILED_TO_START) {
+						return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START);
+					}
+					// Wait until running
+					long timeout = 2000;
+					while ((this.status == STARTING) && (timeout > 0)) {
+						timeout = timeout - 100;
+						try {
+							Thread.sleep(100);
+						} catch (InterruptedException e) {
+							e.printStackTrace();
+						}
+					}
+					if (timeout == 0) {
+						return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START);						
+					}
+				}
+	            this.lastAccessTime = System.currentTimeMillis();
+				JSONObject r = new JSONObject();
+				r.put("x", this.x);
+				r.put("y", this.y);
+				r.put("z", this.z);
+				// TODO: Should timestamp be sent?
+				r.put("timestamp", this.timestamp);
+				return new PluginResult(status, r);
+			}
+			else if (action.equals("setTimeout")) {
+				try {
+					float timeout = Float.parseFloat(args.getString(0));
+					this.setTimeout(timeout);
+					return new PluginResult(status, 0);
+				} catch (NumberFormatException e) {
+					status = PluginResult.Status.INVALID_ACTION;
+					e.printStackTrace();
+				} catch (JSONException e) {
+					status = PluginResult.Status.JSON_EXCEPTION;
+					e.printStackTrace();
+				}
+			}
+			else if (action.equals("getTimeout")) {
+				float f = this.getTimeout();
+				return new PluginResult(status, f);
+			}
+			return new PluginResult(status, result);
+		} catch (JSONException e) {
+			return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+		}
+	}
+
+	/**
+	 * 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("getStatus")) {
+			return true;
+		}
+		else if (action.equals("getAcceleration")) {
+			// Can only return value if RUNNING
+			if (this.status == RUNNING) {
+				return true;
+			}
+		}
+		else if (action.equals("getTimeout")) {
+			return true;
+		}
+		return false;
+	}
+    
+    /**
+     * Called by AccelBroker when listener is to be shut down.
+     * Stop listener.
+     */
+    public void onDestroy() {
+    	this.stop();    	
+    }
+
+    //--------------------------------------------------------------------------
+    // LOCAL METHODS
+    //--------------------------------------------------------------------------
+
+    /**
+     * Start listening for acceleration sensor.
+     * 
+     * @return 			status of listener
+     */
+    public int start() {
+
+		// If already starting or running, then just return
+        if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) {
+        	return this.status;
+        }
+
+        // Get accelerometer from sensor manager
+        List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
+        
+        // If found, then register as listener
+        if ((list != null) && (list.size() > 0)) {
+            this.mSensor = list.get(0);
+            this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST);
+            this.setStatus(AccelListener.STARTING);
+            this.lastAccessTime = System.currentTimeMillis();
+        }
+        
+        // If error, then set status to error
+        else {
+            this.setStatus(AccelListener.ERROR_FAILED_TO_START);
+        }
+        
+        return this.status;
+    }
+
+    /**
+     * Stop listening to acceleration sensor.
+     */
+    public void stop() {
+        if (this.status != AccelListener.STOPPED) {
+        	this.sensorManager.unregisterListener(this);
+        }
+        this.setStatus(AccelListener.STOPPED);
+    }
+
+    /**
+     * Called when the accuracy of the sensor has changed.
+     * 
+     * @param sensor
+     * @param accuracy
+     */
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    /**
+     * Sensor listener event.
+     * 
+     * @param SensorEvent event
+     */
+    public void onSensorChanged(SensorEvent event) {
+    	
+    	// Only look at accelerometer events
+        if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
+            return;
+        }
+        
+        // If not running, then just return
+        if (this.status == AccelListener.STOPPED) {
+        	return;
+        }
+        
+        // Save time that event was received
+        this.timestamp = System.currentTimeMillis();
+        this.x = event.values[0];
+        this.y = event.values[1];
+        this.z = event.values[2];            
+
+        this.setStatus(AccelListener.RUNNING);
+
+        // If values haven't been read for TIMEOUT time, then turn off accelerometer sensor to save power
+		if ((this.timestamp - this.lastAccessTime) > this.TIMEOUT) {
+			this.stop();
+		}		
+    }
+
+    /**
+     * Get status of accelerometer sensor.
+     * 
+     * @return			status
+     */
+	public int getStatus() {
+		return this.status;
+	}
+		
+	/**
+	 * Set the timeout to turn off accelerometer sensor if getX() hasn't been called.
+	 * 
+	 * @param timeout		Timeout in msec.
+	 */
+	public void setTimeout(float timeout) {
+		this.TIMEOUT = timeout;
+	}
+	
+	/**
+	 * Get the timeout to turn off accelerometer sensor if getX() hasn't been called.
+	 * 
+	 * @return timeout in msec
+	 */
+	public float getTimeout() {
+		return this.TIMEOUT;
+	}
+	
+	/**
+	 * Set the status and send it to JavaScript.
+	 * @param status
+	 */
+	private void setStatus(int status) {
+		this.status = status;
+	}
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/App.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/App.java b/framework/src/org/apache/cordova/App.java
new file mode 100755
index 0000000..9b1c96a
--- /dev/null
+++ b/framework/src/org/apache/cordova/App.java
@@ -0,0 +1,198 @@
+/*
+       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;
+
+import org.apache.cordova.api.LOG;
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import java.util.HashMap;
+
+/**
+ * This class exposes methods in DroidGap that can be called from JavaScript.
+ */
+public class App extends Plugin {
+
+    /**
+     * Executes the request and returns PluginResult.
+     *
+     * @param action        The action to execute.
+     * @param args          JSONArry of arguments for the plugin.
+     * @param callbackId    The callback id used when calling back into JavaScript.
+     * @return              A PluginResult object with a status and message.
+     */
+    public PluginResult execute(String action, JSONArray args, String callbackId) {
+        PluginResult.Status status = PluginResult.Status.OK;
+        String result = "";
+
+        try {
+        	if (action.equals("clearCache")) {
+        		this.clearCache();
+        	}
+        	else if (action.equals("loadUrl")) {
+            	this.loadUrl(args.getString(0), args.optJSONObject(1));
+            }
+        	else if (action.equals("cancelLoadUrl")) {
+            	this.cancelLoadUrl();
+            }
+        	else if (action.equals("clearHistory")) {
+            	this.clearHistory();
+            }
+            else if (action.equals("backHistory")) {
+                this.backHistory();
+            }
+        	else if (action.equals("overrideBackbutton")) {
+            	this.overrideBackbutton(args.getBoolean(0));
+            }
+        	else if (action.equals("isBackbuttonOverridden")) {
+            	boolean b = this.isBackbuttonOverridden();
+            	return new PluginResult(status, b);
+            }
+        	else if (action.equals("exitApp")) {
+            	this.exitApp();
+            }
+            return new PluginResult(status, result);
+        } catch (JSONException e) {
+            return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // LOCAL METHODS
+    //--------------------------------------------------------------------------
+
+	/**
+	 * Clear the resource cache.
+	 */
+	public void clearCache() {
+		((DroidGap)this.ctx).clearCache();
+	}
+	
+	/**
+	 * Load the url into the webview.
+	 * 
+	 * @param url
+	 * @param props			Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...)
+	 * @throws JSONException 
+	 */
+	public void loadUrl(String url, JSONObject props) throws JSONException {
+		LOG.d("App", "App.loadUrl("+url+","+props+")");
+		int wait = 0;
+		boolean openExternal = false;
+		boolean clearHistory = false;
+
+		// If there are properties, then set them on the Activity
+		HashMap<String, Object> params = new HashMap<String, Object>();
+		if (props != null) {
+			JSONArray keys = props.names();
+			for (int i=0; i<keys.length(); i++) {
+				String key = keys.getString(i); 
+				if (key.equals("wait")) {
+					wait = props.getInt(key);
+				}
+				else if (key.equalsIgnoreCase("openexternal")) {
+					openExternal = props.getBoolean(key);
+				}
+				else if (key.equalsIgnoreCase("clearhistory")) {
+					clearHistory = props.getBoolean(key);
+				}
+				else {
+					Object value = props.get(key);
+					if (value == null) {
+
+					}
+					else if (value.getClass().equals(String.class)) {
+						params.put(key, (String)value);
+					}
+					else if (value.getClass().equals(Boolean.class)) {
+						params.put(key, (Boolean)value);
+					}
+					else if (value.getClass().equals(Integer.class)) {
+						params.put(key, (Integer)value);
+					}
+				}
+			}
+		}
+
+		// If wait property, then delay loading
+
+		if (wait > 0) {
+			try {
+				synchronized(this) {
+					this.wait(wait);
+				}
+			} catch (InterruptedException e) {
+				e.printStackTrace();
+			}
+		}
+		((DroidGap)this.ctx).showWebPage(url, openExternal, clearHistory, params);
+	}
+
+	/**
+	 * Cancel loadUrl before it has been loaded.
+	 */
+	public void cancelLoadUrl() {
+		((DroidGap)this.ctx).cancelLoadUrl();
+	}
+	
+    /**
+     * Clear page history for the app.
+     */
+    public void clearHistory() {
+    	((DroidGap)this.ctx).clearHistory();
+    }
+    
+    /**
+     * Go to previous page displayed.
+     * This is the same as pressing the backbutton on Android device.
+     */
+    public void backHistory() {
+        ((DroidGap)this.ctx).backHistory();
+    }
+
+    /**
+     * Override the default behavior of the Android back button.
+     * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
+     * 
+     * @param override		T=override, F=cancel override
+     */
+    public void overrideBackbutton(boolean override) {
+    	LOG.i("DroidGap", "WARNING: Back Button Default Behaviour will be overridden.  The backbutton event will be fired!");
+    	((DroidGap)this.ctx).bound = override;
+    }
+
+    /**
+     * Return whether the Android back button is overridden by the user.
+     * 
+     * @return boolean
+     */
+    public boolean isBackbuttonOverridden() {
+    	return ((DroidGap)this.ctx).bound;
+    }
+
+    /**
+     * Exit the Android application.
+     */
+    public void exitApp() {
+    	((DroidGap)this.ctx).endActivity();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/AudioHandler.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java
new file mode 100755
index 0000000..d7df63e
--- /dev/null
+++ b/framework/src/org/apache/cordova/AudioHandler.java
@@ -0,0 +1,364 @@
+/*
+       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;
+
+import android.content.Context;
+import android.media.AudioManager;
+import java.util.ArrayList;
+
+import org.apache.cordova.api.LOG;
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * This class called by CordovaActivity to play and record audio.  
+ * The file can be local or over a network using http.
+ * 
+ * Audio formats supported (tested):
+ * 	.mp3, .wav
+ * 
+ * Local audio files must reside in one of two places:
+ * 		android_asset: 		file name must start with /android_asset/sound.mp3
+ * 		sdcard:				file name is just sound.mp3
+ */
+public class AudioHandler extends Plugin {
+
+    public static String TAG = "AudioHandler";
+	HashMap<String,AudioPlayer> players;	// Audio player object
+	ArrayList<AudioPlayer> pausedForPhone;     // Audio players that were paused when phone call came in
+	
+	/**
+	 * Constructor.
+	 */
+	public AudioHandler() {
+		this.players = new HashMap<String,AudioPlayer>();
+		this.pausedForPhone = new ArrayList<AudioPlayer>();
+	}
+
+	/**
+	 * Executes the request and returns PluginResult.
+	 * 
+	 * @param action 		The action to execute.
+	 * @param args 			JSONArry of arguments for the plugin.
+	 * @param callbackId	The callback id used when calling back into JavaScript.
+	 * @return 				A PluginResult object with a status and message.
+	 */
+	public PluginResult execute(String action, JSONArray args, String callbackId) {
+		PluginResult.Status status = PluginResult.Status.OK;
+		String result = "";		
+		
+		try {
+			if (action.equals("startRecordingAudio")) {
+				this.startRecordingAudio(args.getString(0), args.getString(1));
+			}
+			else if (action.equals("stopRecordingAudio")) {
+				this.stopRecordingAudio(args.getString(0));
+			}
+			else if (action.equals("startPlayingAudio")) {
+				this.startPlayingAudio(args.getString(0), args.getString(1));
+			}
+			else if (action.equals("seekToAudio")) {
+				this.seekToAudio(args.getString(0), args.getInt(1));
+			}
+			else if (action.equals("pausePlayingAudio")) {
+				this.pausePlayingAudio(args.getString(0));
+			}
+			else if (action.equals("stopPlayingAudio")) {
+				this.stopPlayingAudio(args.getString(0));
+			} else if (action.equals("setVolume")) {
+			   try {
+				   this.setVolume(args.getString(0), Float.parseFloat(args.getString(1)));
+			   } catch (NumberFormatException nfe) {
+				   //no-op
+			   }
+			} else if (action.equals("getCurrentPositionAudio")) {
+				float f = this.getCurrentPositionAudio(args.getString(0));
+				return new PluginResult(status, f);
+			}
+			else if (action.equals("getDurationAudio")) {
+				float f = this.getDurationAudio(args.getString(0), args.getString(1));
+				return new PluginResult(status, f);
+			}
+			else if (action.equals("release")) {
+				boolean b = this.release(args.getString(0));
+				return new PluginResult(status, b);
+			}
+			return new PluginResult(status, result);
+		} catch (JSONException e) {
+			e.printStackTrace();
+			return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+		}
+	}
+
+	/**
+	 * 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("getCurrentPositionAudio")) {
+			return true;
+		}
+		else if (action.equals("getDurationAudio")) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Stop all audio players and recorders.
+	 */
+	public void onDestroy() {
+        for (AudioPlayer audio : this.players.values()) {
+            audio.destroy();
+        }
+        this.players.clear();
+	}
+	
+    /**
+     * Called when a message is sent to plugin. 
+     * 
+     * @param id            The message id
+     * @param data          The message data
+     */
+    public void onMessage(String id, Object data) {
+        
+        // If phone message
+        if (id.equals("telephone")) {
+            
+            // If phone ringing, then pause playing
+            if ("ringing".equals(data) || "offhook".equals(data)) {
+                
+                // Get all audio players and pause them
+                for (AudioPlayer audio : this.players.values()) {
+                    if (audio.getState() == AudioPlayer.MEDIA_RUNNING) {
+                        this.pausedForPhone.add(audio);
+                        audio.pausePlaying();
+                    }
+                }
+
+            }
+            
+            // If phone idle, then resume playing those players we paused
+            else if ("idle".equals(data)) {
+                for (AudioPlayer audio : this.pausedForPhone) {
+                    audio.startPlaying(null);
+                }
+                this.pausedForPhone.clear();
+            }
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // LOCAL METHODS
+    //--------------------------------------------------------------------------
+	
+	/**
+	 * Release the audio player instance to save memory.
+	 * 
+	 * @param id				The id of the audio player
+	 */
+	private boolean release(String id) {
+    	if (!this.players.containsKey(id)) {
+    		return false;
+    	}
+    	AudioPlayer audio = this.players.get(id);
+    	this.players.remove(id);
+    	audio.destroy();
+    	return true;
+	}
+
+	/**
+	 * Start recording and save the specified file.
+	 * 
+	 * @param id				The id of the audio player
+	 * @param file				The name of the file
+	 */
+    public void startRecordingAudio(String id, String file) {
+    	// If already recording, then just return;
+    	if (this.players.containsKey(id)) {
+    		return;
+    	}
+    	AudioPlayer audio = new AudioPlayer(this, id);
+    	this.players.put(id, audio);
+    	audio.startRecording(file);
+    }
+
+    /**
+     * Stop recording and save to the file specified when recording started.
+     * 
+	 * @param id				The id of the audio player
+     */
+    public void stopRecordingAudio(String id) {
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio != null) {
+    		audio.stopRecording();
+    		this.players.remove(id);
+    	}
+    }
+    
+    /**
+     * Start or resume playing audio file.
+     * 
+	 * @param id				The id of the audio player
+     * @param file				The name of the audio file.
+     */
+    public void startPlayingAudio(String id, String file) {
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio == null) {
+    		audio = new AudioPlayer(this, id);
+    		this.players.put(id, audio);
+    	}
+    	audio.startPlaying(file);
+    }
+
+    /**
+     * Seek to a location.
+     * 
+     * 
+	 * @param id				The id of the audio player
+	 * @param miliseconds		int: number of milliseconds to skip 1000 = 1 second
+     */
+    public void seekToAudio(String id, int milliseconds) {
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio != null) {
+    		audio.seekToPlaying(milliseconds);
+    	}
+    }
+    
+    /**
+     * Pause playing.
+     * 
+	 * @param id				The id of the audio player
+     */
+    public void pausePlayingAudio(String id) {
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio != null) {
+    		audio.pausePlaying();
+    	}
+    }
+
+    /**
+     * Stop playing the audio file.
+     * 
+	 * @param id				The id of the audio player
+     */
+    public void stopPlayingAudio(String id) {
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio != null) {
+    		audio.stopPlaying();
+    		//audio.destroy();
+    		//this.players.remove(id);
+    	}
+    }
+    
+    /**
+     * Get current position of playback.
+     * 
+	 * @param id				The id of the audio player
+     * @return 					position in msec
+     */
+    public float getCurrentPositionAudio(String id) {
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio != null) {
+    		return(audio.getCurrentPosition()/1000.0f);
+    	}
+    	return -1;
+    }
+    
+    /**
+     * Get the duration of the audio file.
+     * 
+	 * @param id				The id of the audio player
+     * @param file				The name of the audio file.
+     * @return					The duration in msec.
+     */
+    public float getDurationAudio(String id, String file) {
+    	
+    	// Get audio file
+    	AudioPlayer audio = this.players.get(id);
+    	if (audio != null) {
+    		return(audio.getDuration(file));
+    	}
+    	
+    	// If not already open, then open the file
+    	else {
+    		audio = new AudioPlayer(this, id);
+    		this.players.put(id, audio);
+    		return(audio.getDuration(file));
+    	}
+    }  
+    
+    /**
+     * Set the audio device to be used for playback.
+     * 
+     * @param output			1=earpiece, 2=speaker
+     */
+    public void setAudioOutputDevice(int output) {
+		AudioManager audiMgr = (AudioManager) this.ctx.getSystemService(Context.AUDIO_SERVICE);
+		if (output == 2) {
+			audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL);
+		}
+		else if (output == 1) {
+			audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL);
+		}
+		else {
+			System.out.println("AudioHandler.setAudioOutputDevice() Error: Unknown output device.");
+		}
+    }
+    
+    /**
+     * Get the audio device to be used for playback.
+     * 
+     * @return					1=earpiece, 2=speaker
+     */
+    public int getAudioOutputDevice() {
+		AudioManager audiMgr = (AudioManager) this.ctx.getSystemService(Context.AUDIO_SERVICE);
+		if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) {
+			return 1;
+		}
+		else if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_SPEAKER) {
+			return 2;
+		}
+		else {
+			return -1;
+		}
+    }
+
+    /**
+     * Set the volume for an audio device
+     *
+     * @param id				The id of the audio player
+     * @param volume            Volume to adjust to 0.0f - 1.0f
+     */
+    public void setVolume(String id, float volume) {
+        AudioPlayer audio = this.players.get(id);
+        if (audio != null) {
+            audio.setVolume(volume);
+        } else {
+            System.out.println("AudioHandler.setVolume() Error: Unknown Audio Player " + id);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/AudioPlayer.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java
new file mode 100755
index 0000000..4ffeaa2
--- /dev/null
+++ b/framework/src/org/apache/cordova/AudioPlayer.java
@@ -0,0 +1,450 @@
+/*
+       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;
+
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class implements the audio playback and recording capabilities used by Cordova.
+ * It is called by the AudioHandler Cordova class.
+ * Only one file can be played or recorded per class instance.
+ * 
+ * Local audio files must reside in one of two places:
+ * 		android_asset: 		file name must start with /android_asset/sound.mp3
+ * 		sdcard:				file name is just sound.mp3
+ */
+public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener {
+
+    private static final String LOG_TAG = "AudioPlayer";
+
+    // AudioPlayer states
+	public static int MEDIA_NONE = 0;
+	public static int MEDIA_STARTING = 1;
+	public static int MEDIA_RUNNING = 2;
+	public static int MEDIA_PAUSED = 3;
+	public static int MEDIA_STOPPED = 4;
+	
+	// AudioPlayer message ids
+	private static int MEDIA_STATE = 1;
+	private static int MEDIA_DURATION = 2;
+    private static int MEDIA_POSITION = 3;
+	private static int MEDIA_ERROR = 9;
+	
+	// Media error codes
+    private static int MEDIA_ERR_NONE_ACTIVE    = 0;
+    private static int MEDIA_ERR_ABORTED        = 1;
+    private static int MEDIA_ERR_NETWORK        = 2;
+    private static int MEDIA_ERR_DECODE         = 3;
+    private static int MEDIA_ERR_NONE_SUPPORTED = 4;
+	
+	private AudioHandler handler;					// The AudioHandler object
+	private String id;								// The id of this player (used to identify Media object in JavaScript)
+	private int state = MEDIA_NONE;					// State of recording or playback
+	private String audioFile = null;				// File name to play or record to
+	private float duration = -1;					// Duration of audio
+
+	private MediaRecorder recorder = null;			// Audio recording object
+	private String tempFile = null;					// Temporary recording file name
+	
+	private MediaPlayer mPlayer = null;				// Audio player object
+	private boolean prepareOnly = false;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param handler			The audio handler object
+	 * @param id				The id of this audio player
+	 */
+	public AudioPlayer(AudioHandler handler, String id) {
+		this.handler = handler;
+		this.id = id;
+        this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3";
+	}	
+
+	/**
+	 * Destroy player and stop audio playing or recording.
+	 */
+	public void destroy() {
+		
+		// Stop any play or record
+		if (this.mPlayer != null) {
+	        if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
+	            this.mPlayer.stop();
+	            this.setState(MEDIA_STOPPED);
+	        }
+			this.mPlayer.release();
+			this.mPlayer = null;
+		}
+		if (this.recorder != null) {
+			this.stopRecording();
+			this.recorder.release();
+			this.recorder = null;
+		}
+	}
+
+	/**
+	 * Start recording the specified file.
+	 * 
+	 * @param file				The name of the file
+	 */
+	public void startRecording(String file) {
+		if (this.mPlayer != null) {
+			Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode.");
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_ABORTED+");");
+		}
+		
+		// Make sure we're not already recording
+		else if (this.recorder == null) {
+			this.audioFile = file;
+			this.recorder = new MediaRecorder();
+			this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+			this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP);
+			this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB);
+			this.recorder.setOutputFile(this.tempFile);
+			try {
+				this.recorder.prepare();
+				this.recorder.start();
+				this.setState(MEDIA_RUNNING);
+				return;
+			} catch (IllegalStateException e) {
+				e.printStackTrace();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_ABORTED+");");			
+		}
+		else {
+		    Log.d(LOG_TAG, "AudioPlayer Error: Already recording.");
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_ABORTED+");");			
+		}
+	}
+	
+	/**
+	 * Save temporary recorded file to specified name
+	 * 
+	 * @param file
+	 */
+	public void moveFile(String file) {
+		
+		/* this is a hack to save the file as the specified name */
+		File f = new File(this.tempFile);
+		f.renameTo(new File("/sdcard/" + file));
+	}
+	
+    /**
+     * Stop recording and save to the file specified when recording started.
+     */
+	public void stopRecording() {
+		if (this.recorder != null) {
+			try{
+				if (this.state == MEDIA_RUNNING) {
+					this.recorder.stop();
+					this.setState(MEDIA_STOPPED);
+				}
+				this.moveFile(this.audioFile);
+			}
+			catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+	}	
+	
+    /**
+     * Start or resume playing audio file.
+     * 
+     * @param file				The name of the audio file.
+     */
+	public void startPlaying(String file) {
+		if (this.recorder != null) {
+		    Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode.");
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_ABORTED+");");
+		}
+		
+		// If this is a new request to play audio, or stopped
+		else if ((this.mPlayer == null) || (this.state == MEDIA_STOPPED)) {
+			try {
+				// If stopped, then reset player
+				if (this.mPlayer != null) {
+					this.mPlayer.reset();
+				}
+				// Otherwise, create a new one
+				else {
+					this.mPlayer = new MediaPlayer();
+				}
+				this.audioFile = file;
+				
+				// If streaming file
+				if (this.isStreaming(file)) {
+					this.mPlayer.setDataSource(file);
+					this.mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);  
+					this.setState(MEDIA_STARTING);
+					this.mPlayer.setOnPreparedListener(this);		
+					this.mPlayer.prepareAsync();
+				}
+				
+				// If local file
+				else {
+					if (file.startsWith("/android_asset/")) {
+						String f = file.substring(15);
+						android.content.res.AssetFileDescriptor fd = this.handler.ctx.getBaseContext().getAssets().openFd(f);
+						this.mPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+					}
+					else {
+						this.mPlayer.setDataSource("/sdcard/" + file);
+					}
+					this.setState(MEDIA_STARTING);
+					this.mPlayer.setOnPreparedListener(this);		
+					this.mPlayer.prepare();
+
+					// Get duration
+					this.duration = getDurationInSeconds();
+				}
+			} 
+			catch (Exception e) { 
+				e.printStackTrace(); 
+				this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_ABORTED+");");			
+			}
+		}
+
+		// If we have already have created an audio player
+		else {
+			
+			// If player has been paused, then resume playback
+			if ((this.state == MEDIA_PAUSED) || (this.state == MEDIA_STARTING)) {
+				this.mPlayer.start();
+				this.setState(MEDIA_RUNNING);
+			}
+			else {
+			    Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: "+this.state);
+				this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_ABORTED+");");			
+			}
+		}
+	} 
+
+	/**
+	 * Seek or jump to a new time in the track.
+	 */
+	public void seekToPlaying(int milliseconds) {
+		if (this.mPlayer != null) {
+		    this.mPlayer.seekTo(milliseconds);
+		    Log.d(LOG_TAG, "Send a onStatus update for the new seek");
+		    this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_POSITION+", "+milliseconds/1000.0f+");");
+		}
+	}
+	
+	/**
+	 * Pause playing.
+	 */
+	public void pausePlaying() {
+		
+		// If playing, then pause
+		if (this.state == MEDIA_RUNNING) {
+			this.mPlayer.pause();
+			this.setState(MEDIA_PAUSED);
+		}
+		else {
+		    Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: "+this.state);			
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_NONE_ACTIVE+");");			
+		}
+	}
+
+    /**
+     * Stop playing the audio file.
+     */
+	public void stopPlaying() {
+		if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
+			this.mPlayer.stop();
+			this.setState(MEDIA_STOPPED);
+		}
+		else {
+		    Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: "+this.state);			
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+MEDIA_ERR_NONE_ACTIVE+");");			
+		}
+	}
+	
+	/**
+	 * Callback to be invoked when playback of a media source has completed.
+	 * 
+	 * @param mPlayer			The MediaPlayer that reached the end of the file 
+	 */
+	public void onCompletion(MediaPlayer mPlayer) {
+		this.setState(MEDIA_STOPPED);
+    } 
+	
+    /**
+     * Get current position of playback.
+     * 
+     * @return 					position in msec or -1 if not playing
+     */
+	public long getCurrentPosition() {
+		if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) {
+		    int curPos = this.mPlayer.getCurrentPosition();
+		    this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_POSITION+", "+curPos/1000.0f+");");
+			return curPos;
+		} 
+		else { 
+			return -1; 
+		}
+	}
+	
+	/**
+	 * Determine if playback file is streaming or local.
+	 * It is streaming if file name starts with "http://"
+	 * 
+	 * @param file				The file name
+	 * @return					T=streaming, F=local
+	 */
+	public boolean isStreaming(String file) {
+		if (file.contains("http://") || file.contains("https://")) {
+			return true;
+		} 
+		else {
+			return false;
+		}
+	}
+	
+   /**
+     * Get the duration of the audio file.
+     * 
+     * @param file				The name of the audio file.
+     * @return					The duration in msec.
+     * 							-1=can't be determined
+     * 							-2=not allowed
+     */
+	public float getDuration(String file) {
+		
+		// Can't get duration of recording
+		if (this.recorder != null) {
+			return(-2); // not allowed
+		}
+		
+		// If audio file already loaded and started, then return duration
+		if (this.mPlayer != null) {
+			return this.duration;
+		}
+		
+		// If no player yet, then create one
+		else {
+			this.prepareOnly = true;
+			this.startPlaying(file);
+			
+			// This will only return value for local, since streaming
+			// file hasn't been read yet.
+			return this.duration;
+		}
+	}
+
+	/**
+	 * Callback to be invoked when the media source is ready for playback. 
+	 * 
+	 * @param mPlayer			The MediaPlayer that is ready for playback 
+	 */
+	public void onPrepared(MediaPlayer mPlayer) {
+		// Listen for playback completion
+		this.mPlayer.setOnCompletionListener(this);
+
+		// If start playing after prepared
+		if (!this.prepareOnly) {
+			
+			// Start playing
+			this.mPlayer.start();
+
+			// Set player init flag
+			this.setState(MEDIA_RUNNING);
+		}
+		
+		// Save off duration
+		this.duration = getDurationInSeconds();	
+		this.prepareOnly = false;
+
+		// Send status notification to JavaScript
+		this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_DURATION+","+this.duration+");");
+		
+	}
+
+	/**
+	 * By default Android returns the length of audio in mills but we want seconds
+	 * 
+	 * @return length of clip in seconds
+	 */
+	private float getDurationInSeconds() {
+		return (this.mPlayer.getDuration() / 1000.0f);
+	}
+
+	/**
+	 * Callback to be invoked when there has been an error during an asynchronous operation
+	 *  (other errors will throw exceptions at method call time).
+	 *  
+	 * @param mPlayer			the MediaPlayer the error pertains to
+	 * @param arg1				the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED)
+	 * @param arg2				an extra code, specific to the error.
+	 */
+	public boolean onError(MediaPlayer mPlayer, int arg1, int arg2) {
+	    Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2+")");
+
+		// TODO: Not sure if this needs to be sent?
+		this.mPlayer.stop();
+		this.mPlayer.release();
+		
+		// Send error notification to JavaScript
+		this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+arg1+");");
+		return false;
+	}
+	
+	/**
+	 * Set the state and send it to JavaScript.
+	 * 
+	 * @param state
+	 */
+	private void setState(int state) {
+		if (this.state != state) {
+			this.handler.sendJavascript("Cordova.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+state+");");
+		}
+		
+		this.state = state;
+	}
+	
+	/**
+	 * Get the audio state.
+	 * 
+	 * @return int
+	 */
+	public int getState() {
+	    return this.state;
+	}
+
+	/**
+	 * Set the volume for audio player
+	 *
+	 * @param volume
+	 */
+    public void setVolume(float volume) {
+        this.mPlayer.setVolume(volume, volume);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/AuthenticationToken.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/AuthenticationToken.java b/framework/src/org/apache/cordova/AuthenticationToken.java
new file mode 100644
index 0000000..9ed6f2d
--- /dev/null
+++ b/framework/src/org/apache/cordova/AuthenticationToken.java
@@ -0,0 +1,51 @@
+package org.apache.cordova;
+
+/**
+ * The Class AuthenticationToken defines the userName and password to be used for authenticating a web resource
+ */
+public class AuthenticationToken {
+    private String userName;
+    private String password;
+    
+    /**
+     * Gets the user name.
+     * 
+     * @return the user name
+     */
+    public String getUserName() {
+        return userName;
+    }
+    
+    /**
+     * Sets the user name.
+     * 
+     * @param userName
+     *            the new user name
+     */
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+    
+    /**
+     * Gets the password.
+     * 
+     * @return the password
+     */
+    public String getPassword() {
+        return password;
+    }
+    
+    /**
+     * Sets the password.
+     * 
+     * @param password
+     *            the new password
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+    
+    
+    
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/BatteryListener.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/BatteryListener.java b/framework/src/org/apache/cordova/BatteryListener.java
new file mode 100755
index 0000000..84024b2
--- /dev/null
+++ b/framework/src/org/apache/cordova/BatteryListener.java
@@ -0,0 +1,156 @@
+/*
+       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;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+public class BatteryListener extends Plugin {
+    
+    private static final String LOG_TAG = "BatteryManager";
+
+    BroadcastReceiver receiver;
+
+    private String batteryCallbackId = null;
+    
+    /**
+     * Constructor.
+     */
+    public BatteryListener() {
+        this.receiver = null;
+    }
+    
+    /**
+     * Executes the request and returns PluginResult.
+     * 
+     * @param action        The action to execute.
+     * @param args          JSONArry of arguments for the plugin.
+     * @param callbackId    The callback id used when calling back into JavaScript.
+     * @return              A PluginResult object with a status and message.
+     */
+    public PluginResult execute(String action, JSONArray args, String callbackId) {
+        PluginResult.Status status = PluginResult.Status.INVALID_ACTION;
+        String result = "Unsupported Operation: " + action; 
+                
+        if (action.equals("start")) {
+        	if (this.batteryCallbackId != null) {
+        		return new PluginResult(PluginResult.Status.ERROR, "Battery listener already running.");
+        	}
+            this.batteryCallbackId = callbackId;
+
+            // We need to listen to power events to update battery status
+            IntentFilter intentFilter = new IntentFilter() ;
+            intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+            if (this.receiver == null) {
+                this.receiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) { 
+                        updateBatteryInfo(intent);              
+                    }
+                };
+                ctx.registerReceiver(this.receiver, intentFilter);
+            }
+
+            // Don't return any result now, since status results will be sent when events come in from broadcast receiver 
+            PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
+            pluginResult.setKeepCallback(true);
+            return pluginResult;
+        } 
+        
+        else if (action.equals("stop")) {
+            removeBatteryListener();
+            this.sendUpdate(new JSONObject(), false); // release status callback in JS side
+            this.batteryCallbackId = null;
+            return new PluginResult(PluginResult.Status.OK);
+        }
+        
+        return new PluginResult(status, result);
+    }
+    
+    /**
+     * Stop battery receiver.
+     */
+    public void onDestroy() {
+        removeBatteryListener();
+    }
+
+    /**
+     * Stop the battery receiver and set it to null.
+     */
+    private void removeBatteryListener() {
+        if (this.receiver != null) {
+            try {
+                this.ctx.unregisterReceiver(this.receiver);
+                this.receiver = null;
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error unregistering battery receiver: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Creates a JSONObject with the current battery information
+     * 
+     * @param batteryIntent the current battery information
+     * @return a JSONObject containing the battery status information
+     */
+    private JSONObject getBatteryInfo(Intent batteryIntent) {
+        JSONObject obj = new JSONObject();
+        try {
+            obj.put("level", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, 0));
+            obj.put("isPlugged", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_PLUGGED, -1) > 0 ? true : false);
+        } catch (JSONException e) {
+            Log.e(LOG_TAG, e.getMessage(), e);
+        }
+        return obj;
+    }
+
+    /**
+     * Updates the JavaScript side whenever the battery changes
+     * 
+     * @param batteryIntent the current battery information
+     * @return
+     */
+    private void updateBatteryInfo(Intent batteryIntent) {    
+        sendUpdate(this.getBatteryInfo(batteryIntent), true);
+    }
+    
+    /**
+     * Create a new plugin result and send it back to JavaScript
+     * 
+     * @param connection the network info to set as navigator.connection
+     */
+    private void sendUpdate(JSONObject info, boolean keepCallback) {
+    	if (this.batteryCallbackId != null) {
+    		PluginResult result = new PluginResult(PluginResult.Status.OK, info);
+    		result.setKeepCallback(keepCallback);
+    		this.success(result, this.batteryCallbackId);
+    	}
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-android/blob/664a061d/framework/src/org/apache/cordova/CallbackServer.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CallbackServer.java b/framework/src/org/apache/cordova/CallbackServer.java
new file mode 100755
index 0000000..6b6ce70
--- /dev/null
+++ b/framework/src/org/apache/cordova/CallbackServer.java
@@ -0,0 +1,431 @@
+/*
+       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;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.LinkedList;
+
+import android.util.Log;
+
+/**
+ * This class provides a way for Java to run JavaScript in the web page that has loaded Cordova.
+ * The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
+ * statements that are to be executed on the web page.
+ * 
+ * The process flow for XHR is:
+ * 1. JavaScript makes an async XHR call. 
+ * 2. The server holds the connection open until data is available. 
+ * 3. The server writes the data to the client and closes the connection. 
+ * 4. The server immediately starts listening for the next XHR call. 
+ * 5. The client receives this XHR response, processes it.
+ * 6. The client sends a new async XHR request.
+ *
+ * The CallbackServer class requires the following permission in Android manifest file
+ * 		<uses-permission android:name="android.permission.INTERNET" />
+ * 
+ * If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
+ * This can be determined by the client by calling CallbackServer.usePolling().  
+ * 
+ * The process flow for polling is:
+ * 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
+ * 2. If statement available, then client processes it.
+ * 3. The client repeats #1 in loop. 
+ */
+public class CallbackServer implements Runnable {
+	
+	private static final String LOG_TAG = "CallbackServer";
+
+	/**
+	 * The list of JavaScript statements to be sent to JavaScript.
+	 */
+	private LinkedList<String> javascript;
+	
+	/**
+	 * The port to listen on.
+	 */
+	private int port;
+	
+	/**
+	 * The server thread.
+	 */
+	private Thread serverThread;
+	
+	/**
+	 * Indicates the server is running.
+	 */
+	private boolean active;
+	
+	/**
+	 * Indicates that the JavaScript statements list is empty
+	 */
+	private boolean empty;
+	
+	/**
+	 * Indicates that polling should be used instead of XHR.
+	 */
+	private boolean usePolling = true;
+	
+	/**
+	 * Security token to prevent other apps from accessing this callback server via XHR
+	 */
+	private String token;
+	
+	/**
+	 * Constructor.
+	 */
+	public CallbackServer() {
+		//System.out.println("CallbackServer()");
+		this.active = false;
+		this.empty = true;
+		this.port = 0;
+		this.javascript = new LinkedList<String>();		
+	}
+	
+	/**
+	 * Init callback server and start XHR if running local app.
+	 * 
+	 * If Cordova app is loaded from file://, then we can use XHR
+	 * otherwise we have to use polling due to cross-domain security restrictions.
+	 * 
+	 * @param url			The URL of the Cordova app being loaded
+	 */
+	public void init(String url) {
+		//System.out.println("CallbackServer.start("+url+")");
+		this.active = false;
+		this.empty = true;
+		this.port = 0;
+		this.javascript = new LinkedList<String>();		
+
+		// Determine if XHR or polling is to be used
+		if ((url != null) && !url.startsWith("file://")) {
+			this.usePolling = true;
+			this.stopServer();
+		}
+		else if (android.net.Proxy.getDefaultHost() != null) {
+			this.usePolling = true;
+			this.stopServer();
+		}
+		else {
+			this.usePolling = false;
+			this.startServer();
+		}
+	}
+	
+    /**
+     * Re-init when loading a new HTML page into webview.
+     * 
+     * @param url           The URL of the Cordova app being loaded
+     */
+	public void reinit(String url) {
+	    this.stopServer();
+	    this.init(url);
+	}
+	
+	/**
+	 * Return if polling is being used instead of XHR.
+	 * 
+	 * @return
+	 */
+	public boolean usePolling() {
+		return this.usePolling;
+	}
+	
+	/**
+	 * Get the port that this server is running on.
+	 * 
+	 * @return
+	 */
+	public int getPort() {
+		return this.port;
+	}
+	
+	/**
+	 * Get the security token that this server requires when calling getJavascript().
+	 * 
+	 * @return
+	 */
+	public String getToken() {
+		return this.token;
+	}
+	
+	/**
+	 * Start the server on a new thread.
+	 */
+	public void startServer() {
+		//System.out.println("CallbackServer.startServer()");
+		this.active = false;
+		
+		// Start server on new thread
+		this.serverThread = new Thread(this);
+		this.serverThread.start();
+	}
+
+	/**
+	 * Restart the server on a new thread.
+	 */
+	public void restartServer() {
+		
+		// Stop server
+		this.stopServer();
+				
+		// Start server again
+		this.startServer();
+	}
+
+	/**
+	 * Start running the server.  
+	 * This is called automatically when the server thread is started.
+	 */
+	public void run() {
+		
+		// Start server
+		try {
+			this.active = true;
+			String request;
+			ServerSocket waitSocket = new ServerSocket(0);
+			this.port = waitSocket.getLocalPort();
+			//System.out.println("CallbackServer -- using port " +this.port);
+			this.token = java.util.UUID.randomUUID().toString();
+			//System.out.println("CallbackServer -- using token "+this.token);
+
+			 while (this.active) {
+				 //System.out.println("CallbackServer: Waiting for data on socket");
+				 Socket connection = waitSocket.accept();
+				 BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()),40);
+				 DataOutputStream output = new DataOutputStream(connection.getOutputStream());
+				 request = xhrReader.readLine();
+				 String response = "";
+				 //System.out.println("CallbackServerRequest="+request);
+				 if (this.active && (request != null)) {
+					 if (request.contains("GET")) {
+						 
+						 // Get requested file
+						 String[] requestParts = request.split(" "); 
+						 
+						 // Must have security token
+						 if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
+							 //System.out.println("CallbackServer -- Processing GET request");
+
+							 // Wait until there is some data to send, or send empty data every 10 sec 
+							 // to prevent XHR timeout on the client 
+							 synchronized (this) { 
+								 while (this.empty) { 
+									 try { 
+										 this.wait(10000); // prevent timeout from happening
+										 //System.out.println("CallbackServer>>> break <<<");
+										 break;
+									 } 
+									 catch (Exception e) { }
+								 } 
+							 }
+
+							 // If server is still running
+							 if (this.active) {
+
+								 // If no data, then send 404 back to client before it times out
+								 if (this.empty) {
+									 //System.out.println("CallbackServer -- sending data 0");
+									 response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
+								 }
+								 else {
+									 //System.out.println("CallbackServer -- sending item");
+									 response = "HTTP/1.1 200 OK\r\n\r\n";
+									 String js = this.getJavascript();
+									 if (js != null) {
+										 response += encode(js, "UTF-8");
+									 }
+								 }
+							 }
+							 else {
+								 response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";							 
+							 }
+						 }
+						 else {
+							 response = "HTTP/1.1 403 Forbidden\r\n\r\n ";						 
+						 }
+					 }
+					 else {
+						 response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
+					 }
+					 //System.out.println("CallbackServer: response="+response);
+					 //System.out.println("CallbackServer: closing output");
+					 output.writeBytes(response);
+					 output.flush();
+				 }
+				 output.close();
+				 xhrReader.close();
+			 }
+		 } catch (IOException e) {
+			 e.printStackTrace();
+		 }
+		 this.active = false;
+		 //System.out.println("CallbackServer.startServer() - EXIT");
+	}
+		
+	/**
+	 * Stop server.  
+	 * This stops the thread that the server is running on.
+	 */
+	public void stopServer() {
+		//System.out.println("CallbackServer.stopServer()");
+		if (this.active) {
+			this.active = false;
+
+			// Break out of server wait
+			synchronized (this) { 
+				this.notify();
+			}
+		}		
+	}
+
+    /**
+     * Destroy
+     */
+    public void destroy() {
+    	this.stopServer();
+    }
+
+	/**
+	 * Get the number of JavaScript statements.
+	 * 
+	 * @return int
+	 */
+	public int getSize() {
+	    synchronized(this) {
+	        int size = this.javascript.size();
+	        return size;
+	    }
+	}
+	
+	/**
+	 * Get the next JavaScript statement and remove from list.
+	 *  
+	 * @return String
+	 */
+	public String getJavascript() {
+	    synchronized(this) {
+	        if (this.javascript.size() == 0) {
+	            return null;
+	        }
+	        String statement = this.javascript.remove(0);
+	        if (this.javascript.size() == 0) { 
+	            this.empty = true;
+	        }
+	        return statement;
+	    }
+	}
+	
+	/**
+	 * Add a JavaScript statement to the list.
+	 * 
+	 * @param statement
+	 */
+	public void sendJavascript(String statement) {
+	    synchronized (this) { 
+	        this.javascript.add(statement);
+	        this.empty = false;
+	        this.notify();
+	    }
+	}
+	
+	/* The Following code has been modified from original implementation of URLEncoder */
+	
+	/* start */
+	
+	/*
+     *  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.
+     */
+	static final String digits = "0123456789ABCDEF";
+
+    /**
+     * This will encode the return value to JavaScript.  We revert the encoding for 
+     * common characters that don't require encoding to reduce the size of the string 
+     * being passed to JavaScript.
+     * 
+     * @param s to be encoded
+     * @param enc encoding type
+     * @return encoded string
+     */
+	public static String encode(String s, String enc) throws UnsupportedEncodingException {
+        if (s == null || enc == null) {
+            throw new NullPointerException();
+        }
+        // check for UnsupportedEncodingException
+        "".getBytes(enc);
+        
+        // Guess a bit bigger for encoded form
+        StringBuilder buf = new StringBuilder(s.length() + 16);
+        int start = -1;
+        for (int i = 0; i < s.length(); i++) {
+            char ch = s.charAt(i);
+            if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
+                    || (ch >= '0' && ch <= '9')
+                    || " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) {
+                if (start >= 0) {
+                    convert(s.substring(start, i), buf, enc);
+                    start = -1;
+                }
+                if (ch != ' ') {
+                    buf.append(ch);
+                } else {
+                    buf.append(' ');
+                }
+            } else {
+                if (start < 0) {
+                    start = i;
+                }
+            }
+        }
+        if (start >= 0) {
+            convert(s.substring(start, s.length()), buf, enc);
+        }
+        return buf.toString();
+    }
+
+    private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException {
+        byte[] bytes = s.getBytes(enc);
+        for (int j = 0; j < bytes.length; j++) {
+            buf.append('%');
+            buf.append(digits.charAt((bytes[j] & 0xf0) >> 4));
+            buf.append(digits.charAt(bytes[j] & 0xf));
+        }
+    }
+    
+    /* end */
+}