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.
Survey
✅ Thank you for completing the survey!
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" />
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).
2
android:name="com.google.android.gms.version"
3
android:value="@integer/google_play_services_version" />
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).
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"
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.
02
private static final LatLng CHANDLER = new LatLng(33.455,-112.0668);
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"))
14
protected void onCreate(Bundle savedInstanceState) {
15
super.onCreate(savedInstanceState);
16
setContentView(R.layout.geolocation_view);
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)));
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
006
private LocationClient mLocationClient;
010
static class StoreLocation {
011
public LatLng mLatLng;
013
StoreLocation(LatLng latlng, String id) {
020
protected void onCreate(Bundle savedInstanceState) {
021
super.onCreate(savedInstanceState);
022
setContentView(R.layout.geolocation_view);
024
mLocationClient = new LocationClient(this, this, this);
026
// Create a new broadcast receiver to receive updates from the listeners and service
027
mGeofenceBroadcastReceiver = new ResturantGeofenceReceiver();
029
// Create an intent filter for the broadcast receiver
030
mIntentFilter = new IntentFilter();
032
// Action for broadcast Intents that report successful addition of geofences
033
mIntentFilter.addAction(ACTION_GEOFENCES_ADDED);
035
// Action for broadcast Intents that report successful removal of geofences
036
mIntentFilter.addAction(ACTION_GEOFENCES_REMOVED);
038
// Action for broadcast Intents containing various types of geofencing errors
039
mIntentFilter.addAction(ACTION_GEOFENCE_ERROR);
041
// All Location Services sample apps use this category
042
mIntentFilter.addCategory(CATEGORY_LOCATION_SERVICES);
046
mRegisterGeofenceButton = (Button)findViewById(R.id.geofence_switch);
047
mGeofenceState = CAN_START_GEOFENCE;
052
protected void onResume() {
054
// Register the broadcast receiver to receive status updates
055
LocalBroadcastManager.getInstance(this).registerReceiver(
056
mGeofenceBroadcastReceiver, mIntentFilter);
060
* Create a Geofence list
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)
068
ALLRESTURANTLOCATIONS[ix].mLatLng.latitude, ALLRESTURANTLOCATIONS[ix].mLatLng.longitude, GEOFENCERADIUS)
069
.setExpirationDuration(Geofence.NEVER_EXPIRE)
071
mGeofenceList.add(fence);
075
// callback function when the mRegisterGeofenceButton is clicked
076
public void onRegisterGeofenceButtonClick(View view) {
077
if (mGeofenceState == CAN_REGISTER_GEOFENCE) {
079
mGeofenceState = GEOFENCE_REGISTERED;
080
mGeofenceButton.setText(R.string.unregister_geofence);
081
mGeofenceButton.setClickable(true);
083
unregisterGeofences();
084
mGeofenceButton.setText(R.string.register_geofence);
085
mGeofenceButton.setClickable(true);
086
mGeofenceState = CAN_REGISTER_GEOFENCE;
090
private boolean checkGooglePlayServices() {
091
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
092
if (result == ConnectionResult.SUCCESS) {
096
Dialog errDialog = GooglePlayServicesUtil.getErrorDialog(
099
CONNECTION_FAILURE_RESOLUTION_REQUEST);
101
if (errorDialog != null) {
109
public void registerGeofences() {
111
if (!checkGooglePlayServices()) {
115
mRequestType = REQUEST_TYPE.ADD;
118
// Try to add geofences
119
requestConnectToLocationClient();
120
} catch (UnsupportedOperationException e) {
121
// handle the exception
126
public void unregisterGeofences() {
128
if (!checkGooglePlayServices()) {
132
// Record the type of removal
133
mRequestType = REQUEST_TYPE.REMOVE;
135
// Try to make a removal request
137
mCurrentIntent = getRequestPendingIntent());
138
requestConnectToLocationClient();
140
} catch (UnsupportedOperationException e) {
141
// handle the exception
145
public void requestConnectToLocationServices () throws UnsupportedOperationException {
146
// If a request is not already in progress
147
if (!mRequestInProgress) {
148
mRequestInProgress = true;
150
locationClient().connect();
153
// Throw an exception and stop the request
154
throw new UnsupportedOperationException();
160
* Get a location client and disconnect from Location Services
162
private void requestDisconnectToLocationServices() {
164
// A request is no longer in progress
165
mRequestInProgress = false;
167
locationClient().disconnect();
169
if (mRequestType == REQUEST_TYPE.REMOVE) {
170
mCurrentIntent.cancel();
176
* returns A LocationClient object
178
private GooglePlayServicesClient locationClient() {
179
if (mLocationClient == null) {
181
mLocationClient = new LocationClient(this, this, this);
183
return mLocationClient;
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
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();
197
// Send a request to add the current geofences
198
mLocationClient.addGeofences(mGeofenceList, mGeofencePendingIntent, this);
201
else if (mRequestType == REQUEST_TYPE.REMOVE){
203
mLocationClient.removeGeofences(mCurrentIntent, this);
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 {
010
public void onDisconnected() {
011
mRequestInProgress = false;
012
mLocationClient = null;
018
* Handle the result of adding the geofences
021
public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) {
023
// Create a broadcast Intent that notifies other components of success or failure
024
Intent broadcastIntent = new Intent();
026
// Temp storage for messages
029
// If adding the geocodes was successful
030
if (LocationStatusCodes.SUCCESS == statusCode) {
032
// Create a message containing all the geofence IDs added.
033
msg = getString(R.string.add_geofences_result_success,
034
Arrays.toString(geofenceRequestIds));
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
043
R.string.add_geofences_result_failure,
045
Arrays.toString(geofenceRequestIds)
047
broadcastIntent.setAction(ACTION_GEOFENCE_ERROR)
048
.addCategory(CATEGORY_LOCATION_SERVICES)
049
.putExtra(EXTRA_GEOFENCE_STATUS, msg);
052
LocalBroadcastManager.getInstance(this)
053
.sendBroadcast(broadcastIntent);
055
// request to disconnect the location client
056
requestDisconnectToLocationServices();
060
* Implementation of OnConnectionFailedListener.onConnectionFailed
061
* If a connection or disconnection request fails, report the error
062
* connectionResult is passed in from Location Services
065
public void onConnectionFailed(ConnectionResult connectionResult) {
067
if (connectionResult.hasResolution()) {
070
connectionResult.startResolutionForResult(this,
071
CONNECTION_FAILURE_RESOLUTION_REQUEST);
073
catch (SendIntentException e) {
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);
088
public void onRemoveGeofencesByPendingIntentResult(int statusCode,
089
PendingIntent requestIntent) {
091
// Create a broadcast Intent that notifies other components of success or failure
092
Intent broadcastIntent = new Intent();
094
// If removing the geofences was successful
095
if (statusCode == LocationStatusCodes.SUCCESS) {
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));
104
// removing the geocodes failed
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,
113
LocalBroadcastManager.getInstance(this)
114
.sendBroadcast(broadcastIntent);
116
// request to disconnect the location client
117
requestDisconnectToLocationServices();
121
public class ResturantGeofenceReceiver extends BroadcastReceiver {
125
public void onReceive(Context context, Intent intent) {
126
String action = intent.getAction();
128
// Intent contains information about errors in adding or removing geofences
129
if (TextUtils.equals(action, ACTION_GEOFENCE_ERROR)) {
130
// handleGeofenceError(context, intent);
132
else if (TextUtils.equals(action, ACTION_GEOFENCES_ADDED)
134
TextUtils.equals(action, ACTION_GEOFENCES_REMOVED)) {
135
// handleGeofenceStatus(context, intent);
137
else if (TextUtils.equals(action, ACTION_GEOFENCE_TRANSITION)) {
138
// handleGeofenceTransition(context, intent);
148
public PendingIntent getRequestPendingIntent() {
149
return createRequestPendingIntent();
152
private PendingIntent createRequestPendingIntent() {
154
if (mGeofencePendingIntent != null) {
156
// Return the existing intent
157
return mGeofencePendingIntent;
159
// If no PendingIntent exists
162
// Create an Intent pointing to the IntentService
163
Intent intent = new Intent(this,
164
ReceiveGeofenceTransitionIntentService.class);
166
return PendingIntent.getService(
170
PendingIntent.FLAG_UPDATE_CURRENT);
176
public void onRemoveGeofencesByRequestIdsResult(int statusCode,
177
String[] geofenceRequestIds) {
179
// it should not come here because we only remove geofences by PendingIntent
180
// Disconnect the location client
181
requestDisconnection();
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 {
03
* Sets an identifier for the service
05
public ReceiveGeofenceTransitionIntentService() {
06
super("ReceiveGeofenceTransitionsIntentService");
10
protected void onHandleIntent(Intent intent) {
12
// Create a local broadcast Intent
13
Intent broadcastIntent = new Intent();
15
// Give it the category for all intents sent by the Intent Service
16
broadcastIntent.addCategory(CATEGORY_LOCATION_SERVICES);
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);
25
// Get the type of transition (entry or exit)
27
LocationClient.getGeofenceTransition(intent);
29
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER) ||
30
(transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
32
// Post a notification
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