How to Implement map and geofence features in Android business apps

How to Implement map and geofence features in Android business apps
HIGHLIGHTS

Here's how you can implement geo-fence features into your Android app targeted at business users

Here's a guide on how to integrate map and geolocation features into an Android business app. This guide specfically tells you on how to includes overlaying store locations on Google Maps*, using geofence to notify the user when the device enters the store proximities.

Overview

In this case study, we will incorporate maps and geolocation functionality into a restaurant business app for Android tablets (Figure 1). The user can access the geolocation functionality from the main menu item “Locations and Geofences” (Figure 2).


Figure 1 The Restaurant App Main Screen


Figure 2 The Flyout Menu Items

Displaying Store Locations on Google Maps

For a business app, showing the store locations on maps is very graphical and helpful to the user (Figure 3). The Google Maps Android API provides an easy way to incorporate Google Maps into your Android apps.

Google Maps Android API v2

Google Maps Android API v2 is part of the Google Play Services APK. To create an Android app which uses the Google Maps Android API v2 requires setting up the development environment by downloading and configuring the Google Play services SDK, obtaining an API key, and adding the required settings in your app’s AndroidManifest.xml file.

First you need to set up Google Play Services SDK by following http://developer.android.com/google/play-services/setup.html.

Then you register your project and obtain an API key from Google Developers Console https://console.developers.google.com/project. You will need to add the API key in your AndroidManifest.xml file.


Figure 3 The Restaurant App Shows Store Locations on Google Maps.

Specifying App Settings in the Application Manifest

To use Google Maps Android API v2, some permissions and features are required to be specified as children of the <manifest> element (Code Example 1). They include the necessary permissions for network connection, external storage, and access to the location. Also OpenGL ES version 2 feature is needed for the Google Maps Android API.

 
01 <uses-permission android:name=”android.permission.INTERNET"/>
02 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
03 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
04 <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
05 <!-- The following two permissions are not required to use
06      Google Maps Android API v2, but are recommended. -->
07 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
08 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
09 <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
10  
11   <uses-feature
12     android:glEsVersion="0x00020000"
13     android:required="true"/>

Code Example 1. Recommended permissions to specify for an app which uses Google Maps Android API. Include the “ACCESS_MOCK_LOCATION” permission only if you test your app with mock locations **

Also as children of the <application> element, we specify the Google Play Services version and the API key we obtained in <meta-data> elements (Code Example 2).

 
1 <meta-data
2         android:name="com.google.android.gms.version"
3         android:value="@integer/google_play_services_version" />
4    
5 <meta-data
6       android:name="com.google.android.maps.v2.API_KEY"
7     android:value="copy your API Key here"/>

Code Example 2. Specify Google Play Services Version and API Key **

Adding a Map Fragment

First in your activity layout xml file, add a MapFragment element (Code Example 3).

 
1 <fragment
2     android:id="@+id/storelocationmap"
3     android:layout_width="fill_parent"
4     android:layout_height="fill_parent"
5     android:name="com.google.android.gms.maps.MapFragment"
6 />

Code Example 3. Add a MapFragment in the Activity Layout **

In your activity class, you can retrieve the Google Maps MapFragment object and draw the store icons at each store location.

 
01
02 private static final LatLng CHANDLER = new LatLng(33.455,-112.0668);
03
04 private static final StoreLocation[] ALLRESTURANTLOCATIONS = new StoreLocation[] {
05         new StoreLocation(new LatLng(33.455,-112.0668), new String("Phoenix, AZ")),
06         new StoreLocation(new LatLng(33.5123,-111.9336), new String("SCOTTSDALE, AZ")),
07         new StoreLocation(new LatLng(33.3333,-111.8335), new String("Chandler, AZ")),
08         new StoreLocation(new LatLng(33.4296,-111.9436), new String("Tempe, AZ")),
09         new StoreLocation(new LatLng(33.4152,-111.8315), new String("Mesa, AZ")),
10         new StoreLocation(new LatLng(33.3525,-111.7896), new String("Gilbert, AZ"))
11 };
12 …   
13       @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.geolocation_view);
17          
18         mMap = ((MapFragment) getFragmentManager().findFragmentById(R.id.storelocationmap)).getMap();
19         mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(CHANDLER, ZOOM_LEVEL));
20         Drawable iconDrawable = getResources().getDrawable(R.drawable.ic_launcher);
21         Bitmap iconBmp = ((BitmapDrawable) iconDrawable).getBitmap();
22         for(int ix = 0; ix < ALLRESTURANTLOCATIONS.length; ix++) {
23             mMap.addMarker(new MarkerOptions()
24                 .position(ALLRESTURANTLOCATIONS[ix].mLatLng)
25                 .icon(BitmapDescriptorFactory.fromBitmap(iconBmp)));
26         }
27

