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 */
+}