## Windows Phone Using Location Services (GPS)

GPS 101

Let’s spend a few minutes to just learn the basics of GPS, in order to better use it in our code. What GPS boils down to—the nitty-gritty—are two floating-point numbers representing the X and Y position on the Earth. The X value has traditionally been called longitude, and the Y value has been known as the latitude. From a game programming perspective, this is an easier way to grasp the terms, but a naval veteran would scoff at the overly simplistic way this is being presented. We’ll gloss over issues of precision in order to grasp the concepts first.

Longitude represents the “X” or horizontal coordinate on the surface of the Earth, running east or west from the zero point.

Latitude represents the “Y” or vertical coordinate on the surface of the Earth, running north or south from the zero point.

The origin (0,0) is located about 400 miles off the western coast of Africa, southwest of Nigeria and south of Ghana. From that origin point, longitude increases to the right (east), and decreases to the left (west); latitude increases up (north), and decreases down (south). In other words, it is oriented exactly like the Cartesian coordinate system we’ve been using all along for our trig-heavy examples. This makes translating GPS coordinates for the purpose of making a reality game a cinch!

To help make sense of the coordinate system, Table 17.1 shows the approximate latitude and longitude values of several major cities in the world, formatted in a way that makes sense to game programmers (such that longitude comes before latitude— remember, we aren’t navigating here). Note that these are far from precise, just rough estimates to present the general location of each city. More precise GPS coordinates will include up to six decimal places of increasing precision, down to just 10 feet or less in granularity.

If you want to learn more about latitude and longitude coordinates, there is an interactive world map available online at http://itouchmap.com/latlong.html.

Windows Phone Location Services

XNA provides us with a geographic location service in a library located in a namespace called System.Device.Location. This library is not included in the project’s references by default, so we must add it to use this library in our program.

1. Right-click References in the Solution Explorer, and then choose Add Reference.
2. In the dialog box that comes up, there is a list with the .NET tab already in view, as shown in Figure 17.1. Select System.Device from the list and click the OK button.
3. The geographic location services library is in a namespace called System.Device.Location, which must be added with a using statement to any program that needs these services:
[code]
using System.Device.Location;
[/code]

Using the Location Services

To read the current device’s GPS location, we create an object using the GeoCoordinateWatcher class:

[code]
GeoCoordinateWatcherSim watcher;
[/code]

It is okay to create the watcher object in Initialize() or LoadContent(), or in response to a user event:

[code]
watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
[/code]

At this point, the object is created but is not yet receiving any GPS data. We have to create an event handler to handle position status change events. The trigger that causes such an event is movement of the device, which can be fine-tuned with the MovementThreshold property:

[code]
watcher.MovementThreshold = 20;
[/code]

The first event we’ll tap into is StatusChanged. A new event method will need to be created to correspond with the name of the method passed to this new event object. In this case, the example is using a string called statusText, which can be printed out from the main Draw() call. Optionally, a programmer-defined status could be set here and used elsewhere in the game:

[code]
watcher.StatusChanged += new EventHandler
<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
void watcher_StatusChanged(object sender,
GeoPositionStatusChangedEventArgs e)
{
switch (e.Status)
{
case GeoPositionStatus.Disabled:
statusText += “Location service has been disabledn”;
break;
case GeoPositionStatus.Initializing:
statusText += “Location service is initializingn”;
break;
case GeoPositionStatus.NoData:
statusText += “Location service is not returning any datan”;
break;
statusText += “Location service is receiving datan”;
break;
}
}
[/code]

The actual movement of the GPS device triggers position change events that we can tap into with the PositionChanged event. A similar event method will have to be created for this event as well. In this example, a GeoCoordinate variable called coord is set using the passed parameter that contains the GPS location data:

[code]
watcher.PositionChanged += new EventHandler
<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);
void watcher_PositionChanged(object sender,
GeoPositionChangedEventArgs<GeoCoordinate> e)
{
coord = e.Position.Location;
}
[/code]

Simulating Position Changes

The WP7 emulator does not have a GPS receiver, and even if your PC has one, the emulator doesn’t know how to use it—the emulator is a self-contained system that only uses the networking of your PC to simulate connectivity. I say “simulate” because in a real WP7 device, that Internet connection would come through the airwaves, presumably G3 or G4, depending on what the service provider supports.

There is a workaround for the limitation. If you want to create a game that uses location services, it’s a given you must be able to test it extensively, and even with a real WP7 device, testing GPS code can be a challenge. So, even with hardware, it may be preferred to develop this code with a GPS simulation rather than the real thing. With a simulation, you can define the location data yourself and write the gameplay code to respond to location data in a predictable way. Only the final testing stages of the game would need to be done “in the field.”

So, a question arises: How do we simulate GPS data?

The solution is to write a class that inherits from GeoLocationWatcher and then fill in data events with a timer that generates real-time updates via GeoLocation events. Voilà!

GeoLocationSim

There are three classes involved in the geographic location simulator. The first is GeoLocationSim, which inherits directly from GeoCoordinateWatcher, the main GPS class in XNA. There are quite a few properties, events, and methods defined in this abstract class that are required to pass this off as a legitimate GeoLocation class so that it works with normal GeoLocation code, but we don’t need all of that for testing purposes. Nevertheless, they are all required. In the sample project for this hour, I have added all three classes in a source file called GeoLocationSim.cs. First, take a look at Listing 17.1, the code for the sim class.

LISTING 17.1 Base GeoLocation Simulation Class

