Orientation Sensor Tips in Android

I went through some pretty frustrating times recently trying to detect the physical orientation of the mobile device in Android recently so I figure I’ll share my experience and what I learned here to hopefully prevent some pain for others out there.

Android has sensors to handle everything from acceleration to tricorder readings and all of them are managed via the SensorManager class. In order to detect changes to any sensor’s reading you need to register a SensorListener with the manager.

First, I recommend creating your SensorListener inside your activity.

1
2
3
4
5
6
7
private final SensorListener sl = new SensorListener() {
	public void onSensorChanged(int sensor, float[] values) {
	}
 
	public void onAccuracyChanged(int sensor, int accuracy) {
	}
};

Next you’ll want to find the Android SensorManager and register your SensorListener to listen for SENSOR_ORIENTATION events. It’s best to put this code in your onCreate function of your Activity.

1
2
3
4
5
6
// Locate the SensorManager using Activity.getSystemService
SensorManager sm;
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
 
// Register your SensorListener
sm.registerListener(sl, SensorManager.SENSOR_ORIENTATION SensorManager.SENSOR_DELAY_NORMAL);

Now your listener will be catching orientation events from the SensorManager. As the orientation changes the onSensorChanged method of your listener will be repeatedly called. The first parameter of the method should match the SensorManager.SENSOR_ORIENTATION value, assuming you didn’t register it to listen to any other sensors. The second parameter will contain a float array of 3 values; Azimuth, Pitch, and Roll respectively.

Azimuth is used to detect your compass direction, where 0 = North, 90 = East, etc.. Pitch is used to track your devices side to side orientation, where vertical is around 0, tilted so the left side is on top is around 90, and tilted so the right side is on top is around -90. The documentation for pitch says the values range between -180 and 180, however when I tested this with my G1 device I found the values would not go beyond +/-90. As the device is tilted upside down the values would return back to 0 instead of +/- 180. This would make detecting whether the device is being held upside down or not rather difficult. I have not found a solution for this yet. The final value in the float array, roll, represents the degree to which the device is tilted back and forth.

I was only concerned with the side to side Orientation of the device, so I monitored only the pitch value like below and was able to easily track the device orientation.

1
2
3
4
5
6
7
8
float pitch = values[2];
if (pitch <= 45 && pitch >= -45) {
	// mostly vertical
} else if (pitch < -45) {
	// mostly right side up
} else if (pitch > 45) {
	// mostly left side up
}

