Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Plugging Into Plantronics Headset Sensor Events via Android

19 Dec 2012 1  
This posting will show you how to use an undocumented and unsupported feature in the Plantronics Voyager Legend and Voyager Pro headsets that will allow you to receive headset events over Android’s XEvent mechanism.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

 

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.

This posting will show you how to use an undocumented and unsupported feature in the Plantronics Voyager Legend and Voyager Pro headsets that will allow you to receive headset events over Android’s XEvent mechanism.

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– for example a battery could have 10 levels, 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 “10” 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.

  1. Previous experience with developing Android applications.
  2. 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.
  3. 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("&#8226;");
                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.

This article was written by Cary Bran. Cary works for Plantronics out of his backyard shed located in Seattle, Washington. His job title is Senior Director, Advanced Software and Architecture. What that translates to is he leads the research and development of next generation software and device concepts.

He's been developing solutions in the unified communications and collaboration space since 1998. In recent history, he's served as a software architect, technical strategist and contributor to the IETF and W3C WebRTC standardization efforts. He has a BS in Computer Science and an MBA from the Foster School of Business at the University of Washington.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here