mapping demo

36
Previous | Next | Home Mapping Demo Let us now construct a somewhat more involved project that will show how to use the Google mapping libraries to add new functionalities to that possible with the Android API alone. In this project we shall Implement layouts involving editable textfields and buttons. Read and interpret input in textfields, and limit the type of data that the textfield will accept. Learn about text autocompletion. Launch new screens based on widget actions, using intents. Register new activities and set permissions in the manifest file for using the mapping API; obtain a key permitting Google Maps to be employed with Android. Learn how to implement a MapView from the Google Maps API, how to overlay data displays on that map, how to cause the map to respond to touch events, and how to use the keyboard to toggle map properties. Learn how to send a map to a specified set of coordinates. Implement geocoding (finding a location based on an input name) using our map. Cause the device to track its present location on the map using LocationServices. Begin learning how to manage resources using the lifecycle methods of Android. Add an options menu and a preferences screen. Use a theme to automatically format an information screen. Because this will be a little more complex than our first examples, we outline it first according to the prescription suggested in Creating an Application. Outlining the Project First we sketch our goals. We wish to implement a project that will do the following: Demonstrate how to accept a user-input location and open Google maps on that location (place name or latitude/longitude for input). 1. Implement in the resulting map view a toggle between satellite view and map view, and a toggle to turn traffic view on and off. 2. Add capability to send the device to the present GPS location and display in Google maps, overlay a point indicating the present position and a compass indicating direction, and track the location continuously re-centering the map if the device moves. 3. Add to the resulting map view continuously-updated information about the latitude/longitude, number of GPS satellites being tracked, the precision of the fix, and so on. Add a way to toggle this on and off. 4. Add an options popup menu with Settings, Help, and Quit options. In the settings options, allow the user to change parameters controlling the accuracy versus power consumption of the GPS fix, and to toggle the compass view on and off. 5. In the figure below we sketch roughly the layout of how we propose to organize the main screen and secondary screens to accomplish this, assign proposed names for the required class and XML files, list special permissions and keys we are going to need, and list some strings that we will need as resources (we will undoubtedly need others, but it is clear that we will need these at least). The connecting arrows between the different screens also suggest qualitatively the event handlers and associated actions that will be required. Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html 1 of 36 4/8/2012 3:37 AM

Upload: luongbeta

Post on 27-Oct-2014

48 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Mapping Demo

Previous | Next | Home Mapping Demo

Let us now construct a somewhat more involved project that will show how to use the Google mapping libraries to addnew functionalities to that possible with the Android API alone. In this project we shall

Implement layouts involving editable textfields and buttons.Read and interpret input in textfields, and limit the type of data that the textfield will accept.Learn about text autocompletion.Launch new screens based on widget actions, using intents.Register new activities and set permissions in the manifest file for using the mapping API; obtain a keypermitting Google Maps to be employed with Android.Learn how to implement a MapView from the Google Maps API, how to overlay data displays on that map,how to cause the map to respond to touch events, and how to use the keyboard to toggle map properties.Learn how to send a map to a specified set of coordinates.Implement geocoding (finding a location based on an input name) using our map.Cause the device to track its present location on the map using LocationServices.Begin learning how to manage resources using the lifecycle methods of Android.Add an options menu and a preferences screen.Use a theme to automatically format an information screen.

Because this will be a little more complex than our first examples, we outline it first according to the prescriptionsuggested in Creating an Application.

Outlining the Project

First we sketch our goals. We wish to implement a project that will do the following:

Demonstrate how to accept a user-input location and open Google maps on that location (place name orlatitude/longitude for input).

1.

Implement in the resulting map view a toggle between satellite view and map view, and a toggle to turntraffic view on and off.

2.

Add capability to send the device to the present GPS location and display in Google maps, overlay a pointindicating the present position and a compass indicating direction, and track the location continuouslyre-centering the map if the device moves.

3.

Add to the resulting map view continuously-updated information about the latitude/longitude, number of GPSsatellites being tracked, the precision of the fix, and so on. Add a way to toggle this on and off.

4.

Add an options popup menu with Settings, Help, and Quit options. In the settings options, allow the user tochange parameters controlling the accuracy versus power consumption of the GPS fix, and to toggle thecompass view on and off.

5.

In the figure below we sketch roughly the layout of how we propose to organize the main screen and secondary screens toaccomplish this, assign proposed names for the required class and XML files, list special permissions and keys we aregoing to need, and list some strings that we will need as resources (we will undoubtedly need others, but it is clear that wewill need these at least). The connecting arrows between the different screens also suggest qualitatively the event handlersand associated actions that will be required.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

1 of 36 4/8/2012 3:37 AM

Page 2: Mapping Demo

We will undoubtedly have to modify this some as we develop the project, but this gives us a clear starting point.

Creating the Project

Now that we have sketched the outlines of the project, it is time to construct it. Open Eclipse and click File > New >Android Project.

We are going to choose to target Android 2.1-update1 for this project, which will involve the Google mapping API so wewill need the Google APIs for API Level 7, but we will specifiy a minimum SDK level of 3 (corresponding to Android1.5). Fill out the resulting window as in the following figure.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

2 of 36 4/8/2012 3:37 AM

Page 3: Mapping Demo

and click Finish. A project MappingDemo should now appear in the left panel of the Eclipse interface. Open itsAndroidManifest.xml file. Since we may test on an actual device, select the Application tab at the bottom and setDebuggable to true (which will automatically insert a debuggable = true attribute in the manifest file). Then select theAndroidManifest.xml tab at the bottom to display the file. Add to the file lines that give permission for internet access,coarse-grain and fine-grain location information, and access to the Google maps library:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lightcone.mappingdemo" android:versionCode="1" android:versionName="1.0"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true" <activity android:name=".MappingDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

3 of 36 4/8/2012 3:37 AM

Page 4: Mapping Demo

</intent-filter> </activity> <uses-library android:name="com.google.android.maps" /> </application> <uses-sdk android:minSdkVersion="3" /> </manifest>

Save the AndroidManifest.xml file. Open the strings.xml file under res/values and edit it to define the strings

<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Mapping Demo</string> <string name="app_name">Mapping Demo</string> <string name="locLabel">Location:</string> <string name="goLabel">Go</string> <string name="latLabel">Lat</string> <string name="longLabel">Long</string> <string name="trackLabel">Track Present Location</string> <string name="satLabel">Sat</string> <string name="mapLabel">Map</string> </resources>

Save the file strings.xml.

In Eclipse, choose Project > Clean, select the MappingDemo project and click OK. This will do a clean build of theproject. There should be no errors at this point (no red xs showing on any files in the project). If there are red xs, hover themouse over the xs and the things underlined in wavy red to see what the errors are and how to fix them, and fix them (seethe example of using Eclipse systematically to correct errors in WebView Demo).

The Opening Screen

We are now ready to lay out the opening screen of our app, and then to tie that to the class defined by MappingDemo.java.Open res/layout and double-click on main.xml to open it in the editor. From the diagram outlining our project above wesee that on the opening screen we need to lay out three editable text fields, corresponding to the Android class EditText andthree buttons, corresponding to the Android class Button. (The Options menu popup opens when MENU is selected on thedevice and will be dealt with later). The layout sketched in our outline can be achieved if we edit the main.xml file to read

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="10dip"> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <EditText android:id="@+id/geocode_input" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1.0" android:hint="(Place name)" android:lines="1" android:inputType="text" /> <Button android:id="@+id/geocode_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/goLabel" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="3sp" android:orientation="horizontal"> <EditText

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

4 of 36 4/8/2012 3:37 AM

Page 5: Mapping Demo

android:id="@+id/lat_input" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1.0" android:hint="(Lat deg)" android:lines="1" android:inputType="numberDecimal|numberSigned" /> <EditText android:id="@+id/lon_input" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1.0" android:hint="(Lon deg)" android:lines="1" android:inputType="numberDecimal|numberSigned" /> <Button android:id="@+id/latlong_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/goLabel" /> </LinearLayout> <Button android:id="@+id/presentLocation_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/trackLabel" /> </LinearLayout>

The documentation explaining the components of this layout may be found at LinearLayout, EditText, and Button, butbriefly

The parent LinearLayout has an orientation="vertical" attribute, so the parent container will distributethings vertically.

The child LinearLayouts have orientation="horizontal" attributes, so they will distribute their childrenhorizontally.

The wrap_content values for attributes mean to wrap the content of the container into as compact a displayas possible and the fill_parent values for attributes mean to spread the components out so that they fill theparent container in the corresponding direction (horizontal or vertical).

The layout_weight values control the relative space allocated for each component within the parentcontainer, so setting this equal to 1 for all components in a container causes the space to be allocated on anequal footing for all components.

The layout_gravity="center_horizontal" attribute for the last button causes it to be centered horizontally.

@string is Android's way of referencing the strings.xml resource file, so the text="@string/stringValue"constructions assign labels based on the values of the strings defined in strings.xml. For example,android:text="@string/trackLabel assigns to a label the text contained in the string trackLabel defined insrc/values/strings.xml.