The primary source of my previously mentioned frustration was from a class I first attempted to use called OrientationListener which extends the SensorListener class. From the name and the documentation you might think this would be the ideal class to use for listening to orientation changes, but it isn’t, at least as far as I can tell. When registered in the same manner above the class will call back on it’s onOrientationChanged funciton instead. The documentation says that values passed to this method represent the degrees of orientation of the device between 0 and 359 however in testing with my G1 I found the values to be completely useless and incorrect. Perhaps my G1 isn’t functioning properly (although the above method using the SensorListener works just fine) or more likely I’m just implementing the OrientationListener incorrectly. I don’t know because I haven’t taken a look at the source code behind it yet. Perhaps this class needs to be depricated though, regardless, I’d recommend steering clear of it until some more appropriate examples of using it surface.

  • Trackback are closed
  • Comments (15)
  1. Absolutely pent content material , Really enjoyed studying.

  2. subject matter, all your {points of view|

    • G.
    • December 16th, 2011

    Hi, I have a little problem with it.
    I use accelerometer and magnetic field like you showed, but when my phone lie on the table the values are changing very fast like crazy. When it’s should show 10, it’s shows values between 6 and 11.
    So this is my questions: Why is that? How I can fix it? I need that to be very accurate.

    And I also want use calibrate – sometimes you can see on other apps a warning that your device have to be calibrate and you have to wave your phone in a figure 8 pattern to reset magnetometr in your device.

    i checked it in onAccuracyChanged, i checke the accuracy variable, but I don’t sure that it’s enought. Is it?

    I hope you can help me,
    Sorry for mistakes if any there, probably there are. ;)

    Greetings from Poland for all of you:)

    • Lawrence D’Oliveiro
    • August 20th, 2011

    I have done another sample app that uses OpenGL to display a compass arrow that takes into account all three angle readings. The code is here:

    https://github.com/ldo/3D-Compass

    • Pranav
    • July 19th, 2011

    Bow to thee!! thanks a lot… I have ruined enough grey cells on this… you saved some from getting wasted!

    • iamZoltanVaradi
    • June 24th, 2011

    Thanks for the code it helped me very much!

    However if you only use value[2] to determine the orientation, you will never know if the phone is straight or straight,but upside down. for that, you’ll ned value[1] too. if it’s positive, then it’s upside down, if it’s negative, then it’s held straight. hope it helps.

    Zoli

    • Shell
    • March 19th, 2011

    the m in mGravity and mGeo… implies that they’re module (global) level variables declared at the top of the class.

    they’re likely just float arrays…

    private float[] mGravity;
    private float[] mGeomagnetic;

    • em
    • February 8th, 2011

    I have a question about the mGravity and mGeomagnetic variables. I realized that they’re of type float[]. But i can’t see where you declared them. I’m just curious of what the size should be when you initialize them.

    • tenaicous
    • December 1st, 2010

    What versions of Android does this apply to?

    • Ridcully
    • October 8th, 2010

    Thank you for info, that pitch only goes up to 90 degrees. This confused me immensly.

    As to your latest post: The event/listener pattern still works as described by you, but getting the orientation values got quite complicated:

    First of all, you’ve got to register the listener for ACCELEROMETER and MAGNETOMETER:


    Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    Sensor magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    if (accelerometer != null && magnetometer != null) {
    mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
    mSensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI);
    } else {
    Log.e(TAG, "no accelerometer!");
    }

    Then in the onSensorChanged method you get events for both sensors. Using them you can get the orientation values like so:


    public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
    mGravity = event.values;
    }
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
    mGeomagnetic = event.values;
    }
    if (mGravity != null && mGeomagnetic != null) {
    float R[] = new float[9];
    float I[] = new float[9];
    boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
    if (success) {
    float orientation[] = new float[3];
    SensorManager.getOrientation(R, orientation);
    // at this point, orientation contains the azimut, pitch and roll values.
    }
    }
    ...

  3. You are both correct. Good catch on my mix-up there. I wanted roll (values[2]) but I inappropriately called it pitch in this post. My bad.

    However, I haven’t worked in the SensorManager lately and this being a year old post I know some of this has changed, multiple times in fact. I should do an update post.

    The docs say SensorManager.SENSOR_ORIENTATION is deprecated now and point to Sensor.TYPE_ORIENTATION, which also says it’s deprecated and points to SensorManager.getOrientation(). However that’s an active method of looking up orientation and not applicable for most people’s usage where the previous event/listener pattern worked great. Oh well, like I said, I need to research it up again and make an update post.

    Thanks for calling me out my mixups though.

    • jhavatar
    • June 30th, 2010

    I concur with Abhinav.

    If you want pitch, use “values[1]“. Not “values[2]“.

  4. Hi,

    You got it wrong about the pitch.
    Sideways rotation is called Roll, whereas rotation forwards or backwards is Pitch.

    Android allows pitch values from -180 to 180, and roll values from -90 to 90.
    If your Roll exceeds 90, it assumes that your pitch has gone from 0 to -180, and starts decreasing your roll.

    • Sr. topo
    • February 20th, 2010

    This was really helpful, I was also trying to set up my activity by using OrientationEventListener with no success. Your approach looks ok and works fine. Thanks for sharing!

  5. Hey,

    I made a quick image guide to help me with this – feel free to copy/change it in any way!

    Guide Picture
    http://vmat.imgur.com/miscellaneous/ssz07

    Original File
    http://www.mediafire.com/file/2ywyhjm1ogo/android%20orientation.graffle

    If you modify it, I do hope that you leave the URL, but it’s up to you :)