Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e9950b10 authored by Hemant Gupta's avatar Hemant Gupta Committed by Ivan Podogov
Browse files

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
parent 529ae49e
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -394,5 +394,12 @@
                android:name="android.accounts.AccountAuthenticator"
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
                android:resource="@xml/authenticator" />
        </service>
        </service>
        <service
            android:name = ".hid.HidDevService"
            android:enabled="@bool/profile_supported_hidd">
            <intent-filter>
                <action android:name="android.bluetooth.IBluetoothHidDevice" />
            </intent-filter>
        </service>
    </application>
    </application>
</manifest>
</manifest>
+1 −0
Original line number Original line Diff line number Diff line
@@ -11,6 +11,7 @@ LOCAL_SRC_FILES:= \
    com_android_bluetooth_avrcp.cpp \
    com_android_bluetooth_avrcp.cpp \
    com_android_bluetooth_avrcp_controller.cpp \
    com_android_bluetooth_avrcp_controller.cpp \
    com_android_bluetooth_hid.cpp \
    com_android_bluetooth_hid.cpp \
    com_android_bluetooth_hidd.cpp \
    com_android_bluetooth_hdp.cpp \
    com_android_bluetooth_hdp.cpp \
    com_android_bluetooth_pan.cpp \
    com_android_bluetooth_pan.cpp \
    com_android_bluetooth_gatt.cpp \
    com_android_bluetooth_gatt.cpp \
+2 −0
Original line number Original line Diff line number Diff line
@@ -84,6 +84,8 @@ int register_com_android_bluetooth_avrcp_controller(JNIEnv* env);


int register_com_android_bluetooth_hid(JNIEnv* env);
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_hdp(JNIEnv* env);


int register_com_android_bluetooth_pan(JNIEnv* env);
int register_com_android_bluetooth_pan(JNIEnv* env);
+6 −0
Original line number Original line Diff line number Diff line
@@ -1321,6 +1321,12 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) {
    return JNI_ERR;
    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);
  status = android::register_com_android_bluetooth_hdp(e);
  if (status < 0) {
  if (status < 0) {
    ALOGE("jni hdp registration failure: %d", status);
    ALOGE("jni hdp registration failure: %d", status);
+504 −0
Original line number Original line Diff line number Diff line
/*
 * 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));
}
}
Loading