blob: 5dc21227f4c97ca83068c82e8a637b9f063a2efb [file] [log] [blame]
/******************************************************************************
*
* Copyright 2021 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 "BluetoothMCPService_jni"
#define LOG_NDEBUG 0
#include <base/bind.h>
#include <base/callback.h>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <vector>
#include "android_runtime/AndroidRuntime.h"
#include "base/logging.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_mcp.h"
#include "hardware/bluetooth.h"
using bluetooth::mcp_server::McpServerCallbacks;
using bluetooth::mcp_server::McpServerInterface;
using bluetooth::Uuid;
static McpServerInterface* sMcpServerInterface = nullptr;
namespace android {
static jmethodID method_OnConnectionStateChanged;
static jmethodID method_MediaControlPointChangedRequest;
static jmethodID method_TrackPositionChangedRequest;
static jmethodID method_PlayingOrderChangedRequest;
static std::shared_timed_mutex interface_mutex;
static jobject mCallbacksObj = nullptr;
static std::shared_timed_mutex callbacks_mutex;
class McpServerCallbacksImpl : public McpServerCallbacks {
public:
~McpServerCallbacksImpl() = default;
void OnConnectionStateChange(int state, const RawAddress& bd_addr) override {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnConnectionStateChanged,
(jint)state, addr.get());
}
void MediaControlPointChangeReq(uint8_t state, const RawAddress& bd_addr) override {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
return;
}
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(jbyte*)&bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_MediaControlPointChangedRequest,
(jint)state, addr.get());
}
void TrackPositionChangeReq(int32_t position) override {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_TrackPositionChangedRequest,
(jint)position);
}
void PlayingOrderChangeReq(uint32_t order) override {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_PlayingOrderChangedRequest,
(jint)order);
}
};
static McpServerCallbacksImpl sMcpServerCallbacks;
static void classInitNative(JNIEnv* env, jclass clazz) {
LOG(INFO) << __func__ << ": class init native";
method_OnConnectionStateChanged =
env->GetMethodID(clazz, "OnConnectionStateChanged", "(I[B)V");
LOG(INFO) << __func__ << ": class init native 1";
method_MediaControlPointChangedRequest =
env->GetMethodID(clazz, "MediaControlPointChangedRequest", "(I[B)V");
LOG(INFO) << __func__ << ": class init native 2";
method_TrackPositionChangedRequest =
env->GetMethodID(clazz, "TrackPositionChangedRequest", "(I)V");
method_PlayingOrderChangedRequest =
env->GetMethodID(clazz, "PlayingOrderChangedRequest", "(I)V");
LOG(INFO) << __func__ << ": succeeds";
}
//<TBD> uuid not fixed
Uuid uuid = Uuid::FromString("00008fd1-0000-1000-8000-00805F9B34FB");
static void initNative(JNIEnv* env, jobject object) {
std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == nullptr) {
LOG(ERROR) << "Bluetooth module is not loaded";
return;
}
if (sMcpServerInterface != nullptr) {
LOG(INFO) << "Cleaning up McpServer Interface before initializing...";
sMcpServerInterface->Cleanup();
sMcpServerInterface = nullptr;
}
if (mCallbacksObj != nullptr) {
LOG(INFO) << "Cleaning up McpServer callback object";
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = nullptr;
}
if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
LOG(ERROR) << "Failed to allocate Global Ref for Mcp Controller Callbacks";
return;
}
LOG(INFO) << "mcs callback initialized";
sMcpServerInterface = (McpServerInterface* )btInf->get_profile_interface(
BT_PROFILE_MCP_ID);
if (sMcpServerInterface == nullptr) {
LOG(ERROR) << "Failed to get Bluetooth Hearing Aid Interface";
return;
}
sMcpServerInterface->Init(&sMcpServerCallbacks, uuid);
}
static void cleanupNative(JNIEnv* env, jobject object) {
std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == nullptr) {
LOG(ERROR) << "Bluetooth module is not loaded";
return;
}
if (sMcpServerInterface != nullptr) {
sMcpServerInterface->Cleanup();
sMcpServerInterface = nullptr;
}
if (mCallbacksObj != nullptr) {
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = nullptr;
}
}
static jboolean mediaControlPointOpcodeSupportedNative(JNIEnv* env, jobject object,
jint feature) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->MediaControlPointOpcodeSupported(feature);
return JNI_TRUE;
}
static jboolean mediaControlPointNative(JNIEnv* env, jobject object,
jint value) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->MediaControlPoint(value);
return JNI_TRUE;
}
static jboolean mediaStateNative(JNIEnv* env, jobject object,
jint state) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->MediaState(state);
return JNI_TRUE;
}
static jboolean mediaPlayerNameNative(JNIEnv* env, jobject object,
jstring playerName) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
const char *nativeString = env->GetStringUTFChars(playerName, nullptr);
if (!nativeString) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
sMcpServerInterface->MediaPlayerName((uint8_t*)nativeString);
env->ReleaseStringUTFChars(playerName, nativeString);
return JNI_TRUE;
}
static jboolean trackChangedNative(JNIEnv* env, jobject object,
jint status) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->TrackChanged((bool)status);
return JNI_TRUE;
}
static jboolean trackPositionNative(JNIEnv* env, jobject object,
jint playPosition) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->TrackPosition(playPosition);
return JNI_TRUE;
}
static jboolean trackDurationNative(JNIEnv* env, jobject object,
jint duration) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->TrackDuration(duration);
return JNI_TRUE;
}
static jboolean trackTitleNative(JNIEnv* env, jobject object,
jstring title) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
const char *nativeString = env->GetStringUTFChars(title, nullptr);
if (!nativeString) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
sMcpServerInterface->TrackTitle((uint8_t*)nativeString);
env->ReleaseStringUTFChars(title, nativeString);
return JNI_TRUE;
}
static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
jint profile, jint set_id,
jbyteArray address) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, nullptr);
RawAddress bd_addr = RawAddress::kEmpty;
if (addr) {
bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
}
if (bd_addr == RawAddress::kEmpty) {
LOG(INFO) << __func__ << " active device is null";
}
sMcpServerInterface->SetActiveDevice(bd_addr, set_id, profile);
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_TRUE;
}
static jboolean bondStateChangeNative(JNIEnv* env, jobject object,
jint state, jbyteArray address) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, nullptr);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress* tmpraw = (RawAddress*)addr;
sMcpServerInterface->BondStateChange(*tmpraw, state);
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_TRUE;
}
static jboolean playingOrderSupportedNative(JNIEnv* env, jobject object,
jint order) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->PlayingOrderSupported(order);
return JNI_TRUE;
}
static jboolean playingOrderNative(JNIEnv* env, jobject object,
jint order) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->PlayingOrder(order);
return JNI_TRUE;
}
static jboolean contentControlIdNative(JNIEnv* env, jobject object,
jint ccid) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
sMcpServerInterface->ContentControlId(ccid);
return JNI_TRUE;
}
static jboolean disconnectMcpNative(JNIEnv* env, jobject object,
jbyteArray address) {
LOG(INFO) << __func__;
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sMcpServerInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, nullptr);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress* tmpraw = (RawAddress*)addr;
sMcpServerInterface->DisconnectMcp(*tmpraw);
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_TRUE;
}
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initNative", "()V", (void*)initNative},
{"cleanupNative", "()V", (void*)cleanupNative},
{"mediaStateNative", "(I)Z", (void*)mediaStateNative},
{"mediaPlayerNameNative", "(Ljava/lang/String;)Z", (void*)mediaPlayerNameNative},
{"mediaControlPointOpcodeSupportedNative", "(I)Z", (void*)mediaControlPointOpcodeSupportedNative},
{"mediaControlPointNative", "(I)Z", (void*)mediaControlPointNative},
{"trackChangedNative", "(I)Z", (void*)trackChangedNative},
{"trackTitleNative", "(Ljava/lang/String;)Z", (void*)trackTitleNative},
{"trackPositionNative", "(I)Z", (void*)trackPositionNative},
{"trackDurationNative", "(I)Z", (void*)trackDurationNative},
{"playingOrderSupportedNative", "(I)Z", (void*)playingOrderSupportedNative},
{"playingOrderNative", "(I)Z", (void*)playingOrderNative},
{"setActiveDeviceNative", "(II[B)Z", (void*)setActiveDeviceNative},
{"contentControlIdNative", "(I)Z", (void*)contentControlIdNative},
{"disconnectMcpNative", "([B)Z", (void*)disconnectMcpNative},
{"bondStateChangeNative", "(I[B)Z", (void*)bondStateChangeNative},
};
int register_com_android_bluetooth_mcp(JNIEnv* env) {
return jniRegisterNativeMethods(
env, "com/android/bluetooth/mcp/McpNativeInterface",
sMethods, NELEM(sMethods));
}
} // namespace android