[code]
abstract public class GeoLocationSim : GeoCoordinateWatcher
{
private GeoPosition<GeoCoordinate> current;
private Timer timer;
public GeoLocationSim()
{
current = new GeoPosition<GeoCoordinate>();
Status = GeoPositionStatus.Initializing;
RaiseStatusChanged();
}
private void RaiseStatusChanged()
{
GeoPositionStatusChangedEventArgs args =
new GeoPositionStatusChangedEventArgs(Status);
if (StatusChanged != null)
{
StatusChanged(this, args);
}
}
private void RaisePositionChanged()
{
GeoPositionChangedEventArgs<GeoCoordinate> args =
new GeoPositionChangedEventArgs<GeoCoordinate>(current);
if (PositionChanged != null)
PositionChanged(this, args);
}
public void OnTimerCallback(object state)
{
try
{
if (Status == GeoPositionStatus.Initializing)
{
Status = GeoPositionStatus.NoData;
RaiseStatusChanged();
}
StartGetCurrentPosition();
TimeSpan next = GetNextInterval();
timer.Change(next, next);
}
catch (Exception)
{
throw;
}
}
protected void UpdateLocation(double longitude, double latitude)
{
GeoCoordinate location = new GeoCoordinate(latitude, longitude);
if (!location.Equals(current.Location))
{
current = new GeoPosition<GeoCoordinate>(
DateTimeOffset.Now, location);
{
RaiseStatusChanged();
}
RaisePositionChanged();
}
}
abstract protected TimeSpan GetNextInterval();
abstract protected void StartGetCurrentPosition();
//override base property
public GeoPositionPermission Permission
{
get { return GeoPositionPermission.Granted; }
}
//override base property
public GeoPosition<GeoCoordinate> Position
{
get { return current; }
}
//override base event
public event EventHandler<GeoPositionChangedEventArgs
<GeoCoordinate>> PositionChanged;
//override base method
public void Start(bool suppressPermissionPrompt)
{
Start();
}
//override base method
public void Start()
{
TimeSpan span = GetNextInterval();
timer = new Timer(OnTimerCallback, null, span, span);
}
//override base property
public GeoPositionStatus Status
{
get;
protected set;
}
//override base event
public event EventHandler
<GeoPositionStatusChangedEventArgs> StatusChanged;
//override base method
public void Stop()
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
Status = GeoPositionStatus.Disabled;
RaiseStatusChanged();
}
//override base method
public bool TryStart(bool suppressPermissionPrompt, TimeSpan timeout)
{
Start();
return true;
}
}
[/code]

Filling in GPS Data with Timing

SampleGeoCoord is a helper class that is used to fill in GPS position data with timing. Each position coordinate corresponds to a one-second interval at which the position update event is triggered. So, this class supplies longitude, latitude, and time.

[code]
public class SampleGeoCoord
{
public double Longitude { get; set; }
public double Latitude { get; set; }
public TimeSpan Time { get; set; }
public SampleGeoCoord(double Longitude, double Latitude, int seconds)
{
this.Longitude = Longitude;
this.Latitude = Latitude;
this.Time = new TimeSpan(0, 0, seconds);
}
}
[/code]

GeoCoordinateWatcherSim

The GeoCoordinateWatcherSim is our main workhorse simulation class, inheriting directly from GeoLocationSim. This class puts the GeoLocationSim properties, methods, and events to work using data populated within an array of SampleGeoCoord objects. In the example coming up that uses this class, I’ve centered the coordinates around Los Angeles, with 60 seconds of random locations within a radius of about 100 miles around the city coordinates (-118, 34). Listing 17.2 contains the code for the GeoCoordinateWatcherSim class.

LISTING 17.2 Usable GeoCoordinateWatcherSim Worker Class

[code]
public class GeoCoordinateWatcherSim : GeoLocationSim
{
List<SampleGeoCoord> events;
int currentEventId;
Random rand = new Random();
public GeoCoordinateWatcherSim(GeoPositionAccuracy accuracy)
{
currentEventId = 0;
events = new List<SampleGeoCoord>();
//create random coordinates in Los Angeles
for (int n = 1; n < 60; n++)
{
double Long = -118 – rand.Next(2) – rand.NextDouble();
double Lat = 33 + rand.Next(2) + rand.NextDouble();
}
}
private SampleGeoCoord Current
{
get
{
return events[currentEventId % events.Count];
}
}
protected override void StartGetCurrentPosition()
{
this.UpdateLocation(Current.Longitude, Current.Latitude);
currentEventId++;
}
protected override TimeSpan GetNextInterval()
{
return Current.Time;
}
}
[/code]

Creating the Geo Position Demo

Let’s write a program to demonstrate the GeoCoordinateWatcherSim class in action. The example requires only a font, because it just prints out the longitude and latitude of the geographical coordinate data and the status of the watcher. The code for the Geo Position Demo program is found in Listing 17.3, and Figure 17.2 shows the program running. Note that this example will work on a WP7 device without the simulated data with a single line change, from

[code]
watcher = new GeoCoordinateWatcherSim(…);
[/code]

to

[code]
watcher = new GeoCoordinateWatcher(…);
[/code]

