Finding iBeacons with Google Glass

Chris Hardy

Inspired by Mike Bluestein’s Find the Monkey blog series on using iBeacons with iOS and Android, I was excited to see that the latest release of XE16 and XE17 for Google Glass (and Xamarin Component) updates Glass to Android KitKat 4.4 (Level 19), opening up the opportunity to interact with Bluetooth Low Energy devices.

With all the pieces in place, it was time to get the Android app ported over to Google Glass so you can focus on where you’re walking while you find the monkey with maximum efficiency. As in the original Android app, we’ll use the Glass running Android as the locator to find our Monkey beacon running on an iPad. Hooking on to the new Android APIs, we can display a Google Glass LiveCard that updates with how warm we’re getting to the monkey.

To get started, you can download the source code from GitHub. And, by the way, if you really want to dive into Glass development in C#, sign up for my webinar, “Developing with Google Glass and Xamarin,” on Thursday, May 22 at 8am PDT to get an overview of the Glass Development Kit (GDK) component.

[youtube http://www.youtube.com/watch?v=BTtBnUl_2-g]

To launch our Google Glass application, we’re going to use our custom voice command “Find the monkey”, to do this, we use an intent filter on our service that will load up our live card and start searching for the monkey. We use the VOICE_TRIGGER action along with the voice trigger metadata to declare this as follows

[IntentFilter (new String[]{ "com.google.android.glass.action.VOICE_TRIGGER" })]
[MetaData ("com.google.android.glass.VoiceTrigger", Resource = "@xml/voicetriggerstart")]

Our voicetriggerstart Android resource then can allow us to pick what the exact phrase that is used to open up the application via a voice trigger

<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="Find the monkey">
</trigger>

Since XE16, voice triggers with keywords (vs commands) need to add an explicit permission to allow these to be displayed in the list of applications and triggers on the Google Glass device, so we need to add the following permission directly to our AndroidManifest.xml file.

<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />

Find The Monkey Image

We’re going to be updating a live card on Google Glass so we’ll want to start and run the application as an Android Service (compared to an application like in the Android application version). In our OnStartCommand we can then set up our remote view (which is used to create content to display) and set the content of this to “Finding the monkey…” and then present this view to the live card to display to the user.

livecard = new LiveCard (Application, "beacon");
remoteViews = new RemoteViews (PackageName, Resource.Layout.LiveCardBeacon);
remoteViews.SetTextViewText (Resource.Id.LivecardContent, "Finding the monkey...");
livecard.SetViews (remoteViews);

In our OnCreate method, we can use the same code that we had from the previous Android app to set up our iBeaconManager, for this example we’re just using the range data of the beacon. You could always extend the app and have it monitor and notify you if you’re near the monkey but for now we’ll scan every 2.5 seconds for 2 seconds for active Monkey Beacons.

_iBeaconManager = IBeaconManager.GetInstanceForApplication (this);

_iBeaconManager.SetForegroundScanPeriod (2000);
_iBeaconManager.SetForegroundBetweenScanPeriod (2500);

_rangeNotifier = new RangeNotifier ();

_rangingRegion = new Region (monkeyId, UUID, null, null);
_iBeaconManager.Bind (this);

_rangeNotifier.DidRangeBeaconsInRegionComplete += RangingBeaconsInRegion;

Now when the RangingBeaconsInRegion method gets called, we handle this exactly the same way as you would in the original Android app, this will then call our UpdateDisplay method which we customize for the Google Glass device by modifying the Live Cards’ background colour and setting the text to whatever we pass in. Yellow and white text does not read too well so we also set the text to be black if we’re showing the Yellow background. After that, we just set the livecard view back to our remoteViews and this will update our live card for us.

if (color == Android.Graphics.Color.Yellow)
	remoteViews.SetTextColor (Resource.Id.LivecardContent, Android.Graphics.Color.Black);
else
	remoteViews.SetTextColor (Resource.Id.LivecardContent, Android.Graphics.Color.White);
remoteViews.SetInt(Resource.Id.Framelayout1, "setBackgroundColor", color);

remoteViews.SetTextViewText (Resource.Id.LivecardContent, message);

livecard.SetViews (remoteViews);

Getting Warmer Found The Monkey

To allow a user to stop finding the monkey, we want to provide a menu option for this. This is just the same as a normal menu inside of Android and would look like the following `MenuActivity`

[Activity (Theme = "@style/MenuTheme")]
public class MenuActivity : Activity
{

   public override void OnAttachedToWindow ()
   {
      base.OnAttachedToWindow ();
      OpenOptionsMenu ();
   }

   public override bool OnCreateOptionsMenu (IMenu menu)
   {
      MenuInflater.Inflate (Resource.Menu.MonkeySearch, menu);
      return true;
   }

   public override bool OnOptionsItemSelected (IMenuItem item)
   {
      switch (item.ItemId) {
      case Resource.Id.Stop:
         StopService (new Intent (this, typeof(MainService)));
         return true;
      default:
         return base.OnOptionsItemSelected (item);
      }
   }

   public override void OnOptionsMenuClosed (IMenu menu)
   {
      Finish ();
   }
}

We use a custom style on the menu item to enable the transparancy of the menu item over the top of the existing live card, if there were more options than just “Stop”, then this would automatically allow the user to swipe through multiple menu options without any extra work. When the StopService call is made after selecting the “Stop” menu item, the OnDestroy method will run on our service.

public override void OnDestroy()
{
   if (livecard != null && livecard.IsPublished) 
   {
      _rangeNotifier.DidRangeBeaconsInRegionComplete -= RangingBeaconsInRegion;

      _iBeaconManager.StopRangingBeaconsInRegion (_rangingRegion);
      _iBeaconManager.UnBind (this);

      livecard.Unpublish();
      livecard = null;
   }
   base.OnDestroy();
}

This is where we stop our _rangeNotifier and unbind the iBeaconManager and for the Google Glass specific functionality, we unpublish the livecard and set this to null, this will then remove the live card from the Glass timeline.

Don’t forget to sign up for the webinar, “Developing with Google Glass and Xamarin,” on Thursday, May 22 at 8am PDT.