From d5646584ee42f04bb764e1736b8b5d1452968b40 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Wed, 11 Aug 2010 13:47:50 -0500 Subject: [PATCH] Change accelerometer to use JavaScript setInterval for watch. --- framework/assets/js/accelerometer.js | 211 ++++++++++++++---- framework/src/com/phonegap/AccelBroker.java | 168 ++++++++++++-- framework/src/com/phonegap/AccelListener.java | 147 ++++++------ 3 files changed, 391 insertions(+), 135 deletions(-) diff --git a/framework/assets/js/accelerometer.js b/framework/assets/js/accelerometer.js index 106d17ae..26e31348 100644 --- a/framework/assets/js/accelerometer.js +++ b/framework/assets/js/accelerometer.js @@ -1,97 +1,210 @@ -function Acceleration(x, y, z) -{ +function Acceleration(x, y, z) { this.x = x; this.y = y; this.z = z; this.timestamp = new Date().getTime(); } -var accelListeners = []; - /** * This class provides access to device accelerometer data. * @constructor */ function Accelerometer() { - /** - * The last known acceleration. - */ - this.lastAcceleration = null; + /** + * The last known acceleration. type=Acceleration() + */ + this.lastAcceleration = null; + /** + * List of accelerometer watch listeners + */ + this.accelListeners = {}; + /** + * List of accelerometer watch timers + */ + this.accelTimers = {}; + /** + * Next id to use + */ + this.listenerId = 0; } +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"]; + /** * Asynchronously aquires the current acceleration. * @param {Function} successCallback The function to call when the acceleration * data is available - * @param {Function} errorCallback The function to call when there is an error + * @param {Function} errorCallback The function to call when there is an error * getting the acceleration data. * @param {AccelerationOptions} options The options for getting the accelerometer data * such as timeout. */ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) { - // If the acceleration is available then call success - // If the acceleration is not available then call error - // Created for iPhone, Iphone passes back _accel obj litteral - if (typeof successCallback == "function") { - if(this.lastAcceleration) - successCallback(accel); - else - { - watchAcceleration(this.gotCurrentAcceleration, this.fail); - } - } -} + // successCallback required + if (typeof successCallback != "function") { + console.log("Accelerometer Error: successCallback is not a function"); + return; + } + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Accelerometer Error: errorCallback is not a function"); + return; + } -Accelerometer.prototype.gotCurrentAcceleration = function(key, x, y, z) -{ - var a = new Acceleration(x,y,z); - a.x = x; - a.y = y; - a.z = z; - a.win = accelListeners[key].win; - a.fail = accelListeners[key].fail; - this.timestamp = new Date().getTime(); - this.lastAcceleration = a; - accelListeners[key] = a; - if (typeof a.win == "function") { - a.win(a); + // Get current acceleration from native + var status = Accel.getStatus(); + //console.log("getCurrentAcceleration: status="+status); + + // If running, then call successCallback + if (status == Accelerometer.RUNNING) { + try { + var accel = new Acceleration(Accel.getX(), Accel.getY(), Accel.getZ()); + successCallback(accel); + } catch (e) { + console.log("Accelerometer Error in successCallback: " + e); + } + } + + // If not running, then start it + else { + + Accel.start(); + + var status = Accel.getStatus(); + //console.log("getAcceleration: status="+status); + + // Wait until sensor has 1 reading + if (status == Accelerometer.STARTING) { + Accel.getX(); + status = Accel.getStatus(); // get status again to see if started or error + } + + // If sensor is running + if (status == Accelerometer.RUNNING) { + try { + var accel = new Acceleration(Accel.getX(), Accel.getY(), Accel.getZ()); + successCallback(accel); + } catch (e) { + console.log("Accelerometer Error in successCallback: " + e); + } + } + + // If sensor error + else { + console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); + try { + if (errorCallback) { + errorCallback(status); + } + } catch (e) { + console.log("Accelerometer Error in errorCallback: " + e); + } + } + + //@todo: don't clear now, wait until x seconds since last request + this.clearWatch(""); } } - /** * Asynchronously aquires the acceleration repeatedly at a given interval. + * * @param {Function} successCallback The function to call each time the acceleration * data is available - * @param {Function} errorCallback The function to call when there is an error + * @param {Function} errorCallback The function to call when there is an error * getting the acceleration data. * @param {AccelerationOptions} options The options for getting the accelerometer data * such as timeout. */ - Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) { - // TODO: add the interval id to a list so we can clear all watches - var frequency = (options != undefined)? options.frequency : 10000; - var accel = new Acceleration(0,0,0); - accel.win = successCallback; - accel.fail = errorCallback; - accel.opts = options; - var key = accelListeners.push( accel ) - 1; - Accel.start(frequency, key); + + var frequency = (options != undefined)? options.frequency : 10000; + + // successCallback required + if (typeof successCallback != "function") { + console.log("Accelerometer Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Accelerometer Error: errorCallback is not a function"); + return; + } + + var key = this.listenerId++; + this.accelListeners[key] = key; + var obj = this; + Accel.start(); + + // Start watch timer + this.accelTimers[key] = setInterval(function() { + //console.log("Interval timer: key="+key+" timer="+obj.accelTimers[key]+" freq="+frequency); + var status = Accel.getStatus(); + //console.log("watchAcceleration: status="+status); + + if (status == Accelerometer.RUNNING) { + try { + // If getCurrentAcceleration(), then clear this watch + if (frequency == 0) { + obj.clearWatch(key); + } + var accel = new Acceleration(Accel.getX(), Accel.getY(), Accel.getZ()); + successCallback(accel); + } catch (e) { + console.log("Accelerometer Error in successCallback: " + e); + } + } + else { + console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); + try { + obj.clearWatch(key); + if (errorCallback) { + errorCallback(status); + } + } catch (e) { + console.log("Accelerometer Error in errorCallback: " + e); + } + } + }, (frequency ? frequency : 1)); + + return key; } /** * Clears the specified accelerometer watch. + * * @param {String} watchId The ID of the watch returned from #watchAcceleration. */ Accelerometer.prototype.clearWatch = function(watchId) { - Accel.stop(watchId); -} -Accelerometer.prototype.epicFail = function(watchId, message) { - accelWatcher[key].fail(); + // Stop javascript timer & remove from timer list + if (watchId && this.accelTimers[watchId]) { + clearInterval(this.accelListeners[watchId]); + delete this.accelTimers[watchId]; + } + + // Remove from watch list + if (watchId && this.accelListeners[watchId]) { + delete this.accelListeners[watchId]; + } + + // Stop native sensor if no more listeners + var size = 0; + var key; + for (key in this.accelListeners) { + if (this.accelListeners.hasOwnProperty(key)) size++; + } + if (size == 0) { + Accel.stop(); + } } PhoneGap.addConstructor(function() { diff --git a/framework/src/com/phonegap/AccelBroker.java b/framework/src/com/phonegap/AccelBroker.java index 7ec31b3f..32365369 100644 --- a/framework/src/com/phonegap/AccelBroker.java +++ b/framework/src/com/phonegap/AccelBroker.java @@ -1,34 +1,156 @@ package com.phonegap; -import java.util.HashMap; - import android.content.Context; import android.webkit.WebView; +/** + * This class manages access to the accelerometer from JavaScript. + * One, free running accelerometer listener is created. + * It's state is controlled by start() and stop(). + * JavaScript is responsible for starting, stopping, and retrieving status and values. + * + * Since there may be some delay between starting and the first available value, when + * retrieving values from JavaScript, the thread sleeps until the first value is + * received or until 1 sec elapses. + * + * @author bcurtis + * + */ public class AccelBroker { - private WebView mAppView; - private Context mCtx; - private HashMap accelListeners; + + public static int STOPPED = 0; + public static int STARTING = 1; + public static int RUNNING = 2; + public static int ERROR_FAILED_TO_START = 3; + public static int ERROR_NOT_FOUND = 4; - public AccelBroker(WebView view, Context ctx) - { - mCtx = ctx; - mAppView = view; - accelListeners = new HashMap(); + private WebView mAppView; // WebView object + private Context mCtx; // Activity (DroidGap) object + private AccelListener listener; // Accelerator listener + + /** + * Constructor + * + * @param view + * @param ctx + */ + public AccelBroker(WebView view, Context ctx) + { + mCtx = ctx; + mAppView = view; + + // Create listener + listener = new AccelListener(mCtx, mAppView); + } + + /** + * Start listening to acceleration sensor. + * + * @return true if started, false if not + */ + public boolean start() + { + // 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. + * + * @return true if stopped, false if not + */ + public boolean stop() + { + listener.stop(); + return (listener.status == AccelBroker.STOPPED); + } + + /** + * Wait until sensor is done starting up. + * If a request for values is made while sensor is still starting, then delay thread until first reading is made. + */ + void waitToStart() { + if (listener.status == AccelBroker.STARTING) { + System.out.println("AccelBroker.waitToStart..."); + long timeout = 1000; // wait at most 1 sec + while ((listener.status == AccelBroker.STARTING) && (timeout > 0)) { + try { + Thread.sleep(10); + timeout = timeout - 10; + } + catch (InterruptedException e) { + } + } + } + } + + /** + * 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() { + + // Wait for startup + this.waitToStart(); + + // 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. + * If sensor is still starting, wait until 1st value is acquired before returning. + * + * @return x value + */ + public float getX() { + this.waitToStart(); + return listener.x; + } + + /** + * Get Y value of last accelerometer value. + * If sensor is still starting, wait until 1st value is acquired before returning. + * + * @return y value + */ + public float getY() { + this.waitToStart(); + return listener.y; } - - public String start(int freq, String key) - { - AccelListener listener = new AccelListener(key, freq, mCtx, mAppView); - listener.start(freq); - accelListeners.put(key, listener); - return key; - } - - public void stop(String key) - { - AccelListener acc = accelListeners.get(key); - acc.stop(); + + /** + * Get Z value of last accelerometer value. + * If sensor is still starting, wait until 1st value is acquired before returning. + * + * @return z value + */ + public float getZ() { + this.waitToStart(); + return listener.x; } } diff --git a/framework/src/com/phonegap/AccelListener.java b/framework/src/com/phonegap/AccelListener.java index 8ce6c152..2afca703 100644 --- a/framework/src/com/phonegap/AccelListener.java +++ b/framework/src/com/phonegap/AccelListener.java @@ -12,69 +12,90 @@ import android.webkit.WebView; public class AccelListener implements SensorEventListener{ - WebView mAppView; - Context mCtx; - String mKey; - Sensor mSensor; - int mTime = 10000; - boolean started = false; - - private SensorManager sensorManager; - - private long lastUpdate = -1; - - public AccelListener(String key, int freq, Context ctx, WebView appView) - { - mCtx = ctx; - mAppView = appView; - mKey = key; - mTime = freq; - sensorManager = (SensorManager) mCtx.getSystemService(Context.SENSOR_SERVICE); - - } - - public void start(int time) - { - mTime = time; - List list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - if (list.size() > 0) - { - this.mSensor = list.get(0); - this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST); - } - else - { - mAppView.loadUrl("javascript:navigator.accelerometer.epicFail(" + mKey + ", 'Failed to start')"); - } - } - - public void stop() - { - if(started) - sensorManager.unregisterListener(this); - } - - + WebView mAppView; // WebView object + Context mCtx; // Activity (DroidGap) object + + float x,y,z; // most recent acceleration values + long timeStamp; // time of most recent value + int status; // status of listener - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO Auto-generated method stub - - } + private SensorManager sensorManager;// Sensor manager + Sensor mSensor; // Acceleration sensor returned by sensor manager + + /** + * Create an accelerometer listener. + * + * @param ctx The Activity (DroidGap) object + * @param appView + */ + public AccelListener(Context ctx, WebView appView) { + this.mCtx = ctx; + this.mAppView = appView; + this.sensorManager = (SensorManager) mCtx.getSystemService(Context.SENSOR_SERVICE); + this.x = 0; + this.y = 0; + this.z = 0; + this.timeStamp = 0; + this.status = AccelBroker.STOPPED; + } + + /** + * Start listening for acceleration sensor. + * + * @return status of listener + */ + public int start() { + + // 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_UI); //SENSOR_DELAY_FASTEST); + this.status = AccelBroker.STARTING; + } + + // If error, then set status to error + else { + this.status = AccelBroker.ERROR_FAILED_TO_START; + } + + return this.status; + } + + /** + * Stop listening to acceleration sensor. + */ + public void stop() { + if (this.status == AccelBroker.RUNNING) { + this.sensorManager.unregisterListener(this); // unregister listener + } + this.status = AccelBroker.STOPPED; + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO Auto-generated method stub + } + + /** + * Sensor listener event. + * + * @param SensorEvent event + */ + public void onSensorChanged(SensorEvent event) { + // Only look at accelerometer events + if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { + return; + } + this.status = AccelBroker.RUNNING; + + // 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]; + } - public void onSensorChanged(SensorEvent event) { - if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) - return; - long curTime = System.currentTimeMillis(); - if (lastUpdate == -1 || (curTime - lastUpdate) > mTime) { - lastUpdate = curTime; - - float x = event.values[0]; - float y = event.values[1]; - float z = event.values[2]; - //mAppView.loadUrl("javascript:gotAccel(" + x + ", " + y + "," + z + " )"); - mAppView.loadUrl("javascript:navigator.accelerometer.gotCurrentAcceleration(" + mKey + "," + x + "," + y + "," + z + ")"); - } - } - - }