LISTING 17.3 The Geo Position Demo Program

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
TouchLocation oldTouch;
Random rand;
SpriteFont font;
string statusText = ““;
GeoCoordinateWatcherSim watcher = null;
GeoCoordinate coord = null;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
oldTouch = new TouchLocation();
}
protected override void Initialize()
{
base.Initialize();
StartGeoLocation();
}
{
rand = new Random();
spriteBatch = new SpriteBatch(GraphicsDevice);
}
{
watcher.Stop();
}
protected override void Update(GameTime gameTime)
{
ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin(SpriteSortMode.FrontToBack,
BlendState.AlphaBlend);
spriteBatch.DrawString(font, “Latitude: “ +
coord.Latitude.ToString(“0.000”),
new Vector2(100, 10), Color.White);
spriteBatch.DrawString(font, “Longitude: “ +
coord.Longitude.ToString(“0.000”),
new Vector2(100, 30), Color.White);
spriteBatch.DrawString(font, statusText,
new Vector2(100, 100), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
void StartGeoLocation()
{
coord = new GeoCoordinate();
//try to create geo coordinate watcher
if (watcher == null)
{
statusText += “Starting location service…n”;
watcher = new GeoCoordinateWatcherSim(
GeoPositionAccuracy.Default);
watcher.MovementThreshold = 20;
watcher.StatusChanged += new EventHandler
<GeoPositionStatusChangedEventArgs>(
watcher_StatusChanged);
watcher.PositionChanged += new EventHandler
<GeoPositionChangedEventArgs<GeoCoordinate>>
(watcher_PositionChanged);
watcher.Start();
}
}
void watcher_StatusChanged(object sender,
GeoPositionStatusChangedEventArgs e)
{
switch (e.Status)
{
case GeoPositionStatus.Disabled:
statusText += “Location service has been disabledn”;
break;
case GeoPositionStatus.Initializing:
statusText += “Location service is initializingn”;
break;
case GeoPositionStatus.NoData:
statusText += “Location service is not returning any datan”;
break;
statusText += “Location service is receiving datan”;
break;
}
}
void watcher_PositionChanged(object sender,
GeoPositionChangedEventArgs<GeoCoordinate> e)
{
coord = e.Position.Location;
}
}
[/code]

There are many uses for GPS tracking, not to mention potential multiplayer games, but one thing to keep in mind is that GPS only provides location data, but there’s no transmitting of that data. After the location is received, that’s it—it’s data, and it’s not transmitted anywhere. GPS is read-only. So, if you have in mind a game, there must still be a network infrastructure connecting all the players, wherein each player will transmit his or her GPS location to the other players over the network. The WP7 platform supports Xbox Live for networking, so that is likely the next subject to study if you’re interested in making a networked game.

## Case Study

The Album Application

In the mobile version of the AIR application, the user takes a picture or pulls one from the camera roll. She can save it to a dedicated database, along with an audio caption and geolocation information. The group of saved images is viewable in a scrollable menu. Images can be sent from the device to the desktop via a wireless network.

In the desktop version, the user can see an image at full resolution on a large screen. It can be saved to the desktop, and it can also be edited and uploaded to a photo service.

Design

The design is simple, using primary colors and crisp type. The project was not developed for flexible layout. It was created at 800×480 resolution with auto-orientation turned off; you can use it as a base from which to experiment developing for other resolutions. The art is provided in a Flash movie to use in Flash Professional or as an .swc file to import into Flash Builder by selecting Properties→ActionScript Build Path→Library Path and clicking on “Add swc.”

Architecture

The source code consists of the Main document class and the model, view, and events packages (see Figure 17-1).

The model package contains the AudioManager, the SQLManager, the GeoService, and the PeerService. The view package contains the NavigationManager and the various views. The events package contains the various custom events.

The SQLManager class is static, so it can be accessed from anywhere in the application without instantiation. The other model classes are passed by reference.

Flow

The flow of the application is straightforward. The user goes through a series of simple tasks, one step at a time. In the opening screen, the OpeningView, the user can select a new picture or go to the group menu of images, as shown in Figure 17-2.

From the AddView page, the user can open the Media Gallery or launch the camera, as shown in Figure 17-3. Both choices go to the same CameraView view. An id parameter is passed to the new view to determine the mode.

The image data received from either of the sources is resized to fit the dimensions of the stage. The user can take another picture or accept the current photograph if satisfied. The image url is sent to the SQLManager to store in its class variable current Photo of type Object, and the application goes to the CaptionView.

In the CaptionView, the user can skip the caption-recording step or launch the Audio Manager. The recording is limited to four seconds and automatically plays the caption sound back. The user can record again or choose to keep the audio. The AudioMan ager compresses the recording as a WAV file and saves it on the SD card. Its url is saved in the SQLManager’s currentPhoto object. The next step is to add geographic information.

In the GeoView, the user can skip or launch the GeoService. This service creates an instance of the GeoLocation, fetches coordinates, and then requests the corresponding city and country from the Yahoo! API. As in the previous steps, the geodata is saved in the SQLManager’s currentPhoto object. These three steps are shown in Figure 17-4.

In the SavingView mode, data saving can be skipped or the data can be saved. For the latter, the SQLManager opens an SQL connection and saves the data, then closes the connection. The application goes back to the OpeningView.

Back at our starting point, another navigation choice is the Group menu. The Menu View page requests the number of images saved for the SQLManager and displays it as a list of items. If the list height is taller than the screen, it becomes scrollable. Selecting one of the items takes the user to the PhotoView screen. The SavingView page and MenuView page are shown in Figure 17-5.

The PhotoView displays the image selected in the MenuView. Choosing to connect calls the PeerService to set up a P2P connection using the WiFi network. Once it is established, the data is requested from the SQLManager using the item ID. The data is then sent. It includes the byteArray from the image, a WAV file for the audio, and the city and country as text. These steps are displayed in Figure 17-6.

Permissions

This application needs the following permissions to access the Internet, write to the SD card, and access GPS sensors, the camera, and the microphone:

[code]

<android>
<![CDATA[
<manifest>
<uses-permission
android:name=”android.permission.INTERNET”/>
<uses-permission
android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-permission
android:name=”android.permission.ACCESS_FINE_LOCATION”/>
<uses-permission
android:name=”android.permission.ACCESS_COARSE_LOCATION”/>
<uses-permission
android:name=”android.permission.CAMERA”/>
<uses-permission
android:name=”android.permission.RECORD_AUDIO”/>
</manifest>
]]>
</android>

[/code]

The ViewManager class discussed here is almost identical. The flow is a step-by-step process whereby the user can choose to skip the steps that are optional.

Images

The CameraView is used to get an image, either by using the media library or by taking one using the camera. The choice is based on a parameter passed from the previous screen. The process of receiving the bytes, scaling, and displaying the image is the same regardless of the image source. It is done by a utility class called BitmapDataSizing and is based on the dimensions of the screen.

To improve this application, check if an image is already saved when the user selects it again to avoid duplicates.

Audio

The audio caption is a novel way to save a comment along with the image. There is no image service that provides the ability to package an audio commentary, but you could build such an application.

WAV files using the Adobe class WAVReader and then extracted using a third-party library. Here, we create an Album directory on the SD card and a mySounds directory inside it to store the WAV files.

Reverse Geolocation

the process of using geographical coordinates to get an address location such as a city and a street address.

In this application, we are only interested in the city name and country. Therefore, coarse data is sufficient. We do not need to wait for the GPS data to stabilize. As soon as we get a response for the Yahoo! service, we move on to the next step.

SQLite

SQLManager is a static class, so it can be accessed from anywhere in the application. The Main class holds an object which stores information related to a photo until it is complete and ready to be saved:

[code]

var currentPhoto:Object = {photo:””, audio:””, geo:””};

[/code]

The photo property stores the path to where the image is saved in the Gallery. The audio property stores the path to where the WAV file is located and the geo property stores a string with city and country information.

From the SavingView view, the object is saved in the myAlbum.db file on the SD card.

P2P Connection

The peer-to-peer connection is used to send the image, audio caption, and location over a LAN. This example is to demonstrate the potential of what you can do more than a proper use case because the transfer is slow, unless the information is sent in packets and reassembled. This technology is feasible for fairly small amounts of data and has a lot of potential for gaming and social applications.

Once the user has selected an image, she can transfer it to a companion desktop application from the SavingView view. The PeerService class handles the communication to the LAN and the posting of data.

The MenuView that displays the images saved in the database has scrolling capability if the content is larger than the height of the screen.

There are two challenges to address. The first is the performance of a potentially large list to scroll. The second is the overlapping interactive objects. The scrollable list contains elements that also respond to a mouse event. Both functionalities need to work without conflicting.

We only need to scroll the view if the container is larger than the device height, so there is no need to add unnecessary code. Let’s check its dimensions in the onShow method:

[code]

function onShow():void {
deviceHeight = stage.stageHeight;
container = new Sprite();
// populate container
if (container.height > deviceHeight) {
trace(“we need to add scrolling functionality”);
}
}

[/code]

If the container sprite is taller than the screen, let’s add the functionality to scroll. Touch events do not perform as well as mouse events. Because we only need one touch point, we will use a mouse event to detect the user interaction. Note that we set cacheAsBit map to true on the container to improve rendering:

[code]

function onShow():void {
if (container.height > deviceHeight) {
container.cacheAsBitmap = true;
touchBegin, false, 0, true);
touchEnd, false, 0, true);
}
}

