How to Implement map and geofence features in Android business apps

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

Published Date
30 - Jul - 2014
| Last Updated
23 - Apr - 2015
 
How to Implement map and geofence features in Android business ap...

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-permissionandroid:name=”android.permission.INTERNET"/>
02<uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/>
03<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
04<uses-permissionandroid: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-permissionandroid:name="android.permission.ACCESS_COARSE_LOCATION"/>
08<uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION"/>
09<uses-permissionandroid: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
02privatestaticfinalLatLng CHANDLER = newLatLng(33.455,-112.0668);
03
04privatestaticfinalStoreLocation[] ALLRESTURANTLOCATIONS = newStoreLocation[] {
05        newStoreLocation(newLatLng(33.455,-112.0668), newString("Phoenix, AZ")),
06        newStoreLocation(newLatLng(33.5123,-111.9336), newString("SCOTTSDALE, AZ")),
07        newStoreLocation(newLatLng(33.3333,-111.8335), newString("Chandler, AZ")),
08        newStoreLocation(newLatLng(33.4296,-111.9436), newString("Tempe, AZ")),
09        newStoreLocation(newLatLng(33.4152,-111.8315), newString("Mesa, AZ")),
10        newStoreLocation(newLatLng(33.3525,-111.7896), newString("Gilbert, AZ"))
11};
12…   
13      @Override
14    protectedvoidonCreate(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(intix = 0; ix < ALLRESTURANTLOCATIONS.length; ix++) {
23            mMap.addMarker(newMarkerOptions()
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.


 
001publicclassGeolocationActivity extendsActivity implements
002        GooglePlayServicesClient.ConnectionCallbacks
003
004{
005…  
006    privateLocationClient mLocationClient;
007     
008
009 
010    staticclassStoreLocation {
011        publicLatLng mLatLng;
012        publicString mId;
013        StoreLocation(LatLng latlng, String id) {
014            mLatLng = latlng;
015            mId = id;
016        }
017    }
018 
019    @Override
020    protectedvoidonCreate(Bundle savedInstanceState) {
021        super.onCreate(savedInstanceState);
022        setContentView(R.layout.geolocation_view);
023 
024        mLocationClient = newLocationClient(this, this, this);
025 
026        // Create a new broadcast receiver to receive updates from the listeners and service
027        mGeofenceBroadcastReceiver = newResturantGeofenceReceiver();
028 
029        // Create an intent filter for the broadcast receiver
030        mIntentFilter = newIntentFilter();
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    protectedvoidonResume() {
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    publicvoidcreateGeofences() {
063        for(intix=0; ix > ALLRESTURANTLOCATIONS.length; ix++) {
064            Geofence fence = newGeofence.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    publicvoidonRegisterGeofenceButtonClick(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    privatebooleancheckGooglePlayServices() {
091        intresult = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
092        if(result == ConnectionResult.SUCCESS) {
093            returntrue;
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        returnfalse;
106   }
107 
108 
109    publicvoidregisterGeofences() {
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    publicvoidunregisterGeofences() {
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    publicvoidrequestConnectToLocationServices () throwsUnsupportedOperationException {
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            thrownewUnsupportedOperationException();
155        }
156    }
157 
158 
159    /**
160     * Get a location client and disconnect from Location Services
161     */
162    privatevoidrequestDisconnectToLocationServices() {
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    privateGooglePlayServicesClient locationClient() {
179        if(mLocationClient == null) {
180 
181            mLocationClient = newLocationClient(this, this, this);
182        }
183        returnmLocationClient;
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
189request the current location or start periodic updates
190     */
191    @Override
192    publicvoidonConnected(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        elseif(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.


 
001publicclassGeolocationActivity extendsActivity implements
002        OnAddGeofencesResultListener,
003        OnRemoveGeofencesResultListener,
004        GooglePlayServicesClient.ConnectionCallbacks,
005        GooglePlayServicesClient.OnConnectionFailedListener {
006…  
007 
008 
009    @Override
010    publicvoidonDisconnected() {
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    publicvoidonConnectionFailed(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 = newIntent(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    publicvoidonRemoveGeofencesByPendingIntentResult(intstatusCode,
089            PendingIntent requestIntent) {
090 
091        // Create a broadcast Intent that notifies other components of success or failure
092        Intent broadcastIntent = newIntent();
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    publicclassResturantGeofenceReceiver extendsBroadcastReceiver {
122   
123 
124      @Override
125        publicvoidonReceive(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            elseif(TextUtils.equals(action, ACTION_GEOFENCES_ADDED)
133                    ||
134                    TextUtils.equals(action, ACTION_GEOFENCES_REMOVED)) {
135                // handleGeofenceStatus(context, intent);
136            }
137            elseif(TextUtils.equals(action, ACTION_GEOFENCE_TRANSITION)) {
138                // handleGeofenceTransition(context, intent);
139            }
140            else{
141                // handle error
142            }
143             
144        }
145    }
146 
147 
148    publicPendingIntent getRequestPendingIntent() {
149        returncreateRequestPendingIntent();
150    }
151 
152    privatePendingIntent createRequestPendingIntent() {
153 
154        if(mGeofencePendingIntent != null) {
155 
156            // Return the existing intent
157            returnmGeofencePendingIntent;
158 
159        // If no PendingIntent exists
160        } else{
161 
162            // Create an Intent pointing to the IntentService
163            Intent intent = newIntent(this,
164                ReceiveGeofenceTransitionIntentService.class);
165 
166            returnPendingIntent.getService(
167                    this,
168                    0,
169                    intent,
170                    PendingIntent.FLAG_UPDATE_CURRENT);
171        }
172    }
173 
174 
175    @Override
176publicvoidonRemoveGeofencesByRequestIdsResult(intstatusCode,
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).


 
01publicclassReceiveGeofenceTransitionIntentService extendsIntentService {
02    /**
03     * Sets an identifier for the service
04     */
05    publicReceiveGeofenceTransitionIntentService() {
06        super("ReceiveGeofenceTransitionsIntentService");
07    }
08 
09    @Override
10    protectedvoidonHandleIntent(Intent intent) {
11         
12        // Create a local broadcast Intent
13        Intent broadcastIntent = newIntent();
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            interrorCode = LocationClient.getErrorCode(intent);
23        }
24        else{
25            // Get the type of transition (entry or exit)
26            inttransition =
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