HIDD: Add support for HID Device Role
This patch adds support for HID Device Role which enables phone being
used as a Bluetooth keyboad or mouse.
Change-Id: I931867442111ad997a34a166c7b2ec1daf317ddd
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7e0cdc2..cb5642d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -394,5 +394,12 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
+ <service
+ android:name = ".hid.HidDevService"
+ android:enabled="@bool/profile_supported_hidd">
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothHidDevice" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/jni/Android.mk b/jni/Android.mk
index fe5c482..971b614 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -11,6 +11,7 @@
com_android_bluetooth_avrcp.cpp \
com_android_bluetooth_avrcp_controller.cpp \
com_android_bluetooth_hid.cpp \
+ com_android_bluetooth_hidd.cpp \
com_android_bluetooth_hdp.cpp \
com_android_bluetooth_pan.cpp \
com_android_bluetooth_gatt.cpp \
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index e63eba5..77ef51d 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -84,6 +84,8 @@
int register_com_android_bluetooth_hid(JNIEnv* env);
+int register_com_android_bluetooth_hidd(JNIEnv* env);
+
int register_com_android_bluetooth_hdp(JNIEnv* env);
int register_com_android_bluetooth_pan(JNIEnv* env);
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 5f69601..bfc843d 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -1321,6 +1321,12 @@
return JNI_ERR;
}
+ status = android::register_com_android_bluetooth_hidd(e);
+ if (status < 0) {
+ ALOGE("jni hidd registration failure: %d", status);
+ return JNI_ERR;
+ }
+
status = android::register_com_android_bluetooth_hdp(e);
if (status < 0) {
ALOGE("jni hdp registration failure: %d", status);
diff --git a/jni/com_android_bluetooth_hidd.cpp b/jni/com_android_bluetooth_hidd.cpp
new file mode 100644
index 0000000..9b19dd8
--- /dev/null
+++ b/jni/com_android_bluetooth_hidd.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BluetoothHidDevServiceJni"
+
+#define LOG_NDEBUG 0
+
+#define CHECK_CALLBACK_ENV \
+ if (!checkCallbackThread()) { \
+ ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__); \
+ return; \
+ }
+
+#include "android_runtime/AndroidRuntime.h"
+#include "com_android_bluetooth.h"
+#include "hardware/bt_hd.h"
+#include "utils/Log.h"
+
+#include <string.h>
+
+namespace android {
+
+static jmethodID method_onApplicationStateChanged;
+static jmethodID method_onConnectStateChanged;
+static jmethodID method_onGetReport;
+static jmethodID method_onSetReport;
+static jmethodID method_onSetProtocol;
+static jmethodID method_onIntrData;
+static jmethodID method_onVirtualCableUnplug;
+
+static const bthd_interface_t* sHiddIf = NULL;
+static jobject mCallbacksObj = NULL;
+static JNIEnv* sCallbackEnv = NULL;
+
+static bool checkCallbackThread() {
+ sCallbackEnv = getCallbackEnv();
+
+ if (sCallbackEnv != AndroidRuntime::getJNIEnv() || sCallbackEnv == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+static void application_state_callback(bt_bdaddr_t* bd_addr,
+ bthd_application_state_t state) {
+ jboolean registered = JNI_FALSE;
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV
+
+ if (state == BTHD_APP_STATE_REGISTERED) {
+ registered = JNI_TRUE;
+ }
+
+ if (bd_addr) {
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("%s: failed to allocate storage for bt_addr", __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t),
+ (jbyte*)bd_addr);
+ } else {
+ addr = NULL;
+ }
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onApplicationStateChanged,
+ addr, registered);
+
+ if (addr) {
+ sCallbackEnv->DeleteLocalRef(addr);
+ }
+}
+
+static void connection_state_callback(bt_bdaddr_t* bd_addr,
+ bthd_connection_state_t state) {
+ jbyteArray addr;
+
+ CHECK_CALLBACK_ENV
+
+ addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
+ if (!addr) {
+ ALOGE("%s: failed to allocate storage for bt_addr", __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t),
+ (jbyte*)bd_addr);
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectStateChanged,
+ addr, (jint)state);
+
+ sCallbackEnv->DeleteLocalRef(addr);
+}
+
+static void get_report_callback(uint8_t type, uint8_t id,
+ uint16_t buffer_size) {
+ CHECK_CALLBACK_ENV
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetReport, type, id,
+ buffer_size);
+}
+
+static void set_report_callback(uint8_t type, uint8_t id, uint16_t len,
+ uint8_t* p_data) {
+ jbyteArray data;
+
+ CHECK_CALLBACK_ENV
+
+ data = sCallbackEnv->NewByteArray(len);
+ if (!data) {
+ ALOGE("%s: failed to allocate storage for report data", __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(data, 0, len, (jbyte*)p_data);
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetReport, (jbyte)type,
+ (jbyte)id, data);
+
+ sCallbackEnv->DeleteLocalRef(data);
+}
+
+static void set_protocol_callback(uint8_t protocol) {
+ CHECK_CALLBACK_ENV
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetProtocol, protocol);
+}
+
+static void intr_data_callback(uint8_t report_id, uint16_t len,
+ uint8_t* p_data) {
+ jbyteArray data;
+
+ CHECK_CALLBACK_ENV
+
+ data = sCallbackEnv->NewByteArray(len);
+ if (!data) {
+ ALOGE("%s: failed to allocate storage for report data", __FUNCTION__);
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(data, 0, len, (jbyte*)p_data);
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onIntrData,
+ (jbyte)report_id, data);
+
+ sCallbackEnv->DeleteLocalRef(data);
+}
+
+static void vc_unplug_callback(void) {
+ CHECK_CALLBACK_ENV
+
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVirtualCableUnplug);
+}
+
+static bthd_callbacks_t sHiddCb = {
+ sizeof(sHiddCb),
+
+ application_state_callback,
+ connection_state_callback,
+ get_report_callback,
+ set_report_callback,
+ set_protocol_callback,
+ intr_data_callback,
+ vc_unplug_callback,
+};
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+ ALOGV("%s: done", __FUNCTION__);
+
+ method_onApplicationStateChanged =
+ env->GetMethodID(clazz, "onApplicationStateChanged", "([BZ)V");
+ method_onConnectStateChanged =
+ env->GetMethodID(clazz, "onConnectStateChanged", "([BI)V");
+ method_onGetReport = env->GetMethodID(clazz, "onGetReport", "(BBS)V");
+ method_onSetReport = env->GetMethodID(clazz, "onSetReport", "(BB[B)V");
+ method_onSetProtocol = env->GetMethodID(clazz, "onSetProtocol", "(B)V");
+ method_onIntrData = env->GetMethodID(clazz, "onIntrData", "(B[B)V");
+ method_onVirtualCableUnplug =
+ env->GetMethodID(clazz, "onVirtualCableUnplug", "()V");
+}
+
+static void initNative(JNIEnv* env, jobject object) {
+ const bt_interface_t* btif;
+ bt_status_t status;
+
+ ALOGV("%s enter", __FUNCTION__);
+
+ if ((btif = getBluetoothInterface()) == NULL) {
+ ALOGE("Cannot obtain BT interface");
+ return;
+ }
+
+ if (sHiddIf != NULL) {
+ ALOGW("Cleaning up interface");
+ sHiddIf->cleanup();
+ sHiddIf = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ ALOGW("Cleaning up callback object");
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+
+ if ((sHiddIf = (bthd_interface_t*)btif->get_profile_interface(
+ BT_PROFILE_HIDDEV_ID)) == NULL) {
+ ALOGE("Cannot obtain interface");
+ return;
+ }
+
+ if ((status = sHiddIf->init(&sHiddCb)) != BT_STATUS_SUCCESS) {
+ ALOGE("Failed to initialize interface (%d)", status);
+ sHiddIf = NULL;
+ return;
+ }
+
+ mCallbacksObj = env->NewGlobalRef(object);
+
+ ALOGV("%s done", __FUNCTION__);
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ if (sHiddIf != NULL) {
+ ALOGI("Cleaning up interface");
+ sHiddIf->cleanup();
+ sHiddIf = NULL;
+ }
+
+ if (mCallbacksObj != NULL) {
+ ALOGI("Cleaning up callback object");
+ env->DeleteGlobalRef(mCallbacksObj);
+ mCallbacksObj = NULL;
+ }
+
+ ALOGV("%s done", __FUNCTION__);
+}
+
+static void fill_qos(JNIEnv* env, jintArray in, bthd_qos_param_t* out) {
+ // set default values
+ out->service_type = 0x01; // best effort
+ out->token_rate = out->token_bucket_size = out->peak_bandwidth =
+ 0; // don't care
+ out->access_latency = out->delay_variation = 0xffffffff; // don't care
+
+ if (in == NULL) return;
+
+ jsize len = env->GetArrayLength(in);
+
+ if (len != 6) return;
+
+ uint32_t* buf = (uint32_t*)calloc(len, sizeof(uint32_t));
+
+ if (buf == NULL) return;
+
+ env->GetIntArrayRegion(in, 0, len, (jint*)buf);
+
+ out->service_type = (uint8_t)buf[0];
+ out->token_rate = buf[1];
+ out->token_bucket_size = buf[2];
+ out->peak_bandwidth = buf[3];
+ out->access_latency = buf[4];
+ out->delay_variation = buf[5];
+
+ free(buf);
+}
+
+static jboolean registerAppNative(JNIEnv* env, jobject thiz, jstring name,
+ jstring description, jstring provider,
+ jbyte subclass, jbyteArray descriptors,
+ jintArray p_in_qos, jintArray p_out_qos) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+ bthd_app_param_t app_param;
+ bthd_qos_param_t in_qos;
+ bthd_qos_param_t out_qos;
+ jsize size;
+ uint8_t* data;
+
+ size = env->GetArrayLength(descriptors);
+ data = (uint8_t*)malloc(size);
+
+ if (data != NULL) {
+ env->GetByteArrayRegion(descriptors, 0, size, (jbyte*)data);
+
+ app_param.name = env->GetStringUTFChars(name, NULL);
+ app_param.description = env->GetStringUTFChars(description, NULL);
+ app_param.provider = env->GetStringUTFChars(provider, NULL);
+ app_param.subclass = subclass;
+ app_param.desc_list = data;
+ app_param.desc_list_len = size;
+
+ fill_qos(env, p_in_qos, &in_qos);
+ fill_qos(env, p_out_qos, &out_qos);
+
+ bt_status_t ret = sHiddIf->register_app(&app_param, &in_qos, &out_qos);
+
+ ALOGV("%s: register_app() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ env->ReleaseStringUTFChars(name, app_param.name);
+ env->ReleaseStringUTFChars(description, app_param.description);
+ env->ReleaseStringUTFChars(provider, app_param.provider);
+
+ free(data);
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean unregisterAppNative(JNIEnv* env, jobject thiz) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+
+ bt_status_t ret = sHiddIf->unregister_app();
+
+ ALOGV("%s: unregister_app() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean sendReportNative(JNIEnv* env, jobject thiz, jint id,
+ jbyteArray data) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+ jsize size;
+ uint8_t* buf;
+
+ size = env->GetArrayLength(data);
+ buf = (uint8_t*)malloc(size);
+
+ if (buf != NULL) {
+ env->GetByteArrayRegion(data, 0, size, (jbyte*)buf);
+
+ bt_status_t ret =
+ sHiddIf->send_report(BTHD_REPORT_TYPE_INTRDATA, id, size, buf);
+
+ ALOGV("%s: send_report() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ free(buf);
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean replyReportNative(JNIEnv* env, jobject thiz, jbyte type,
+ jbyte id, jbyteArray data) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+ jsize size;
+ uint8_t* buf;
+
+ size = env->GetArrayLength(data);
+ buf = (uint8_t*)malloc(size);
+
+ if (buf != NULL) {
+ int report_type = (type & 0x03);
+ env->GetByteArrayRegion(data, 0, size, (jbyte*)buf);
+
+ bt_status_t ret =
+ sHiddIf->send_report((bthd_report_type_t)report_type, id, size, buf);
+
+ ALOGV("%s: send_report() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ free(buf);
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean reportErrorNative(JNIEnv* env, jobject thiz, jbyte error) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+
+ bt_status_t ret = sHiddIf->report_error(error);
+
+ ALOGV("%s: report_error() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean unplugNative(JNIEnv* env, jobject thiz) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+
+ bt_status_t ret = sHiddIf->virtual_cable_unplug();
+
+ ALOGV("%s: virtual_cable_unplug() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean connectNative(JNIEnv* env, jobject thiz) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+
+ bt_status_t ret = sHiddIf->connect();
+
+ ALOGV("%s: connect() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static jboolean disconnectNative(JNIEnv* env, jobject thiz) {
+ ALOGV("%s enter", __FUNCTION__);
+
+ jboolean result = JNI_FALSE;
+
+ bt_status_t ret = sHiddIf->disconnect();
+
+ ALOGV("%s: disconnect() returned %d", __FUNCTION__, ret);
+
+ if (ret == BT_STATUS_SUCCESS) {
+ result = JNI_TRUE;
+ }
+
+ ALOGV("%s done (%d)", __FUNCTION__, result);
+
+ return result;
+}
+
+static JNINativeMethod sMethods[] = {
+ {"classInitNative", "()V", (void*)classInitNative},
+ {"initNative", "()V", (void*)initNative},
+ {"cleanupNative", "()V", (void*)cleanupNative},
+ {"registerAppNative",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;B[B[I[I)Z",
+ (void*)registerAppNative},
+ {"unregisterAppNative", "()Z", (void*)unregisterAppNative},
+ {"sendReportNative", "(I[B)Z", (void*)sendReportNative},
+ {"replyReportNative", "(BB[B)Z", (void*)replyReportNative},
+ {"reportErrorNative", "(B)Z", (void*)reportErrorNative},
+ {"unplugNative", "()Z", (void*)unplugNative},
+ {"connectNative", "()Z", (void*)connectNative},
+ {"disconnectNative", "()Z", (void*)disconnectNative},
+};
+
+int register_com_android_bluetooth_hidd(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/bluetooth/hid/HidDevService",
+ sMethods, NELEM(sMethods));
+}
+}
diff --git a/res/values/config.xml b/res/values/config.xml
index 3a5f95e..4282d29 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -30,6 +30,7 @@
<bool name="profile_supported_sap">false</bool>
<bool name="profile_supported_pbapclient">false</bool>
<bool name="profile_supported_mapmce">false</bool>
+ <bool name="profile_supported_hidd">false</bool>
<!-- If true, we will require location to be enabled on the device to
fire Bluetooth LE scan result callbacks in addition to having one
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 57ab1d7..7cba064 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -39,6 +39,7 @@
import com.android.bluetooth.mapclient.MapClientService;
import com.android.bluetooth.sap.SapService;
import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.hid.HidDevService;
public class Config {
private static final String TAG = "AdapterServiceConfig";
@@ -46,27 +47,20 @@
* List of profile services.
*/
@SuppressWarnings("rawtypes")
- //Do not inclue OPP and PBAP, because their services
- //are not managed by AdapterService
+ // Do not inclue OPP and PBAP, because their services
+ // are not managed by AdapterService
private static final Class[] PROFILE_SERVICES = {
- HeadsetService.class,
- A2dpService.class,
- A2dpSinkService.class,
- HidService.class,
- HealthService.class,
- PanService.class,
- GattService.class,
- BluetoothMapService.class,
- HeadsetClientService.class,
- AvrcpControllerService.class,
- SapService.class,
- PbapClientService.class,
- MapClientService.class
- };
+ HeadsetService.class, A2dpService.class,
+ A2dpSinkService.class, HidService.class,
+ HealthService.class, PanService.class,
+ GattService.class, BluetoothMapService.class,
+ HeadsetClientService.class, AvrcpControllerService.class,
+ SapService.class, PbapClientService.class,
+ MapClientService.class, HidDevService.class};
/**
* Resource flag to indicate whether profile is supported or not.
*/
- private static final int[] PROFILE_SERVICES_FLAG = {
+ private static final int[] PROFILE_SERVICES_FLAG = {
R.bool.profile_supported_hs_hfp,
R.bool.profile_supported_a2dp,
R.bool.profile_supported_a2dp_sink,
@@ -79,8 +73,8 @@
R.bool.profile_supported_avrcp_controller,
R.bool.profile_supported_sap,
R.bool.profile_supported_pbapclient,
- R.bool.profile_supported_mapmce
- };
+ R.bool.profile_supported_mapmce,
+ R.bool.profile_supported_hidd};
private static Class[] SUPPORTED_PROFILES = new Class[0];
@@ -166,6 +160,8 @@
profileIndex = BluetoothProfile.PBAP_CLIENT;
} else if (profile == MapClientService.class) {
profileIndex = BluetoothProfile.MAP_CLIENT;
+ } else if (profile == HidDevService.class) {
+ profileIndex = BluetoothProfile.HID_DEVICE;
}
return profileIndex;
diff --git a/src/com/android/bluetooth/hid/HidDevService.java b/src/com/android/bluetooth/hid/HidDevService.java
new file mode 100644
index 0000000..a4662c9
--- /dev/null
+++ b/src/com/android/bluetooth/hid/HidDevService.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.hid;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidDeviceAppConfiguration;
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHidDevice;
+import android.bluetooth.IBluetoothHidDeviceCallback;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+/** @hide */
+public class HidDevService extends ProfileService {
+
+ private static final boolean DBG = true;
+
+ private static final String TAG = HidDevService.class.getSimpleName();
+
+ private static final int MESSAGE_APPLICATION_STATE_CHANGED = 1;
+ private static final int MESSAGE_CONNECT_STATE_CHANGED = 2;
+ private static final int MESSAGE_GET_REPORT = 3;
+ private static final int MESSAGE_SET_REPORT = 4;
+ private static final int MESSAGE_SET_PROTOCOL = 5;
+ private static final int MESSAGE_INTR_DATA = 6;
+ private static final int MESSAGE_VC_UNPLUG = 7;
+
+ private boolean mNativeAvailable = false;
+
+ private BluetoothDevice mHidDevice = null;
+
+ private int mHidDeviceState = BluetoothHidDevice.STATE_DISCONNECTED;
+
+ private BluetoothHidDeviceAppConfiguration mAppConfig = null;
+
+ private IBluetoothHidDeviceCallback mCallback = null;
+
+ private BluetoothHidDeviceDeathRecipient mDeathRcpt;
+
+ static { classInitNative(); }
+
+ private final Handler mHandler = new Handler() {
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG)
+ Log.v(TAG, "handleMessage(): msg.what=" + msg.what);
+
+ switch (msg.what) {
+ case MESSAGE_APPLICATION_STATE_CHANGED: {
+ BluetoothDevice device =
+ msg.obj != null ? getDevice((byte[])msg.obj) : null;
+ boolean success = (msg.arg1 != 0);
+
+ try {
+ if (mCallback != null)
+ mCallback.onAppStatusChanged(device, mAppConfig, success);
+ else
+ break;
+ } catch (RemoteException e) {
+ Log.e(TAG, "e=" + e.toString());
+ e.printStackTrace();
+ }
+
+ if (success) {
+ mDeathRcpt = new BluetoothHidDeviceDeathRecipient(HidDevService.this,
+ mAppConfig);
+ if (mCallback != null) {
+ IBinder binder = mCallback.asBinder();
+ try {
+ binder.linkToDeath(mDeathRcpt, 0);
+ Log.i(TAG, "IBinder.linkToDeath() ok");
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ } else if (mDeathRcpt != null) {
+ if (mCallback != null) {
+ IBinder binder = mCallback.asBinder();
+ try {
+ binder.unlinkToDeath(mDeathRcpt, 0);
+ Log.i(TAG, "IBinder.unlinkToDeath() ok");
+ } catch (NoSuchElementException e) {
+ e.printStackTrace();
+ }
+ mDeathRcpt.cleanup();
+ mDeathRcpt = null;
+ }
+ }
+
+ if (!success) {
+ mAppConfig = null;
+ mCallback = null;
+ }
+
+ break;
+ }
+
+ case MESSAGE_CONNECT_STATE_CHANGED: {
+ BluetoothDevice device = getDevice((byte[])msg.obj);
+ int halState = msg.arg1;
+ int state = convertHalState(halState);
+
+ try {
+ if (mCallback != null)
+ mCallback.onConnectionStateChanged(device, state);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+
+ broadcastConnectionState(device, state);
+ break;
+ }
+
+ case MESSAGE_GET_REPORT:
+ byte type = (byte)msg.arg1;
+ byte id = (byte)msg.arg2;
+ int bufferSize = msg.obj == null ? 0 : ((Integer)msg.obj).intValue();
+
+ try {
+ if (mCallback != null)
+ mCallback.onGetReport(type, id, bufferSize);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ break;
+
+ case MESSAGE_SET_REPORT: {
+ byte reportType = (byte)msg.arg1;
+ byte reportId = (byte)msg.arg2;
+ byte[] data = ((ByteBuffer)msg.obj).array();
+
+ try {
+ if (mCallback != null)
+ mCallback.onSetReport(reportType, reportId, data);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ break;
+ }
+
+ case MESSAGE_SET_PROTOCOL:
+ byte protocol = (byte)msg.arg1;
+
+ try {
+ if (mCallback != null)
+ mCallback.onSetProtocol(protocol);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ break;
+
+ case MESSAGE_INTR_DATA:
+ byte reportId = (byte)msg.arg1;
+ byte[] data = ((ByteBuffer)msg.obj).array();
+
+ try {
+ if (mCallback != null)
+ mCallback.onIntrData(reportId, data);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ break;
+
+ case MESSAGE_VC_UNPLUG:
+ try {
+ if (mCallback != null)
+ mCallback.onVirtualCableUnplug();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ break;
+ }
+ }
+ };
+
+ private static class BluetoothHidDeviceDeathRecipient
+ implements IBinder.DeathRecipient {
+ private HidDevService mService;
+ private BluetoothHidDeviceAppConfiguration mAppConfig;
+
+ public BluetoothHidDeviceDeathRecipient(
+ HidDevService service, BluetoothHidDeviceAppConfiguration config) {
+ mService = service;
+ mAppConfig = config;
+ }
+
+ @Override
+ public void binderDied() {
+ Log.w(TAG, "Binder died, need to unregister app :(");
+ mService.unregisterApp(mAppConfig);
+ }
+
+ public void cleanup() {
+ mService = null;
+ mAppConfig = null;
+ }
+ }
+
+ private static class BluetoothHidDeviceBinder
+ extends IBluetoothHidDevice.Stub implements IProfileServiceBinder {
+
+ private static final String TAG =
+ BluetoothHidDeviceBinder.class.getSimpleName();
+
+ private HidDevService mService;
+
+ public BluetoothHidDeviceBinder(HidDevService service) {
+ mService = service;
+ }
+
+ @Override
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ private HidDevService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "HidDevice call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable()) {
+ return mService;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean registerApp(BluetoothHidDeviceAppConfiguration config,
+ BluetoothHidDeviceAppSdpSettings sdp,
+ BluetoothHidDeviceAppQosSettings inQos,
+ BluetoothHidDeviceAppQosSettings outQos,
+ IBluetoothHidDeviceCallback callback) {
+ if (DBG)
+ Log.v(TAG, "registerApp()");
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.registerApp(config, sdp, inQos, outQos, callback);
+ }
+
+ @Override
+ public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
+ if (DBG)
+ Log.v(TAG, "unregisterApp()");
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.unregisterApp(config);
+ }
+
+ @Override
+ public boolean sendReport(int id, byte[] data) {
+ if (DBG)
+ Log.v(TAG, "sendReport(): id=" + id);
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.sendReport(id, data);
+ }
+
+ @Override
+ public boolean replyReport(byte type, byte id, byte[] data) {
+ if (DBG)
+ Log.v(TAG, "replyReport(): type=" + type + " id=" + id);
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.replyReport(type, id, data);
+ }
+
+ @Override
+ public boolean unplug() {
+ if (DBG)
+ Log.v(TAG, "unplug()");
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.unplug();
+ }
+
+ @Override
+ public boolean connect() {
+ if (DBG)
+ Log.v(TAG, "connect()");
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.connect();
+ }
+
+ @Override
+ public boolean disconnect() {
+ if (DBG)
+ Log.v(TAG, "disconnect()");
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.disconnect();
+ }
+
+ @Override
+ public boolean reportError(byte error) {
+ if (DBG)
+ Log.v(TAG, "reportError(), error = " + error);
+
+ HidDevService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.reportError(error);
+ }
+ }
+
+ @Override
+ protected IProfileServiceBinder initBinder() {
+ return new BluetoothHidDeviceBinder(this);
+ }
+
+ synchronized boolean registerApp(BluetoothHidDeviceAppConfiguration config,
+ BluetoothHidDeviceAppSdpSettings sdp,
+ BluetoothHidDeviceAppQosSettings inQos,
+ BluetoothHidDeviceAppQosSettings outQos,
+ IBluetoothHidDeviceCallback callback) {
+ if (DBG)
+ Log.v(TAG, "registerApp()");
+
+ if (mAppConfig != null) {
+ return false;
+ }
+
+ mAppConfig = config;
+ mCallback = callback;
+
+ return registerAppNative(sdp.name, sdp.description, sdp.provider,
+ sdp.subclass, sdp.descriptors,
+ inQos == null ? null : inQos.toArray(),
+ outQos == null ? null : outQos.toArray());
+ }
+
+ synchronized boolean
+ unregisterApp(BluetoothHidDeviceAppConfiguration config) {
+ if (DBG)
+ Log.v(TAG, "unregisterApp()");
+
+ if (mAppConfig == null || config == null || !config.equals(mAppConfig)) {
+ return false;
+ }
+
+ return unregisterAppNative();
+ }
+
+ synchronized boolean sendReport(int id, byte[] data) {
+ if (DBG)
+ Log.v(TAG, "sendReport(): id=" + id);
+
+ return sendReportNative(id, data);
+ }
+
+ synchronized boolean replyReport(byte type, byte id, byte[] data) {
+ if (DBG)
+ Log.v(TAG, "replyReport(): type=" + type + " id=" + id);
+
+ return replyReportNative(type, id, data);
+ }
+
+ synchronized boolean unplug() {
+ if (DBG)
+ Log.v(TAG, "unplug()");
+
+ return unplugNative();
+ }
+
+ synchronized boolean connect() {
+ if (DBG)
+ Log.v(TAG, "connect()");
+
+ return connectNative();
+ }
+
+ synchronized boolean disconnect() {
+ if (DBG)
+ Log.v(TAG, "disconnect()");
+
+ return disconnectNative();
+ }
+
+ synchronized boolean reportError(byte error) {
+ if (DBG)
+ Log.v(TAG, "reportError(): error = " + error);
+
+ return reportErrorNative(error);
+ }
+
+ @Override
+ protected boolean start() {
+ if (DBG)
+ Log.d(TAG, "start()");
+
+ initNative();
+ mNativeAvailable = true;
+
+ return true;
+ }
+
+ @Override
+ protected boolean stop() {
+ if (DBG)
+ Log.d(TAG, "stop()");
+
+ return true;
+ }
+
+ @Override
+ protected boolean cleanup() {
+ if (DBG)
+ Log.d(TAG, "cleanup()");
+
+ if (mNativeAvailable) {
+ cleanupNative();
+ mNativeAvailable = false;
+ }
+
+ return true;
+ }
+
+ private synchronized void onApplicationStateChanged(byte[] address,
+ boolean registered) {
+ if (DBG)
+ Log.v(TAG, "onApplicationStateChanged(): registered=" + registered);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_APPLICATION_STATE_CHANGED);
+ msg.obj = address;
+ msg.arg1 = registered ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+
+ private synchronized void onConnectStateChanged(byte[] address, int state) {
+ if (DBG)
+ Log.v(TAG, "onConnectStateChanged(): address=" +
+ Arrays.toString(address) + " state=" + state);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
+ msg.obj = address;
+ msg.arg1 = state;
+ mHandler.sendMessage(msg);
+ }
+
+ private synchronized void onGetReport(byte type, byte id, short bufferSize) {
+ if (DBG)
+ Log.v(TAG, "onGetReport(): type=" + type + " id=" + id + " bufferSize=" +
+ bufferSize);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
+ msg.obj = bufferSize > 0 ? new Integer(bufferSize) : null;
+ msg.arg1 = type;
+ msg.arg2 = id;
+ mHandler.sendMessage(msg);
+ }
+
+ private synchronized void onSetReport(byte reportType, byte reportId,
+ byte[] data) {
+ if (DBG)
+ Log.v(TAG, "onSetReport(): reportType=" + reportType + " reportId=" +
+ reportId);
+
+ ByteBuffer bb = ByteBuffer.wrap(data);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
+ msg.arg1 = reportType;
+ msg.arg2 = reportId;
+ msg.obj = bb;
+ mHandler.sendMessage(msg);
+ }
+
+ private synchronized void onSetProtocol(byte protocol) {
+ if (DBG)
+ Log.v(TAG, "onSetProtocol(): protocol=" + protocol);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL);
+ msg.arg1 = protocol;
+ mHandler.sendMessage(msg);
+ }
+
+ private synchronized void onIntrData(byte reportId, byte[] data) {
+ if (DBG)
+ Log.v(TAG, "onIntrData(): reportId=" + reportId);
+
+ ByteBuffer bb = ByteBuffer.wrap(data);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_INTR_DATA);
+ msg.arg1 = reportId;
+ msg.obj = bb;
+ mHandler.sendMessage(msg);
+ }
+
+ private synchronized void onVirtualCableUnplug() {
+ if (DBG)
+ Log.v(TAG, "onVirtualCableUnplug()");
+
+ Message msg = mHandler.obtainMessage(MESSAGE_VC_UNPLUG);
+ mHandler.sendMessage(msg);
+ }
+
+ private void broadcastConnectionState(BluetoothDevice device, int newState) {
+ if (DBG)
+ Log.v(TAG, "broadcastConnectionState(): device=" + device.getAddress() +
+ " newState=" + newState);
+
+ if (mHidDevice != null && mHidDevice != device) {
+ Log.w(TAG, "Connection state changed for unknown device, ignoring");
+ return;
+ }
+
+ int prevState = mHidDeviceState;
+ mHidDeviceState = newState;
+
+ Log.i(TAG, "connection state for " + device.getAddress() + ": " +
+ prevState + " -> " + newState);
+
+ if (prevState == newState) {
+ return;
+ }
+
+ notifyProfileConnectionStateChanged(device, BluetoothProfile.HID_DEVICE,
+ newState, prevState);
+
+ Intent intent =
+ new Intent(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ sendBroadcast(intent, BLUETOOTH_PERM);
+ }
+
+ private static int convertHalState(int halState) {
+ switch (halState) {
+ case CONN_STATE_CONNECTED:
+ return BluetoothProfile.STATE_CONNECTED;
+ case CONN_STATE_CONNECTING:
+ return BluetoothProfile.STATE_CONNECTING;
+ case CONN_STATE_DISCONNECTED:
+ return BluetoothProfile.STATE_DISCONNECTED;
+ case CONN_STATE_DISCONNECTING:
+ return BluetoothProfile.STATE_DISCONNECTING;
+ default:
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ }
+
+ private final static int CONN_STATE_CONNECTED = 0;
+ private final static int CONN_STATE_CONNECTING = 1;
+ private final static int CONN_STATE_DISCONNECTED = 2;
+ private final static int CONN_STATE_DISCONNECTING = 3;
+
+ private native static void classInitNative();
+ private native void initNative();
+ private native void cleanupNative();
+ private native boolean registerAppNative(String name, String description,
+ String provider, byte subclass,
+ byte[] descriptors, int[] inQos,
+ int[] outQos);
+ private native boolean unregisterAppNative();
+ private native boolean sendReportNative(int id, byte[] data);
+ private native boolean replyReportNative(byte type, byte id, byte[] data);
+ private native boolean unplugNative();
+ private native boolean connectNative();
+ private native boolean disconnectNative();
+ private native boolean reportErrorNative(byte error);
+}