Code Example 4. Draw the Store Icons on Google Maps **

Sending Geofence Notifications

A geofence is a circular area defined by the latitude and longitude coordinates of a point and a radius. An Android app can register geofences with the Android Location Services. The Android app can also specify an expiration duration for a geofence. Whenever a geofence transition happens, for example, when the Android device enters or exits a registered geofence, the Android Location Services will inform the Android app.

In our Restaurant app, we can define a geofence for each store location. When the device enters the proximity of the store, the app sends a notification for example, “You have entered the proximity of your favorite restaurant!” (Figure 4).


Figure 4 A geofence is defined as a circular area by a point of interest and a radius

Registering and Unregistering Geofences

In Android SDK, Location Services is also part of Google Play services APK under the “Extras” directory.

To request geofence monitoring, first we need to specify the "ACCESS_FINE_LOCATION" permission in the app’s manifest, which we have already done in the previous sections.

We also need to check the availability of the Google Play Services (the checkGooglePlayServices() method in Code Example 5). After the locationClient().connect() call and the location client has successfully established a connection, the Location Services will call the onConnected(Bundle bundle) function, where the location client can request adding or removing geofences.

 
001 public class GeolocationActivity extends Activity implements
002         GooglePlayServicesClient.ConnectionCallbacks
003
004 {
005 …  
006     private LocationClient mLocationClient;
007      
008
009  
010     static class StoreLocation {
011         public LatLng mLatLng;
012         public String mId;
013         StoreLocation(LatLng latlng, String id) {
014             mLatLng = latlng;
015             mId = id;
016         }
017     }
018  
019     @Override
020     protected void onCreate(Bundle savedInstanceState) {
021         super.onCreate(savedInstanceState);
022         setContentView(R.layout.geolocation_view);
023  
024         mLocationClient = new LocationClient(this, this, this);
025  
026         // Create a new broadcast receiver to receive updates from the listeners and service
027         mGeofenceBroadcastReceiver = new ResturantGeofenceReceiver();
028  
029         // Create an intent filter for the broadcast receiver
030         mIntentFilter = new IntentFilter();
031  
032         // Action for broadcast Intents that report successful addition of geofences
033         mIntentFilter.addAction(ACTION_GEOFENCES_ADDED);
034  
035         // Action for broadcast Intents that report successful removal of geofences
036         mIntentFilter.addAction(ACTION_GEOFENCES_REMOVED);
037  
038         // Action for broadcast Intents containing various types of geofencing errors
039         mIntentFilter.addAction(ACTION_GEOFENCE_ERROR);
040  
041         // All Location Services sample apps use this category
042         mIntentFilter.addCategory(CATEGORY_LOCATION_SERVICES);
043  
044         createGeofences();
045  
046         mRegisterGeofenceButton = (Button)findViewById(R.id.geofence_switch);
047         mGeofenceState = CAN_START_GEOFENCE;
048      
049     }
050      
051     @Override
052     protected void onResume() {
053         super.onResume();
054         // Register the broadcast receiver to receive status updates
055         LocalBroadcastManager.getInstance(this).registerReceiver(
056             mGeofenceBroadcastReceiver, mIntentFilter);
057     }
058          
059     /**
060      * Create a Geofence list
061      */
062     public void createGeofences() {
063         for(int ix=0; ix > ALLRESTURANTLOCATIONS.length; ix++) {
064             Geofence fence = new Geofence.Builder()
065                 .setRequestId(ALLRESTURANTLOCATIONS[ix].mId)
066                 .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
067                 .setCircularRegion(
068                     ALLRESTURANTLOCATIONS[ix].mLatLng.latitude, ALLRESTURANTLOCATIONS[ix].mLatLng.longitude, GEOFENCERADIUS)
069                 .setExpirationDuration(Geofence.NEVER_EXPIRE)
070                 .build();
071             mGeofenceList.add(fence);
072         }
073     }
074  
075     // callback function when the mRegisterGeofenceButton is clicked
076     public void onRegisterGeofenceButtonClick(View view) {
077         if (mGeofenceState == CAN_REGISTER_GEOFENCE) {
078             registerGeofences();
079             mGeofenceState = GEOFENCE_REGISTERED;
080             mGeofenceButton.setText(R.string.unregister_geofence);
081             mGeofenceButton.setClickable(true);           
082         else {
083             unregisterGeofences();
084             mGeofenceButton.setText(R.string.register_geofence);
085             mGeofenceButton.setClickable(true);
086             mGeofenceState = CAN_REGISTER_GEOFENCE;
087         }
088     }
089  
090     private boolean checkGooglePlayServices() {
091         int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
092         if (result == ConnectionResult.SUCCESS) {
093             return true;
094         }
095         else {
096             Dialog errDialog = GooglePlayServicesUtil.getErrorDialog(
097                     result,
098                     this,
099                     CONNECTION_FAILURE_RESOLUTION_REQUEST);
100  
101             if (errorDialog != null) {
102                 errorDialog.show();
103             }
104         }
105         return false;
106    }
107  
108  
109     public void registerGeofences() {
110      
111         if (!checkGooglePlayServices()) {
112  
113             return;
114         }
115         mRequestType = REQUEST_TYPE.ADD;
116  
117         try {
118             // Try to add geofences
119             requestConnectToLocationClient();
120         } catch (UnsupportedOperationException e) {
121             // handle the exception
122         }
123          
124     }
125  
126     public void unregisterGeofences() {
127  
128         if (!checkGooglePlayServices()) {
129             return;
130         }
131  
132         // Record the type of removal
133           mRequestType = REQUEST_TYPE.REMOVE;
134  
135         // Try to make a removal request
136         try {
137             mCurrentIntent = getRequestPendingIntent());
138             requestConnectToLocationClient();
139  
140         } catch (UnsupportedOperationException e) {
141             // handle the exception
142         }
143     }
144  
145     public void requestConnectToLocationServices () throws UnsupportedOperationException {
146         // If a request is not already in progress
147         if (!mRequestInProgress) {
148             mRequestInProgress = true;
149  
150             locationClient().connect();
151         }
152         else {
153             // Throw an exception and stop the request
154             throw new UnsupportedOperationException();
155         }
156     }
157  
158  
159     /**
160      * Get a location client and disconnect from Location Services
161      */
162     private void requestDisconnectToLocationServices() {
163  
164         // A request is no longer in progress
165         mRequestInProgress = false;
166  
167         locationClient().disconnect();
168          
169         if (mRequestType == REQUEST_TYPE.REMOVE) {
170             mCurrentIntent.cancel();
171         }
172  
173     }
174  
175     /**
176      * returns A LocationClient object
177      */
178     private GooglePlayServicesClient locationClient() {
179         if (mLocationClient == null) {
180  
181             mLocationClient = new LocationClient(this, this, this);
182         }
183         return mLocationClient;
184  
185 }
186  
187     /*
188      Called back from the Location Services when the request to connect the client finishes successfully. At this point, you can
189 request the current location or start periodic updates
190      */
191     @Override
192     public void onConnected(Bundle bundle) {
193         if (mRequestType == REQUEST_TYPE.ADD) {
194         // Create a PendingIntent for Location Services to send when a geofence transition occurs
195         mGeofencePendingIntent = createRequestPendingIntent();
196  
197         // Send a request to add the current geofences
198         mLocationClient.addGeofences(mGeofenceList, mGeofencePendingIntent, this);
199  
200         }
201         else if (mRequestType == REQUEST_TYPE.REMOVE){
202  
203             mLocationClient.removeGeofences(mCurrentIntent, this);       
204         }
205 }
206
207 }

