Getting Tilt Data on an Android Phone

I haven't posted in a while, so I thought I'd post a little bit of code that I wrote for an android phone app that I'm working on. You see, Google recently deprecated one of the older ways of getting tilt data from the Android OS, but they didn't really document the new way of doing it. Plus, most resources I found online still use the old method, so I wrote a class to get the tilt data in an easy-to-use manner. (And without using any deprecated functions.)

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 TiltCalc {

private boolean needsRecalc = false;
private float[] tilt_data = {0, 0, 0}, gravity = {0, 0, 0}, magnet = {0, 0, 0};

// Change this to make the sensors respond quicker, or slower:
private static final int delay = SensorManager.SENSOR_DELAY_GAME;


// Special class used to handle sensor events:
private final SensorEventListener listen = new SensorEventListener() {
public void onSensorChanged(SensorEvent e) {
final float[] vals = e.values, target;

// Just capture the Gyroscope data, if it exists:
//if(e.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
// System.arraycopy(vals, 0, tilt_data, 0, 3);
// return;
//}

// Else, we'll capture the data, and mark the class for a re-calc:
target = (e.sensor.getType() == Sensor.TYPE_ACCELEROMETER) ? gravity : magnet;
needsRecalc = true;
System.arraycopy(vals, 0, target, 0, 3);
}

public void onAccuracyChanged(Sensor event, int res) {}
};

// The constructor will use a context object to register itself for various inputs:
public TiltCalc(Context c) {
SensorManager man = (SensorManager) c.getSystemService(Context.SENSOR_SERVICE);

Sensor mag_sensor = man.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
Sensor acc_sensor = man.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//Sensor gyr_sensor = man.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

// Turns out Android's gyroscope doesn't work this way, so it's been disabled for now...
//if(man.registerListener(listen, gyr_sensor, delay)) {
// Log.d("TiltCalc", "Gyroscope detected, and successfully connected.");

// Use an accelerometer+compass approach:
//} else
if( man.registerListener(listen, mag_sensor, delay) &&
man.registerListener(listen, acc_sensor, delay) ) {
Log.d("TiltCalc", "No gyroscope, falling back on accelerometer+compass.");

} else {
Log.d("TiltCalc", "No acceptable hardware found.");

// We will remove the listener, just in case one of the accelerometer sensors
// registered, just not the other one:
man.unregisterListener(listen);
}
}

// Will return the most up-to-date tilt data in the vals[] array
public void getTilt(float[] vals) {

// If some of the data has been changed, then we need to recalculate some things...
if(needsRecalc) {
float[] R={0,0,0,0,0,0,0,0,0};

// Calculate the rotation matrix, and use that to get the orientation:
if(SensorManager.getRotationMatrix(R, null, gravity, magnet))
SensorManager.getOrientation(R, tilt_data);

needsRecalc = false;
}

System.arraycopy(tilt_data, 0, vals, 0, 3);
}
}

The constructor needs a context object, so that it can register itself for sensor updates. The easiest way to do this is to call the function in the main activity, using 'this' as the context. (Activity is a subclass of Context...)

Then, you would just call getTilt(float[] vals) to get the 3 tilt values. That function will return the 3 tilt values into the float[] that is passed into the class. The tilt values will be setup like:

Diagram showing the tilt axes

So vals[0] is rotating the phone around like a compass, vals[1] is tilting the phone up and down, and vals[2] is tilting the phone left and right.

This class is coded so that if you have an accurate gyroscope in the phone, the class will use that to get the tilt data. However, in most cases, you won't have a gyroscope, so the class will fall back on a less accurate (but still perfectly usable) accelerometer + compass method of calculating tilt.

Note: I do not have a phone with a gyroscope, so that code is ENTIRELY untested. Plus, since Google didn't document the output format of gyroscope updates, this code is based on the very bold assumption that Google wouldn't give tilt data in differing formats based on the data's source. (EDIT: Turns out this *WAS* a very bold assumption... :P)

Anyway, this code is public domain, so have fun. :)

EDIT (19 Jan 2011): Now that there are Android phones with gyroscopes, we know that they don't work in the way I was expecting, so the gyroscope code has been commented out. I'll update the code again once I know for sure how they work...

Loading OpenGL Textures in Android

There is a thousand pages on this already, but many of them are needlessly slow, incorrect, outdated, or poorly written, so I thought I'd throw my little bit of code into the mix, since I haven't posted in a while:

The following code will load a Drawable resource into OpenGL, and will return the integer you will bind to use this texture, while handling all mipmaping. Note: the texture's dimensions MUST be powers of two:

// Get a new texture id:
private static int newTextureID(GL10 gl) {
int[] temp = new int[1];
gl.glGenTextures(1, temp, 0);
return temp[0];
}

// Will load a texture out of a drawable resource file, and return an OpenGL texture ID:
private int loadTexture(GL10 gl, Context context, int resource) {

// In which ID will we be storing this texture?
int id = newTextureID(gl);

// We need to flip the textures vertically:
Matrix flip = new Matrix();
flip.postScale(1f, -1f);

// This will tell the BitmapFactory to not scale based on the device's pixel density:
// (Thanks to Matthew Marshall for this bit)
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = false;

// Load up, and flip the texture:
Bitmap temp = BitmapFactory.decodeResource(context.getResources(), resource, opts);
Bitmap bmp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
temp.recycle();

gl.glBindTexture(GL10.GL_TEXTURE_2D, id);

// Set all of our texture parameters:
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);

