diff --git a/framework/assets/js/accelerometer.js b/framework/assets/js/accelerometer.js index ed553ed8..5b0fdc96 100644 --- a/framework/assets/js/accelerometer.js +++ b/framework/assets/js/accelerometer.js @@ -16,48 +16,17 @@ function Accelerometer() { */ this.lastAcceleration = null; - /** - * List of accelerometer watch listeners - */ - this.accelListeners = {}; - /** * List of accelerometer watch timers */ - this.accelTimers = {}; - - /** - * Next id to use - */ - this.listenerId = 0; - - /** - * Timer that turns off accelerometer when it reaches maximum time. - * This timer is only used for getCurrentAcceleration. - */ - this.turnOffTimer = 0; - - // Turn off accelerometer if not accessed for certain amount of time - setInterval(function() { - navigator.accelerometer.turnOffTimer += 10; - if (navigator.accelerometer.turnOffTimer > Accelerometer.MAX_TIMER) { - Accel.stop("timer"); - navigator.accelerometer.turnOffTimer = 0; - } - }, 10000); + this.timers = {}; } Accelerometer.STOPPED = 0; Accelerometer.STARTING = 1; Accelerometer.RUNNING = 2; Accelerometer.ERROR_FAILED_TO_START = 3; -Accelerometer.ERROR_NOT_FOUND = 4; -Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start", "Listener not found"]; - -/** - * Time (in seconds) to turn off accelerometer if getCurrentAcceleration() hasn't been called. - */ -Accelerometer.MAX_TIMER = 30; +Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; /** * Asynchronously aquires the current acceleration. @@ -96,9 +65,7 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error // If not running, then start it else { - Accel.start("timer"); - navigator.accelerometer.turnOffTimer = 0; - var obj = this; + Accel.start(); // Wait until started var timer = setInterval(function() { @@ -117,16 +84,16 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error } // If accelerometer error - else { - console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); - try { - if (errorCallback) { - errorCallback(status); + else { + console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); + try { + if (errorCallback) { + errorCallback(status); + } + } catch (e) { + console.log("Accelerometer Error in errorCallback: " + e); + } } - } catch (e) { - console.log("Accelerometer Error in errorCallback: " + e); - } - } } }, 10); } @@ -141,6 +108,7 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error * @return String The watch id that must be passed to #clearWatch to stop watching. */ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) { + // Default interval (10 sec) var frequency = (options != undefined)? options.frequency : 10000; @@ -156,12 +124,17 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb return; } - var id = ""+(navigator.accelerometer.listenerId++); - navigator.accelerometer.accelListeners[id] = id; - Accel.start(id); + // Make sure accelerometer timeout > frequency + 10 sec + var timeout = Accel.getTimeout(); + if (timeout < (frequency + 10000)) { + Accel.setTimeout(frequency + 10000); // set to frequency + 10 sec + } + + var id = PhoneGap.createUUID(); + Accel.start(); // Start watch timer - navigator.accelerometer.accelTimers[id] = setInterval(function() { + navigator.accelerometer.timers[id] = setInterval(function() { var status = Accel.getStatus(); // If accelerometer is running @@ -199,22 +172,9 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb Accelerometer.prototype.clearWatch = function(id) { // Stop javascript timer & remove from timer list - if (id && navigator.accelerometer.accelTimers[id]) { - clearInterval(navigator.accelerometer.accelTimers[id]); - delete navigator.accelerometer.accelTimers[id]; - } - - // Remove from watch list - if (id && navigator.accelerometer.accelListeners[id]) { - delete navigator.accelerometer.accelListeners[id]; - } - - // Stop accelerometer for this watch listener - if (id) { - try { - Accel.stop(id); - } catch (e) { - } + if (id && navigator.accelerometer.timers[id]) { + clearInterval(navigator.accelerometer.timers[id]); + delete navigator.accelerometer.timers[id]; } } diff --git a/framework/assets/js/compass.js b/framework/assets/js/compass.js index bb3e220a..82d3b8be 100644 --- a/framework/assets/js/compass.js +++ b/framework/assets/js/compass.js @@ -6,90 +6,169 @@ function Compass() { /** * The last known Compass position. */ - this.lastHeading = null; - this.lastError = null; - this.callbacks = { - onHeadingChanged: [], - onError: [] - }; + this.lastHeading = null; + + /** + * List of compass watch timers + */ + this.timers = {}; }; +Compass.STOPPED = 0; +Compass.STARTING = 1; +Compass.RUNNING = 2; +Compass.ERROR_FAILED_TO_START = 3; +Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; + /** * Asynchronously aquires the current heading. - * @param {Function} successCallback The function to call when the heading - * data is available - * @param {Function} errorCallback The function to call when there is an error - * getting the heading data. - * @param {PositionOptions} options The options for getting the heading data - * such as timeout. + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. + * @param {PositionOptions} options The options for getting the heading data such as timeout. */ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { - if (this.lastHeading == null) { - CompassHook.start(); - } - else - if (typeof successCallback == "function") { - successCallback(this.lastHeading); - } -}; + + // successCallback required + if (typeof successCallback != "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Get current compass status + var status = CompassHook.getStatus(); + + // If running, then call successCallback + if (status == Compass.RUNNING) { + try { + var heading = CompassHook.getHeading(); + successCallback(heading); + } catch (e) { + console.log("Compass Error in successCallback: " + e); + } + } + + // If not running, then start it + else { + CompassHook.start(); + + // Wait until started + var timer = setInterval(function() { + var status = CompassHook.getStatus(); + if (status != Compass.STARTING) { + clearInterval(timer); + + // If compass is running + if (status == Compass.RUNNING) { + try { + var heading = CompassHook.getHeading(); + successCallback(heading); + } catch (e) { + console.log("Compass Error in successCallback: " + e); + } + } + + // If compass error + else { + console.log("Compass Error: "+ Compass.ERROR_MSG[status]); + try { + if (errorCallback) { + errorCallback(status); + } + } catch (e) { + console.log("Compass Error in errorCallback: " + e); + } + } + } + }, 10); + } +} /** * Asynchronously aquires the heading repeatedly at a given interval. - * @param {Function} successCallback The function to call each time the heading - * data is available - * @param {Function} errorCallback The function to call when there is an error - * getting the heading data. - * @param {HeadingOptions} options The options for getting the heading data - * such as timeout and the frequency of the watch. + * + * @param {Function} successCallback The function to call each time the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. + * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. + * @return String The watch id that must be passed to #clearWatch to stop watching. */ Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { - // Invoke the appropriate callback with a new Position object every time the implementation - // determines that the position of the hosting device has changed. - - this.getCurrentHeading(successCallback, errorCallback, options); - var frequency = 100; - if (typeof(options) == 'object' && options.frequency) - frequency = options.frequency; - var self = this; - return setInterval(function() { - self.getCurrentHeading(successCallback, errorCallback, options); - }, frequency); -}; + // Default interval (100 msec) + var frequency = (options != undefined) ? options.frequency : 100; + + // successCallback required + if (typeof successCallback != "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Make sure compass timeout > frequency + 10 sec + var timeout = CompassHook.getTimeout(); + if (timeout < (frequency + 10000)) { + CompassHook.setTimeout(frequency + 10000); // set to frequency + 10 sec + } + + var id = PhoneGap.createUUID(); + CompassHook.start(); + + // Start watch timer + navigator.compass.timers[id] = setInterval(function() { + var status = CompassHook.getStatus(); + + // If compass is running + if (status == Compass.RUNNING) { + try { + var heading = CompassHook.getHeading(); + successCallback(heading); + } catch (e) { + console.log("Compass Error in successCallback: " + e); + } + } + + // If compass had error + else if (status != Compass.STARTING) { + console.log("Compass Error: "+ Compass.ERROR_MSG[status]); + try { + navigator.compass.clearWatch(id); + if (errorCallback) { + errorCallback(status); + } + } catch (e) { + console.log("Compass Error in errorCallback: " + e); + } + } + }, (frequency ? frequency : 1)); + + return id; +} /** * Clears the specified heading watch. - * @param {String} watchId The ID of the watch returned from #watchHeading. + * + * @param {String} id The ID of the watch returned from #watchHeading. */ -Compass.prototype.clearWatch = function(watchId) { - clearInterval(watchId); -}; +Compass.prototype.clearWatch = function(id) { - -/** - * Called by the geolocation framework when the current heading is found. - * @param {HeadingOptions} position The current heading. - */ -Compass.prototype.setHeading = function(heading) { - this.lastHeading = heading; - for (var i = 0; i < this.callbacks.onHeadingChanged.length; i++) { - var f = this.callbacks.onHeadingChanged.shift(); - f(heading); + // Stop javascript timer & remove from timer list + if (id && navigator.compass.timers[id]) { + clearInterval(navigator.compass.timers[id]); + delete navigator.compass.timers[id]; } -}; - -/** - * Called by the geolocation framework when an error occurs while looking up the current position. - * @param {String} message The text of the error message. - */ -Compass.prototype.setError = function(message) { - this.lastError = message; - for (var i = 0; i < this.callbacks.onError.length; i++) { - var f = this.callbacks.onError.shift(); - f(message); - } -}; +} PhoneGap.addConstructor(function() { if (typeof navigator.compass == "undefined") navigator.compass = new Compass(); diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 6be5b591..b3607324 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -185,4 +185,30 @@ PhoneGap.JSCallback = function() { xmlhttp.open("GET", "http://127.0.0.1:"+CallbackServer.getPort()+"/" , true); xmlhttp.send(); -} +}; + +/** + * Create a UUID + * + * @return + */ +PhoneGap.createUUID = function() { + return PhoneGap.UUIDcreatePart(4) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(6); +}; + +PhoneGap.UUIDcreatePart = function(length) { + var uuidpart = ""; + for (var i=0; i listenerIds; // List of listener ids - - /** - * Constructor - * - * @param view - * @param ctx - */ - public AccelBroker(WebView view, DroidGap ctx) - { - mCtx = ctx; - mAppView = view; - - // Create listener - listener = new AccelListener(mCtx, mAppView); - - listenerIds = new HashMap(); - } - - /** - * Start listening to acceleration sensor. - * - * @param String id The id of the listener - * @return true if started, false if not - */ - public boolean start(String id) - { - // Track start for listener - listenerIds.put(id, 1); - - // Start listener if necessary - if ((listener.status != AccelBroker.RUNNING) && (listener.status != AccelBroker.STARTING)) { - listener.start(); - } - - return ((listener.status == AccelBroker.RUNNING) || (listener.status == AccelBroker.STARTING)); - } - - /** - * Stop listening for acceleration sensor. - * - * @param String id The id of the listener - * @return true if stopped, false if not - */ - public boolean stop(String id) - { - // Stop tracking listener - if (listenerIds.containsKey(id)) { - listenerIds.remove(id); - } - - // If no more listeners, then stop accelerometer - if (listenerIds.isEmpty()) { - listener.stop(); - } - - return (listener.status == AccelBroker.STOPPED); - } - - /** - * Destroy listener - */ - public void destroy() { - listener.destroy(); - } - - /** - * Get result of the last request or update if watching. - * If sensor is still starting, wait until 1st value is acquired before returning. - * - * NOTE: NOT USED - DO WE NEED THIS, SINCE JSON MUST BE PARSED ON JS SIDE? - * - * @param key listener id - * @return String representation of JSON object - */ - public String getResult() { - - // If acceleration values - if (listener.status == AccelBroker.RUNNING) { - return "{status:" + listener.status + ",value:{x:" + listener.x + ", y:" + listener.y + ", z:" + listener.z + "}}"; - } - - // If error or not running - return "{status:" + listener.status + ",value:null}"; - } - - /** - * Get status of accelerometer sensor. - * - * @return status - */ - public int getStatus() { - return listener.status; - } - - /** - * Get X value of last accelerometer value. - * - * @return x value - */ - public float getX() { - return listener.x; - } - - /** - * Get Y value of last accelerometer value. - * - * @return y value - */ - public float getY() { - return listener.y; - } - - /** - * Get Z value of last accelerometer value. - * - * @return z value - */ - public float getZ() { - return listener.x; - } - -} diff --git a/framework/src/com/phonegap/AccelListener.java b/framework/src/com/phonegap/AccelListener.java index 3338970e..d25fef79 100644 --- a/framework/src/com/phonegap/AccelListener.java +++ b/framework/src/com/phonegap/AccelListener.java @@ -15,12 +15,20 @@ import android.webkit.WebView; */ public class AccelListener 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 + WebView mAppView; // WebView object DroidGap mCtx; // DroidGap object 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 @@ -31,7 +39,7 @@ public class AccelListener implements SensorEventListener{ * @param ctx The Activity (DroidGap) object * @param appView */ - public AccelListener(DroidGap ctx, WebView appView) { + public AccelListener(WebView appView, DroidGap ctx) { this.mCtx = ctx; this.mAppView = appView; this.sensorManager = (SensorManager) mCtx.getSystemService(Context.SENSOR_SERVICE); @@ -39,7 +47,7 @@ public class AccelListener implements SensorEventListener{ this.y = 0; this.z = 0; this.timeStamp = 0; - this.status = AccelBroker.STOPPED; + this.status = AccelListener.STOPPED; } /** @@ -48,21 +56,26 @@ public class AccelListener implements SensorEventListener{ * @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 list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - //list = null; // @test failure AccelBroker.ERROR_FAILED_TO_START // 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.status = AccelBroker.STARTING; + this.status = AccelListener.STARTING; + this.lastAccessTime = System.currentTimeMillis(); } // If error, then set status to error else { - this.status = AccelBroker.ERROR_FAILED_TO_START; + this.status = AccelListener.ERROR_FAILED_TO_START; } return this.status; @@ -72,10 +85,10 @@ public class AccelListener implements SensorEventListener{ * Stop listening to acceleration sensor. */ public void stop() { - if (this.status == AccelBroker.RUNNING) { + if (this.status != AccelListener.STOPPED) { this.sensorManager.unregisterListener(this); } - this.status = AccelBroker.STOPPED; + this.status = AccelListener.STOPPED; } /** @@ -83,7 +96,7 @@ public class AccelListener implements SensorEventListener{ * Stop listener. */ public void destroy() { - this.sensorManager.unregisterListener(this); + this.stop(); } public void onAccuracyChanged(Sensor sensor, int accuracy) { @@ -108,7 +121,69 @@ public class AccelListener implements SensorEventListener{ this.y = event.values[1]; this.z = event.values[2]; - this.status = AccelBroker.RUNNING; + this.status = 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; + } + + /** + * Get X value of last accelerometer value. + * + * @return x value + */ + public float getX() { + this.lastAccessTime = System.currentTimeMillis(); + return this.x; + } + + /** + * Get Y value of last accelerometer value. + * + * @return y value + */ + public float getY() { + this.lastAccessTime = System.currentTimeMillis(); + return this.y; + } + + /** + * Get Z value of last accelerometer value. + * + * @return z value + */ + public float getZ() { + this.lastAccessTime = System.currentTimeMillis(); + return this.x; + } + + /** + * 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; + } + } diff --git a/framework/src/com/phonegap/CompassListener.java b/framework/src/com/phonegap/CompassListener.java index 044a23af..ed314f9a 100644 --- a/framework/src/com/phonegap/CompassListener.java +++ b/framework/src/com/phonegap/CompassListener.java @@ -10,8 +10,7 @@ import android.content.Context; import android.webkit.WebView; /** - * This class listens to the compass sensor and calls navigator.compass.setHeading(heading) - * method in JavaScript every sensor change event it receives. + * This class listens to the compass sensor and stores the latest heading value. */ public class CompassListener implements SensorEventListener{ @@ -19,11 +18,16 @@ public class CompassListener implements SensorEventListener{ 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 WebView mAppView; // WebView object DroidGap mCtx; // Activity (DroidGap) object int status; // status of listener + float heading; // most recent heading value + long timeStamp; // time of most recent value + long lastAccessTime; // time the value was last retrieved private SensorManager sensorManager;// Sensor manager Sensor mSensor; // Compass sensor returned by sensor manager @@ -39,6 +43,8 @@ public class CompassListener implements SensorEventListener{ this.mCtx = ctx; this.mAppView = appView; this.sensorManager = (SensorManager) mCtx.getSystemService(Context.SENSOR_SERVICE); + this.timeStamp = 0; + this.status = CompassListener.STOPPED; } /** @@ -47,6 +53,11 @@ public class CompassListener implements SensorEventListener{ * @return status of listener */ public int start() { + + // If already starting or running, then just return + if ((this.status == CompassListener.RUNNING) || (this.status == CompassListener.STARTING)) { + return this.status; + } // Get accelerometer from sensor manager List list = this.sensorManager.getSensorList(Sensor.TYPE_ORIENTATION); @@ -56,6 +67,7 @@ public class CompassListener implements SensorEventListener{ this.mSensor = list.get(0); this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_NORMAL); this.status = CompassListener.STARTING; + this.lastAccessTime = System.currentTimeMillis(); } // If error, then set status to error @@ -97,9 +109,51 @@ public class CompassListener implements SensorEventListener{ // We only care about the orientation as far as it refers to Magnetic North float heading = event.values[0]; + // Save heading + this.timeStamp = System.currentTimeMillis(); + this.heading = heading; this.status = CompassListener.RUNNING; - - // TODO This is very expensive to process every event. Should this use polling from JS instead? - mCtx.sendJavascript("navigator.compass.setHeading(" + heading + ");"); + + // If heading hasn't been read for TIMEOUT time, then turn off compass sensor to save power + if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) { + this.stop(); + } + } + + /** + * Get status of compass sensor. + * + * @return status + */ + public int getStatus() { + return this.status; + } + + /** + * Get the most recent compass heading. + * + * @return heading + */ + public float getHeading() { + this.lastAccessTime = System.currentTimeMillis(); + return this.heading; + } + + /** + * Set the timeout to turn off compass sensor if getHeading() hasn't been called. + * + * @param timeout Timeout in msec. + */ + public void setTimeout(float timeout) { + this.TIMEOUT = timeout; + } + + /** + * Get the timeout to turn off compass sensor if getHeading() hasn't been called. + * + * @return timeout in msec + */ + public float getTimeout() { + return this.TIMEOUT; } } diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index b9e7928d..4238cf4d 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -62,7 +62,7 @@ public class DroidGap extends Activity { private Device gap; private GeoBroker geo; - private AccelBroker accel; + private AccelListener accel; private CameraLauncher launcher; private ContactManager mContacts; private FileUtils fs; @@ -225,7 +225,7 @@ public class DroidGap extends Activity { { callbackServer = new CallbackServer(); gap = new Device(appView, this); - accel = new AccelBroker(appView, this); + accel = new AccelListener(appView, this); launcher = new CameraLauncher(appView, this); mContacts = new ContactManager(appView, this); fs = new FileUtils(appView);