padding attributes assign padding around components and lines restricts the number of lines displayed in atext field.

hint places hint text in a textfield and inputType attributes restrict the types of input a field will accept, asexplained further below.

Thus, reading vertically, main.xml should lay out a row containing a textfield and a button arrayed across the entire screen,then a row with two textfields and a button arrayed across the entire screen, and finally a row with one button centered onthe screen.

Let's check our progress. Execute MappingDemo on an emulator or a phone (which must implement the Google maps API ifit is an emulator). You should see something like the following image

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

5 of 36 4/8/2012 3:37 AM

Page 6: Mapping Demo

which is from a Motorola Backflip phone running Android 1.5.

Restrictions on TextFields

Notice that the XML attributes used in laying out this page have given certain capabilities and placed certain restrictions ontext fields automatically. The first text field is set up to accept general text, but the other two text fields will accept onlydecimal numbers, possibly with a negative sign.

Try it and see; you should find that you can type anything into the first field, but only digits, a single period, and a minussign (only if it is the first entry) in the other two fields. We have also indicated with the android:hint attributes furtherguidance for the user. For example, android:hint="(Lat deg)" inserts initially in the corresponding field a hint that the useris to enter the latitude in degrees (this hint will disappear as soon as the user begins to type into the field). When the hintsare displayed in the text fields, the actual content of the field is the empty string, which is the reason for the logic statementsin the event handlers defined later that do not launch the intents if the fields have not been filled in.

If you execute on a device it is possible that autocompletion will be enabled on the first textfield. For example, on the Motorola Backflip sold by AT&T, if you type in the string "nair"you should get something like the following figure,

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

6 of 36 4/8/2012 3:37 AM

Page 7: Mapping Demo

which illustrates text completion in the first EditText field. For example, if we wereentering Nairobi, we could select it off the bar below the text field at this point, withouttyping further.

