/*******************************************************************************
* Code contributed to the webinos project
*
* Licensed 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.
*
* Copyright 2011-2012 Paddy Byers
*
******************************************************************************/
package org.webinos.android.impl;
import java.util.List;
import org.webinos.api.deviceorientation.Acceleration;
import org.webinos.api.deviceorientation.DeviceorientationManager;
import org.webinos.api.deviceorientation.MotionCB;
import org.webinos.api.deviceorientation.MotionEvent;
import org.webinos.api.deviceorientation.OrientationCB;
import org.webinos.api.deviceorientation.OrientationEvent;
import org.webinos.api.deviceorientation.RotationRate;
import org.meshpoint.anode.AndroidContext;
import org.meshpoint.anode.module.IModule;
import org.meshpoint.anode.module.IModuleContext;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
public class DeviceorientationImpl extends DeviceorientationManager implements
IModule {
private Context androidContext;
private SensorManager sensorManager;
private OrientationListener orientationListener;
private Sensor orientationSensor;
private Sensor magneticSensor;
private AccelerometerListener accelerometerListener;
private Sensor accelerometerSensor;
private Sensor linearAccelerometerSensor;
private boolean integrateLinearAcceleration;
private static final String TAG = "org.webinos.android.impl.DeviceorientationImpl";
/*****************************
* DeviceorientationManager methods
*****************************/
@Override
public synchronized void watchOrientation(OrientationCB orientationCb) {
(orientationListener = new OrientationListener(orientationCb)).start();
sensorManager.registerListener(orientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
@Override
public synchronized void watchMotion(MotionCB motionCb) {
(accelerometerListener = new AccelerometerListener(motionCb)).start();
sensorManager.registerListener(accelerometerListener, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
if(linearAccelerometerSensor != null)
sensorManager.registerListener(accelerometerListener, linearAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
if(magneticSensor != null)
sensorManager.registerListener(accelerometerListener, magneticSensor, SensorManager.SENSOR_DELAY_GAME);
}
@Override
public synchronized void unwatchOrientation() {
if(orientationListener != null) {
sensorManager.unregisterListener(orientationListener);
orientationListener.kill();
orientationListener = null;
}
}
@Override
public synchronized void unwatchMotion() {
if(accelerometerListener != null) {
sensorManager.unregisterListener(accelerometerListener);
accelerometerListener.kill();
accelerometerListener = null;
}
}
/*****************************
* IModule methods
*****************************/
@Override
public Object startModule(IModuleContext ctx) {
androidContext = ((AndroidContext)ctx).getAndroidContext();
sensorManager = (SensorManager)androidContext.getSystemService(Context.SENSOR_SERVICE);
/* get orientation sensor - legacy devices */
List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
if(sensorList.isEmpty())
Log.e(TAG, "No orientation device found");
else
orientationSensor = sensorList.get(0);
/* get magnetic field sensor */
sensorList = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
if(sensorList.isEmpty())
Log.e(TAG, "No magnetic field device found");
else
magneticSensor = sensorList.get(0);
/* get motion sensors */
sensorList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if(sensorList.isEmpty()) {
Log.e(TAG, "No accelerometer device found");
} else {
accelerometerSensor = sensorList.get(0);
sensorList = sensorManager.getSensorList(Sensor.TYPE_LINEAR_ACCELERATION);
if(sensorList.isEmpty()) {
Log.e(TAG, "No linear accelerometer device found");
integrateLinearAcceleration = true;
} else {
linearAccelerometerSensor = sensorList.get(0);
}
}
if(accelerometerSensor == null && orientationSensor == null) {
Log.e(TAG, "No orientation or accelerometer device found - aborting");
return null;
}
return this;
}
@Override
public void stopModule() {
unwatchOrientation();
unwatchMotion();
}
/*****************************
* Helpers
*****************************/
class OrientationListener extends Thread implements SensorEventListener {
private OrientationCB orientationCb;
private OrientationEvent pendingEvent;
private boolean isKilled;
private OrientationListener(OrientationCB orientationCb) {
this.orientationCb = orientationCb;
}
private void kill() {
isKilled = true;
interrupt();
}
private synchronized void postOrientation(OrientationEvent ev) {
pendingEvent = ev;
notify();
}
@Override
public void run() {
while(!isKilled) {
synchronized(this) {
try {
wait();
} catch(InterruptedException ie) { break; }
if(pendingEvent != null) {
OrientationEvent ev = pendingEvent;
pendingEvent = null;
if(orientationCb != null)
orientationCb.onOrientationEvent(ev);
}
}
}
}
@Override
public void onAccuracyChanged(Sensor arg0, int arg1) {}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
OrientationEvent ev = new OrientationEvent();
ev.alpha = sensorEvent.values[0];
ev.beta = sensorEvent.values[1];
ev.gamma = sensorEvent.values[2];
ev.absolute = true;
postOrientation(ev);
}
}
class AccelerometerListener extends Thread implements SensorEventListener {
private MotionCB motionCb;
private float[] lastAccelerationValues;
private float[] lastMagneticValues;
private long lastMagneticTime;
private float[] R = new float[9];
private float[] I = new float[9];
private float[] orientation = new float[3];
private Acceleration pendingAcceleration;
private Acceleration pendingLinearAcceleration;
private Acceleration gravity;
private MotionEvent pendingEvent;
private long lastMotionEventTime;
private OrientationEvent lastOrientation;
private RotationRate lastRotationRate;
private long lastOrientationEventTime;
private boolean isKilled;
private final float alpha = 0.8F;
private static final double degreesPerRadian = 180 / Math.PI;
private AccelerometerListener(MotionCB motionCb) {
this.motionCb = motionCb;
lastMotionEventTime = System.currentTimeMillis();
if(integrateLinearAcceleration)
gravity = new Acceleration();
}
private void kill() {
isKilled = true;
interrupt();
}
private synchronized void updateRotationRate(OrientationEvent ev, long time) {
long timeDelta = time - lastOrientationEventTime;
if(timeDelta > 0 && lastOrientationEventTime > 0) {
RotationRate result = new RotationRate();
double factor = degreesPerRadian * 1000 / timeDelta;
result.alpha = factor * (ev.alpha - lastOrientation.alpha);
result.beta = factor * (ev.beta - lastOrientation.beta);
result.gamma = factor * (ev.alpha - lastOrientation.alpha);
lastOrientation = ev;
lastOrientationEventTime = time;
lastRotationRate = result;
}
}
private synchronized void postAcceleration(Acceleration acc, Acceleration lin) {
MotionEvent ev = new MotionEvent();
ev.acceleration = lin;
ev.accelerationIncludingGravity = acc;
ev.rotationRate = lastRotationRate;
long thisEventTime = System.currentTimeMillis();
ev.interval = (double)(thisEventTime - lastMotionEventTime);
lastMotionEventTime = thisEventTime;
pendingEvent = ev;
notify();
}
@Override
public void run() {
while(!isKilled) {
synchronized(this) {
try {
wait();
} catch(InterruptedException ie) { break; }
if(pendingEvent != null) {
MotionEvent ev = pendingEvent;
pendingEvent = null;
if(motionCb != null)
motionCb.onMotionEvent(ev);
}
}
}
}
@Override
public void onAccuracyChanged(Sensor arg0, int arg1) {}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
Acceleration acc = null, lin = null;
if(sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
lastAccelerationValues = sensorEvent.values.clone();
acc = new Acceleration();
acc.x = sensorEvent.values[SensorManager.AXIS_X - 1];
acc.y = sensorEvent.values[SensorManager.AXIS_Y - 1];
acc.z = sensorEvent.values[SensorManager.AXIS_Z - 1];
if(integrateLinearAcceleration) {
gravity.x = alpha * gravity.x + (1 - alpha) * acc.x;
gravity.y = alpha * gravity.y + (1 - alpha) * acc.y;
gravity.z = alpha * gravity.z + (1 - alpha) * acc.z;
lin = new Acceleration();
lin.x = acc.x - gravity.x;
lin.y = acc.y - gravity.y;
lin.z = acc.z - gravity.z;
postAcceleration(acc, lin);
return;
}
pendingAcceleration = acc;
} else if(sensorEvent.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
lin = new Acceleration();
lin.x = sensorEvent.values[SensorManager.AXIS_X - 1];
lin.y = sensorEvent.values[SensorManager.AXIS_Y - 1];
lin.z = sensorEvent.values[SensorManager.AXIS_Z - 1];
pendingLinearAcceleration = lin;
} else if(sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
lastMagneticValues = sensorEvent.values.clone();
lastMagneticTime = sensorEvent.timestamp;
}
/*
* This procedure to combine information from the magnetic field sensor and the
* accelerometer to get the rotation matrix, and hence the orientation.
* However, it doesn't seem to give meaningful data on the Nexus S at least.
* So to get orientation, we're sticking with the (legacy) ORIENTATION sensor
* for now.
*/
if(lastAccelerationValues != null && lastMagneticValues != null) {
SensorManager.getRotationMatrix(R, I, lastAccelerationValues, lastMagneticValues);
SensorManager.getOrientation(R, orientation);
OrientationEvent orientationEvent = new OrientationEvent();
orientationEvent.alpha = orientation[0];
orientationEvent.beta = orientation[1];
orientationEvent.gamma = orientation[2];
updateRotationRate(orientationEvent, lastMagneticTime);
lastAccelerationValues = lastMagneticValues = null;
}
if(pendingAcceleration != null && pendingLinearAcceleration != null) {
postAcceleration(pendingAcceleration, pendingLinearAcceleration);
pendingAcceleration = pendingLinearAcceleration = null;
}
}
}
}