package de.westnordost.streetcomplete.tangram;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import java.util.Timer;
import java.util.TimerTask;
/** Component that gets the sensor data from accelerometer and magnetic field, smoothens it out and
* makes callbacks to report a simple rotation to its parent.
*/
public class CompassComponent implements SensorEventListener
{
private SensorManager sensorManager;
private Sensor accelerometer, magnetometer;
private Timer compassTimer;
private CompassAnimator compassAnimator;
private float[] gravity, geomagnetic;
/** time the compass needle needs in order to rotate into a new direction (from sensor data).
* The sensor data is a bit erratic, so this smoothens it out. */
private static final int DURATION = 200;
// the compass doesn't move that fast, this is more than enough
private static final int RotationUpdateFPS = 30;
private Listener listener;
public interface Listener
{
void onRotationChanged(float rotation);
}
public void setListener(Listener listener)
{
this.listener = listener;
}
public void onCreate(SensorManager sensorManager)
{
this.sensorManager = sensorManager;
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
public void onResume()
{
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI);
compassTimer = new Timer();
compassAnimator = new CompassAnimator();
compassTimer.scheduleAtFixedRate(compassAnimator, 0, 1000/RotationUpdateFPS);
}
public void onPause()
{
compassTimer.cancel();
sensorManager.unregisterListener(this);
}
@Override public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
@Override public void onSensorChanged(SensorEvent event)
{
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
gravity = event.values;
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
geomagnetic = event.values;
if (gravity != null && geomagnetic != null)
{
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, gravity, geomagnetic);
if (success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
float azimut = orientation[0]; // orientation contains: azimut, pitch and roll
compassAnimator.targetRotation = azimut;
}
}
}
/** dampens the erratic-ness of the sensors by <b>animating towards</b> the calculated rotation
* and not directly setting it */
private class CompassAnimator extends TimerTask
{
private float INITIAL = -9999;
private float currentRotation = INITIAL;
private long lastTime = System.currentTimeMillis();
volatile float targetRotation = INITIAL;
@Override public void run()
{
if(targetRotation == INITIAL) return;
if(currentRotation == INITIAL)
{
currentRotation = targetRotation;
}
else if(currentRotation != targetRotation)
{
float deltaRotation = targetRotation - currentRotation;
while (deltaRotation > +Math.PI) deltaRotation -= 2*Math.PI;
while (deltaRotation < -Math.PI) deltaRotation += 2*Math.PI;
long currentTime = System.currentTimeMillis();
long deltaTime = currentTime - lastTime;
if(deltaTime > DURATION) currentRotation = targetRotation;
else currentRotation += deltaRotation * deltaTime / DURATION;
}
if(listener != null) listener.onRotationChanged(currentRotation);
lastTime = System.currentTimeMillis();
}
}
}