Mr
 Mr. Rogers' IB Computer Science -- 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

Description

An application that demonstrates the accelerometer and GPS features of the Android operating system and G1 phone. It simulates a north-pointing compass as well as outputs numerical values for the accelerometer.

Practical uses for this application include mounting it on a satellite and be able to position it precisely; one would have information about its location, orientation, and angle which would assist in performing experiments such as one with involving interferometry. Similarly, this application could help one position a telescope to view known objects in space.

Features

• Makes use of gyroscope, accelerometer, and the compass
• Rotating display so no craning of the neck is necessary to read
• Demonstrates layouts programatically as opposed to using XML

Version

Started September 29, 2010.
Version 1 with just compass image completed October 2, 2010.
Version 1.5 (current) completed October 14, 2010.

Future Plans

Possibilities for this application can include making each portion of the screen its own "tab", and being able to switch between those. That way, each section has more room for display, so the text values can be larger, and less strain on the eyes is needed. Furthermore, the tilt of the compass could be a checkbox option, so that the compass is completely visible without regard to the physical tilt of the screen.

Instructions

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 image tilts side to side when the phone tilts, in the same way if one was to tilt a real physical compass. Similarly, the values of the GPS and accelerometer also remain parallel to the ground 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.

(click image to zoom)

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 a series of LinearLayouts 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. The XML file is the one generated by Eclipse with one modification to enable the use of the location sensor. This screenshot shows the placement of the compass image used. It is stored in the project folder/res (resources)/drawable/compass.jpg. This is later called in the Draw class with the line BitmapFactory.decodeResource(context.getResources(), R.drawable.compass);

Compass.java

This is the main Activity class that is run when the application is started. It takes care of setting up and updating the accelerometer and location sensors, as well as handling starting and pausing the application. It also lays out the physical appearance of the screen; this is done in a different manner than the way that is usually done with the XML. Instead, it puts a LinearLayout inside another LinearLayout, which sets up the overall layout look.

```

package com.lichard49.Compass;

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.LinearLayout;

/**
* The main initial Activity that is started when the application begins
* Sets up location and acceleration sensors
* Sets up layout
*
* @author Richard
*
*/

public class Compass extends Activity
implements SensorEventListener, LocationListener
{
//Fields dealing with display classes
Draw draw;
Draw2 draw2;
Draw3 draw3;

//Fields dealing with location sensor
LocationManager locationManager;
String bestProvider;

//Fields dealing with acceleration sensor
Sensor sensor;
SensorManager sensorManager;
final int rate = SensorManager.SENSOR_DELAY_GAME;

/**
* Method called at startup
*
* @param savedInstanceState Satisfies "extends Activity"
*/
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

//accel
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

//GPS (location)
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
bestProvider = locationManager.getBestProvider(criteria, false);

//drawing and display
draw = new Draw(this);
draw2 = new Draw2(this);
draw3 = new Draw3(this);

//layout
LinearLayout view = new LinearLayout(this);
view.setOrientation(LinearLayout.VERTICAL);

LinearLayout topView = new LinearLayout(this);
topView.setOrientation(LinearLayout.HORIZONTAL);

setContentView(view);
}

///////////////// Basics
/**
* Called when the application is in the background
*/
public void onPause()
{
super.onPause();
sensorManager.unregisterListener(this);
//this is important because Android applications
//do not close, they are in the background
//so resources would be hogged otherwise
}

/**
* Called when the application is started
* or in the foreground again
*/
public void onResume()
{
super.onResume();
sensorManager.registerListener(this, sensor, rate);
bestProvider, 0, 0, this);
}

//==================Accel=====================
/**
* Called when the values of the acceleration sensor changes
*
* @param e Details about the change
*/
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;

//refresh displays
draw.invalidate();
draw2.invalidate();
draw3.invalidate();
}

/**
* Called when accuracy of the sensor is changed
*
* @param sen Which sensor's accuracy changed
* @param acc The new accuracy degree
*/
public void onAccuracyChanged(Sensor sen, int acc)
{
}

//==================GPS=====================
/**
* Called when the location changes
*
*  @param location The new location
*/
public void onLocationChanged(Location location)
{
//update location
if(location != null)
Base.location = new Coordinate(location.getLongitude(), location.getLatitude());
}

/**
* Called when the provider of location (satellite)
* is disabled
*
* @param arg0 The provider
*/
public void onProviderDisabled(String arg0)
{
}

/**
* Called when the provider of location is enabled
*
* @param arg0 The provider
*/
public void onProviderEnabled(String arg0)
{
}

/**
* Called when status of the provider of location changes
*
* @param arg0 The provider
* @param arg1 The status
*/
public void onStatusChanged(String arg0, int arg1, Bundle arg2)
{
}
}

```