Code Example 5. Request Geofence Monitoring by Location Services **

Implementing the Location Service Callbacks

The location service requests are usually non-blocking or asynchronous calls. Actually in Code Example 5 in the previous section, we have already implemented one of these functions: after the locationClient().connect()call and the location client establishes a connection, the Location Services will call the onConnected(Bundle bundle) function. Code Example 6 lists other location callback functions we need to implement.

 
001 public class GeolocationActivity extends Activity implements
002         OnAddGeofencesResultListener,
003         OnRemoveGeofencesResultListener,
004         GooglePlayServicesClient.ConnectionCallbacks,
005         GooglePlayServicesClient.OnConnectionFailedListener {
006 …  
007  
008  
009     @Override
010     public void onDisconnected() {
011         mRequestInProgress = false;
012         mLocationClient = null;
013 }
014  
015      
016  
017     /*
018      * Handle the result of adding the geofences
019      */
020     @Override
021     public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) {
022  
023         // Create a broadcast Intent that notifies other components of success or failure
024         Intent broadcastIntent = new Intent();
025  
026         // Temp storage for messages
027         String msg;
028  
029         // If adding the geocodes was successful
030         if (LocationStatusCodes.SUCCESS == statusCode) {
031  
032             // Create a message containing all the geofence IDs added.
033             msg = getString(R.string.add_geofences_result_success,
034                     Arrays.toString(geofenceRequestIds));
035  
036             // Create an Intent to broadcast to the app
037             broadcastIntent.setAction(ACTION_GEOFENCES_ADDED)
038                            .addCategory(CATEGORY_LOCATION_SERVICES)
039                            .putExtra(EXTRA_GEOFENCE_STATUS, msg);
040         // If adding the geofences failed
041         } else {
042             msg = getString(
043                     R.string.add_geofences_result_failure,
044                     statusCode,
045                     Arrays.toString(geofenceRequestIds)
046             );
047             broadcastIntent.setAction(ACTION_GEOFENCE_ERROR)
048                            .addCategory(CATEGORY_LOCATION_SERVICES)
049                            .putExtra(EXTRA_GEOFENCE_STATUS, msg);
050         }
051  
052         LocalBroadcastManager.getInstance(this)
053             .sendBroadcast(broadcastIntent);
054  
055         // request to disconnect the location client
056         requestDisconnectToLocationServices();
057     }
058  
059     /*
060      * Implementation of OnConnectionFailedListener.onConnectionFailed
061      * If a connection or disconnection request fails, report the error
062      * connectionResult is passed in from Location Services
063      */
064     @Override
065     public void onConnectionFailed(ConnectionResult connectionResult) {
066         mInProgress = false;
067         if (connectionResult.hasResolution()) {
068  
069             try {
070                 connectionResult.startResolutionForResult(this,
071                     CONNECTION_FAILURE_RESOLUTION_REQUEST);
072             }
073             catch (SendIntentException e) {
074                 // log the error
075             }
076         }
077         else {
078             Intent errorBroadcastIntent = new Intent(ACTION_CONNECTION_ERROR);
079             errorBroadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES)
080                      .putExtra(EXTRA_CONNECTION_ERROR_CODE,
081                                  connectionResult.getErrorCode());
082              LocalBroadcastManager.getInstance(this)
083                  .sendBroadcast(errorBroadcastIntent);
084         }
085     }
086      
087     @Override
088     public void onRemoveGeofencesByPendingIntentResult(int statusCode,
089             PendingIntent requestIntent) {
090  
091         // Create a broadcast Intent that notifies other components of success or failure
092         Intent broadcastIntent = new Intent();
093  
094         // If removing the geofences was successful
095         if (statusCode == LocationStatusCodes.SUCCESS) {
096  
097             // Set the action and add the result message
098             broadcastIntent.setAction(ACTION_GEOFENCES_REMOVED);
099             broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS,
100                     getString(R.string.remove_geofences_intent_success));
101  
102         }
103         else {
104             // removing the geocodes failed
105  
106  
107             // Set the action and add the result message
108             broadcastIntent.setAction(ACTION_GEOFENCE_ERROR);
109             broadcastIntent.putExtra(EXTRA_GEOFENCE_STATUS,
110                     getString(R.string.remove_geofences_intent_failure,
111                         statusCode));
112         }
113         LocalBroadcastManager.getInstance(this)
114                 .sendBroadcast(broadcastIntent);
115  
116         // request to disconnect the location client
117         requestDisconnectToLocationServices();
118     }
119  
120          
121     public class ResturantGeofenceReceiver extends BroadcastReceiver {
122    
123  
124       @Override
125         public void onReceive(Context context, Intent intent) {
126             String action = intent.getAction();
127  
128             // Intent contains information about errors in adding or removing geofences
129             if (TextUtils.equals(action, ACTION_GEOFENCE_ERROR)) {
130                 // handleGeofenceError(context, intent);
131             }
132             else if (TextUtils.equals(action, ACTION_GEOFENCES_ADDED)
133                     ||
134                     TextUtils.equals(action, ACTION_GEOFENCES_REMOVED)) {
135                 // handleGeofenceStatus(context, intent);
136             }
137             else if (TextUtils.equals(action, ACTION_GEOFENCE_TRANSITION)) {
138                 // handleGeofenceTransition(context, intent);
139             }
140             else {
141                 // handle error
142             }
143              
144         }
145     }
146  
147  
148     public PendingIntent getRequestPendingIntent() {
149         return createRequestPendingIntent();
150     }
151  
152     private PendingIntent createRequestPendingIntent() {
153  
154         if (mGeofencePendingIntent != null) {
155  
156             // Return the existing intent
157             return mGeofencePendingIntent;
158  
159         // If no PendingIntent exists
160         } else {
161  
162             // Create an Intent pointing to the IntentService
163             Intent intent = new Intent(this,
164                 ReceiveGeofenceTransitionIntentService.class);
165  
166             return PendingIntent.getService(
167                     this,
168                     0,
169                     intent,
170                     PendingIntent.FLAG_UPDATE_CURRENT);
171         }
172     }
173  
174  
175     @Override
176 public void onRemoveGeofencesByRequestIdsResult(int statusCode,
177     String[] geofenceRequestIds) {
178  
179         // it should not come here because we only remove geofences by PendingIntent
180         // Disconnect the location client
181         requestDisconnection();
182     }

