What is Proximity Marketing?

Successful organizations continuously seek innovative ways to engage their customers. An airline, for example, might solicit interest by offering passengers a free beverage when in-flight WiFi is purchased. A retail outlet can draw more website traffic when they offer free shipping for online purchases.

As organizations look forward, fine-tuning their marketing strategies that embrace technology help them align with our fast-paced, on-the-go culture. An increasingly popular approach to driving user engagement involves proximity marketing.

Proximity marketing means targeting current and potential customers by way of personalized notifications sent to their device based on their location. By deploying a set of one or more proximity beacons in a hotel bar lounge, for instance, drink special notifications can be sent to customers entering the lounge.

This post will focus on integrating a Gimbal solution into an existing app. Gimbal is a mobile customer engagement and location solutions company. They develop technology to help organizations deliver custom content to their mobile users.

Location Detection and Verification with Gimbal Beacons

Gimbal beacons broadcast an omnidirectional radial capable of detecting a Bluetooth-enabled device. The device must be within a certain range of the beacon in order for an "entry" to be detected. Furthermore, some vendors require the device be within a defined range for a specific amount of time before registering an entry, so as to avoid false positives. If someone forgets their coat in the lounge, and only spends 20 seconds within range, then a notification should not be triggered.

The following diagram illustrates an end-to-end interaction between a user's device and a Gimbal beacon.


Source: Qualcomm

Given the open and public nature of beacon broadcasts, beacon validity is a real concern. Gimbal Proximity beacons must use robust authentication measures to mitigate two specific vulnerabilities:

  1. Phony beacons that broadcast a Proximity UUID (a unique beacon identifier) can fool any listening app into believing it has encountered a valid beacon.

  2. Proximity UUIDs are publicly broadcast in most scenarios, so other apps could register to listen to a beacon.

Without security measures in place, third-party apps could solicit patrons that should not be targeted. There are two methods Gimbal facilitates to mitigate these vulnerabilities.

  1. Secure Beacons
    Support for Eddystone-EID (Ephemeral ID), which provides beacon owners with a secure option for transmitting beacon signals and provides control for which applications are able to access their beacons

  2. Beacon Verification
    When a user's device encounters a beacon, the Gimbal SDK registers with Android OS to look for a unique descriptor in the Bluetooth payload. Once a correct descriptor is found, the SDK resolves for access by checking against the Gimbal cloud to determine if the application will get a callback. For this reason, beacon verification requires an Internet connection via WiFi or a cellular connection to perform calls to the Gimbal API services façade.

Beacon Management Using Gimbal Manager

Gimbal beacons are configured and easily managed through the Gimbal Manager, a web-based dashboard, provided to beacon owners and developers. Basic beacon information including latitude/longitude location, beacon name, and even the RSSI (Received Signal Strength Indicator) level at which entries and exits are registered, can be edited from the Gimbal Manager.

Additionally, custom attributes can be created in the Gimbal Manager as a means to apply metadata to one or a group of beacons. If product owners are interested in collecting user data, for instance, attributes can be added to collect a user ID after an entry event.

Source: Gimbal

Gimbal SDK Integration

Integrating the Gimbal SDK into an Android application is a fairly straightforward process. A sample app (available in iOS and Android) can be downloaded from the Gimbal Manager. It simply records entry and exit events by enabling the Gimbal Services within a home activity launched at startup.

What about cases where users can open the app to another activity, say with deep linking? How can we still launch the Gimbal Services?

The example below lists steps to launch the Gimbal Services at app startup rather than relying on an activity.

Step 1: Get the Gimbal SDK

Download the Gimbal SDK at https://manager.gimbal.com/sdk_downloads


Step 2: Add Gimbal Libraries and Dependencies

The following steps (with instructions) are included in the SDK documentation located at https://docs.gimbal.com/android/v2/devguide.html and should be done before writing any code.

  • Add Gimbal Dependency Libraries to the project's libs folder
  • Create a Gimbal Application in the Gimbal Manager
  • Add any beacons or geofences in Gimbal Manager
  • Add a 'Place' (a place is a set of one or more beacons or a geofence)

Step 3: Set up Permissions

Ensure the following permissions have been included inside the AndroidManifest.xml file. They are required for the Gimbal service to function correctly.

Note: Android 6.0 (Marshmallow) includes runtime permissions checks. Spend the time to understand this, as it will impact functionality if the Gimbal Service is running in the background. Documentation with sample code can be found at https://docs.gimbal.com/android/v2/devguide.html#runtime_permission_marshmallow.