[/code]

To determine if the mode is to scroll or to click an element that is part of the container, we start a timeout. We will see later why we need this timer in relation to the elements:

[code]

import flash.utils.setTimeout;
var oldY:Number = 0.0;
var newY:Number = 0.0;
var timeout:int;
function touchBegin(event:MouseEvent):void {
oldY = event.stageY;
newY = event.stageY;
timeout = setTimeOut(startMove, 400);
}

[/code]

When the time expires, we set the mode to scrollable by calling the startMove method. We want to capture the position change on MOUSE_MOVE but only need to render the change to the screen on ENTER_FRAME. This guarantees a smoother and more consistent motion. UpdateAfterEvent should never be used in mobile development because it is too demanding for devices:

[code]

function startMove(event:MouseEvent):void {
touchMove, false, 0, true);
}

[/code]

When the finger moves, we update the value of the newY coordinate:

[code]

function touchMove(event:MouseEvent):void {
newY = event.stageY;
}

[/code]

On the enterFrame event, we render the screen using the new position. The container is moved according to the new position. To improve performance, we show and hide the elements that are not in view using predefined bounds:

[code]

var totalChildren:int = container.numChildren;
var topBounds:int = -30;
function frameEvent(event:Event):void {
if (newY != oldY) {
var newPos = newY – oldY;
oldY = newY;
container.y += newPos;
for (var i:int = 0; i < totalChildren; i++) {
var mc:MovieClip = container.getChildAt(i) as MovieClip;
var pos:Number = container.y + mc.y;
mc.visible = (pos > topBounds && pos < deviceHeight);
}

}
}

[/code]

On touchEnd, the listeners are removed:

[code]

function touchEnd(event:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, touchMove);
stage.removeEventListener(Event.ENTER_FRAME, frameEvent);
}

[/code]

As mentioned before, elements in the container have their own mouse event listeners:

[code]

[/code]

On mouse down, the boolean variable isMoving is set to false and the visual cue indicates that the element was selected:

[code]

var isMoving:Boolean = false;
var selected:MovieClip;
function timeMe():void {
isMoving = false;
selected = event.currentTarget as MovieClip;
selected.what.textColor = 0x336699;
}

[/code]

On mouse up and within the time allowed, the stage listeners and the timeout are removed. If the boolean isMoving is still set to false and the target is the selected item, the application navigates to the next view:

[code]

