Smart-phones
are taking over the mobile world, this is a fact. Since GPS devices are
usually found embedded in those phones, there is already a notable rise
in applications that take advantage of the offered geographical
positioning functionality. A type of those applications is the one of
Location Based Services,
where the service exploits knowledge about where the mobile user is
globally positioned at. Pretty common are also the applications that use
geocoding (finding associated geographic coordinates from geographic data, such as a street address) and
reverse geocoding
(providing information based on given coordinates). One other aspect of
that type of applications is the creation of proximity alerts. As their
name suggests, these are alerts that get generated when the user is
physically located near a specific
Point Of Interest (POI).
Proximity alert are going to be a “hot” thing the following years,
since a lot of applications are going to make use of them, with the most
prominent example being targeted advertising. In this tutorial I am
going to show you how to take advantage of Android’s built-in proximity
alert capabilities.
Before we begin, it would be helpful to have
read introductory articles about location based application and/or
geocoding. You might want to take a look at some previous tutorials of
mine, such as
“Android Location Based Services Application – GPS location” and
“Android Reverse Geocoding with Yahoo API – PlaceFinder”. One other thing to note is that this tutorial was inspired by a very cool tutorial named
“Developing Proximity Alerts for Mobile Applications using the Android Platform”.
This is a four part tutorial, which gets a little complicated at some
points and might intimidate a beginner. For that reason, I decided to
provided a shorter and more straightforward tutorial.
What we will
build is a simple application that stores the user’s coordinates for a
point that interests him and then provide a notification when the user
is near that point. The coordinates are retrieved on demand when the
user is located at that point.
We begin by creating a new Eclipse
project, named “AndroidProximityAlertProject” in our case. We also
create a main activity for our application under the name
“ProxAlertActivity”. Here is what the application’s main page will look
like:

Here is the declaration file for the main UI layout, named “main.xml”:
<?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="fill_parent"
>
<EditText
android:id="@+id/point_latitude"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dip"
android:layout_marginRight="25dip"
/>
<EditText
android:id="@+id/point_longitude"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dip"
android:layout_marginRight="25dip"
/>
<Button
android:id="@+id/find_coordinates_button"
android:text="Find Coordinates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/save_point_button"
android:text="Save Point"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
Let’s now get started with the interesting stuff. First of all, we need a reference to the
LocationManager class, which provides access to the system location services. This is done via a call to the
getSystemService method of our activity. We can then use the
requestLocationUpdates
method in order to request notifications when the user’s location
changes. This is not strictly required when developing proximity alerts,
but I will use it here in order to calculate the distance between the
point of interest and the current user location. At any given time, we
can call the
getLastKnownLocation
method and retrieve the last known location of a specific provider, in
our case the GPS device. Finally, we will make use of the
addProximityAlert
method that can be used to set a proximity alert for a location given
by specific coordinates (latitude, longitude) and a given radius. We can
also optionally define an expiration time for that alert if we wish to
monitor the alert for a specific time period. A
PendingIntent can also be provided, which will be used to generate an
Intent to fire when entry to or exit from the alert region is detected.
All these are translated into code as follows:
package com.javacodegeeks.android.lbs;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class ProxAlertActivity extends Activity {
private static final long MINIMUM_DISTANCECHANGE_FOR_UPDATE = 1; // in Meters
private static final long MINIMUM_TIME_BETWEEN_UPDATE = 1000; // in Milliseconds
private static final long POINT_RADIUS = 1000; // in Meters
private static final long PROX_ALERT_EXPIRATION = -1;
private static final String POINT_LATITUDE_KEY = "POINT_LATITUDE_KEY";
private static final String POINT_LONGITUDE_KEY = "POINT_LONGITUDE_KEY";
private static final String PROX_ALERT_INTENT =
"com.javacodegeeks.android.lbs.ProximityAlert";
private static final NumberFormat nf = new DecimalFormat("##.########");
private LocationManager locationManager;
private EditText latitudeEditText;
private EditText longitudeEditText;
private Button findCoordinatesButton;
private Button savePointButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
MINIMUM_TIME_BETWEEN_UPDATE,
MINIMUM_DISTANCECHANGE_FOR_UPDATE,
new MyLocationListener()
);
latitudeEditText = (EditText) findViewById(R.id.point_latitude);
longitudeEditText = (EditText) findViewById(R.id.point_longitude);
findCoordinatesButton = (Button) findViewById(R.id.find_coordinates_button);
savePointButton = (Button) findViewById(R.id.save_point_button);
findCoordinatesButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
populateCoordinatesFromLastKnownLocation();
}
});
savePointButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
saveProximityAlertPoint();
}
});
}
private void saveProximityAlertPoint() {
Location location =
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location==null) {
Toast.makeText(this, "No last known location. Aborting...",
Toast.LENGTH_LONG).show();
return;
}
saveCoordinatesInPreferences((float)location.getLatitude(),
(float)location.getLongitude());
addProximityAlert(location.getLatitude(), location.getLongitude());
}
private void addProximityAlert(double latitude, double longitude) {
Intent intent = new Intent(PROX_ALERT_INTENT);
PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
locationManager.addProximityAlert(
latitude, // the latitude of the central point of the alert region
longitude, // the longitude of the central point of the alert region
POINT_RADIUS, // the radius of the central point of the alert region, in meters
PROX_ALERT_EXPIRATION, // time for this proximity alert, in milliseconds, or -1 to indicate no expiration
proximityIntent // will be used to generate an Intent to fire when entry to or exit from the alert region is detected
);
IntentFilter filter = new IntentFilter(PROX_ALERT_INTENT);
registerReceiver(new ProximityIntentReceiver(), filter);
}
private void populateCoordinatesFromLastKnownLocation() {
Location location =
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location!=null) {
latitudeEditText.setText(nf.format(location.getLatitude()));
longitudeEditText.setText(nf.format(location.getLongitude()));
}
}
private void saveCoordinatesInPreferences(float latitude, float longitude) {
SharedPreferences prefs =
this.getSharedPreferences(getClass().getSimpleName(),
Context.MODE_PRIVATE);
SharedPreferences.Editor prefsEditor = prefs.edit();
prefsEditor.putFloat(POINT_LATITUDE_KEY, latitude);
prefsEditor.putFloat(POINT_LONGITUDE_KEY, longitude);
prefsEditor.commit();
}
private Location retrievelocationFromPreferences() {
SharedPreferences prefs =
this.getSharedPreferences(getClass().getSimpleName(),
Context.MODE_PRIVATE);
Location location = new Location("POINT_LOCATION");
location.setLatitude(prefs.getFloat(POINT_LATITUDE_KEY, 0));
location.setLongitude(prefs.getFloat(POINT_LONGITUDE_KEY, 0));
return location;
}
public class MyLocationListener implements LocationListener {
public void onLocationChanged(Location location) {
Location pointLocation = retrievelocationFromPreferences();
float distance = location.distanceTo(pointLocation);
Toast.makeText(ProxAlertActivity.this,
"Distance from Point:"+distance, Toast.LENGTH_LONG).show();
}
public void onStatusChanged(String s, int i, Bundle b) {
}
public void onProviderDisabled(String s) {
}
public void onProviderEnabled(String s) {
}
}
}
In the
onCreate method we hook up the location manager with a custom class that implements the
LocationListener interface and allows to get notified on location changes via the
onLocationChanged method. We will see how to handle the updates later. We also find the various UI widgets and attach
OnClickListeners to the buttons.
When
the user wants to find his current coordinates, the
“populateCoordinatesFromLastKnownLocation” method is invoked. Inside
that, we use the
getLastKnownLocation method and retrieve a
Location object. The
EditTexts are then populated with the retrieved location information.
Similarly,
when the user wants to save the point and provide alerts for that
(“saveProximityAlertPoint”), the location info is first retrieved. Then,
we save the
latitude and
longitude information as preference data using the
SharedPreferences class and more specifically the
SharedPreferences.Editor. Finally, we create a
PendingIntent by using the
getBroadcast static method. For the encapsulated
Intent, we create an
IntentFilter and use the
registerReceiver method to associate a custom
BroadcastReceiver
with the specific intent filter. Note that this binding could
alternatively be achieved in a declarative way using the manifest file.
Now
let’s examine how we handle user’s location changes. In the implemented
method of the “MyLocationListener” class, we extract the stored
location info (“retrievelocationFromPreferences”) from the
SharedPreferences class. Then, we use the
distanceTo
method to calculate the distance between the two locations, the current
one and the one corresponding to the point of interest. This is done
for debugging purposes, so that we know if we have actually entered the
area around the point.
The final step is to handle the events of
entering the area of the point of interest. This is done inside the
“ProximityIntentReceiver” class that extends the
BroadcastReceiver
and responds to the custom intent that we attached to the location
manager when adding the proximity alert. The handling occurs inside the
onReceive method, which gets invoked upon event. Inside that, we retrieve the value of the
KEY_PROXIMITY_ENTERING
key from the associated intent, which indicates whether a proximity
alert is entering (true) or exiting (false). The code is the following:
package com.javacodegeeks.android.lbs;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.location.LocationManager;
import android.util.Log;
public class ProximityIntentReceiver extends BroadcastReceiver {
private static final int NOTIFICATION_ID = 1000;
@Override
public void onReceive(Context context, Intent intent) {
String key = LocationManager.KEY_PROXIMITY_ENTERING;
Boolean entering = intent.getBooleanExtra(key, false);
if (entering) {
Log.d(getClass().getSimpleName(), "entering");
}
else {
Log.d(getClass().getSimpleName(), "exiting");
}
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, null, 0);
Notification notification = createNotification();
notification.setLatestEventInfo(context,
"Proximity Alert!", "You are near your point of interest.", pendingIntent);
notificationManager.notify(NOTIFICATION_ID, notification);
}
private Notification createNotification() {
Notification notification = new Notification();
notification.icon = R.drawable.ic_menu_notifications;
notification.when = System.currentTimeMillis();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_LIGHTS;
notification.ledARGB = Color.WHITE;
notification.ledOnMS = 1500;
notification.ledOffMS = 1500;
return notification;
}
}
The code is pretty straightforward. After we determine whether
we have an entering or exiting proximity alert, we are ready to provide a
custom notification. To do so, we first take reference of the
appropriate service, i.e. the
NotificationManager. Through that service, we may send alerts to the user, wrapped around
Notification objects. The notifications can be customized upon will and may include
vibration,
flashing lights etc. We also added a specific icon that will appear to the status bar. The
setLatestEventInfo is preferred when we just want to add a basic title and text message. You can find more about notifications
here. Additionally, we can use a
PendingIntent
in order to define an activity to be invoked when the user acknowledges
the notification by clicking on it. However, to keep things simple, I
do not use an intent to be launched in my example.
Finally, let’s see what the Android manifest file looks like:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.javacodegeeks.android.lbs"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".ProxAlertActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>
Nothing special here, just remember to add the necessary permissions, i.e.
We are now ready to test our application. Launch the Eclipse configuration. Then, go to the
DDMS
view of Eclipse and look for the “Emulation Control” tab. There, among
other things, you will find the “Location Controls” section, which can
send mock location data to the emulator. In the “Manual” tab, just hit
the “Send” button, there are already some coordinates set up.

After
that, the GPS provider will have a last known location that can provide
upon request. So, hit the “Find Coordinates” button and this is what
you will get:

Then,
hit the “Save Point” in order to declare the current location as a
point of interest. The location coordinates will be saved in the
preferences and the proximity alert will be added to the location
manager.
Next, let’s simulate the fact that the user leaves the
location. Change the value of the coordinates, for example change
latitude as follows: 37.422006 ? 37.522006. We are now quite far from
the point of interest. Let’s suppose now that we are approaching it,
thus change the latitude to a closer value, for example: 37.522006 ?
37.423006.

This
is inside the radius of the point (this was set to 1000 meters) thus
the alert is triggered and our receiver gets notified. There, we create
our notification and send it via the notification manager. The
notification appears at the status bar as follows:

That’s it, a quick guide on how to implement proximity alerts with the Android platform. You can find
here the Eclipse project created for the needs of this tutorial.