Automatic text completion in an EditText field can be implemented using the classAutoCompleteTextView, which extends EditText. It requires that the programmer supply thelist of words against which the autocompletion will be checked. It appears that in this caseon the Backflip phone, autocompletion has been enabled for EditText fields against arudimentary dictionary (the dictionary does not appear to be too extensive, because somecommon words don't trigger autocompletion---for example "triangle" and "square" are in itsword list, but not "pentagon").

Adding Listeners

We have the initial screen laid out as we desired, so now we need to attach some actions to its widgets. OpenMappingDemo.java. To listen for button clicks on the opening screen we add "implements OnClickListener" to the classdefinition, which necessitates importing several Android classes (if not already imported) and adding an onClickView(Viewv) method stub, as described in WebView Demo. With the resulting changes MappingDemo.java now reads

package com.lightcone.mappingdemo; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; public class MappingDemo extends Activity implements OnClickListener { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override public void onClick(View v) { // TODO Auto-generated method stub } }

Now we fill out the onClick method to deal with click events on the main screen. Consulting main.xml we see that the threebuttons have id's geocode_button, latlong_button, and presentLocation_button, respectively. When these buttons are

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

7 of 36 4/8/2012 3:37 AM

Page 8: Mapping Demo

pressed we wish to launch new screens, which we will accomplish with intents, and we will define the contents of the newscreens with two new classes: ShowTheMap (when either of the first two buttons is pushed) and MapMe (when the thirdbutton is pushed).

Let's first define the stubs for the new classes that we will need (with the details to be filled in below). Create the classShowTheMap:

In Eclipse, right-click the package name under MappingDemo (e.g., com.lightcone.mappingdemo).1.

Select New > Class on the resulting screen.2.

Give the class the name ShowTheMap and select the Superclass com.google.android.maps.MapActivity.Leave everything else at the default values.

3.

Click OK.4.

In the same manner, create the class MapMe.

New files ShowTheMap.java and MapMe.java will now appear in the src/<packagename> directory. Check them to besure that they extend MapActivity, and that they contain a stub for the method isRouteDisplayed(), which the user isrequired to implement because in the class MapActivity that we are extending this method is abstract (they should if thecorrect choices were made above). We'll edit these new classes later.

Now we modify the class MappingDemo by adding click listeners for all buttons and adding a switch statement in theonClick method to sort out which button has been pushed (which requires two new imports). The class listing with changesin red is:

package com.lightcone.mappingdemo;

import android.app.Activity; import android.os.Bundle; import android.content.Intent; import android.util.Log; import android.view.View; import android.view.View.OnClickListener;

public class MappingDemo extends Activity implements OnClickListener { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Add Click listeners for all buttons View firstButton = findViewById(R.id.geocode_button); firstButton.setOnClickListener(this); View secondButton = findViewById(R.id.latlong_button); secondButton.setOnClickListener(this); View thirdButton = findViewById(R.id.presentLocation_button); thirdButton.setOnClickListener(this);

} @Override public void onClick(View v) {

switch(v.getId()){ case R.id.geocode_button: Log.i("Button","Button 1 pushed"); Intent j = new Intent(this, ShowTheMap.class); startActivity(j); break; case R.id.latlong_button: Log.i("Button","Button 2 pushed"); Intent k = new Intent(this, ShowTheMap.class); startActivity(k); break; case R.id.presentLocation_button: Log.i("Button","Button 3 pushed"); Intent m = new Intent(this, MapMe.class); startActivity(m); break;

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

8 of 36 4/8/2012 3:37 AM

Page 9: Mapping Demo

}

} }

where we have inserted some diagnostic logcat statements to allow us to check the logic of the event handling. Let's test it.Launch an emulator or target a device and click one of the Go buttons. Uh-oh; the app dies with an error message requiringa forced close. Checking the logcat output, we see the reason: the logcat output contains the error message

06-14 10:47:10.696 E/AndroidRuntime( 3000): android.content.ActivityNotFoundException: Unable to find explicit activityclass {com.lightcone.mappingdemo/com.lightcone.mappingdemo.ShowTheMap}; have you declared this activity in yourAndroidManifest.xml?

Android requires that all activities be registered in the AndroidManifest.xml file. Since we forgot to do that for the newactivities that we added corresponding to the classes ShowTheMap and MapMe, the Android runtime is unable to findthose classes. We fix that by adding to AndroidManifest.xml within the application tag declarations for the new activitiessuch that the application tag now reads

<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true"> <activity android:name=".MappingDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ShowTheMap" android:label="Lat/Long Location"> </activity> <activity android:name=".MapMe" android:label="Track Present Location"> </activity> <uses-library android:name="com.google.android.maps" /> </application>

which registers the new activities ShowTheMap and MapMe. Now if we launch the app on an emulator or phone and pushthe buttons we get the expected results:

If we click the top button a blank screen with the title Lat/Long Location appears (the title is specified by theandroid:label attribute in the activity tag of the manifest) and in the logcat output we see our diagnosticmessage "Button 1 pushed".

1.

If we click the Return button (the curved back arrow on the phone or emulator) to return to the main screenand click the second button the same new screen appears as before, but now "Button 2 pushed" appears in thelogcat output,

2.

Finally if we return to the main screen and push the bottom button we get a new screen with the title "TrackPresent Location" and "Button 3 pushed" is output in the logcat stream.

3.

Thus, our buttons and intents appear to be behaving correctly.

Implementing a Google Maps Display

Let's now modify the class ShowTheMap so that it can implement a Google maps display centered on a specified latitudeand longitude.

The key class in the Maps external library is MapView, which is a subclass of the standardAndroid class ViewGroup. MapView provides a wrapper around the Google Maps API thatlets you manipulate Maps data and control the view just as for any other View. A MapViewdisplays a map with data obtained from the Google Maps service. It can capture keypressesand touch gestures to pan and zoom, handle network requests for maps tiles, and provide allof the UI elements necessary to control the map. An application can manipulate theMapView using the methods of the MapView class, and can draw a number of Overlay typesover the displayed map that can respond to keypresses and other interactions.

Right-click on layout under res/layout and choose New > Android XML File to create an XML file of type Layout namedshowthemap.xml in the folder res/layout, giving it a LinearLayout root element. Then, edit the file showthemap.xml that

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

9 of 36 4/8/2012 3:37 AM

Page 10: Mapping Demo

this produces to add a view tag inside the parent LinearLayout:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <view android:id="@+id/mv" class="com.google.android.maps.MapView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:clickable="true" android:apiKey="myMapKey" /> </LinearLayout>

There are three things about this view layout that are a little different from things we have done up to now:

The class corresponding to the view is a fully qualified string, com.google.android.maps.MapView, becauseMapView is not in the com.google.android.widget namespace.

1.

The attribute clickable has been set to true.2.

An attribute called an android:apiKey has been inserted.3.

The first of these is required to implement Google map classes (which, recall, are separate from but compatible with theAndroid classes), and the second is necessary to allow some later click actions on the maps that we will create.Explanation of the third is slightly more complicated, but essential. Embedding Google maps under API control will notwork unless a unique apiKey is specified (for each MapView in the project). Thus, before proceeding we must see how toobtain and use an apiKey for maps.

The Google Maps API Key

We are going to use the MapView class to integrate Google Maps into our application. Because MapView gives you accessto Google Maps data, you must register with the Google Maps service before your implementation of MapView will beable to obtain map data. The procedure for doing this is described in the Appendix The Maps API Key. Follow theinstructions given there to obtain a temporary Maps API key that will be necessary in what follows. Insert the Maps APIkey that you obtain in the showthemap.xml file, which should now read

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <view android:id="@+id/mv" class="com.google.android.maps.MapView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:clickable="true" android:apiKey="07WVUg-srWUbhpkGj1mi0w56xD9nMEu0NhqJeYg" /></LinearLayout>

except that you should substitute your value of android:apiKey.

This key is machine-specific for your development computer if you obtained a temporaryone using the debug certificate. Thus, if you develop on more than one machine, each willhave a different apiKey and you will have to change the entry in the showthemap.xml file tothe appropriate one if you move the MappingDemo project---or any project usingMapView---between machines.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

10 of 36 4/8/2012 3:37 AM

Page 11: Mapping Demo

Now open the file ShowTheMap.java and edit it to read

package com.lightcone.mappingdemo; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import android.os.Bundle; import android.view.Window; public class ShowTheMap extends MapActivity { private static double lat = 35.952967; // Temporary test values for lat/long private static double lon = -83.929158 ; private int latE6; private int lonE6; private MapController mapControl; private GeoPoint gp; private MapView mapView; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); // Suppress title bar to give more space setContentView(R.layout.showthemap); // Add map controller with zoom controls mapView = (MapView) findViewById(R.id.mv); mapView.setSatellite(true); mapView.setTraffic(false); mapView.setBuiltInZoomControls(true); // Set android:clickable=true in main.xml int maxZoom = mapView.getMaxZoomLevel(); int initZoom = (int)(0.80*(double)maxZoom); mapControl = mapView.getController(); mapControl.setZoom(initZoom); // Convert lat/long in degrees into integers in microdegrees latE6 = (int) (lat*1e6); lonE6 = (int) (lon*1e6); gp = new GeoPoint(latE6, lonE6); mapControl.animateTo(gp); } // Required method since class extends MapActivity @Override protected boolean isRouteDisplayed() { return false; // Don't display a route } }

The relevant documentation may be found under MapView, MapActivity, and MapController. A MapView is a Viewcontaining a map, ShowTheMap extends MapActivity, which is the class that manages lifecycles and related issues forMapView displays, and MapController supplies methods to manage panning and zooming of a map display. A GeoPointrepresents a latitude/longitude pair, stored as an integer number of microdegrees (which may be obtained from the latitudeand longitude in degrees by multiplying them by a million).

We have for now hardwired in a latitude and longitude to test things; we'll replace that by user input below. Note that theability to pan and zoom requires setBuiltInZoomControls(true), and also requires that the clickable attribute inshowthepage.xml be set to true. If you test this you should find that pressing the second button sends you to a map displaycentered on the latitude and longitude values that we hardwired in (the coordinates correspond to the University ofTennessee, Knoxville).

Using the Keyboard to Toggle Views

Let's use the onKeyDown method of MapActivity (which it inherits from Activity) and the constants of the KeyEvents classto add the ability to toggle between map view and satellite view, and between traffic and no traffic view, using the devicekeyboard. Add the following method to ShowTheMap.java

public boolean onKeyDown(int keyCode, KeyEvent e){ if(keyCode == KeyEvent.KEYCODE_S){ mapView.setSatellite(!mapView.isSatellite());

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

11 of 36 4/8/2012 3:37 AM

Page 12: Mapping Demo

return true; } else if(keyCode == KeyEvent.KEYCODE_T){ mapView.setTraffic(!mapView.isTraffic()); mapControl.animateTo(gp); // To ensure change displays immediately } return(super.onKeyDown(keyCode, e)); }

(which will require adding import android.view.KeyEvent). Now when you execute the program and display a map, youshould be able to toggle between satellite and map view by pressing the "s" key on the phone, and between traffic and notraffic by pressing the "t" key.

Traffic overlays are often provided only for busy urban routes. To get the soft keyboard on amany Android phones, press and hold (long-press) the MENU key. Note, however, that thismay be device dependent. On my Motorola Backflip phone, long-pressing the MENU keybrings up the soft keyboard allowing the toggles between map views described above. Buton my Captivate (Samsung Galaxy S) phone the MENU key has been co-opted to bring up asoft keyboard attached to a search field when long-pressed, which captures the keypressesand doesn't allow the above keyboard toggles to work. This latter behavior illustrates animportant point for user-friendly application development. Since Android can run on manydevices that can be customized by their manufacturers, you cannot always assume thatparticular things like soft keyboards will be accessed the same way on all devices. Thus,the wise developer builds redundancy into an application. In the above example, a simplesolution would be to also allow a toggle between map views in an options menu (see anexample of building an options menu later in this project).

Reading and Interpreting Input in TextFields

Now we wish to enable the input fields to read latitude/longitude and place names, and send the device to thecorresponding location. Let's do the latitude/longitude first. We begin by adding to ShowTheMap a static method to insertthe latitude and longitude. Open ShowTheMap.java and add inside the class definition the method

public static void putLatLong(double latitude, double longitude){ lat = latitude; lon = longitude; }

Then remove the explicit latitude and longitude values that we hardwired in earlier for testing, changing the correspondinglines in ShowTheMap to read

private static double lat; private static double lon;

Now edit MappingDemo.java so that when the second button is pushed we read the user input in the two EditText inputfields and interpret them as latitude and longitude variables. First add a new import and the class variables lat and lon:

import android.widget.EditText;

public class MappingDemo extends Activity implements OnClickListener { public double lat; public double lon; . . .

and then modify the switch-statement case corresponding to the second button to read

case R.id.latlong_button:

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

12 of 36 4/8/2012 3:37 AM

Page 13: Mapping Demo

// Read the latitude and longitude from the input fields EditText latText = (EditText) findViewById(R.id.lat_input); EditText lonText = (EditText) findViewById(R.id.lon_input); String latString = latText.getText().toString(); String lonString = lonText.getText().toString(); // Only execute if user has put entries in both lat and long fields. if(latString.compareTo("") != 0 && lonString.compareTo("") != 0){ lat = Double.parseDouble(latString); lon = Double.parseDouble(lonString); Intent k = new Intent(this, ShowTheMap.class); ShowTheMap.putLatLong(lat,lon); startActivity(k); }

break;

where the if-statement is to ensure that actions are performed only if the user has put entries in both the latitude andlongitude fields. The basic procedure is to

extract the contents of the fields as EditText objects,1.

use EditText methods getText() and toString() to convert this input to strings, and then2.

use the parseDouble method of the Java class Double to convert the strings to doubles.3.

Once we have stored the latitude and longitude in the variables lat and lon, we then use an intent to launch theShowTheMap activity to display the map, and use the putLatLong(lat,lon) method of ShowTheMap to pass the coordinatesinput by the user to use in centering the map display.

Sending the Map to Specific Latitudes and Longitudes

Now if you insert latitude and longitude values in degrees and push the corresponding button, the map display screen shouldcenter on the location corresponding to the input latitude and longitude. For example, try

Latitude = 35.362596, longitude = 138.731060 (Mount Fuji)

Latitude=21.261941, longitude = -157.805901 (Diamond Head, Oahu)

Latitude = -33.856963, longitude = 151.215123 (the Sydney Opera House)

In all these cases the satellite view (toggled with "s" on the device keyboard) is the most interesting, and you may have tochange the map zoom for the best view (touching the screen should give you zoom controls; dragging should give you pancontrol).

As an aside, here is one way to get precise longitude and latitude coordinates for arbitrarylocations: Using Google Maps on your computer, navigate to the desired area, right-click onthe desired point and select Center map here, click Link at top of map, left-click on Pastelink in email or IM field, right-click and select Copy, and past the copy into a text editor. Inthe html address, the field of the form

ll=35.832554,-84.060522

contains the latitude (first number) and longitude (second number) in degrees. Multiplythese by 1e6 (shift decimal 6 places right) to get microdegrees for the maps API arguments.

Geocoding

Now let's make it even simpler for the user to send this mapping application to a location of interest. It would be mucheasier to just type in "Hoover Dam", or "1516 Android Way", or "Big Ben", rather than trying to figure out the latitude andlongitude coordinates for locations. The Android/Google API in fact has classes that allow us to implement that capability

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

13 of 36 4/8/2012 3:37 AM

Page 14: Mapping Demo

with just a few lines of code. The most important resource is the Android Geocoder class, which is a class for handlinggeocoding and reverse geocoding:

Geocoding is the transformation of a location description such as a street address into a (latitude, longitude)coordinate.

Reverse geocoding transforms a (latitude, longitude) coordinate into a partial address.

In the file MappingDemo.java, first add the imports

import java.io.IOException; import java.util.Iterator; import java.util.List; import android.location.Address; import android.location.Geocoder;

and then modify the case-clause corresponding to the first button to read

case R.id.geocode_button:

// Following adapted from Conder and Darcey, pp.321 ff. EditText placeText = (EditText) findViewById(R.id.geocode_input); String placeName = placeText.getText().toString(); // Break from execution if the user has not entered anything in the field if(placeName.compareTo("")==0) break; int numberOptions = 5; String [] optionArray = new String[numberOptions]; Geocoder gcoder = new Geocoder(this); // Note that the Geocoder uses synchronous network access, so in a serious application // it would be best to put it on a background thread to prevent blocking the main UI if network // access is slow. Here we are just giving an example of how to use it so, for simplicity, we // don't put it on a separate thread. try{ List<Address> results = gcoder.getFromLocationName(placeName,numberOptions); Iterator<Address> locations = results.iterator(); String raw = "\nRaw String:\n"; String country; int opCount = 0; while(locations.hasNext()){ Address location = locations.next(); lat = location.getLatitude(); lon = location.getLongitude(); country = location.getCountryName(); if(country == null) { country = ""; } else { country = ", "+country; } raw += location+"\n"; optionArray[opCount] = location.getAddressLine(0)+", "+location.getAddressLine(1)+country+"\n"; opCount ++; } Log.i("Location-List", raw); Log.i("Location-List","\nOptions:\n"); for(int i=0; i<opCount; i++){ Log.i("Location-List","("+(i+1)+") "+optionArray[i]); }

} catch (IOException e){ Log.e("Geocoder", "I/O Failure; is network available?",e); } Intent j = new Intent(this, ShowTheMap.class); ShowTheMap.putLatLong(lat,lon); startActivity(j);

break;

As noted in the code comments above, it may be useful to call getFromLocationName from a thread separate from yourprimary UI thread (see the Geocoder documentation). The getFromLocationName method throws bothIllegalArgumentException (for a null place name) and IOException (if there is an i/o problem like the network not being

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

14 of 36 4/8/2012 3:37 AM

Page 15: Mapping Demo

available). These could be used to make a more user-friendly application (we are already catching the IOException, butnot using it except for diagnostic output). Address is an Android class that represents a location address as a set of strings.

Try entering the locations "hoover dam" or "little mermaid" or "ord" (the airport identifier for Chicago O'Hare Airport) andclicking Go. On a Motorola Backflip phone running Android 1.5, the first two options give the following figures.

("Den Lille Havfrue" is the Danish name for the statue known as "The Little Mermaid" in English.) NOTE: This works(compiled under Android 2.1) with a phone or emulator running Android 1.5, but I found that it may fail due to a networkerror if you attempt to execute it on an emulator running later versions of the operating system.

In the preceding examples, if you check the diagnostics we have sent to logcat you will see that in each case a unique sitewas returned. But in general, a given name may correspond to more than one location. For example, if we enter the address1101 Shevlin Drive (a location where I once lived), our messages sent to the logcat output with Log.i() give

06-15 13:16:19.592 I/Location-List( 8581): Raw String:06-15 13:16:19.592 I/Location-List( 8581): Address[addressLines=[0:"1101 Shevlin Dr",1:"El Cerrito, CA94530",2:"USA"],feature=1101,admin=California,sub-admin=Contra Costa,locality=El Cerrito,thoroughfare=ShevlinDr,postalCode=94530,countryCode=US,countryName=UnitedStates,hasLatitude=true,latitude=37.91892,hasLongitude=true,longitude=-122.294027,phone=null,url=null,extras=null]06-15 13:16:19.592 I/Location-List( 8581): Address[addressLines=[0:"1101 Shevlin Dr",1:"Wayzata, MN55391",2:"USA"],feature=1101,admin=Minnesota,sub-admin=Hennepin,locality=Wayzata,thoroughfare=ShevlinDr,postalCode=55391,countryCode=US,countryName=UnitedStates,hasLatitude=true,latitude=44.964079,hasLongitude=true,longitude=-93.5790595,phone=null,url=null,extras=null]

06-15 13:16:19.592 I/Location-List( 8581): Options:06-15 13:16:19.592 I/Location-List( 8581): (1) 1101 Shevlin Dr, El Cerrito, CA 94530, United States06-15 13:16:19.592 I/Location-List( 8581): (2) 1101 Shevlin Dr, Wayzata, MN 55391, United States

So we see that there are two options, one in El Cerrito, California, and one in Wayzata, Minnesota. With the way we haveset the code up, it will always send the map to the last option (which is not my old address in this case; I lived inCalifornia, not Minnesota). We can see from this output that it would not be too difficult to use the information that we haveextracted to present to the user a choice list (already contained in the array optionArray) when there is more than oneoption, so that the user can select the correct one. We leave that as an exercise.

Causing the Device to Track the Present Location

Now let's add the next activity, which will allow us to go to a screen displaying Google maps with our present locationdisplayed, and track our location if it changes. This will require application of the mapping techniques that we have alreadyemployed, and the invocation of location services to determine the present position of the device.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

15 of 36 4/8/2012 3:37 AM

Page 16: Mapping Demo

We first modify the class MapMe so that it can implement a Google maps display centered on a specified latitude andlongitude. Right-click on layout under res/layout and choose New > Android XML File to create an XML file of typeLayout named mapme.xml in the folder /res/layout, giving it a LinearLayout root element. Then, edit the file mapme.xml togive

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <view android:id="@+id/mv2" class="com.google.android.maps.MapView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:clickable="true" android:apiKey="myMapKey" /> </LinearLayout>

where you should substitute for "myMapKey" your apiKey, as described above. Note that we have to insert an apiKey foreach MapView in the project. In this project we have two MapViews, one in MapMe and one in ShowTheMap. We have toinsert the apiKey in the XML layout file for each (even though the key is the same in the two cases).

Now we must edit the file MapMe.java to do three things:

Require that the class implement the public interface LocationListener:

public class MapMe extends MapActivity implements LocationListener {

which then requires that we add four methods that must be implemented by the user of the interface:

@Override public void onLocationChanged(Location location) { // TODO Auto-generated method stub }

@Override public void onProviderDisabled(String provider) { // TODO Auto-generated method stub }

@Override public void onProviderEnabled(String provider) { // TODO Auto-generated method stub }

@Override public void onStatusChanged(String provider, int status, Bundle extras) { // TODO Auto-generated method stub }

1.

Implement location services for the class so that we can determine the position of the device.2.

Implement a Google MapView of the resulting position similar to what we have already seen, but now onethat changes dynamically to follow the position of the device.

3.

The listing of the complete MapMe class after doing all of this is

package com.lightcone.mappingdemo; import android.content.Context; import android.location.GpsSatellite; import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log;

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

16 of 36 4/8/2012 3:37 AM

Page 17: Mapping Demo

import android.view.KeyEvent; import android.view.Window; import android.widget.Toast; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; public class MapMe extends MapActivity implements LocationListener {

private static double lat;private static double lon;private MapController mapControl;private MapView mapView;LocationManager locman;

Location loc; String provider = LocationManager.GPS_PROVIDER; String TAG = "GPStest";

Bundle locBundle;int numberSats = -1;float satAccuracy = 2000;private float bearing;private double altitude;private float speed;private String currentProvider;

// Following 2 parameters control how often the GPS is called to update location.// These are only suggestions to the hardware. Precision versus power-consumption // considerations govern what these settings should be. The defaults can be reset// in the preferences (settings) menu. (See the updateGPSprefs method below.)

long GPSupdateInterval; // In millisecondsfloat GPSmoveInterval; // In meters

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); // Suppress title bar to give more space setContentView(R.layout.mapme);

GPSupdateInterval = 5000; GPSmoveInterval = 1; // Set up location manager for determining present location of phone locman = (LocationManager)getSystemService(Context.LOCATION_SERVICE); // Listener for GPS Status... final GpsStatus.Listener onGpsStatusChange = new GpsStatus.Listener(){ public void onGpsStatusChanged(int event){ switch(event){ case GpsStatus.GPS_EVENT_STARTED: // Started... break; case GpsStatus.GPS_EVENT_FIRST_FIX: // First Fix... Toast.makeText(MapMe.this, "GPS has 1st fix", Toast.LENGTH_LONG).show(); break; case GpsStatus.GPS_EVENT_STOPPED: // Stopped... break; case GpsStatus.GPS_EVENT_SATELLITE_STATUS: // Satellite update break; } GpsStatus status = locman.getGpsStatus(null); // Not presently doing anything with following status list for individual satellites Iterable<GpsSatellite> satlist = status.getSatellites(); } }; locman.addGpsStatusListener(onGpsStatusChange); locman.requestLocationUpdates(provider,GPSupdateInterval,GPSmoveInterval,this); Log.i(TAG, locman.toString()); // Add map controller with zoom controls mapView = (MapView) findViewById(R.id.mv2); mapView.setSatellite(false);

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

17 of 36 4/8/2012 3:37 AM

Page 18: Mapping Demo

mapView.setTraffic(false); mapView.setBuiltInZoomControls(true); // Set android:clickable=true in main.xml int maxZoom = mapView.getMaxZoomLevel(); int initZoom = (int)(0.95*(double)maxZoom); mapControl = mapView.getController(); mapControl.setZoom(initZoom); }

// This sets the s key on the phone to toggle between satellite and map view// and the t key to toggle between traffic and no traffic view (traffic view// relevant only in urban areas where it is reported). (See Murphy, pp. 304-305)// The map/satellite and traffic/no traffic toggles are independent so there are// four combinations.

public boolean onKeyDown(int keyCode, KeyEvent e){ if(keyCode == KeyEvent.KEYCODE_S){ mapView.setSatellite(!mapView.isSatellite()); return true; } else if(keyCode == KeyEvent.KEYCODE_T){ mapView.setTraffic(!mapView.isTraffic()); centerOnLocation(); // To ensure change displays immediately } return(super.onKeyDown(keyCode, e)); }

// Required method since class extends MapActivity @Override protected boolean isRouteDisplayed() { return false; // Don't display a route }

// Required method since class implements LocationListener interface @Override public void onLocationChanged(Location location) { // Called when location has changed centerOnLocation(); }

// Required method since class implements LocationListener interface @Override public void onProviderDisabled(String provider) { // Called when user disables the location provider. If // requestLocationUpdates is called on an already disabled // provider, this method is called immediately. }

// Required method since class implements LocationListener interface @Override public void onProviderEnabled(String provider) { // Called when the user enables the location provider }

// Required method since class implements LocationListener interface @Override public void onStatusChanged(String provider, int status, Bundle extras) { // Called when the provider status changes. This method is called // when a provider is unable to fetch a location or if the provider // has recently become available after a period of unavailability. centerOnLocation(); }

// Method to query phone location and center map on that location private void centerOnLocation() { loc = locman.getLastKnownLocation(provider); if(loc != null){ lat = loc.getLatitude(); lon = loc.getLongitude(); GeoPoint newPoint = new GeoPoint((int)(lat*1e6),(int)(lon*1e6)); mapControl.animateTo(newPoint); getSatelliteData(); } } // Method to determine the number of satellites contributing to the fix and // various other quantities.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

18 of 36 4/8/2012 3:37 AM

Page 19: Mapping Demo

public void getSatelliteData(){ if(loc != null){ // Determine number of satellites used for the fix locBundle = loc.getExtras(); if(locBundle != null){ numberSats = locBundle.getInt("satellites",-1); }

// Following return 0 if the corresponding boolean (e.g., hasAccuracy) are false. satAccuracy = loc.getAccuracy(); bearing = loc.getBearing(); altitude = loc.getAltitude(); speed = loc.getSpeed(); } }}

The key player here is locman, which is an instance of LocationManager. It is not instantiated directly but rather isinvoked through getSystemService(Context.LOCATION_SERVICE). locman adds a GpsStatus.Listener to listen forchanges in GPS status and registers to receive location updates through the requestLocationUpdates method. The methodcenterOnLocation is used to retrieve a location loc by invoking the getLastKnownLocation method of locman and send themap to that location. It is invoked from both the onStatusChanged and onLocationChanged methods. The methodgetSatelliteData retrieves information about the quality of the satellite information and also bearing, speed, and altitude.The methods of the MapView class have been invoked by the mapView object to set up the map and its controllers.

We have simply specified GPS for the location provider in the above example, but there is agetBestProvider() method for Location Provider that can be invoked once a Criteria objecthas been created. This will choose a location provider based on whether altitude isrequired, the accuracy desired for position, and whether the user is willing to pay forlocation services. Android devices obtain precise locations from GPS fixes (theACCESS_FINE_LOCATION permission in AndroidManifest.xml), but they may offer lessprecise locations from triangulation on cell phone towers and proximity to WIFI hotspots(the ACCESS_COARSE_LOCATION permission in AndroidManifest.xml). For somelocation service applications (for example, "What city are you near?" to determine nearbymovie theaters, or "What country are you in?", to determine the default language to use in anapplication), coarse-grain location is sufficient. On the other hand, an application liketurn-by-turn navigation for driving requires fine-grain location.

If after editing this class you execute the program on an actual device you should find that clicking the Track PresentLocation button opens a map screen that is centered on your present location (if the device is GPS enabled and receivingsignals). On an emulator there is no GPS, but you can simulate positioning the device at specific GPS coordinates by one ofthe procedures described in the Appendix Simulating GPS Position. Once you have clicked the Track Present Locationbutton on the virtual device, you should then see it move the center of the map display to the new simulated coordinates.With a real device having a functioning GPS receiver, you should find that the map display tracks your motion with thedevice.

Managing Resources with onPause() and onResume()

One thing that you will notice on a virtual or real device is that the GPS symbol at the top of the screen may remain on, evenafter you have quit using this app. This is because we still need to do some application lifecycle cleanup, which we mightas well do right now.

The problem is that even when we "quit" the app, the operating system may very well just put it into the background insteadof really killing it, if it decides it has plenty of resources available. Then, as presently written, MapMe will still be askingfor position updates while in the background, which will cause the GPS to remain on when it could be turned off, and thiswill consume a lot of power. Let's fix that by adding the following methods to MapMe

// OnPause() and onResume() methods to handle when app is forced to background by another // process and then resumed. Generally should release resources not needed while in background // and restore when resumed. For example, in the following locman.removeUpdates(this) when // pausing removes the request for GPS updates when MapMe goes into the background. This // permits the GPS engine to shut down (if it is not being used by another program), which saves // a lot of power. If MapMe is resumed (moved from background to foreground), the onResume()

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

19 of 36 4/8/2012 3:37 AM

Page 20: Mapping Demo

// method resumes GPS update requests through the locman.requestLocationUpdates() method.

public void onPause() { super.onPause(); Log.i(TAG,"****** MappingDemo is pausing: Removing GPS update requests to save power"); locman.removeUpdates(this); }

public void onResume(){ super.onResume(); Log.i(TAG,"****** MappingDemo is restarting: Resuming GPS update requests"); locman.requestLocationUpdates(provider,GPSupdateInterval,GPSmoveInterval,this); }

In addition, we modify the onProviderDisabled and onProviderEnabled methods of MapMe to

@Override public void onProviderDisabled(String provider) { locman.removeUpdates(this); }

@Override public void onProviderEnabled(String provider) { locman.requestLocationUpdates(provider,GPSupdateInterval,GPSmoveInterval,this); }

The OnPause() and onResume() methods handle when the app is forced to background by another process and thenresumed, while the onProviderDisabled and onProviderEnabled methods deal with loss and restoration of the GPSprovider. Generally we should release resources not needed while in the background (or if there is no provider available)and restore when resumed. For example, locman.removeUpdates(this) when pausing removes the request for GPS updateswhen MapMe goes into the background. This permits the GPS engine to shut down, which saves a lot of power. If MapMeis moved from background to foreground, the onResume() method resumes GPS update requests through thelocman.requestLocationUpdates() method. For more details, see the LocationListener documentation.

Now you should find that if the app moves to the background the GPS symbol rather quickly turns off (provided thatsomething else on the device is not requesting location services), and if you restart the app the GPS symbol appears againrather quickly.

Adding An Options Menu

Let's go ahead at add the basics of the options menu. First, since one of the options is going to be to quit the application,let's add to MappingDemo.java the following method to exit

public void finishUp(){ finish(); }

Next we add to strings.xml the following strings that we will need (inside <resource> </resource>)

<string name="quit">Exit</string> <string name="help_title">Help</string> <string name="help_text">This is the help file for this application. \n\nItem 1\n<i>Item 2 (in italics) </i>\nItem 3</string> <string name="settings">Settings</string>

(The \n inserts a newline and the tag <i>item</i> puts the enclosed text in italics.) We are going to add icons to theoptions menu, so we must copy them into a resource directory. Let us assume that you have the icons stored somewhere as.png files. (Standard Android menu icons, including the ones we are going to use, may be found at the icon design page.You can also download them from the course image directory. The images that we will require are

.

ic_menu_close_clear_cancel.png,

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

20 of 36 4/8/2012 3:37 AM

Page 21: Mapping Demo

ic_menu_help.png, and

ic_menu_preferences.png

Image resources for Android must be named by strict rules: you can use only lower-caseletters a-z, numbers 0-9, _ and . in these image file names. The preferred format is .png, but.gif and .jpg can be used.

Right-click on MappingDemo/res/drawable-hdpi and select Import. On the resulting screen, select General > FileSystem and click Next. Click the Browse button and navigate to the directory containing the icons you wish to add asresources. Click OK. Then on the resulting screen select the files to be imported, as in the following figure

(you can select all files in the directory by checking the box in the left window). Once the files are selected, click Finish.The new image files should now appear under res/drawable-hdpi.

Our options menu will have three functions: (1) Exit, which will be handled by the finishUp() method given above, (2)Help, and (3) Settings. The last two we will implement by using intents to launch new screens that will be defined by theJava classes to be specified in Help.java and Prefs.java. Let's create the stubs for the Help class, and the correspondingXML layout now. (The Prefs class is special and we will define it later.) Using techniques already described, create thelayout file help.xml in the res/layout directory with content

<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10sp"> <TextView android:id="@+id/help_content" android:layout_width="wrap_content"

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

21 of 36 4/8/2012 3:37 AM

Page 22: Mapping Demo

android:layout_height="wrap_content" android:text="@string/help_text" /> </ScrollView>

and create in the src/<packagename> directory the file Help.java with the content

package com.lightcone.mappingdemo; import android.app.Activity; import android.os.Bundle; public class Help extends Activity { @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.help); } }

Now we are ready to create our options menu. A Menu can be instantiated from an XML file using MenuInflater.However, in this example we are going to create the menu and its event handlers directly in Java code. First add to theclass MappingDemo the imports

import android.view.Menu; import android.view.MenuItem;

then add the following menu-creation method to MappingDemo

@Override public boolean onCreateOptionsMenu(Menu menu){ super.onCreateOptionsMenu(menu); int groupId = 0; int menuItemOrder = Menu.NONE; // Create menu ids for the event handler to reference int menuItemId1 = Menu.FIRST; int menuItemId2 = Menu.FIRST+1; int menuItemId3 = Menu.FIRST+2; // Create menu text int menuItemText1 = R.string.quit; int menuItemText2 = R.string.help_title; int menuItemText3 = R.string.settings; // Add the items to the menu MenuItem menuItem1 = menu.add(groupId, menuItemId1, menuItemOrder, menuItemText1) .setIcon(R.drawable.ic_menu_close_clear_cancel); MenuItem menuItem2 = menu.add(groupId, menuItemId2, menuItemOrder, menuItemText2) .setIcon(R.drawable.ic_menu_help); MenuItem menuItem3 = menu.add(groupId, menuItemId3, menuItemOrder, menuItemText3) .setIcon(R.drawable.ic_menu_preferences); return true; }

and add the following method to MappingDemo to handle events associated with this menu

// Handle events from the popup menu above public boolean onOptionsItemSelected(MenuItem item){ super.onOptionsItemSelected(item); switch(item.getItemId()){ case (Menu.FIRST): finishUp(); return true; case (Menu.FIRST+1): // Actions for help page Intent i = new Intent(this, Help.class); startActivity(i); return true; case(Menu.FIRST+2): // Actions for settings page // These actions will be filled in later

return true; }

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

22 of 36 4/8/2012 3:37 AM

Page 23: Mapping Demo

return false; }

(see Creating Menus and the discussion of Menus in Android User Interfaces for more details). Finally, add thecorresponding intent declaration to the AndroidManifest.xml file:

<activity android:name=".Help" android:label="Help"> </activity>

(within the <application></application> tag). Now if you execute the program and click the MENU button on theemulator or phone you should get a popup options menu at the bottom of the screen, as in the following figure.

If you select Exit from this menu the app should exit, and if you select Help from the menu you should get the display shownin the following figure,

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

23 of 36 4/8/2012 3:37 AM

Page 24: Mapping Demo

which is showing the title corresponding to the string help_title from strings.xml and displaying the text given by the stringhelp_text in strings.xml. We will leave it to the user to edit the text in help_text to produce an appropriate help file, butbefore moving on let's prettify the formatting of the Help screen a little. The screen displayed in the preceding figure isserviceable but rather blah. Let's invoke a theme, as described in Android User Interfaces to make it look a little nicer. Editthe AndroidManifest.xml file and change the activity declaration for .Help to add a Theme.Dialog theme:

<activity android:name=".Help" android:label="Help" android:theme="@android:style/Theme.Dialog"> </activity>

Now if you press Help in the options menu you get the following display

Selecting Settings from the options menu doesn't do anything yet; we will take care of that a little later.

Adding Map Overlays

One of the most important advantages of embedding Google maps in an application using MapView instead of just using themap application built into the phone is that you have control of what to do with it and one of the most important things youcan do with it is to use the Overlay class of com.google.android.maps to overlay symbols and graphics on the map toindicate information, position, orientation, routes, and so on, and those symbols can be programmed so that actions areinitiated when they are touched. Let's see how to modify MappingDemo to do that.

First, there is a built-in class at MyLocationOverlay that implements two special overlays rather automatically inconjunction with MapView: (1) a compass rose showing the current orientation of the device, and (2) a pulsing blue symbolthat represents your current position, with a lighter-colored radius indicating the uncertainty in that position. Let's add thiscapability to our GPS tracking map. To implement it we could simply instantiate MyLocationOverlay. However, we dosomething a little more sophisticated: we first subclass (extend) it, because we are going to override its dispatchTap()method so that we can use the current position symbol to respond to tap events and initiate actions.

Right-click on MappingDemo/<package-name> and select New > Class. In the resulting dialogue window give the newclass the name MyMyLocationOverlay, specify the Superclass as com.google.android.maps.MyLocationOverlay, andclick Finish. Edit the resulting file MyMyLocationOverlay.java so that it reads

package com.lightcone.mappingdemo; import android.content.Context; import android.widget.Toast; import com.google.android.maps.MapView; import com.google.android.maps.MyLocationOverlay; // This class subclasses (extends) MyLocationOverlay so that

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

24 of 36 4/8/2012 3:37 AM

Page 25: Mapping Demo

// we can override its dispatchTap method // to handle tap events on the present location dot. public class MyMyLocationOverlay extends MyLocationOverlay { private Context context; public MyMyLocationOverlay(Context context, MapView mapView) { super(context, mapView); this.context = context; } // Add stub to Override the dispatchTap() method. Will fill in below. @Override protected boolean dispatchTap(){ // More to add later return true; } }

Edit MapMe.java and add a class variable

private MyMyLocationOverlay myLocationOverlay;

and add within the method onCreate after the code implementing the map controller in MapMe.java

// Set up compass and dot for present location map overlay List<Overlay> overlays = mapView.getOverlays(); myLocationOverlay = new MyMyLocationOverlay(this,mapView); overlays.add(myLocationOverlay);

which will require adding the imports

import java.util.List; import com.google.android.maps.Overlay;

and finally modify the onPause and onResume methods in MapMe.java to read

public void onPause() { super.onPause(); Log.i(TAG,"****** MapMe is pausing: Removing GPS update requests to save power"); myLocationOverlay.disableCompass(); myLocationOverlay.disableMyLocation(); locman.removeUpdates(this); } public void onResume(){ super.onResume(); Log.i(TAG,"****** MapMe is restarting: Resuming GPS update requests"); locman.requestLocationUpdates(provider,GPSupdateInterval,GPSmoveInterval,this); myLocationOverlay.enableCompass(); myLocationOverlay.enableMyLocation(); }

(The enable/disable statements in the onPause and onResume methods are necessary because the MyLocationOverlayrequests location updates and we want this to occur only when the activity is in the foreground, not when it is in thebackground.)

Now if you execute MappingDemo on an actual device with active location services, or an emulator with a simulatedposition, and select Track Current Location you should see a pulsing blue circle at your current position and a compassrose indicating an orientation of the device relative to the map. The following figure illustrates for an actual device(Motorola Backflip phone running Android 1.5),

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

25 of 36 4/8/2012 3:37 AM

Page 26: Mapping Demo

and the next figure illustrates the same location but with the phone rotated by about 90 degrees to the right relative to thefirst figure.

When tested on an emulator running Android 1.6 with Google maps, the pulsing blue dotdisplayed for only about 30 seconds after sending the emulator to a simulated position asdescribed above, and the compass rose did not display at all, so you may need an actualdevice to fully test this capability.

In addition to these custom overlays already included in the android API with mapping, there are two additional types ofmap overlays that are extremely useful. The first uses the class ItemizedOverlay to overlay a list of symbols with labels atspecified positions. This, for example, is useful to indicate visually the location of a list of things (for example, ice creamshops or pubs), and event handlers can be attached to the symbols so that actions such as popping up information windowscan be initiated when users touch the symbols. The second uses the class Overlay to create an overlay onto which customgraphics can be drawn by the user. This is useful, for example, if we wish to indicate a route between two points on themap under program control. Below we will illustrate how to implement this second type of overlay and in Map OverlayDemo we will show how to use an ItemizedOverlay.

Setting Up a Preferences Screen

Since specifying settings (preferences or options) for a program is a rather standard procedure for most modern programsrunning on desktop computers, it should not be surprising that a sophisticated operating system like Android provides abuilt-in mechanism for creating preference screens and processing the information associated with changes in settings madeby users. This is implemented through the Android classes PreferenceActivity and Preference (see also this preferencesexample and this tutorial).

Let's use these classes to give the user some options for setting preferences in MappingDemo. We already put in a Settings(Preference) option in the options menu above with a stub for the corresponding event handler, so we just need to create thescreen that this action will display and fill out the event handler. First let's define some new strings we will need. Editstrings.xml to add

<string name="compass_title">Compass</string> <string name="compass_summary">Whether to display orientation compass</string>

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

26 of 36 4/8/2012 3:37 AM

Page 27: Mapping Demo

inside the resources tag.

In Eclipse, if the folder MappingDemo/res/xml does not exist, create it by right-clicking on MappingDemo/res/, selectingNew > Folder, setting Folder Name to "xml", and clicking Finish. Now right click on MappingDemo/res/xml and selectNew > Android XML File. On the resulting screen give prefs.xml for the File Name, Select "Preference" for the type oflayout, and click Finish. Edit the file prefs.xml (note that this special layout file is in res/xml, unlike our normal layout filesthat are in res/layout) so that it reads

<?xml version="1.0" encoding="utf-8"?><PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key = "compass" android:title = "@string/compass_title" android:summary = "@string/compass_summary" android:defaultValue = "true" /></PreferenceScreen>

(You may have to click the prefs.xml tab at the bottom of the screen to display the text of the XML file.)

Now create a new Java class by right-clicking on MappingDemo/src/<packagename>, selecting New > Class, settingName to Prefs, Modifier to public, Superclass to android.preference.PreferenceActivity, and click Finish. Edit theresulting file Prefs.java to read

package com.lightcone.mappingdemo; import android.os.Bundle; import android.preference.PreferenceActivity; public class Prefs extends PreferenceActivity { protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.prefs); } }

Register the Prefs activity by adding to AndroidManifest.xml

<activity android:name=".Prefs" android:label="Prefs"></activity>

inside the applications tag, and edit the onOptionsItemSelected method of MappingDemo.java to add an intent launchingthe Prefs class when the Settings button is clicked:

public boolean onOptionsItemSelected(MenuItem item){ super.onOptionsItemSelected(item); switch(item.getItemId()){ case (Menu.FIRST): finishUp(); return true; case (Menu.FIRST+1): // Actions for help page Intent i = new Intent(this, Help.class); startActivity(i); return true; case(Menu.FIRST+2): // Actions for settings page Intent j = new Intent(this, Prefs.class); startActivity(j); return true; } return false; }

Now if you execute the program, click the MENU button, and select Settings you should get the screen displayed in thefollowing figure,

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

27 of 36 4/8/2012 3:37 AM

Page 28: Mapping Demo

with a checkbox that will toggle on and off when clicked. This checkbox is intended to give the user control over whetherthe orientation compass is displayed when the map in MapMe showing current location is displayed. Let's now add eventhandling that accomplishes this.

The Android class to handle events if the preferences are changed is OnSharedPreferenceChangeListener and we add tothe class definition in Prefs.java "implements OnSharedPreferenceChangeListener", which necessitates adding a couple ofnew imports and a new method onSharedPreferenceChanged. After the required changes Prefs.java has the content

package com.lightcone.mappingdemo;import android.content.Context;import android.content.SharedPreferences;import android.content.SharedPreferences.OnSharedPreferenceChangeListener;import android.os.Bundle;import android.preference.PreferenceActivity;import android.preference.PreferenceManager;import android.util.Log;

public class Prefs extends PreferenceActivity implements OnSharedPreferenceChangeListener {

protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.prefs); // Register a change listener Context context = getApplicationContext(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.registerOnSharedPreferenceChangeListener(this); }

// Inherited abstract method so it must be implemented @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Log.i("Preferences", "Preferences changed, key="+key); } // Static method to return the preference for whether to display compass public static boolean getCompass(Context context){ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("compass", true); }}

The String variable key indicates which element on the preferences page has changed. Of course at the moment the id labelandroid:key = "compass" in prefs.xml is the only element in prefs.xml, but shortly we will add other elements to prefs.xmland the value of key will then allow us to determine which preference has been changed.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

28 of 36 4/8/2012 3:37 AM

Page 29: Mapping Demo

Finally, we modify the onPause and onResume methods in MapMe.java to incorporate a logical decision based on thevalue returned by the Prefs.getCompass (context) method concerning whether to display the compass:

public void onPause() { super.onPause(); Log.i(TAG,"****** MapMe pausing: Removing GPS update requests to save power"); if(Prefs.getCompass(getApplicationContext())) myLocationOverlay.disableCompass(); myLocationOverlay.disableMyLocation(); locman.removeUpdates(this); } public void onResume(){ super.onResume(); Log.i(TAG,"****** MapMe restarting: Resuming GPS update requests"); locman.requestLocationUpdates(provider,GPSupdateInterval,GPSmoveInterval,this); if(Prefs.getCompass(getApplicationContext())) myLocationOverlay.enableCompass(); myLocationOverlay.enableMyLocation(); }

Now if you execute this on a real device you should be able to toggle the compass rose on and off in MapMe by changedthe preferences setting.

Adding Options for GPS Precision and Power Consumption

Let's now add to the preferences user options for trading off position precision against power consumption. First editstrings.xml and add the lines

<string name="gpsPrefs_title">GPS Preferences</string> <string name="gpsPrefs_summary">Location precision (higher precision gives shorter battery life)</string>

Next, we create an XML file to store array data. From Eclipse, right-click on MappingDemo/res/values and select New >Android XML File. Give the file the name arrays.xml, check that the type is Values and that the Folder is res/values, andclick Finish. Edit the resulting file arrays.xml to give

<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="updateOptions"> <item>Highest Precision</item> <item>Medium Precision</item> <item>Lowest Precision</item> </string-array> <string-array name="updateIndex"> <item>1</item> <item>2</item> <item>3</item> </string-array> </resources>

(Note that it is critical that the second array above be a string-array and not just an array---see post1 and post2.Otherwise, at least for Linux, you will get strange error messages.)

Now edit prefs.xml to add a new ListPreference option:

<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key = "compass" android:title = "@string/compass_title" android:summary = "@string/compass_summary" android:defaultValue = "true" /> <ListPreference android:title="@string/gpsPrefs_title" android:summary="@string/gpsPrefs_summary"

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

29 of 36 4/8/2012 3:37 AM

Page 30: Mapping Demo

android:key="gpsPref" android:defaultValue="1" android:entries="@array/updateOptions" android:entryValues="@array/updateIndex" /> </PreferenceScreen>

Because these are shared persistent preferences, if you make a programming error in settingup preferences you may end up storing erroneous preferences data and may have to wipeuser data on either a device or an emulator to get it to not repeat the error after you havecorrected it. If you encounter this problem for a device, you can uninstall the program with

adb -d uninstall com.lightcone.mappingdemo

(assuming it is the only device attached; otherwise you will have to use -s to target its serialnumber), and then execute the corrected program. For an emulator you can issue a similarcommand

adb -e uninstall com.lightcone.mappingdemo

(assuming it is the only emulator running), or you can kill the emulator, restart it usingWindow > Android SDK and AVD Manager, select the emulator and click Start, checkthe Wipe User Data checkbox, and then click Launch.

Now if you execute the program and click the MENU button and choose Settings, you should get the screen in the followingfigure,

and if you click GPS Preferences you should get a screen like this figure.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

30 of 36 4/8/2012 3:37 AM

Page 31: Mapping Demo

If you click a different option on that screen than the current one and then click GPS Preferences again you should see thatthe option has changed. Thus, Android is storing and managing shared preferences that can control the requested precisionof the location services. All that remains is to implement some code to act on those preferences. Open Prefs.java and addthe static method getGPSPref:

// Static method to return the preference for the GPS precision setting public static String getGPSPref(Context context){ return PreferenceManager.getDefaultSharedPreferences(context).getString("gpsPref", "1"); }

then open MapMe.java and add the method

// Method to assign GPS prefs public void updateGPSprefs(){ int gpsPref = Integer.parseInt(Prefs.getGPSPref(getApplicationContext())); switch(gpsPref){ case 1: GPSupdateInterval = 5000; // milliseconds GPSmoveInterval = 1; // meters break; case 2: GPSupdateInterval = 10000; GPSmoveInterval = 100; break; case 3: GPSupdateInterval = 125000; GPSmoveInterval = 1000; break; } }

modify the onResume() method of MapMe to call this new method:

public void onResume(){ super.onResume(); // Check for GPS prefs and set parameters accordingly updateGPSprefs(); locman.requestLocationUpdates(provider,GPSupdateInterval,GPSmoveInterval,this); if(Prefs.getCompass(getApplicationContext())) myLocationOverlay.enableCompass(); myLocationOverlay.enableMyLocation(); Log.i(TAG,"****** MapMe restarting: Resuming GPS update requests."+ " GPSUpdateInterval="+GPSupdateInterval+"ms GPSmoveInterval="+GPSmoveInterval+" m"); }

and remove the assignments of GPSUpdateInterval and GPSmoveInterval in the onCreate method of MapMe, replacingthem by a call to updateGPSprefs():

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); // Suppress title bar to give more space setContentView(R.layout.mapme); updateGPSprefs(); // Set up location manager for determining present location of phone locman = (LocationManager)getSystemService(Context.LOCATION_SERVICE); . . .

Now if you run the program you should be able to reset the GPS preferences to any of the three possibilities and if you thenclick the Track Present Location button the logcat output from the Log.i statement in updateGPSprefs should confirm thatthe GPS precision parameters have been reset. Since in Android shared preferences are persistent once stored, thesepreferences should carry over to new sessions with the application until they are changed.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

31 of 36 4/8/2012 3:37 AM

Page 32: Mapping Demo

The preceding example of setting user preferences to trade off GPS performance versuspower consumption is a nice example of how to implement a potentially useful option.However, on actual devices these particular GPS setting requests might be overridden bysettings baked in by the manufacturer or carrier. For example, when tested on an AT&TMotorola Backflip and an AT&T Samsung Captivate (Galaxy S) I could find no significantdifference in GPS behavior for these different settings.

While we are at it, let's illustrate another type of shared preference input option, the <EditTextPreference></EditTextPreference> tag, which allows editable text input for a preference parameter. Open prefs.xml and add anEditTextPreference tag as follows

<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key = "compass" android:title = "@string/compass_title" android:summary = "@string/compass_summary" android:defaultValue = "true" /> <ListPreference android:title="@string/gpsPrefs_title" android:summary="@string/gpsPrefs_summary" android:key="gpsPref" android:defaultValue="1" android:entries="@array/updateOptions" android:entryValues="@array/updateIndex" /> <EditTextPreference android:name="EditText Preference" android:summary="This allows you to edit the name" android:defaultValue="" android:title="Edit the Name" android:key="editTextPref" /> </PreferenceScreen>

Then if you open the Options menu by clicking the MENU button and then Settings, you should see a display as in thefollowing figure.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

32 of 36 4/8/2012 3:37 AM

Page 33: Mapping Demo

If you select the "Edit the Name" option, you should get a screen similar to the following figure,

with an editable text field that you can change and save. To access and use this preference, add the following static methodto Prefs.java

// Static method to return the preference for the name (only used for demonstration) public static String getTitle(Context context){ return PreferenceManager.getDefaultSharedPreferences(context).getString("editTextPref", ""); }

We won't do anything with this except to illustrate that the edited preference has indeed changed by modifying theonSharedPreferenceChanged method of Prefs.java to

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

33 of 36 4/8/2012 3:37 AM

Page 34: Mapping Demo

@Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Log.i("Preferences", "Preferences changed, key="+key); if(key.compareTo("editTextPref")==0) Log.i("Preferences", " Changed name to " + Prefs.getTitle(getApplicationContext())); }

Now if you change the name in the EditTextPreference field on the Options menu, the logcat output will confirm thechange.

Adding a Display Overlay

We can subclass com.google.android.maps.Overlay to allow us to put text and graphics into an overlay layer on a map.Let's illustrate by adding to the upper part of the MapMe map display a readout of the current latitude and longitude for thedevice, the number of satellites being tracked for the GPS fix, the corresponding accuracy in meters, the altitude, the speed,and the bearing. We shall also implement the capability to toggle this display on and off by tapping on the present-locationsymbol (pulsing blue dot) on the map display.

Right-click on MappingDemo/<packagename> and select New > Class. On the resulting screen set Name toDisplayOverlay and Superclass to com.google.android.maps.Overlay, and click Finish. Edit the resulting fileDisplayOverlay.java to give

package com.lightcone.mappingdemo; import android.graphics.Canvas; import android.graphics.Paint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; public class DisplayOverlay extends Overlay { private Paint paint; private double lat; private double lon; private double satAccuracy; private int numberSats; private float bearing; private double altitude; private float speed; private String currentProvider; public static boolean showData = true; @Override public void draw(Canvas canvas, MapView mapview, boolean shadow) { super.draw(canvas, mapview, shadow); if(showData){ paint = new Paint(); paint.setAntiAlias(true); paint.setARGB(80,255,255,255); canvas.drawRect(0,0,350,33,paint); paint.setTextSize(11); paint.setARGB(180, 0, 0, 0); canvas.drawText("Lat = "+lat+" Long = "+lon+" Alt = "+(int)altitude+" m", 8, 14, paint); canvas.drawText("Sat = "+numberSats+" Accur = "+(int)satAccuracy+" m" +" speed = "+(int)speed+" m/s bearing = "+(int)bearing+" deg", 8, 27, paint); } } // Method to insert updated satellite data public void putSatStuff(double lat, double lon, double satAccuracy, float bearing, double altitude, float speed, String currentProvider, int numberSats){ this.lat = lat; this.lon = lon; this.satAccuracy = satAccuracy; this.bearing = bearing; this.altitude = altitude; this.speed = speed; this.currentProvider = currentProvider; this.numberSats = numberSats; } }

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

34 of 36 4/8/2012 3:37 AM

Page 35: Mapping Demo

In this code we have overriden the draw method of Overlay to draw on a Canvas using style and color attributes specifiedby the Paint class, and added a method putSatStuff to insert into DisplayOverlay the satellite data that was retrieved inMapMe.

Now we modify MapMe.java. First we add class variables displayOverlay and mapOverlays,

public DisplayOverlay displayOverlay; private List<Overlay> mapOverlays;

and then add at the end of the onCreate method

// Set up overlay for data display displayOverlay = new DisplayOverlay(); mapOverlays = mapView.getOverlays(); mapOverlays.add(displayOverlay);

and then add at the end of the MapMe method centerOnLocation

if(displayOverlay != null){ displayOverlay.putSatStuff(lat, lon, satAccuracy, bearing, altitude, speed, currentProvider, numberSats); }

Now if you run the application you should have displayed at the top left the latitude/longitude and satellite information. Thefollowing image

illustrates for a Motorola Backflip phone running Android 1.5 (in this case we have used the preferences defined earlier tosuppress the compass rose). For this example the phone was stationary and indoors, so the speed is zero, the bearing is notmeaningful, and the GPS fix is very poor, as indicated by the large value displayed for the accuracy and the large errorcircle around the pulsing blue point. When this device is outdoors and has a clear view of the sky it is typically tracking 7to 12 satellites with 5-10 meter accuracy, so then the error circle is comparable to the size of the position dot.

Tap Events on the Present-Location Dot

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

35 of 36 4/8/2012 3:37 AM

Page 36: Mapping Demo

Previous | Next | Home

Let's finish this project by adding the capability to toggle the display of satellite data on and off by tapping on the currentlocation dot. Edit MyMyLocation.java and fill out the dispatchTap() method to read

// Override the dispatchTap() method to toggle the data display on and off when // the present location dot is tapped. Also display a short Toast (transient message) to the // user indicating the display status change. @Override protected boolean dispatchTap(){

if(DisplayOverlay.showData){ Toast.makeText(context, "Suppressing data readout", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context,"Display data readout", Toast.LENGTH_SHORT).show(); } // Toggle the GPS data display DisplayOverlay.showData = ! DisplayOverlay.showData;

return true; }

Now you should be able to toggle the satellite data display overlay on and off by tapping on the present-location dot, and ashort transient message (a Toast) should appear on the screen indicating that the display status is changing when you do so.

The complete project for the application described above is archived at the link MappingDemo.

Exercises

1. Our current data readouts are in metric units. Add a preferences checkbox in the settings menu that allows the user totoggle between metric and English (feet and miles) units, and make the corresponding changes in the data readouts.

2. The Geocoder example was implemented on the main UI thread. Write a new version that subclasses AsyncTask to runthis task asynchronously (AsyncTask is an abstract class that must be subclassed to be used). An asynchronous task isdefined to be one that runs on a background thread but publishes its results on the main UI thread. The class AsyncTaskpermits this to be done in rather blackbox fashion, since it performs background operations and publishes results on the UIthread without the user having to manipulate threads and/or handlers directly (that is being done for you under the hood). Ofcourse, you can also do this using using standard Java threading classes if you choose. Hint: See Map Overlay Demo for anexample of using AsyncTask.

3. In the Geocoder example we caught only one exception (IOException) and did not do anything substantial with it.Modify the code to use exception handling to make the user experience more seamless and foolproof.

4. In the Geocoder example we stored multiple matches in the array optionArray, but displayed a map of only the lastmatch in the list by default. Modify the code so that when there is more than one potential match the list of possible matchesis presented to the user and when one of them is selected ShowTheMap displays that location.

Mapping Demo http://eagle.phys.utk.edu/guidry/android/mappingDemo.html

36 of 36 4/8/2012 3:37 AM