The Orient Express

So, I got a new toy^H^H^Hphone the other day, and I started playing around with programming for it. The new one is an Android (Xperia X10 Mini), and I found plenty of resources for programming it.

So, yesterday I spent playing around with the direction sensors - and I came up with a simple gyro/compass. Since this could be a fun basis for a game that I would like to play, in case you write it, I thought I'd share. (And also, becase the implementations I found on the web were quite tied into other stuff.)

So, how do I get at the sensors? I register a listener! Most implementations I've seen register the main object as the listener. That's cool for simple implementations (as if my example isn't a simple implementation!), but IMHO it looks messy if the main object implements a ton of interfaces. Not to mention it discourages code reuse. Thus, I'd rather implement it as its own class. This is what it looks like:

  1. package com.blueturtle.orientexp;
  2.  
  3. import android.hardware.Sensor;
  4. import android.hardware.SensorEvent;
  5. import android.hardware.SensorEventListener;
  6. import android.hardware.SensorManager;
  7.  
  8. public class OrientationListener implements SensorEventListener {
  9. private SensorManager olManager=null;
  10. private Sensor olSensor=null;
  11. private BTBase olContext=null;
  12. private float[] olOrientation = new float[3];
  13.  
  14. // Constructor
  15. public OrientationListener(BTBase context, SensorManager man) {
  16. olContext=context;
  17. olManager = man;
  18. // Get a sensor
  19. olSensor = olManager.getDefaultSensor(
  20. Sensor.TYPE_MAGNETIC_FIELD);
  21. // Debug check, we do have a sensor, right?
  22. // (Comment next two lines when you are tired of the dialog)
  23. olContext.shout("Orientation sensor",
  24. "Orientation sensor name:\n"+olSensor.getName());
  25. // Enable the sensor
  26. enable();
  27. }
  28.  
  29. // All we do when we enable the sensor is registering the listener
  30. public void enable() {
  31. olManager.registerListener(this, olSensor,
  32. SensorManager.SENSOR_DELAY_GAME);
  33. }
  34.  
  35. // And when we disable the sensor, we do it by
  36. // unregistering the listener
  37. public void disable() {
  38. olManager.unregisterListener(this);
  39. }
  40.  
  41. // Unused, but must be "implemented"
  42. public void onAccuracyChanged(Sensor arg0, int arg1) {
  43. }
  44.  
  45. // Copy the sensor data to a local variable
  46. public void onSensorChanged(SensorEvent se) {
  47. for (int i=0; i<3; ++i) {
  48. olOrientation[i] = se.values[i];
  49. }
  50. olContext.update(BTBase.BTBASE_ORIENTATION);
  51. }
  52.  
  53. // Return the current orientation to anyone who asks
  54. public float[] getOrienttation() {
  55. return olOrientation;
  56. }
  57. }

But wait! What's that BTBase thingie? The short answer is this:
  1. package com.blueturtle.orientexp;
  2.  
  3. public interface BTBase {
  4. // Flags
  5. public int BTBASE_ORIENTATION=0x1; // Flag for orientation callbacks
  6. // Interface methods
  7. public void update(int flag); // Callback
  8. public void shout(String title, String msg); // Debug dialog
  9. }

The slightly longer answer is that i want to give the sensors to call back to the main object. So I created an interface that will be called back by the sensors. The flag is used for information about exactly who made the callback (I plan to add more listeners, e.g. for GPS and timers).

But something's missing! What's that? Oh, the main class. For now, all we'll do is write out the values in a simple text view. Here we go:

  1. package com.blueturtle.orientexp;
  2.  
  3. import android.app.Activity;
  4. import android.app.AlertDialog;
  5. import android.content.Context;
  6. import android.hardware.SensorManager;
  7. import android.os.Bundle;
  8. import android.widget.TextView;
  9.  
  10. public class OrientExp extends Activity implements BTBase {
  11. private TextView myYaw=null;
  12. private TextView myPitch=null;
  13. private TextView myRoll=null;
  14. private OrientationListener myListener=null;
  15.  
  16. @Override
  17. public void onCreate(Bundle savedInstanceState) {
  18. super.onCreate(savedInstanceState);
  19.  
  20. setContentView(R.layout.main);
  21. // Get references to the text views used to output the data
  22. // Defined in res/layout/main.xml
  23. myYaw = (TextView) findViewById(R.id.viewyaw);
  24. myPitch = (TextView) findViewById(R.id.viewpitch);
  25. myRoll = (TextView) findViewById(R.id.viewroll);
  26. // Create the listener (the second argument is this object retrieving
  27. // a sensor manager and passing it along)
  28. myListener = new OrientationListener(this,
  29. (SensorManager)getSystemService(Context.SENSOR_SERVICE));
  30. }
  31.  
  32. public void update(int flags) {
  33. // Get the data
  34. float[] data = myListener.getOrienttation();
  35. // Update interface text
  36. myYaw.setText("Yaw: "+ data[0]);
  37. myPitch.setText("Pitch: "+data[1]);
  38. myRoll.setText("Roll: "+data[2]);
  39. }
  40.  
  41. // Create a message dialog for debug messages
  42. public void shout(String title, String msg) {
  43. AlertDialog.Builder dialog = new AlertDialog.Builder(this);
  44. dialog.setTitle(title);
  45. dialog.setMessage(msg);
  46. dialog.show();
  47. }
  48.  
  49. // Disable the orientation sensors on pause
  50. public void onPause() {
  51. super.onPause();
  52. myListener.disable();
  53. }
  54.  
  55. // And resume afterwards
  56. public void onResume() {
  57. super.onResume();
  58. myListener.enable();
  59. }
  60. }

That's it!

Isn't it? No. The textviews are defined in the layout document res/layout/main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <TextView
  8. android:id="@+id/viewyaw" <!-- This id is what we use
  9. to identify the textview in OrientExp.java -->
  10. android:textSize="15pt"
  11. android:layout_width="fill_parent"
  12. android:layout_height="wrap_content"
  13. android:text="Yaw: "
  14. />
  15. <TextView
  16. android:id="@+id/viewpitch"
  17. android:textSize="15pt"
  18. android:layout_width="fill_parent"
  19. android:layout_height="wrap_content"
  20. android:text="Pitch: "
  21. />
  22. <TextView
  23. android:id="@+id/viewroll"
  24. android:textSize="15pt"
  25. android:layout_width="fill_parent"
  26. android:layout_height="wrap_content"
  27. android:text="Roll: "
  28. />
  29. </LinearLayout>

Nothing to write home about here. I create three textveiws with an id, a text size and a default text. The default text is replaced by the activity object when we get our first sensor reading.

That's it. There's no more to the demo. I suppose I could have changed stuff in the manifest, but there's nothing that needs a change there for this test application, so why bother?

Now, all that's needed is a device to try it on. Sure, there is the emulator - but it doesn't excercise the orientation sensors particularly. Of course, there is someone who wrote an simulator for the motion sensors in the emulator: http://code.google.com/p/openintents/wiki/SensorSimulator It isn't all that complicated to implement, but I'll leave that as an excercise for the reader.