Mr
 Mr. Rogers' IB Computer Science - Google Android SmartPhone Programming Project Help boost K-12 science, math, and computer education. Donate your obsolete Android phone to us (see below)! We promise to put it to good use in a classroom.

 Home Projects 2010-2011    Level  Greenville, SC

A Three-Dimensional Compass for the Android Phone

Purpose:

An application that demonstrates the accelerometer and GPS features of the Android operating system and G1 phone.

Usage:

Usage is self-explanatory: the compass image is always North-pointing, and so the phone can determine the direction it is pointing at. Furthermore, the compass tilts side to side when the phone tilts, in the same way if one was to tilt a compass. Similarly, the values of the GPS and accelerometer also turn based on tilt so no cranking of the neck is necessary to read the values. They return the accelerometer values in 3 dimensions (the values used to determine direction and tilt) as well as GPS coordinates (longitude and latitude). The best way to understand is to just try the application.

Class Hierarchy:*

The Compass class is the main activity that is started when the application starts, overriding the onCreate() and such life cycle methods. It uses LinearLayout systems to divide up the screen between three views (Draw, Draw2, Draw3).

Draw gives the visual representation of the compass, pointing North as well as showing tilt angle. Draw2 gives the accelerometer values in numerical format. Draw3 displays longitude and latitude from the GPS system. Both Draw2 and Draw3 rotate and adjust so that it always reads left to right.

Base keeps track of the universal values (accelerometer values and GPS positions) used by all the other classes and mainly is static-based.

Coordinate just encompasses two doubles in order to simplify the coordinate systems.

From the Compass class:

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

onCreate() is called at the start of the application. So we begin with some basics:

//drawing

draw = new Draw(this);

draw2 = new Draw2(this);

draw3 = new Draw3(this);

Creating and defining View objects to be used to later.

//accel

sensorManager = (SensorManager)
getSystemService(SENSOR_SERVICE);

sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

Setting up Accelerometer sensor.

//GPS

locationManager = (LocationManager)
getSystemService(LOCATION_SERVICE);

Criteria criteria = new Criteria();

criteria.setAccuracy(Criteria.ACCURACY_FINE);

bestProvider = locationManager.getBestProvider(criteria, false);

Setting up GPS sensor (LocationManager).

//layout

LinearLayout view = new LinearLayout(this);

view.setOrientation(LinearLayout.VERTICAL);

LinearLayout topView = new LinearLayout(this);

topView.setOrientation(LinearLayout.HORIZONTAL);

setContentView(view);

}

The LinearLayout view encompasses both halves of the screen. The top half is draw and the bottom half is both draw2 and draw3. This is done by embedding another LinearLayout object topView inside view and adding draw2 and draw3 to that. So we have:

draw

 draw2 draw3

In the onPause() and onResume() methods, just remember to register and unregister listeners to save battery and resources. The Android operating system does not close applications but “minimizes” them, and their resources are only freed when another application uses them unless explicitly stated.

 public void onPause() {       super.onPause();       sensorManager.unregisterListener(this);       locationManager.removeUpdates(this); } Unregister. public void onResume() {       super.onResume();       sensorManager.registerListener(this, sensor, rate);       locationManager.requestLocationUpdates(             bestProvider, 0, 0, this); } Register.

In the onSensorChanged() and onLocationChanged() methods, update the Base with the returned values. Furthermore, update all the views with the invalidate() method.

 public void onSensorChanged(SensorEvent e) {       //update       Base.accelValues[0] = e.values[0];       Base.accelValues[1] = (-1*Math.abs(e.values[2])+90)/90;       Base.accelValues[2] = (-1*Math.abs(e.values[1])+90)/90;                   draw.invalidate();       draw2.invalidate();       draw3.invalidate(); } Update accelerometer values public void onLocationChanged(Location location) {       if(location != null)       Base.location = new Coordinate(location.getLongitude(), location.getLatitude()); } Update GPS values.

Moving on to the Draw class:

 public class Draw extends ImageView {       Paint paint;       Bitmap compass; Note that unlike the other classes, ImageView is used here in order to easily incorporate the image to be used for the compass. public Draw(Context context)       {             super(context);             paint = new Paint(); compass = BitmapFactory.                   decodeResource(                   context.getResources(),                   R.drawable.compass);       } Accessing the image as a resource. It is stored under Compass (project) -> res -> drawable -> compass.jpg. public void onDraw(Canvas g)       {             super.onDraw(g);             g.drawColor(Color.WHITE); The onDraw() here acts the same as the paint() method from a JPanel. However the paint object acts as a helper here. g.scale(Base.accelValues[1], Base.accelValues[2], Base.width/2, Base.height/2);             g.rotate(-1*Base.accelValues[0], Base.width/2, Base.height/2);             g.drawBitmap(compass, Base.width/2 - compass.getWidth()/2, Base.height/2 - compass.getHeight()/2, paint);                       } The scale() method reflects the tilt of the phone in the image. Similarly, the rotate() method points the image in the right direction (to the North). Finally the drawBitmap() method draws the image of the compass with the above conditions. Note the use of getWidth() in order to make placement of the image dynamic across different phones. public void onSizeChanged(int w, int h, int oldW, int oldH)       {             Base.width = w;             Base.height = h;       } } This ensures that the program uses the most up-to-date dimensions known and that it only reads the dimensions once as opposed to repeatedly updating the same dimensions.

Next, the Draw2 and Draw3 classes:

They act in very much the same way as Draw. They extend View and use the drawText() method to display values from Base. They both use the rotate() from Canvas as demonstrated above in order to adjust to tilt in the phone.

 Draw 2   public void onDraw(Canvas g) {       g.drawColor(Color.WHITE);       g.scale((float)1.5, (float)1.5);       paint.setColor(Color.BLACK);       g.rotate(-1*Base.accelValues[0], 65, 65);       g.drawText("X: " + String.format("%.4g%n", Base.accelValues[1]), Base.height/4-20, 55, paint);       g.drawText("Y: " + String.format("%.4g%n", Base.accelValues[2]), Base.height/4-20, 65, paint);       g.drawText("Z: " + String.format("%.4g%n", Base.accelValues[0]), Base.height/4-20, 75, paint); } Draw 3   public void onDraw(Canvas g) {       g.drawColor(Color.WHITE);       g.scale((float)1.25, (float)1.25);       paint.setColor(Color.BLACK);       g.rotate(-1*Base.accelValues[0], 65, 65);       g.drawText(Base.location.x+",", Base.height/4-20, 60, paint);       g.drawText(Base.location.y+"", Base.height/4-20, 70, paint); }

Finally, the Base class:

 public class Base {       //[0] = z, [1] = x, [2] = y       public static float[] accelValues = new float[3];                public static Coordinate location = new Coordinate(0, 0);                public static int width;       public static int height; } static-based class that is accessed from all classes. The main Compass class updates the values from the sensors, and the three Draw classes read from it to get the values.

 A screenshot of the emulator. Of course, no values are returned, so everything is at 0.