Code Example 6. IImplement the Location Service Callbacks **

Implementing the Intent Service

Finally, we need to implement the IntentService class to handle the geofence transitions (Code Example 7).

 
01 public class ReceiveGeofenceTransitionIntentService extends IntentService {
02     /**
03      * Sets an identifier for the service
04      */
05     public ReceiveGeofenceTransitionIntentService() {
06         super("ReceiveGeofenceTransitionsIntentService");
07     }
08  
09     @Override
10     protected void onHandleIntent(Intent intent) {
11          
12         // Create a local broadcast Intent
13         Intent broadcastIntent = new Intent();
14  
15         // Give it the category for all intents sent by the Intent Service
16         broadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES);
17  
18      
19         // First check for errors
20         if (LocationClient.hasError(intent)) {
21             // Get the error code with a static method
22             int errorCode = LocationClient.getErrorCode(intent);
23         }
24         else {
25             // Get the type of transition (entry or exit)
26             int transition =
27                     LocationClient.getGeofenceTransition(intent);
28              
29             if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)  ||
30                     (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
31  
32                 // Post a notification
33             }
34             else {
35                 // handle the error
36             }
37         }
38     }
39 }

Code Example 7. Implement an IntentService Class to Handle Geofence Transitions

Summary

In this article, we have discussed how to incorporate mapping and geofencing features into an Android business app. These features support rich geolocation contents and strong location based services and use cases in your apps.

For more such Android resources and tools from Intel, please visit the Intel® Developer Zone

Digit.in
Logo
Digit.in
Logo