How To Make Android Proximity Alerts

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.

0 comments:

Post a Comment

www.comhttp.blogspot.in. Powered by Blogger.