通过 Android 连接 Plantronics 耳机传感器事件





0/5 (0投票)
这篇文章将向您展示如何使用缤特力 Voyager Legend 和 Voyager Pro 耳麦中一个未文档化且不受支持的功能,该功能将允许您通过安卓系统的 XEvent 机制接收耳麦事件。
免责声明:尽管我是缤特力的员工,但这篇帖子是我个人的贡献。耳麦传输事件的机制可能会随时间而变化……但在它们变化之前,请使用您的缤特力 Voyager Pro 或 Voyager Legend 耳麦享受这一切。
这篇文章将向您展示如何使用 缤特力 Voyager Legend 和 Voyager Pro 耳麦中一个未文档化且不受支持的功能,该功能将允许您通过安卓系统的 XEvent 机制接收耳麦事件。
尽管事件是单向的(只读),但它们包含的信息可能足够有趣,可以馈送给能够利用这些事件的应用程序。例如,通过下面的代码示例,将耳麦事件集成到 XMPP 应用程序(如 Beem)中将相当简单。Beem 可以很容易地增强功能,根据耳麦的佩戴状态(佩戴耳麦表示可用,取下耳麦表示忙碌或请勿打扰)切换用户的在线状态。
耳麦将生成哪些类型的事件?
除了连接/断开事件之外,开箱即用的 缤特力 耳麦还会将以下事件的信息发送到您的安卓设备。
耳麦设备信息 (USER-AGENT) – 此事件在耳麦与安卓设备连接后发生,包括制造商、产品 ID、固件版本和序列号(如果可用)等信息。
传感器信息 (SENSORSTATUS) – 此事件在连接时以及传感器状态变化时发生。该事件包括设备传感器的列表和传感器的启用状态。
A2DP 启用信息 (A2DP) – 此事件在连接时发生,并报告耳麦上是否启用了 A2DP。
音频状态信息 (AUDIO) – 此事件在连接时以及耳麦音量级别变化时发生。此事件返回的信息包含耳麦的扬声器和麦克风音量以及编解码器类型。
Vocalyst 电话号码 (VOCALYST) – 此事件在连接时发生,并返回 Vocalyst 语音服务的拨号字符串。
耳麦语言信息 (LANG) – 此事件在连接时发生,并包含耳麦的语音提示语言设置。
电池电量信息 (BATTERY) – 此事件在连接后以及耳麦电池电量发生变化时发生。该事件将包含四位信息
- 级别 – 一个表示充电级别的数字——例如,电池可能有 10 个级别,在 50% 的电量时,级别为 5。
- 级别总数 – 一个表示总共有多少个充电级别的数字。以上述示例为例,您将从这里获得“10”个级别的数字。
- 通话时间(分钟) – 一个表示剩余通话时间估计分钟数的数字。
- 正在充电 – 如果设备正在充电,则为真/假值
连接设备配置文件信息 (CONNECTED) – 此事件在耳麦连接到设备时发生。它将显示设备支持的蓝牙配置文件。
按钮按下 (BUTTON) – 此事件在设备上发生按钮按下后发生。
设备佩戴 (DON) – 当设备内置传感器检测到用户正在佩戴设备时,此事件发生。
设备取下 (DOFF) – 当设备内置传感器检测到用户已取下设备时,此事件发生。
如何从安卓系统获取缤特力耳麦事件
如果您想省去细节,只想要示例应用程序的代码,您可以在下面下载。
我将逐步指导您如何构建一个应用程序,该应用程序将报告 缤特力 耳麦生成的所有事件。最终,应用程序应如下图所示。
您需要准备几样东西才能编写应用程序。
- 以前开发安卓应用程序的经验。
- 运行 OS 3.0 或更高版本的安卓手机设备,低于 3.0 的版本将不支持 XEvent。使用模拟器会使蓝牙变得更加困难。
- 一个 缤特力 Voyager Pro 或 缤特力 Voyager Legend 耳麦。如果您需要帮助找到其中一个设备,我可以为您指明正确的方向。
入门
使用您选择的 IDE 创建一个新的安卓应用程序。应用程序创建后,打开 AndroidManifest.xml 文件并添加使用蓝牙的权限。
<?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>
现在让我们创建一个非常简单的布局来打印耳麦事件。我使用了一个包装在 ScrollView
容器中的 TextView 小部件,这样我就可以在耳麦连接时发生的许多事件中滚动。
封装耳麦事件
我决定创建一个数据持有者类来表示事件,而不是让事件从设备原始传入。下面是这个类的代码,正如您所看到的,它主要负责持有事件类型和随事件生成的元数据。
值得注意的是被重写的 toString()
方法,它返回一个格式化的字符串,用于在 TextView 小部件中打印事件。
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(); } }
现在我们有了一种封装事件的方法,让我们看看如何实际获取事件。
XEvents 通过蓝牙无线电从耳麦到达。为了从手机的蓝牙堆栈接收事件,我创建了一个 android.content.BroadcastReceiver
的实现。
在示例代码中,接收器实现称为 PlantronicsReceiver
。接收器知道如何解析 缤特力 XEvents 并将它们打包为 PlantronicsXEventMessage
对象。PlantronicsXEventMessage
对象使用 android.os.Handler
类传递回安卓应用程序。
下面是 PlantronicsReceiver
的一些相关代码片段,以了解它是如何工作的。为了简洁起见,我删除了解析代码;如果您有兴趣查看消息解析逻辑,请下载下面附带的示例代码。
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 } ... }
整合
现在我们有了一种显示耳麦事件的机制,我们需要将其插入到我们的示例应用程序中。PlantronicsReceiver
需要从实例化 Activity 中获得的第一件事是 Handler 实现。下面这个简单的 Handler 在收到 PlantronicsReceiver
发送的事件时只做一件事,它调用一个打印例程,将事件附加到 TextView 小部件。
/** * 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; } } }
有了 Handler 实现之后,我们需要向 Activity 添加一些代码来注册接收器。以下代码片段展示了如何注册 PlantronicsReceiver
以接收缤特力 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); }
最后需要做的就是将初始化代码添加到 Activity 的 onCreate
方法中。注意 logPane
变量被设置为 main.xml 页面中指定的 TextView 小部件。
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); logPane = (TextView)findViewById(R.id.log); initBluetooth(); }
从这里开始,您应该能够编译并将应用程序部署到您的安卓设备。
要使用该应用程序,请确保您的耳麦已与设备配对。配对后,启动应用程序并享受事件流。要试验耳麦生成的事件,请尝试打开/关闭耳麦,按下按钮,接听电话以及戴上和取下耳麦。
祝您编码愉快。
本文由 Cary Bran 撰写。Cary 在他位于华盛顿州西雅图后院小屋的缤特力公司工作。他的职位是高级总监,高级软件与架构。这意味着他负责下一代软件和设备概念的研发。
他自 1998 年以来一直在统一通信和协作领域开发解决方案。最近,他曾担任软件架构师、技术策略师,并为 IETF 和 W3C WebRTC 标准化工作做出贡献。他拥有计算机科学学士学位和华盛顿大学福斯特商学院的 MBA 学位。