Step 4: Update AndroidManifest.xml

Add services and receiver entries to the AndroidManifest.xml file to allow the Gimbal Services to run in the background. (Implementation of the GimbalService class referenced in the first service node is shown in step 6.)

android:exported="false"/>

android:exported="false" >

android:enabled="true" >

Step 5: Add Gimbal Data and Event objects

The GimbalEvent.java file includes all members and properties that constitute a Gimbal entry or exit event.

public class GimbalEvent implements Serializable {

 private static final long <em>serialVersionUID </em>= 1L;

 public static enum TYPE {
 <em>PLACE_ENTER</em>, <em>PLACE_EXIT</em>, <em>APP_INSTANCE_ID_RESET
 </em>};

 private TYPE type;
 private String title;
 private Date date;
 private Attributes attributes;

 public GimbalEvent() {
 }

 public GimbalEvent(TYPE type, String title, Date date, Attributes attributes) {

 this.type = type;
 this.title = title;
 this.date = date;
 this.attributes = attributes;
 }

 public TYPE getType() {
 return type;
 }

 public void setType(TYPE type) {
 this.type = type;
 }

 public String getTitle() {
 return title;
 }

 public void setTitle(String title) {
 this.title = title;
 }

 public Date getDate() {
 return date;
 }

 public void setDate(Date date) {
 this.date = date;
 }

 public Attributes getAttributes() {
 return attributes;
 }

 public void setAttributes(Attributes attributes){
 this.attributes = attributes;
 }
}

The GimbalDAO.java class is responsible for parsing entry and exit event data into valid JSON format for submittal to Gimbal cloud servers.

The getEvents() method collects entry and exit event data and stores it inside a list of GimbalEvents, which can be used to display in a UI element, if desired.

public class GimbalDAO {

 public static final String <em>GIMBAL_NEW_EVENT_ACTION </em>= "GIMBAL_EVENT_ACTION";
 private static final String <em>EVENTS_KEY </em>= "events";

 public static List getEvents(Context context) {
 List events = new ArrayList();

 try {

 SharedPreferences prefs = PreferenceManager.<em>getDefaultSharedPreferences</em>(context);
 String jsonString = prefs.getString(<em>EVENTS_KEY</em>, null);
 if (jsonString != null) {

 JSONArray jsonArray = new JSONArray(jsonString);
 for (int i = 0; i < jsonArray.length(); i++) {

 final JSONObject jsonObject = jsonArray.getJSONObject(i);
 GimbalEvent event = new GimbalEvent();
 event.setType(TYPE.<em>valueOf</em>(jsonObject.getString("type")));
 event.setTitle(jsonObject.getString("title"));
 event.setDate(new Date(jsonObject.getLong("date")));
 events.add(event);
 }
 }
 }
 catch (JSONException e) {
 e.printStackTrace();
 }
 return events;
 }

Since custom attributes can be defined on specific beacons (as mentioned in the Beacon Management section above), developers have flexibility to specify what information is captured and sent to any non-Gimbal APIs.

The setEvents() method demonstrates how event properties, like event type, can be used to build request content inside a JSON array.

 public static void setEvents(Context context, List events) {

 try {

 SharedPreferences prefs = PreferenceManager.<em>getDefaultSharedPreferences</em>(context);
 JSONArray jsonArray = new JSONArray();

 if (events != null) {

 // loop through gimbal events
 for (GimbalEvent event : events) {

 JSONArray attributes = new JSONArray();

 // loop through attributes for the event
 if (event.getAttributes() != null && event.getAttributes().getAllKeys() != null) {

 for (String key : event.getAttributes().getAllKeys()) {

 JSONObject attribute = new JSONObject();
 attribute.put(key, event.getAttributes().getValue(key));
 attributes.put(attribute);

 }


 JSONObject jsonObject = new JSONObject();
 jsonObject.put("type", event.getType().name());
 jsonObject.put("title", event.getTitle());
 jsonObject.put("date", event.getDate().getTime());
 jsonObject.put("attributes", attributes);
 jsonArray.put(jsonObject);

 String jstr = jsonArray.toString();
 Editor editor = prefs.edit();
 editor.putString(<em>EVENTS_KEY</em>, jstr);
 editor.commit();
 }
 }

 Intent intent = new Intent();
 intent.setAction(<em>GIMBAL_NEW_EVENT_ACTION</em>);
 context.sendBroadcast(intent);
 }
 }

 catch (JSONException e) {
 e.printStackTrace();
 }
 }
}

