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);
+}