function clickAway(event:MouseEvent):void {
touchEnd(event);
clearTimeOut(timeout);
if (selected == event.currentTarget && isMoving == false) {
dispatchEvent(new ClickEvent(ClickEvent.NAV_EVENT,
{view:”speaker”, id:selected.id}));
}

[/code]

Now let’s add to the frameEvent code to handle deactivating the element when scrolling. Check that an element was pressed, and check that the selected variable holds a value and that the motion is more than two pixels. This is to account for screens that are very responsive. If both conditions are met, change the boolean value, reset the look of the element, and set the selected variable to null:

[code]

function frameEvent(event:Event):void {
if (newY != oldY) {
var newPos = newY – oldY;
oldY = newY;
container.y += newPos;
for (var i:int = 0; i < totalChildren; i++) {
var mc:MovieClip = container.getChildAt(i) as MovieClip;
var pos:Number = container.y + mc.y;
mc.visible = (pos > topBounds && pos < deviceHeight);
}
if (selected != null && Math.abs(newPos) > 2) {
isMoving = true;
selected.what.textColor = 0x000000;
selected = null;
}
}
}

[/code]

There are various approaches to handle scrolling. For a large number of elements, the optimal way is to only create as many element containers as are visible on the screen and populate their content on the fly. Instead of moving a large list, move the containers as in a carousel animation and update their content by pulling the data from a Vector or other form of data content.

If you are using Flash Builder and components, look at the Adobe lighthouse package (http://www.adobe.com/devnet/devices/fpmobile.html). It contains DraggableVertical Container for display objects and DraggableVerticalList for items.

Desktop Functionality

The AIR desktop application, as shown in Figure 17-7, is set to receive the data and display it. Seeing a high resolution on a large screen demonstrates how good the camera quality of some devices can be. The image can be saved on the desktop as a JPEG.

Another technology, not demonstrated here, is Pixel Bender, used for image manipulation. It is not available for AIR for Android but is for AIR on the desktop. So this would be another good use case where devices and the desktop can complete one another.

## The Speed Property

We are all familiar with a car speedometer, which tells you how fast you are driving. Although not nearly as precise, geolocationEvent has a speed property which you can use in some applications. You can imagine an application for parasailing, parachuting, or skiing, or if you prefer to remain on the ground, a measuring tool for a remotecontrolled toy.

The speed is returned in meters/second. It is a good unit for small-distance measurement. To convert to kilometers/hour, use:

event.speed * 3.6;

If you are not familiar with the metric system, convert the speed to feet/second as follows:

event.speed * 3.2808399;

To convert to miles/hour, use:

event.speed * 2.23693629; // or (event.speed*3600)/1609.344

Geolocation has inspired the development of many kinds of applications. Here are just a few:

• Foursquare is a geo-based mobile phone application designed to enable users to share their location with friends and add comments on places visited. You earn points when you discover a new place and badges for interesting locations, and you become the mayor of a location if you are its most frequent visitor.
• Geocaching is a worldwide high-tech treasure hunt. A geocacher places an object, also called a geocache, pinpoints its location using GPS, and shares the geocache location online. To play, you enter your postal code, choose a geocode from a list, store the coordinates on your device, and start hunting. Once you find the physical
object, you add your name to a logbook and put the object back or replace it with another object of similar value. You can share stories and photos of your experience.
• The Complex Network of Global Cargo Ship Movements pieced together the routes of cargo ships over the course of a year using a GPS-based vessel tracking system (http://www.wired.com/wiredscience/2010/01/global-shipping-map/). The marine traffic website displays vessels around the world in real time. The system is based on the Automatic Identification System (AIS) automated tracking system. The International Maritime Organization requires vessels to carry an AIS transponder on board, which transmits their position, speed, and course, among some other static information such as the vessel’s name, dimensions, and voyage details (for more information, go to http://www.marinetraffic.com/ais/).
• SiliconSky GPS developed a prototype of an asthma inhaler with built-in GPS tracking capability (http://news.cnet.com/8301-17938_105-10228982-1.html). It tracks asthma inhaler use trends, including the exact time and the geographic location of inhaler users, to better monitor the health issue.
• Nike has partnered with Apple to create NikePlus (http://judgeseyesonly.com/nikeplus_video.html), a running shoe that comes equipped with a GPS sensor and transmits your location data to your iPod.
• To expose the technology which makes all this possible, GoSatWatch tracks and predicts visible satellites, and displays their path and their location (http://www.gosoftworks.com/GoSatWatch/GoSatWatch.html).

## Locating a Device Using Global Positioning System and Network/WiFi Technology

The Android platform uses both GPS and network/WiFi to locate a device. There is no direct way to specify a unique location source, but a workaround is to only add the relevant permission, fine or coarse as defined earlier, in the manifest file.

Using GPS

GPS is a manmade navigation system. Satellites in space constantly broadcast messages with their ephemeris, or position in the sky, and the time. Your mobile device, as a GPS receiver, uses the messages received to determine the distance to each satellite by measuring the transit time of the message. Using triangulation, the device is able to determine its own position as latitude and longitude or on a map. Other information derived from this is direction and speed, calculated from changes in position. Visit the TomTom site for a simple explanation and good graphics on how GPS works (http://www.tomtom
.com/howdoesitwork/page.php?ID=8&CID=2&Language=1).

GPS is the most accurate positioning technology, and it provides the most frequent updates. However, it is not always available, particularly indoors. It also consumes the most battery power.

Position acquisition is often not very accurate initially, but it improves over time. The GPS sensor can take several minutes to get a proper position from satellites. Do not make your application full screen so that the status bar is hidden from the user so that they cannot see network activity and signal strength. If a GPS connection is established, a GPS icon is visible on the top right. If it is trying to establish or fix a lost GPS connection, the icon will blink.

Signal-to-noise ratio

SNR is an algorithm that compensates for inaccurate or ambiguous results due to interference and multipath errors. Interference could be due to weather conditions or signals bouncing off mountains or large buildings. A multipath error is a problem with signals from multiple satellites arriving out of sync if one of them experiences interference.

Assisted GPS

Assisted GPS, also called A-GPS, is used to improve the startup performance or Time To First Fix (TTFF). In this mode, your device uses cell tower positions and triangulates from there to get an initial location quickly. It then switches to GPS when it is available and accurate enough. Figure 10-2 shows GPS Test, an application developed by Chartcross Ltd. It displays the position and signal strength of satellites within view as obtained by the device’s GPS receiver.

Using the Cellular Network and WiFi

If GPS is not available, your application will switch over to cell/WiFi. The network system is a combination of cell towers, WiFi hotspots, and IP-based geolocation. Cell tower signals, although not affected by architecture or bad weather, are less accurate and are dependent on the infrastructure. WiFi hotspots are very precise with enough data points but are only available in urban areas.

How to Know if GPS or WiFi Is Active

The static flash.net.NetworkInfo class provides information about the network interfaces on computers and on some mobile devices. Your application needs permission to access network information. Add it to your application manifest as follows:

<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” />

Check that it is supported on your device:

if (NetworkInfo.isSupported) {
// network information supported
}

NetworkInfo stores a list of possible network interfaces that you can get by calling:

import flash.net.NetworkInfo;
var network:NetworkInfo = NetworkInfo.networkInfo;
for each (var object:NetworkInterface in network.findInterfaces()) {
trace(object.name);
}

For example, the list of interfaces for the Nexus One, Droid 2, and Samsung Galaxy Tab is:

mobile, WIFI, mobile_mms, mobile_supl, mobile_dun, mobile_hipri

You can find out which method is active, and its name, using the following code:

import flash.net.NetworkInterface;
var network:NetworkInfo = NetworkInfo.networkInfo;
for each (var object:NetworkInterface in network.findInterfaces()) {
trace(object.name);
if (object.active) {
if (object.name == “WIFI”) {
// you are using wifi
} else {
// you are using GPS
}
}
}

## Geolocation Classes

The flash.events.GeolocationEvent class is a new Event object that contains updated geolocation information. The new flash.sensors.Geolocation class is a subclass of the EventDispatcher class. It listens for and receives information from the device’s location sensor in the form of a GeolocationEvent.

To use the geolocation classes, first you must add the necessary permissions. In Flash Professional, enable ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION device permissions under File→AIR Android Settings→Permissions. In Flash Builder, select ACCESS_NETWORK_STATE and ACCESS_WIFI_STATE under Mobile Settings→Permissions. To make changes, append your application manifest file as follows:

<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />

Fine location refers to GPS communication, while coarse location refers to network communication.

During development and testing, you must select the checkboxes on your device in Android Settings→Location and Security→Use GPS satellites and Android Settings→Location and Security→Use wireless networks to enable both sensors, as shown in Figure 10-1.

Next, verify that the device running your application supports geolocation:

import flash.sensors.Geolocation;
import flash.events.GeolocationEvent;
if (Geolocation.isSupported) {
// geolocation supported
}

Now let’s write a simple application to listen to geolocation event updates. Make geolocation a class variable, not a local variable, to guarantee that it does not get out of scope:

import flash.sensors.Geolocation;
import flash.events.GeolocationEvent;
var geolocation:Geolocation;
if (Geolocation.isSupported) {
geolocation = new Geolocation();
}
function onTravel(event:GeolocationEvent):void {
trace(event.latitude);
trace(event.longitude);
}

You should see your current latitude and longitude in degrees as floating-point values. My home location, for instance, is latitude 40.74382781982420 and longitude 74.00146007537840.

The user has the ability to enable or disable access to the location sensor on the device. Check the geolocation muted boolean property when you first run your application to see its value. You should also create a listener to receive status updates in case the property changes while the application is running:

import flash.events.StatusEvent;
if (!geolocation.muted) {
} else {
// inform the user to turn on the location sensor
}
function onStatusChange(event:StatusEvent):void {
trace(“status:” + event.code);
if (event.code == “Geolocation.Muted”) {
// inform the user to turn on the location sensor
}
}

If muted is true, or if event.code is equal to Geolocation.Muted, display a message to the user requesting the need for location data.

The GeolocationEvent Class

A GeolocationEvent.UPDATE event is delivered when the listener is first created. Then, it is delivered when a new location update is received from the device/platform. An event is also delivered if the application wakes up after being in the background.

Using the geolocation API drains the battery very quickly. In an effort to save battery life, control the frequency of updates by setting an update interval on the geoloca tion object. Unless you are moving very quickly and want to check your speed, you don’t need to check your location more than once every few seconds or minutes:

geolocation.setRequestedUpdateInterval(10000);

If not specified by you, the updates are based on the device/platform default interval. Look up the hardware documentation if you want to know the device default interval; this is not something you can get in the Android or AIR API.

The Earth is divided using a grid system with latitude from the equator toward the North and South Poles and longitude from Greenwich, England, to the international date line in the Pacific Ocean. Values are positive from the equator going north and from Greenwich going east. Values are negative from the equator going south and from Greenwich going west.

The GeolocationEvent properties are as follows:

• event.latitude ranges from ‒90 to 90 degrees and event.longitude ranges from ‒ 180 to 180 degrees. They are both of data type Number for greater precision.
• event.horizontalAccuracy and event.verticalAccuracy are in meters. This value comes back from the location service and represents how accurate the data is. A small number represents a better reading. Less than 60 meters is usually considered GPS accurate. This measurement may change as the technology improves.
• event.timeStamp is in milliseconds and starts counting from the moment the application initializes. If you need to get a regular update, use a timer instead of GeolocationEvent because it may not fire up at regular intervals.
• event.altitude is in meters and event.speed is in meters/second.
• event.heading, moving toward true north, is an integer. It is not supported on Android devices at the time of this writing, and it returns a value of NaN (Not a Number). However, you can calculate it by comparing longitude and latitude over time to determine a direction, assuming your device moves fast enough.

When your application moves to the background, the geolocation sensor remains active. If you want to overwrite the default behavior, listen to Native Application Event.DEACTIVATE to remove the event listener and Event.ACTIVATE to set it again.

When you first register for geolocation updates, AIR sets location listeners. It also queries for the LastKnownLocation and sends it as the first update. Because it is cached, the initial data may be incorrect until a first real location update is received.

## EXIF Data

EXIF stands for Exchangeable Image File. EXIF data is low-level information stored in JPEG images. EXIF was created by the Japan Electronic Industries Development Association (JEIDA) and became a convention adopted across camera manufacturers, including on mobile devices. You can read about the EXIF format at http://en.wikipedia.org/wiki/Exchangeable_image_file_format and http://www.exif.org/Exif2-2.PDF.

EXIF data can include the date and time the image was created, the camera manufacturer and camera settings, location information, and even a thumbnail image. Visit Jeffrey Friedl’s website at http://regex.info/exif.cgi and load a JPEG image to see the information it contains.

In AIR for Android, you could use the geolocation API to get location information and associate it with the photo you just shot, but it is more efficient to get this information directly from the image if it is available. To store image location on an Android device when taking a picture, the user must have Location & Security→Use GPS Satellites selected and then turn on the camera’s Store Location option.

Several open source AS3 libraries are available for reading EXIF data. I chose the one by Kenichi Ishibashi. You can download his library using Subversion at http://code.shichiseki.jp/as3/ExifInfo/. Ishibashi’s Loader class uses the loadBytes function and passes its data as a ByteArray to access the raw data information. Import his package to your class.

Our first example loads an image from the Gallery, reads its thumbnail data, and displays it. Note that thumbnail creation varies among devices and is not always available. Check that it exists before trying to display it:

import flash.display.MovieClip;
import flash.media.CameraRoll;
import flash.media.MediaPromise;
import flash.events.MediaEvent;
import flash.events.Event;
import flash.net.URLRequest
import jp.shichiseki.exif.*;
var cameraRoll:CameraRoll;
function Exif1() {
if (CameraRoll.supportsBrowseForImage) {
init();
}
}
function init():void {
cameraRoll = new CameraRoll();
cameraRoll.browseForImage();
}
function onSelect(event:MediaEvent):void {
var promise:MediaPromise = event.data as MediaPromise;
}
var exif:ExifInfo = loader.exif as ExifInfo;
if (exif.thumbnailData) {
}
}

The next example also lets you choose an image from the device’s Gallery and display its geographic information. The user must have GPS enabled and must have authorized the camera to save the location when the picture was taken:

import flash.display.MovieClip;
import flash.media.CameraRoll;
import flash.media.MediaPromise;
import flash.events.MediaEvent;
import flash.events.Event;
import flash.net.URLRequest
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldAutoSize;
import jp.shichiseki.exif.*;
var cameraRoll:CameraRoll;
if (CameraRoll.supportsBrowseForImage) {
cameraRoll = new CameraRoll();
cameraRoll.browseForImage();
}
function onSelect(event:MediaEvent):void {
var promise:MediaPromise = event.data as MediaPromise;
}
var exif:ExifInfo = loader.exif as ExifInfo;
var textFormat:TextFormat = new TextFormat();
textFormat.size = 40;
textFormat.color = 0x66CC99;
var where:TextField = new TextField();
where.x = 50;
where.y = 200;
where.defaultTextFormat = textFormat;
where.autoSize = TextFieldAutoSize.LEFT;
if (exif.ifds.gps) {
var gpsIfd:IFD = exif.ifds.gps;
var exifLat:Array = gpsIfd[“GPSLatitude”] as Array;
var latitude:Number = shorten(exifLat, gpsIfd[“GPSLatitudeRef”]);
var exifLon:Array = gpsIfd[“GPSLongitude”] as Array;
var longitude:Number = shorten(exifLon, gpsIfd[“GPSLongitudeRef”]);
where.text = latitude + “n” + longitude;
} else {
where.text = “No geographic information”;
}
}
function shorten(info:Array, reference:String):Number {
var degree:Number = info[0] + (info[1]/60) + (info[2]/3600);
// position from Greenwich and equator
if (reference == “S” || reference == “E”) {
degree * -1;
}
return degree;
}

Base 60 is commonly used to store geographic coordinates in degrees. Degrees, minutes, and seconds are stored separately. Put them back together and sign them depending on whether they are south of the equator and east of Greenwich Mean Time.

Displaying latitude and longitude is not very helpful, nor is it interesting for most users. But you can render a static map using latitude and longitude or retrieve an address.

## GPS

GPS stands for Global Positioning System. GPS is a space-based satellite navigation system, which provides reliable location information to your handheld device.

If your application requires the use of the device’s GPS, you will need to select the ACCESS_FINE_LOCATION permission when you are creating your project.

Let’s review the code below. First, you will notice that there is a private variable named geoLocation declared, of type flash.sensors.GeoLocation. Within application Complete of the application, an event handler function is called, which first checks to see if the device has an available GPS unit by reading the static property of the GeoLocation class. If this property returns as true, a new instance of GeoLocation is created; the data refresh interval is set to 500 milliseconds (.5 seconds) within the setRequestedUpdateInterval method; and an event listener of type GeoLocation Event.UPDATE is added to handle updates. Upon update, the GPS information is read from the event and written to a TextArea within the handleUpdate function.

The results can be seen within Figure 4-2:

<?xml version=”1.0″ encoding=”utf-8″?>
applicationComplete=”application1_applicationCompleteHandler(event)”>
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
import flash.sensors.Geolocation;
privatevar geoLocation:Geolocation;
protectedfunction application1_applicationCompleteHandler
(event:FlexEvent):void {
if(Geolocation.isSupported==true){
geoLocation = new Geolocation();
geoLocation.setRequestedUpdateInterval(500);
handleLocationRequest);
} else {
status.text = “Geolocation feature not supported”;
}
}
privatefunction handleLocationRequest(event:GeolocationEvent):void {
var mph:Number = event.speed*2.23693629;
var kph:Number = event.speed*3.6;
info.text = “Updated: ” + new Date().toTimeString() + “nn”
+ “latitude: ” + event.latitude.toString() + “n”
+ “longitude: ” + event.longitude.toString() + “n”
+ “altitude: ” + event.altitude.toString() + “n”
+ “speed: ” + event.speed.toString() + “n”
+ “speed: ” + mph.toString() + ” MPH n”
+ “speed: ” + kph.toString() + ” KPH n”
+ “horizontal accuracy: ”
+ event.horizontalAccuracy.toString() + “n”
+ “vertical accuracy: ”
+ event.verticalAccuracy.toString();
}
]]>
</fx:Script>
<fx:Declarations>
<!– Place non-visual elements (e.g., services, value objects) here –>
</fx:Declarations>
<s:Label id=”status” text=”Geolocation Info” top=”10″ width=”100%”
textAlign=”center”/>
<s:TextArea id=”info” width=”100%” top=”40″ editable=”false”/>
</s:Application>

## Permissions

The AIR 2.6 release includes the permission options outlined below, which can be selected within the new Flex Mobile project interface of Flash Builder 4.5. This is shown in Figure 3-1. Figure 3-2 shows the warning the user will see when installing an application with permission requests. The permissions are:

INTERNET
Allows applications to open sockets and embed HTML content.
WRITE_EXTERNAL_STORAGE
Allows an application to write to external storage.
Allows the AIR Runtime to mute audio from application, in case of incoming call.
ACCESS_FINE_LOCATION
Allows an application to access GPS location.
DISABLE_KEYGUARD, WAKE_LOCK
Allows applications to access screen dimming provision.
CAMERA
Allows applications to access device camera.
RECORD_AUDIO
Allows applications to access device microphone.
ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE
Allows applications to access information about network interfaces associated with
the device.

These permissions are also editable within the application’s XML configuration file.

Here is a sample of what that looks like:

<android>
<manifest installLocation=”auto”>
<!–Removing the permission android.permission.INTERNET will have the
side effect of preventing you from debugging your application
<uses-permission name=”android.permission.INTERNET”/>
<!–<uses-permission name=”android.permission.WRITE_EXTERNAL_STORAGE”/>–>
<!–<uses-permission name=”android.permission.ACCESS_FINE_LOCATION”/>–>
<!–The DISABLE_KEYGUARD and WAKE_LOCK permissions should be toggled
together in order to access AIR’s SystemIdleMode APIs–>
<!–<uses-permission name=”android.permission.DISABLE_KEYGUARD”/>–>
<!–<uses-permission name=”android.permission.WAKE_LOCK”/>–>
<!–<uses-permission name=”android.permission.CAMERA”/>–>
<!–<uses-permission name=”android.permission.RECORD_AUDIO”/>–>
<!–The ACCESS_NETWORK_STATE and ACCESS_WIFI_STATE permissions should be
toggled together in order to use AIR’s NetworkInfo APIs–>
<!–<uses-permission name=”android.permission.ACCESS_NETWORK_STATE”/>–>
<!–<uses-permission name=”android.permission.ACCESS_WIFI_STATE”/>–>
</manifest>
</android>

## Setting Permissions

The Android security and privacy model requires permissions to access certain features such as GPS. When clicking the Install button, the user sees the list of permissions and can then make an educated decision to opt out if desired.

Use the permission tag in the application descriptor to list all the permissions needed, as shown in the code below. Permissions cannot be modified at runtime.

Just before installing an application, the Android Market displays the permissions required. Figure 4-1 shows that Queue Manager, an application for managing your Netflix queue, only requires the Internet permission.

<uses-permission android:name=”android.permission.INTERNET” />
To make network requests. Can be used for remote debugging
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />
To write data to external storage, such as the SDCard
Read-only access of phone state. Used to mute AIR in case of incoming call
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />
Access the location information. Fine for GPS location, Coarse for Cell/Wi-Fi
<uses-permission android:name=”android.permission.CAMERA” />
Access the device camera
<uses-permission android:name=”android.permission.RECORD_AUDIO” />
Access the device microphone
<uses-permission android:name=”android.permission.DISABLE_KEYGUARD” />
<uses-permission android:name=”android.permission.WAKE_LOCK” />
Prevents device from dimming, going in sleep mode and activating keyguard
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />
Access information on device network interfaces
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” />
Access information on device Wi-Fi networks

Android offers a lot of other settings, only some of which can be set in AIR but not documented. They should all be added to the Android→Manifest Additions→Manifest node.

The Android Launcher activity keeps track of the applications that have been launched.  If a long press is applied on the home button, a list of recently run applications appears. If you do not want your application to be on this list, add excludeFromRecents to your manifest file as follows:

<android>
<![CDATA[
<manifest>
<application android:enabled=”true”>
<activity android:excludeFromRecents=”false”>
<intent-filter>
<action android:name=”android.intent.action.MAIN”/>
<category android:name=”android.intent.category.LAUNCHER”/>
</intent-filter>

</activity>
</application>
</manifest>
]]>
</android>

Applications are installed on the device memory by default. If you select Settings→ Applications→Manage Applications, you will notice that some applications have the option “Move to SD card” (which then becomes “Move to phone”).

However, some Android applications and AIR applications do not have that option. If you type the following at the command line, all applications will now be moved:

To restore the settings, type the following:

If you set the installLocation tag to preferExternal, saving internally is not an option. All of your content, including your application, is stored on the SD card. This could be helpful for large applications. Keep in mind that the content is accessible to other applications, and therefore is vulnerable. If the SD card is full, the system falls back to an installation on the device:

<android>
<manifest>
<attribute name=”android:installLocation” value=”preferExternal”/>
</manifest>
</android>

Read the Android recommendation regarding what should not be installed externally, online at http://developer.android.com/guide/appendix/install-location.html.

Users can erase the application data. If you want to prevent them from doing this, you can add the allowClearUserData attribute to the android node:

<manifest>
<application android:allowClearUserData=”false” />
</manifest>

You can also fine-tune permissions to add specific requirements. For instance, you may have an application which only works for devices with a camera auto-focus. You can add a different type of permission, uses-feature, to inform users of what is needed for your application:

<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />
<uses-feature android:name=”android.hardware.camera.autofocus” />

Android throws an error if a required permission is not set, such as for Internet or GPS, but not others, such as reading the phone state or disabling the keyguard. The criterion is what it considered potentially dangerous for the user. In AIR, the application always fails silently.

Native developers have a larger choice of features, and therefore permissions. You can go to http://developer.android.com/reference/android/Manifest.permission.html to learn about some of the new possibilities. If you see one that you would like to use, send Adobe a feature request, preferably with a use case so that it can be added to the AIR runtime, by going to https://www.adobe.com/cfusion/mmform/index.cfm?name=wishform .

For example, having an API to access contacts would open a lot of possibilities:

## Creating the Application Descriptor

The application descriptor is an external XML file that is bundled with your .swf file during packaging. The application descriptor file is generated automatically, but you can modify the default settings.

The application descriptor contains the application’s settings, such as its screen orientation. It also includes selected permissions. Permissions are set for some specific device functionality, such as GPS.

Using Flash Professional

To edit the application descriptor in Flash Professional, follow these steps:

1. From the IDE, go to File→AIR Android settings.
2. Under the General tab, keep Portrait selected under Aspect Ratio, and then select “Full screen”.
3. Under the Permissions tab, select Internet.
4. Click OK.

Using Flash Builder

To edit the application descriptor in Flash Builder, follow these steps:

1. Under Mobile Settings→Target platforms, select Google Android.
2. Under Permissions, select Internet.
3. Under Application Settings, select Full Screen and deselect “Automatically reorient”.
4. Select Next.
5. Change the Main application file to Main.as.
6. Click Finish.