Step 6: Enable Gimbal Services

This example enables Gimbal Services regardless of which activity the application executes at startup. As such, starting Gimbal Services will not be attached to a specific activity. Rather, an application startup class instance will be responsible for starting the Gimbal Services.

Note: Remember that Android 6.0 uses runtime permissions, so an activity would be necessary to ensure permissions are granted from the user. However, starting the service will not be tied to an activity.

Inside the AndroidManifest.xml file, point to the application's startup class, GimbalEnabledApp.

The Gimbal Services will be started from the onCreate() method inside GimbalEnabledApp.java.

public void onCreate() {

startService(new Intent(<em>getInstance</em>(), GimbalService.class));

}

Note: Requirements may dictate specific behavior for pausing and resuming an app. Be sure to take that into account when starting/stopping the Gimbal Service.

This GimbalService.java class includes all the basic logic to trigger and record an entry or exit event. First, the class creates a linked list to hold any encountered events, as well as setting the Gimbal API key. Next, it initializes a listener responsible for detecting entry or exit events. Finally, it will start the actual service to monitor entry and exit activity.

public class GimbalService extends IntentService {

Instantiating a linked list of GimbalEvent objects and a PlaceEventListener responsible for recording any entry and exit events:

 private static final int <em>MAX_NUM_EVENTS </em>= 100;
 private LinkedList events;
 private PlaceEventListener placeEventListener;

A null constructor is required since we're extending an IntentService.

 public GimbalService(){
 super("GimbalService");
 }

Initialize the class-level placeEventListener to a new PlaceEventListener object, making sure to override the onVisitStart and onVisitEnd methods. Overriding these methods allows us to add events using our GimbalEvent object created in step 5.

 @Override
 public void onCreate() {

 events = new LinkedList(GimbalDAO.<em>getEvents</em>(getApplicationContext()));
 Gimbal.<em>setApiKey</em>(this.getApplication(), [enter Gimbal API Key provided from Gimbal Manager here);
 placeEventListener = new PlaceEventListener() {

 @Override
 public void onVisitStart(Visit visit) {
 addEvent(new GimbalEvent(TYPE.<em>PLACE_ENTER</em>, visit.getPlace().getName(), new Date(visit.getArrivalTimeInMillis()), visit.getPlace().getAttributes()));
 }

 @Override
 public void onVisitEnd(Visit visit) {
 addEvent(new GimbalEvent(TYPE.<em>PLACE_EXIT</em>, visit.getPlace().getName(), new Date(visit.getDepartureTimeInMillis()), visit.getPlace().getAttributes()));
 }
 };

 PlaceManager.<em>getInstance</em>().addListener(placeEventListener);

Gimbal.start() is responsible for enabling the service to start monitoring for entry and exit events.

 if (!Gimbal.<em>isStarted</em>()) {
 Gimbal.<em>start</em>();
 }
 }

The addEvent method ensures only the max number of events is maintained after adding a new event to the class-level 'events' variable. Finally, it calls the setEvents() created in step 5 to build our request content for any API calls.

The Service.START_STICKY in the onStartCommand tells the Android OS to recreate the service after it has enough memory. For this example, the onBind and onHandleIntent base implementations are not needed, so they are overridden to return null.

 private void addEvent(GimbalEvent event) {
 while (events.size() >= <em>MAX_NUM_EVENTS</em>) {
 events.removeLast();
 }

 events.add(0, event);
 GimbalDAO.<em>setEvents</em>(getApplicationContext(), events);
 }

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
 return Service.<em>START_STICKY</em>;
 }

 @Override
 public IBinder onBind(Intent intent) {
 return null;
 }

 @Override
 protected void onHandleIntent(Intent intent) {
 }
}

Final Observations

Given the Gimbal service is referencing GPS location and Bluetooth connectivity, Gimbal-enabled apps will require higher than average power consumption. For this reason, it will be important to allow users to disable the Gimbal Services. Creating an in-app setting, coupled with allowing users to opt out during initial app load, to disable monitoring, will reduce the risk of negative reviews involving high power consumption.

The steps illustrated in this post demonstrate a simple, working version of an Android Gimbal implementation. A more comprehensive solution may include requirements to send push notifications using the Gimbal Communication component. Review the Gimbal SDK documentation to get a full understanding of Gimbal capabilities and limitations.