diff --git a/framework/AndroidManifest.xml b/framework/AndroidManifest.xml index c6eeb949..4bae9257 100644 --- a/framework/AndroidManifest.xml +++ b/framework/AndroidManifest.xml @@ -25,6 +25,7 @@ + diff --git a/framework/assets/js/battery.js b/framework/assets/js/battery.js new file mode 100755 index 00000000..883b2de2 --- /dev/null +++ b/framework/assets/js/battery.js @@ -0,0 +1,124 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +if (!PhoneGap.hasResource("battery")) { +PhoneGap.addResource("battery"); + +/** + * This class contains information about the current battery status. + * @constructor + */ +var Battery = function() { + this._level = null; + this._isPlugged = null; + this._batteryListener = []; + this._lowListener = []; + this._criticalListener = []; +}; + +/** + * Registers as an event producer for battery events. + * + * @param {Object} eventType + * @param {Object} handler + * @param {Object} add + */ +Battery.prototype.eventHandler = function(eventType, handler, add) { + var me = navigator.battery; + if (add) { + // If there are no current registered event listeners start the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + PhoneGap.exec(me._status, me._error, "Battery", "start", []); + } + + // Register the event listener in the proper array + if (eventType === "batterystatus") { + var pos = me._batteryListener.indexOf(handler); + if (pos === -1) { + me._batteryListener.push(handler); + } + } else if (eventType === "batterylow") { + var pos = me._lowListener.indexOf(handler); + if (pos === -1) { + me._lowListener.push(handler); + } + } else if (eventType === "batterycritical") { + var pos = me._criticalListener.indexOf(handler); + if (pos === -1) { + me._criticalListener.push(handler); + } + } + } else { + // Remove the event listener from the proper array + if (eventType === "batterystatus") { + var pos = me._batteryListener.indexOf(handler); + if (pos > -1) { + me._batteryListener.splice(pos, 1); + } + } else if (eventType === "batterylow") { + var pos = me._lowListener.indexOf(handler); + if (pos > -1) { + me._lowListener.splice(pos, 1); + } + } else if (eventType === "batterycritical") { + var pos = me._criticalListener.indexOf(handler); + if (pos > -1) { + me._criticalListener.splice(pos, 1); + } + } + + // If there are no more registered event listeners stop the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + PhoneGap.exec(null, null, "Battery", "stop", []); + } + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = this; + if (me._level != info.level || me._isPlugged != info.isPlugged) { + // Fire batterystatus event + PhoneGap.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (info.level == 20 || info.level == 5) { + if (info.level == 20) { + PhoneGap.fireWindowEvent("batterylow", info); + } + else { + PhoneGap.fireWindowEvent("batterycritical", info); + } + } + } + me._level = info.level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.battery === "undefined") { + navigator.battery = new Battery(); + PhoneGap.addWindowEventHandler("batterystatus", navigator.battery.eventHandler); + PhoneGap.addWindowEventHandler("batterylow", navigator.battery.eventHandler); + PhoneGap.addWindowEventHandler("batterycritical", navigator.battery.eventHandler); + } +}); +} diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index b17a3434..f71bad50 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -27,7 +27,7 @@ var Connection = function() { // set a timer if still offline at the end of timer send the offline event me._timer = setTimeout(function(){ me.type = type; - PhoneGap.fireEvent('offline'); + PhoneGap.fireDocumentEvent('offline'); me._timer = null; }, me.timeout); } else { @@ -37,7 +37,7 @@ var Connection = function() { me._timer = null; } me.type = type; - PhoneGap.fireEvent('online'); + PhoneGap.fireDocumentEvent('online'); } // should only fire this once diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index f9ec35d2..f171a0cb 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -46,7 +46,9 @@ var PhoneGap = { ready: true, commands: [], timer: null - } + }, + documentEventHandler: {}, // Collection of custom document event handlers + windowEventHandler: {} // Collection of custom window event handlers }; /** @@ -381,6 +383,36 @@ document.addEventListener('DOMContentLoaded', function() { // Intercept calls to document.addEventListener and watch for deviceready PhoneGap.m_document_addEventListener = document.addEventListener; +// Intercept calls to window.addEventListener +PhoneGap.m_window_addEventListener = window.addEventListener; + +/** + * Add a custom window event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addWindowEventHandler = function(event, callback) { + PhoneGap.windowEventHandler[event] = callback; +} + +/** + * Add a custom document event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addDocumentEventHandler = function(event, callback) { + PhoneGap.documentEventHandler[event] = callback; +} + +/** + * Intercept adding document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ document.addEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); if (e === 'deviceready') { @@ -398,15 +430,52 @@ document.addEventListener = function(evt, handler, capture) { if (e === 'backbutton') { PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); } - + + // If subscribing to an event that is handled by a plugin + else if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); } }; +/** + * Intercept adding window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If subscribing to an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_addEventListener.call(window, evt, handler, capture); +}; + // Intercept calls to document.removeEventListener and watch for events that // are generated by PhoneGap native code PhoneGap.m_document_removeEventListener = document.removeEventListener; +// Intercept calls to window.removeEventListener +PhoneGap.m_window_removeEventListener = window.removeEventListener; + +/** + * Intercept removing document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ document.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); @@ -415,18 +484,70 @@ document.removeEventListener = function(evt, handler, capture) { PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); } + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); }; /** - * Method to fire event from native code + * Intercept removing window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture */ -PhoneGap.fireEvent = function(type) { +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_removeEventListener.call(window, evt, handler, capture); +}; + +/** + * Method to fire document event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireDocumentEvent = function(type, data) { var e = document.createEvent('Events'); e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } document.dispatchEvent(e); }; +/** + * Method to fire window event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireWindowEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + window.dispatchEvent(e); +}; + /** * If JSON not included, use our own stringify. (Android 1.6) * The restriction on ours is that it must be an array of simple types. diff --git a/framework/res/xml/plugins.xml b/framework/res/xml/plugins.xml index 3d8d48d8..dcc229ae 100644 --- a/framework/res/xml/plugins.xml +++ b/framework/res/xml/plugins.xml @@ -16,4 +16,5 @@ + \ No newline at end of file diff --git a/framework/src/com/phonegap/BatteryListener.java b/framework/src/com/phonegap/BatteryListener.java new file mode 100755 index 00000000..ef939db3 --- /dev/null +++ b/framework/src/com/phonegap/BatteryListener.java @@ -0,0 +1,145 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ +package com.phonegap; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +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); + } + } +} diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java old mode 100644 new mode 100755 index d9ecccd9..f9aca61c --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -1256,7 +1256,7 @@ public class DroidGap extends PhonegapActivity { // If back key is bound, then send event to JavaScript if (this.bound) { - this.appView.loadUrl("javascript:PhoneGap.fireEvent('backbutton');"); + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); return true; } @@ -1278,13 +1278,13 @@ public class DroidGap extends PhonegapActivity { // If menu key else if (keyCode == KeyEvent.KEYCODE_MENU) { - this.appView.loadUrl("javascript:PhoneGap.fireEvent('menubutton');"); + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); return true; } // If search key else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - this.appView.loadUrl("javascript:PhoneGap.fireEvent('searchbutton');"); + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); return true; } @@ -1478,13 +1478,13 @@ public class DroidGap extends PhonegapActivity { // gone away. else if (height > oldHeight) { Log.d(LOG_TAG, "Throw hide keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireEvent('hidekeyboard');"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); } // If the height as gotten smaller then we will assume the soft keyboard has // been displayed. else if (height < oldHeight) { Log.d(LOG_TAG, "Throw show keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireEvent('showkeyboard');"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); } // Update the old height for the next event