// Generate, and load up all of the mipmaps:
for(int level=0, height = bmp.getHeight(), width = bmp.getWidth(); true; level++) {
// Push the bitmap onto the GPU:
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bmp, 0);

// We need to stop when the texture is 1x1:
if(height==1 && width==1) break;

// Resize, and let's go again:
width >>= 1; height >>= 1;
if(width<1) width = 1;
if(height<1) height = 1;

Bitmap bmp2 = Bitmap.createScaledBitmap(bmp, width, height, true);
bmp.recycle();
bmp = bmp2;
}

bmp.recycle();

return id;
}

This bit of code is fairly quick, and it won't accidentally flip the texture vertically. (Lot's of the examples I've seen will do this, since the Android's 2D API, which we use to parse the resource, has Y increasing downwards, whereas OpenGL has Y increasing upwards.)

Enjoy. That code is public domain; I don't care what you do with it. Enjoy! ;)

EDIT (27 Sep 2010): Applied Matthew Marshall's fix for a screen density bug.

Android Stuff, Geek Score, and a Hike

I haven't posted in a little over a week, so I'm just going to throw in a quick post. I've been working with my Nexus One recently. I'm working on a Hexagonal game with OpenGL, and it's going pretty well. It has a pretty nice texture engine and it's running at a pretty consistent 40-45 fps on my N1. I know it can be quicker, but 45fps is quick enough... for now...

Also, my dad and I went on a hike today. TBH, I'm not that into the great outdoors due to it's lack of electrical plugs. However, this hike was pretty cool since, while hiking, we saw loads of tiny critters, and I must say I'm a big fan of those. ;)

First up were the newts. I love newts! They're slow, and clumsy, and these ones have an extremely potent neurotoxin in their skin that causes seizures. (It's the same toxin found in the puffer fish, called tetrodotoxin.) That's why these guys are still alive, even though they're slow and clumsy. Here's one right now:

Newt

He was pretty cool. All-in-all, we saw 24 newts, 2 banana slugs, an alligator lizard, a few freaky looking birds, and a couple butterflies.

Oh, and on the way to the hike, I was checking up on some of the blogs I follow, and I noticed that loads of people on Planet Ubuntu were reporting their Nerd Scores, so I decided to get myself tested...

I am nerdier than 98% of all people. Are you a nerd? Click here to take the Nerd Test, get nerdy images and jokes, and talk on the nerd forum!

That is the highest score I've seen yet... I'm not entirely sure if I should be proud of that or not, but I decided to post it here anyway.

BTW: I took the picture of the Newt with my Nexus One, and left the GPS coordinates in the EXIF data, just in case anyone wants to take a neat hike, and see a ton of Newts... Just watch out... There is a LOT of poison oak on that trail...

Edit: I've finally seen someone with a 100 nerd score, and heard of a 99, so I'm thankfully not the biggest nerd out there anymore. :)

Android Development for the Nexus One, on Linux

A day or two ago, I got my AT&T-enabled Nexus One, and I must say, this thing is awesome! I've spent the last few days going back-and-forth between working on that TunnelTanks game, and playing with this thing. It's crazy cool! So, naturally, I wanted to write software for it. I mean, I learned all about Android development last year, in ECS160, so now it is time to put that knowledge to some good use!

Well, I finally have got it all working... At 3:20am, a day later. Unfortunately, some of the Android Documentation deals with *REALLY* old versions of Ubuntu and is now obsolete, so I thought I'd add a few notes:

  1. I use Ubuntu 9.10, which finally packages the latest Eclipse, which is compatible with the Android SDK. That said, DO NOT USE IT!!! Seriously, it's not worth the effort. Ubuntu removed all of the plugins that are required to use the SDK, so in order to use it, you would have to hunt each and every one down, which is a royal PITA. It's far easier to download the Eclipse Classic tarball from here, and just unzip it into ~/bin. (Ubuntu will automatically add ~/bin to your PATH if that directory exists.)
  2. Installing the SDK is crazy easy. Just stick with Google's tutorial; it still applies.
  3. If you want to upload your program ONTO your Nexus One, then don't follow the Linux directions for device development on Google's site. They're old, and now wrong. First of all, despite the Nexus One being an HTC device, it doesn't use HTC's USB vendor code (0bb4). It uses Google's (18d1). So just create a file in /etc/udev/rules.d called "51-android.rules", and put this in it:
    SUBSYSTEM=="usb", SYSFS{idVendor}=="18d1", SYMLINK+="android_adb", MODE="0666"
    Udev should notice the new file instantly, but if it doesn't, run: "sudo udevadm control --reload-rules". Then, try running "adb devices". If your Nexus One is plugged into the computer's USB, and nothing shows up, verify that the Nexus One is in Debugging Mode (Settings>Applications>Development). If it says it is, try rebooting the Nexus One, and check again.

Hopefully, with those notes, you can then follow the rest of the Android tutorials normally. To finish, I'll just post a picture of my Nexus One with a "Hello World" application running. :)

My nexus one saying 'Hello World'

PS: Thanks Google, for the awesome phone, for the free engraving on the back, and the free overnight shipping! Wow! After waiting over a year-and-a-half for that Pandora open-source gaming device (still waiting, btw...), I forgot that cool electronics can be only 2 days away.

 1

About

User