Disclaimer: though I am an employee of Plantronics this posting is my individual contribution. The mechanisms for how the headset transmits events may be subject to change over time… but until they do enjoy this with your Plantronics Voyager Pro or Voyager Legend headset.
Though the events are one way (read only), the information they contain may be interesting enough to feed into an application that can take advantage of the events. As an example with the code sample below, it would be rather trivial for one to integrate headset events into an XMPP application, such as Beem. Beem could easily be enhanced to toggle the users online presence based upon the headset’s wear state (headset worn for available, headset off for busy or do not disturb).
What Type of Events Will the Headset Generate?
In addition to the connect/disconnect events the Plantronics headset out of the box will send information about the following events to your Android device.
Headset Device Information (USER-AGENT) – this event occurs after connecting the headset with the Android device and includes information about the manufacturer, product ID, firmware version and serial number (if available).
Sensor Information (SENSORSTATUS) – this event occurs at connection and whenever sensor status changes. The event includes a listing of the devices sensors and the sensor’s enabled state.
A2DP Enabled Information (A2DP) – this event occurs at connection and reports whether A2DP is enabled on the headset.
Audio Status Information (AUDIO) – this event occurs at connection, when headset volume levels change. The information returned from this event contains the headset’s speaker and microphone volume and codec type.
Vocalyst Phone Number (VOCALYST) – this event occurs at connection at returns the dial string for the Vocalyst speech service.
Headset Language Information (LANG) – this event occurs at connection and contains the headset’s voice prompt language setting.
Battery Level Information (BATTERY) – this event occurs after connecting and whenever there is a change in the headset battery level. The event will contain four bits of information
- Level – A number that represents the charge level with a zero-based integer – for example, a battery could have 11 levels from 0 - 10, at a 50% charge the level would be 5.
- Number of Levels – A number that indicates how many charging levels there are altogether. Looking at the example above, this is where you would get the “11” levels number from.
- Minutes of Talk Time – A number that represents the estimated number of minutes remaining of talk time.
- Is Charging – A true/false value if the device is charging
Connected Device Profile Information (CONNECTED) – this event occurs when the headset connects to a device. It will surface the BT profile supported by the device.
Button Press (BUTTON) – this event occurs after a button press has occurred on the device.
Device Being Worn (DON) – this event occurs when the device’s onboard sensors detect that it is being worn by the user.
Device Taken Off (DOFF) – this event occurs when the device’s onboard sensors detect that the user has removed the device.
How To Get Access to Plantronics Headset Events From Android
If you want to spare yourself the details and just want the code for the sample app, you can download it below.
What I will step you through is how to build an application that will report up all of the events generated by the Plantronics headset. In the end the application should look like the screen shot below.
You will need a couple of things in order to code up the application.
- Previous experience with developing Android applications.
- An Android phone device that is running OS 3.0 or greater, go lower than 3.0 and XEvent will not be supported. Use the emulator and Bluetooth becomes a whole lot harder.
- A Plantronics Voyager Pro or Plantronics Voyager Legend headset. If you need help locating one of these devices, I can point you in the right direction.
Getting Started
Create a new Android application using the IDE or your choice. After the application is created open the AndroidManifest.xml file and add permission to use Bluetooth.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.plantronics" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="14"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> <activity android:name="XEventExampleActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
Now lets create a very simple layout for printing out the headset events. I used a TextView widget wrapped inside a ScrollView container so I could scroll back over the many events that happen when the headset connects.
Encapsulating the Headset Events
Rather than have the events coming in raw from the device, I decided to create a data holder class to represent the event. Below is the code for this class, as you can see it is largely responsible for holding the event type and the metadata that is generated with the event.Of note is the overridden toString() method, it returns a formatted string for printing out the event in the TextView widget.
public class PlantronicsXEventMessage { //Plantronics Events public static String USER_AGENT_EVENT = "USER-AGENT"; public static String SENSOR_STATUS_EVENT = "SENSORSTATUS"; public static String A2DP_EVENT = "A2DP"; public static String AUDIO_EVENT = "AUDIO"; public static String VOCALYST_EVENT = "VOCALYST"; public static String LANG_EVENT = "LANG"; public static String BATTERY_EVENT = "BATTERY"; public static String CONNECTED_EVENT = "CONNECTED"; public static String BUTTON_EVENT = "BUTTON"; public static String DON_EVENT = "DON"; public static String DOFF_EVENT = "DOFF"; public static String HEADSET_CONNECTED_EVENT = "HEADSET_CONNECTED"; public static String HEADSET_DISCONNECTED_EVENT = "HEADSET_DISCONNECTED"; public static String CALL_STATUS_CHANGED_EVENT = "CALL_STATUS_CHANGED_EVENT"; //holds properties that are transmitted as part of the XEvent private Map<String, Object> messageProperties = new HashMap<String, Object>(); private String eventType; /** * Message must have an event type in order to create * * @param eventType */ public PlantronicsXEventMessage(String eventType) { this.eventType = eventType; } public String getEventType() { return eventType; } public void addProperty(String key, Object value) { messageProperties.put(key, value); } public Map<String, Object> getProperties() { return messageProperties; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Plantronics Event " + eventType + "<br/>"); if (!messageProperties.isEmpty()) { sb.append("Event Properties:<br/>"); for (String key : messageProperties.keySet()) { sb.append("•"); sb.append(key); sb.append(":"); sb.append(messageProperties.get(key)); sb.append("<br/>"); } } return sb.toString(); } }
Now that we have a way to encapsulate the event lets look at how we actually get the events.The XEvents arrive from the headset over the Bluetooth radio. To receive the events from the phone’s Bluetooth stack, I created an implementation of the android.content.BroadcastReceiver.In the sample code, the receiver implementation is called PlantronicsReceiver. The receiver knows how to parse the Plantronics XEvents and package them up as PlantronicsXEventMessage objects. The PlantronicsXEventMessage objects are passed back to the Android application using an android.os.Handler class.Below are some of relevant code sections of the PlantronicsReceiver to see how it works. I removed the parsing code for brevity; if you are interested in seeing the message parsing logic, please download the sample code attached below.
public class PlantronicsReceiver extends BroadcastReceiver { … public void onReceive(Context context, final Intent intent) { String action = intent.getAction(); BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE); String bdAddr = device == null ? null : device.getAddress(); if (ACTION_ACL_CONNECTED.equals(action)) { //do something – connect event } else if (ACTION_ACL_DISCONNECTED.equals(action)) { //do something – disconnect event } else if (ACTION_VENDOR_SPECIFIC_HEADSET_EVENT.equals(action)) { //Process the XEvent from the Plantronics headset PlantronicsXEventMessage message = generateMessageFromEvent(intent); if (message != null) { Message msg = handler.obtainMessage(HEADSET_EVENT, message); handler.sendMessage(msg); } } else if (ACTION_AUDIO_STATE_CHANGED.equals(action)) { //do something } else { Log.d(TAG, "Action came in and was not processed: " + action); } } /** * Unpackages the raw Plantronics XEvent message into a PlantronicsXEventMessage class * * @param intent * @return */ private PlantronicsXEventMessage generateMessageFromEvent(Intent intent) { Bundle eventExtras = intent.getExtras(); //get the arguments that the headset passed out Object[] args = (Object[]) eventExtras.get(EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); String eventName = (String) args[0]; PlantronicsXEventMessage m = new PlantronicsXEventMessage(eventName); Log.d(TAG, "Event from Plantronics headset = " + eventName); if (PlantronicsXEventMessage.AUDIO_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.VOCALYST_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.A2DP_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.SENSOR_STATUS_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.USER_AGENT_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.LANG_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.BATTERY_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.CONNECTED_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.BUTTON_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.DON_EVENT.equals(eventName)) { //parsing omitted } else if (PlantronicsXEventMessage.DOFF_EVENT.equals(eventName)) { //parsing omitted } ... }
Putting It All Together
Now that we have a mechanism for surfacing the headset events, we need to plug it into our sample application. The first thing the PlantronicsReceiver will need from the instantiating Activity is a Handler implementation. The simple Handler below does one thing when it receives the event from the PlantronicsReceiver, it calls a print routine that will append the event to the TextView widget.
/** * Handler for BluetoothReceiver events */ public class BluetoothHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case PlantronicsReceiver.HEADSET_EVENT: PlantronicsXEventMessage message = (PlantronicsXEventMessage)msg.obj; writeToLogPane(message.toString()); break; default: break; } } }
With the Handler implementation in place, we need to add some code to the Activity to register the receiver. The following snippet shows how to register the PlantronicsReceiver to receive Plantronics XEvents.
/** * Initialization routine that registers the handler with the receiver and sets up * the intent filters * for the PlantronicsReceiver to receive XEvent messages from the Plantronics * device */ private void initBluetooth() { btHandler = new BluetoothHandler(); btReceiver = new PlantronicsReceiver(btHandler); btIntentFilter = new IntentFilter(); btIntentFilter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + BluetoothAssignedNumbers.PLANTRONICS); btIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); btIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); btIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); btIntentFilter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); btIntentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); btIntentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); registerReceiver(btReceiver, btIntentFilter); }
To finish up all that is needed is to add the initialization code to the onCreate method of the activity. Note the logPane variable is set to the TextView widget specified in the main.xml page.
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); logPane = (TextView)findViewById(R.id.log); initBluetooth(); }
From here you should be able to compile and deploy the app to your android device.To use the app make sure you have paired your headset with the device. After pairing, launch the application and enjoy the events streaming by. To experiment with the headset generated events try turning the headset on/off, pressing buttons, taking a call and putting the headset on and taking it off.
Happy coding.-Cary
Comments
Hi Cary,
This is great stuff!
Last time, I was discussing our contextual intelligence with a major UC partner, and he asked if we have opened the contextual data info on other mobile platform, so these mobile app can also benefit from this techonlogy. As the application are going mobile more and more. What you describled here is exactly what customers are asking for.
So besides andriod, how is this supported on other platform, like iOS, win phone 8, blackberry?
Thanks
Jet
Thanks for the feedback Jet! Currently XEvent is only supported by Android 3.0 and greater, so this example only runs on Android. In the future integrations with other mobile devices will likely be possible and published here - so stay tuned
-Cary
No problem.
Expecting more your interesting article posted here! Really learned a lot.
Thanks,
Jet
Hi Cary, Great post thanks. Couldn't get it to work and thought I was going mad until I found this...
http://code.google.com/p/android/issues/detail?id=39847
Looks like xevents are broken in Android 4.2 - or do you have a cunning workaround ?
Ivan
Hi Ivan,
Android 4.2.2 should have the fix.
HTH,
-Cary
Hi Cary,
Thanks for that. It works great on 4.03 anyway.
Ivan.
Great post.
I have a question though, I need to purchase a headset for an Android project and I need to be able to map the headset's keys in my application. Is this process common for every Plantronics device? I was thinking about M55 and M25.
Thank you very much
where do you go to purchase the headset to get started developing
Hi James,
This link takes you to some suggestions of stockists for Plantronics devices:
Plantronics | Voyager Legend
Amazon and Best Buy are some suggestions...
Thanks,
Lewis.
Hi Cary,
Our partner Huawei want to integrate the battery metering feature with their IP phone (cal control has supported by HFP), they have two phone modes with direct BT connection:
Thansk
Jet
Hi Jie,
We don't today have an SDK kit for the mobile Linux platform. However there may be ways we could support the partner to integrate for that platform so please encourage them to get in touch with myself or and we will try to help
Thanks,
Lewis.
Do you have code to just connect to the bluetooth headset? From my understanding, this app works after the bluetooth headset has already been connected, correct?
Hi Brian,
To connect the headset to the Android device, you need to pair and connect the headset you are trying to integrate with. There are online examples of how to programmatically pair and connect to a paired device. A good place to start is here: Bluetooth | Android Developers
HTH,
-Cary
Hi,
BluetoothDevice.ACTION_ACL_CONNECTED
andBluetoothDevice.ACTION_ACL_CONNECTED
. The main event I need to receive is the pressed Call Button. My expectation was to receiveBluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT
but I wasn't able to catch it. Can you please give me some hint how it works with bluetooth headset like I have? Thanks for your response. TomasHI
Hi! I need help, How can I
Hi,
Unfortunately the M180 is not support by our SDK.
For a list of products supported by the SDK, please refer to this link: http://www.plantronics.com/us/product/plantronics-hub-desktop
Thanks,
Lewis.
So, this means, what I can't
Hi,
Which device and operating system are you using? And which language are you coding in?
Thanks,
Lewis.
Hi,
Hi,
Plantronics don't offer an official mobile SDK for integrating headset features (button events) etc into your app.
When paired to the Android phone the device will implement call control with the buttons using standard Bluetooth HFP (hands free profile). This is fine to answer/end call for the phone application on the device.
However, despite not having an official SDK some plantronics devices do surface events including in some cases buttons via the Android XEVENT feature.
The article describes this: /article/plugging-plantronics-headset-sensor-events-android
Have you tried the xevent approach in the article? It may be that the M180 does not implement these features.
Thanks,
Lewis
I tried to catch XEVENT, but
Hi,
Try a fast double-press on all the buttons, or a long press on all the buttons, and just see if any of those actions come through as an XEVENT.
If not you may be out of luck. I have seen XEVENT used in the past with the Voyager Legend UC with a double press on the mute button. This button event was reported as XEVENT and could be used for 3rd party app.
Thanks,
Lewis.
Thank you so mach, my headset
Is the source code still
I am joining the question.
Hi Jaco,
Unfortunately this blog post has never had sample code other than what is embedded in the blog post.
Thanks,
Lewis.
Hi ,
I am glad to read this