Friday, May 28, 2010

Let's make a call


Now we know when we're close to our friends, what are we likely to want to do when we're close? Drop in! But we're polite so we'll call them first. Let's change our list item click function to call the friend we've clicked. We can do this by firing a DIAL_ACTION intent.
    Intent i = new Intent(); i.setAction(DIAL_ACTION); i.setData(new ContentURI(numbers.get(position))); startActivity(i);
The phone dialer has registered an IntentReceiver filtered onDIAL_ACTION so it will react to this.

Set up a map activity and create an overlay to show where you are in relation to your friends


Half of the fun in having location sensitive information is drawing it on a map. Create a new activity class to display a map centered on our current location with markers at our friends locations. While we're at it we can draw a line from our position to each of our friends.
The map control itself is called a MapView, but we can only use aMapView in a MapActivity, so we'll change the inheritance of this activity to MapActivity.
    public class MyMapViewActivity extends MapActivity
To display the map we need to create a new MapView and set it as the content for our activity in the OnCreate method.
    MapView mapView = new MapView(this); setContentView(mapView);
This will make the MapView fill the entire screen, so use views likeLinearLayout if we want to create a more complicated UI layout.
We'll want to get access to the OverlayController and MapController, so create global variables to store them and assign the references within the OnCreate method. We'll also be using the Locationinformation, so get a reference to that too. With the references assigned set your map zoom and starting location using theMapController. When you're finished OnCreate should look something like this.
    protected void onCreate(Bundle icicle) {
      super.onCreate(icicle); MapView mapView = new MapView(this); mapController = mapView.getController(); overlayController = mapView.createOverlayController(); locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); mapController.zoomTo(9); setContentView(mMapView); updateView();
    }
updateView is where we do the work. Start by getting our current location and convert the Lat/Long to a map Point, then centre the map on our current location.
    Double lat = location.getLatitude()*1E6; Double lng = location.getLongitude()*1E6; Point point = new Point(lat.intValue(), lng.intValue()); mapController.centerMapTo(point, false);
The only thing left to do on our map is draw markers and link them up with lines. To do this you need to create a new class that extends Overlay, and add this using the OverlayController.
    MyLocationOverlay myLocationOverlay = new MyLocationOverlay(); overlayController.add(myLocationOverlay, true);
The work in the Overlay class is done by overriding the draw method.
    protected class MyLocationOverlay extends Overlay {
      @Override public void draw(Canvas canvas, PixelCalculator calculator, boolean shadow) {
        ... [ draw things here ] ...
      }
    }
I start by drawing a 'marker' on my current location. There doesn't seem to be support for 'traditional' Google Maps markers but you can achieve the same thing by drawing on the map canvas; I chose to draw small circles as markers. First you need to use thePixelCalculator to convert your Lat/Long points to screen coordinates, then create a Paint object to define the colours and settings for your brush. Then paint your markers.
    int[] screenCoords = new int[2]; calculator.getPointXY(point, screenCoords); RectF oval = new RectF(...); Paint paint = new Paint(); paint.setARGB(200, 255, 0, 0); canvas.drawOval(oval, paint);
I add my friends locations the same way as before, iterating over my address book grabbing names and locations. I filter out anyone too far away (say 10km) and draw markers, names (drawText), and joining lines (drawLine) to those nearby.

Refresh our list when we move


Given the location sensitive nature of WamF it makes sense to update the display whenever we move. Do this by asking theLocationManager to trigger a new Intent when our location provider notices we've moved.
    List providers = locationManager.getProviders(); LocationProvider provider = providers.get(0); Intent intent = new Intent(LOCATION_CHANGED); locationManager.requestUpdates(provider, minTime, minDistance, intent);
Intents in Android are like events in traditional event driven programming, so we're triggering a LOCATION_CHANGED event/intent every time we move by a minimum distance after a minimum time. The next step is to create an IntentReceiver (event handler), so create a new internal class that extends IntentReceiver and override the ReceiveIntent event to call our update method.
    public class myIntentReceiver extends IntentReceiver {
      @Override public void onReceiveIntent(Context context, Intent intent) {
        updateList();
      }
    }
We then have our activity listen for a LOCATION_CHANGED intent by registering the event handler and specifying the intent it should be listening for (LOCATION_CHANGED). Do this in the onCreate method or create a new menu option to start/stop the automatic updates.
    filter = new IntentFilter(LOCATION_CHANGED); receiver = new myIntentReceiver(); registerReceiver(receiver, filter);
Keep your phone running light by registering / unregistering the receiver when the activity Pauses and Resumes – there's no point in listening for location changes if we can't see the list.

Iterate over the address book pulling out names, locations, and phone numbers


A less publicized feature of Android is the ability to share content between applications. We're going to use this feature to populate our List with our contacts' names and their current distance from our phone so we create an updateList method that we call after we've gotten our current location.
Use the ContentResolver to return a query that provides access to data shared using Content Providers. Queries are returned ascursors that provide access to the underlying data tables. The data we're interested in is accessed using the People content provider.
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null, null, null); startManagingCursor(c);
The Cursor is a managed way of controlling your position (Row) in the underlying table. We get access to the data by specifying the column that holds the information we're after. Rather than memorising the column index for each Content Provider we can use constants from the People class as a shortcut.
    int coordIdx = c.getColumnIndex(People.NOTES); int phoneIdx = c.getColumnIndex(People.PhonesColumns.NUMBER); int nameIdx = c.getColumnIndex(People.NAME);
Now iterate over the table using the cursor storing the results in arrays. You'll note that we're pulling our contacts' location from theNotes field. In reality we'd want to figure this out based on their address using a geocoding lookup.
    List listItems = new ArrayList(); c.first(); do {
      String name = c.getString(nameIdx); String coords = c.getString(coordIdx); String phone = c.getString(phoneIdx); ... [ Process the lat/long from the coordinates ] ... ... [ Storing their location under variable loc ] ... String distStr = String.valueOf(location.distanceTo(loc)/1000); name = name + " (" + distStr + "km)"; listItems.add(name); numbers.add("tel:" + phone);
    } while(c.next());
Then we assign our list of strings to the array using an ArrayAdapter.
    ArrayAdapter notes = new ArrayAdapter(this, R.layout.notes_row, items); setListAdapter(notes);

Android Concepts

Describe the APK format.
The APK file is compressed the AndroidManifest.xml file, application code (.dex files), resource files, and other files. A project is compiled into a single .apk file.

What is an action?
A description of something that an Intent sender desires.

What is activity?
A single screen in an application, with supporting Java code.

What is intent?
A class (Intent) describes what a caller desires to do. The caller sends this intent to Android's intent resolver, which finds the most suitable activity for the intent.

How is nine-patch image different from a regular bitmap?
It is a resizable bitmap resource that can be used for backgrounds or other images on the device. The NinePatch class permits drawing a bitmap in nine sections. The four corners are unscaled; the four edges are scaled in one axis, and the middle is scaled in both axes.

What languages does Android support for application development?
Android applications are written using the Java programming language.

What is a resource?
A user-supplied XML, bitmap, or other file, injected into the application build process, which can later be loaded from code.

Use the Location Based Services to figure out where we are and request updates when we move


Possibly the most enticing of the Android features are the Location Based Services that give your application geographical context through Location Providers (GPS etc). Android includes a mock provider called 'gps' that marches back and forth through San Fransisco. Alternatively you can create your own mock providers in XML.
You use the LocationManager to find your current position.
    locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); Location location = locationManager.getCurrentLocation("gps");