Draw.java

This class takes care of drawing the compass image, and extends ImageView to do this. It orients the image in the correct direction by getting the accelerometer values, and using the rotate() method to rotate the canvas. This class also stores the screen size whenever the screen size is changed, such as at startup. Not only does this make it adaptable to any phone, but it also reduces the number of times the program has to get the dimension.

```

package com.lichard49.Compass;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.widget.ImageView;

/**
* Draws the compass image
* Uses acceleration information to rotate the image
*
* @author Richard
*
*/
public class Draw extends ImageView
{
Paint paint;
Bitmap compass;

/**
* constructor
* @param context Activity context for a View
*/
public Draw(Context context)
{
super(context);

paint = new Paint();

//get image of compass
compass = BitmapFactory.decodeResource(
context.getResources(), R.drawable.compass);
}

/**
* similar to "paint" method, invalidate is used to "repaint"
*
* @param g Canvas object
*/
public void onDraw(Canvas g)
{
super.onDraw(g);

g.drawColor(Color.WHITE);

//rotate affects everything afterwards
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);
}

/**
* store screen width and height at start up so onDraw method
* does not need to repeatedly get dimensions (efficiency)
*
* @param w New width
* @param h New height
* @param oldW Old width
* @param oldH Old height
*/
public void onSizeChanged(int w, int h, int oldW, int oldH)
{
Base.width = w;
Base.height = h;
}
}

```

Draw2.java and Draw3.java

These two classes act in pretty much the same way as Draw does. They display the sensor values and rotate them so they are parallel to the ground.

 ``` package com.lichard49.Compass; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; /**  * Draws the text containing numerical accelerometer values  * Rotates so the text is always parallel to the ground  *   * @author Richard  *  */ public class Draw2 extends View {         private Paint paint = new Paint();                  /**          * Constructor          *           * @param context Activity context for View          */         public Draw2(Context context)         {                 super(context);         }                  /**          * Similar to "paint" method          *           * @param g Canvas object          */         public void onDraw(Canvas g)         {                 g.drawColor(Color.WHITE);                                  //fake large font                 g.scale((float)1.5, (float)1.5);                 paint.setColor(Color.BLACK);                                  //maintain text parallel to ground                 g.rotate(-1*Base.accelValues[0]-180, 65, 65);                                  //round off values to the nearest 3 digits                 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);         } } ``` ``` package com.lichard49.Compass; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; /**  * Draws location values  *   * @author Richard  *  */ public class Draw3 extends View {         private Paint paint = new Paint();                  /**          * Constructor          *           * @param c Context for View          */         public Draw3(Context c)         {                 super(c);         }                  /**          * Similar to "paint" method          *           * @param g Canvas object          */         public void onDraw(Canvas g)         {                 g.drawColor(Color.WHITE);                                  //fake large font                 g.scale((float)1.25, (float)1.25);                 paint.setColor(Color.BLACK);                                  //maintain text parallel to ground                 g.rotate(-1*Base.accelValues[0]-180, 65, 65);                                  //separate coordinates into two lines                 g.drawText(Base.location.x+",", Base.height/4-20, 60, paint);                 g.drawText(Base.location.y+"", Base.height/4-20, 70, paint);         } } ```

Coordinate.java

This class just simplifies keeping track of the location sensor values. It just ties two doubles together and makes them easily accesible.

```

package com.lichard49.Compass;

/**
* Ties two doubles together
* Simplifies location storing
*
* @author Richard
*
*/

public class Coordinate
{
//public so they can be directly accessed
public double x;
public double y;

/**
* Constructor to pass in values and easily
* constrcut a coordinate
*
* @param xx X coordinate
* @param yy Y coordinate
*/
public Coordinate(double xx, double yy)
{
x = xx;
y = yy;
}

/**
* getString for easy output
*
* @return String The coordinates
*/
public String toString()
{
return x + ", " + y;
}
}
```

Base.java

This class also simplifies information storing and accessing by making the sensor values accessible to all classes, through static.

```
package com.lichard49.Compass;
/**
* Static class to make these values accessable
* to all classes
* Holds values for location, acceleration,
* width and height of the screen
*
* @author Richard
*
*/
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;
}

```

AndroidManifest.xml

Some additional notes about the XML file: it is the one genarated by Eclipse when creating a new application. The only modification made was adding the line <uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"/> in order to enable the location sensor.

```

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lichard49.Compass"
android:versionCode="1"
android:versionName="1.0">

<!-- This allows use of the location sensor -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>

<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Compass"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

```