How to Implement map and geofence features in Android business apps

Updated on 23-Apr-2015
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

Connect On :