From 4b3255e4fde77c99763b30182a8a0df423e04832 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Thu, 12 Aug 2010 10:51:12 -0500 Subject: [PATCH] Accelerometer updates: - Removed thread delay to get accelerometer values. - Override Activity lifecycle methods to manage JavaScript timers and enable window.onunload to be called. This is needed for accelerometer to shut down correctly. --- framework/assets/js/accelerometer.js | 145 ++++++++++-------- framework/src/com/phonegap/AccelBroker.java | 76 ++++----- framework/src/com/phonegap/AccelListener.java | 21 ++- framework/src/com/phonegap/DroidGap.java | 43 ++++++ 4 files changed, 172 insertions(+), 113 deletions(-) mode change 100644 => 100755 framework/src/com/phonegap/DroidGap.java diff --git a/framework/assets/js/accelerometer.js b/framework/assets/js/accelerometer.js index 26e31348..ed553ed8 100644 --- a/framework/assets/js/accelerometer.js +++ b/framework/assets/js/accelerometer.js @@ -10,22 +10,41 @@ function Acceleration(x, y, z) { * @constructor */ function Accelerometer() { + /** * 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; + + /** + * 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); } Accelerometer.STOPPED = 0; @@ -35,14 +54,17 @@ 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; + /** * 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 - * getting the acceleration data. - * @param {AccelerationOptions} options The options for getting the accelerometer data - * such as timeout. + * + * @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 getting the acceleration data. + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. */ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) { @@ -58,13 +80,13 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error return; } - // Get current acceleration from native + // Get current acceleration status var status = Accel.getStatus(); - //console.log("getCurrentAcceleration: status="+status); // If running, then call successCallback if (status == Accelerometer.RUNNING) { try { + navigator.accelerometer.turnOffTimer = 0; var accel = new Acceleration(Accel.getX(), Accel.getY(), Accel.getZ()); successCallback(accel); } catch (e) { @@ -74,29 +96,27 @@ 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() { + var status = Accel.getStatus(); + if (status != Accelerometer.STARTING) { + clearInterval(timer); - var status = Accel.getStatus(); - //console.log("getAcceleration: status="+status); + // If accelerometer 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); + } + } - // 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 + // If accelerometer error else { console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); try { @@ -107,24 +127,21 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error console.log("Accelerometer Error in errorCallback: " + e); } } - - //@todo: don't clear now, wait until x seconds since last request - this.clearWatch(""); + } + }, 10); } } /** * 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 - * getting the acceleration data. - * @param {AccelerationOptions} options The options for getting the accelerometer data - * such as timeout. + * @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 getting the acceleration data. + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. + * @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; // successCallback required @@ -139,33 +156,29 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb return; } - var key = this.listenerId++; - this.accelListeners[key] = key; - var obj = this; - Accel.start(); + var id = ""+(navigator.accelerometer.listenerId++); + navigator.accelerometer.accelListeners[id] = id; + Accel.start(id); // Start watch timer - this.accelTimers[key] = setInterval(function() { - //console.log("Interval timer: key="+key+" timer="+obj.accelTimers[key]+" freq="+frequency); + navigator.accelerometer.accelTimers[id] = setInterval(function() { var status = Accel.getStatus(); - //console.log("watchAcceleration: status="+status); + // If accelerometer is running 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 { + + // If accelerometer had error + else if (status != Accelerometer.STARTING) { console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); try { - obj.clearWatch(key); + navigator.accelerometer.clearWatch(id); if (errorCallback) { errorCallback(status); } @@ -175,35 +188,33 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb } }, (frequency ? frequency : 1)); - return key; + return id; } /** * Clears the specified accelerometer watch. * - * @param {String} watchId The ID of the watch returned from #watchAcceleration. + * @param {String} id The id of the watch returned from #watchAcceleration. */ -Accelerometer.prototype.clearWatch = function(watchId) { +Accelerometer.prototype.clearWatch = function(id) { // Stop javascript timer & remove from timer list - if (watchId && this.accelTimers[watchId]) { - clearInterval(this.accelListeners[watchId]); - delete this.accelTimers[watchId]; + if (id && navigator.accelerometer.accelTimers[id]) { + clearInterval(navigator.accelerometer.accelTimers[id]); + delete navigator.accelerometer.accelTimers[id]; } // Remove from watch list - if (watchId && this.accelListeners[watchId]) { - delete this.accelListeners[watchId]; + if (id && navigator.accelerometer.accelListeners[id]) { + delete navigator.accelerometer.accelListeners[id]; } - // 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(); + // Stop accelerometer for this watch listener + if (id) { + try { + Accel.stop(id); + } catch (e) { + } } } diff --git a/framework/src/com/phonegap/AccelBroker.java b/framework/src/com/phonegap/AccelBroker.java index 32365369..94eb9903 100644 --- a/framework/src/com/phonegap/AccelBroker.java +++ b/framework/src/com/phonegap/AccelBroker.java @@ -1,20 +1,16 @@ 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(). + * It's state is controlled by start(id) and stop(id). * 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 - * + * When all listener ids that were started are stopped, the accelerometer listener is stopped. */ public class AccelBroker { @@ -27,6 +23,7 @@ public class AccelBroker { private WebView mAppView; // WebView object private Context mCtx; // Activity (DroidGap) object private AccelListener listener; // Accelerator listener + private HashMap listenerIds; // List of listener ids /** * Constructor @@ -41,15 +38,21 @@ public class AccelBroker { // Create listener listener = new AccelListener(mCtx, mAppView); + + listenerIds = new HashMap(); } /** * Start listening to acceleration sensor. * - * @return true if started, false if not + * @param String id The id of the listener + * @return true if started, false if not */ - public boolean start() - { + 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(); @@ -61,33 +64,31 @@ public class AccelBroker { /** * Stop listening for acceleration sensor. * - * @return true if stopped, false if not + * @param String id The id of the listener + * @return true if stopped, false if not */ - public boolean stop() - { - listener.stop(); - return (listener.status == AccelBroker.STOPPED); + 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); } /** - * 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. + * Destroy listener */ - 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) { - } - } - } + 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. @@ -98,10 +99,7 @@ public class AccelBroker { * @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 + "}}"; @@ -122,34 +120,28 @@ public class AccelBroker { /** * 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; } /** * 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 2afca703..8139e4d8 100644 --- a/framework/src/com/phonegap/AccelListener.java +++ b/framework/src/com/phonegap/AccelListener.java @@ -1,6 +1,5 @@ package com.phonegap; - import java.util.List; import android.hardware.Sensor; @@ -10,6 +9,10 @@ import android.hardware.SensorManager; import android.content.Context; import android.webkit.WebView; +/** + * This class listens to the accelerometer sensor and stores the latest + * acceleration values x,y,z. + */ public class AccelListener implements SensorEventListener{ WebView mAppView; // WebView object @@ -53,7 +56,7 @@ public class AccelListener implements SensorEventListener{ // 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.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST); this.status = AccelBroker.STARTING; } @@ -70,10 +73,18 @@ public class AccelListener implements SensorEventListener{ */ public void stop() { if (this.status == AccelBroker.RUNNING) { - this.sensorManager.unregisterListener(this); // unregister listener + this.sensorManager.unregisterListener(this); } this.status = AccelBroker.STOPPED; } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void destroy() { + this.sensorManager.unregisterListener(this); + } public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO Auto-generated method stub @@ -85,17 +96,19 @@ public class AccelListener implements SensorEventListener{ * @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]; + + this.status = AccelBroker.RUNNING; } } diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java old mode 100644 new mode 100755 index 1011a7fb..84f3715f --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -139,11 +139,54 @@ public class DroidGap extends Activity { } @Override + /** + * Called by the system when the device configuration changes while your activity is running. + * + * @param Configuration newConfig + */ public void onConfigurationChanged(Configuration newConfig) { //don't reload the current page when the orientation is changed super.onConfigurationChanged(newConfig); } + + @Override + /** + * Called when the system is about to start resuming a previous activity. + */ + protected void onPause(){ + super.onPause(); + + // Pause JavaScript timers (including setInterval) + appView.pauseTimers(); + } + + @Override + /** + * Called when the activity will start interacting with the user. + */ + protected void onResume(){ + super.onResume(); + + // Resume JavaScript timers (including setInterval) + appView.resumeTimers(); + } + @Override + /** + * The final call you receive before your activity is destroyed. + */ + public void onDestroy() { + super.onDestroy(); + + // Load blank page so that JavaScript onunload is called + appView.loadUrl("about:blank"); + + // Clean up objects + if (accel != null) { + accel.destroy(); + } + } + private void bindBrowser(WebView appView) { gap = new Device(appView, this);