| /* |
| * Copyright (C) 2014 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 "HdmiCecControllerJni" |
| |
| #define LOG_NDEBUG 1 |
| |
| #include <JNIHelp.h> |
| #include <ScopedPrimitiveArray.h> |
| |
| #include <cstring> |
| |
| #include <android_os_MessageQueue.h> |
| #include <android_runtime/AndroidRuntime.h> |
| #include <android_runtime/Log.h> |
| #include <hardware/hdmi_cec.h> |
| #include <sys/param.h> |
| #include <utils/Looper.h> |
| #include <utils/RefBase.h> |
| |
| namespace android { |
| |
| static struct { |
| jmethodID handleIncomingCecCommand; |
| jmethodID handleHotplug; |
| } gHdmiCecControllerClassInfo; |
| |
| class HdmiCecController { |
| public: |
| HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj, |
| const sp<Looper>& looper); |
| |
| void init(); |
| |
| // Send message to other device. Note that it runs in IO thread. |
| int sendMessage(const cec_message_t& message); |
| // Add a logical address to device. |
| int addLogicalAddress(cec_logical_address_t address); |
| // Clear all logical address registered to the device. |
| void clearLogicaladdress(); |
| // Get physical address of device. |
| int getPhysicalAddress(); |
| // Get CEC version from driver. |
| int getVersion(); |
| // Get vendor id used for vendor command. |
| uint32_t getVendorId(); |
| // Get Port information on all the HDMI ports. |
| jobjectArray getPortInfos(); |
| // Set a flag and its value. |
| void setOption(int flag, int value); |
| // Set audio return channel status. |
| void setAudioReturnChannel(bool flag); |
| // Whether to hdmi device is connected to the given port. |
| bool isConnected(int port); |
| |
| jobject getCallbacksObj() const { |
| return mCallbacksObj; |
| } |
| |
| private: |
| static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF; |
| static void onReceived(const hdmi_event_t* event, void* arg); |
| |
| hdmi_cec_device_t* mDevice; |
| jobject mCallbacksObj; |
| sp<Looper> mLooper; |
| }; |
| |
| // RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL |
| // may keep its own lifetime, we need to copy it in order to delegate |
| // it to service thread. |
| class CecEventWrapper : public LightRefBase<CecEventWrapper> { |
| public: |
| CecEventWrapper(const hdmi_event_t& event) { |
| // Copy message. |
| switch (event.type) { |
| case HDMI_EVENT_CEC_MESSAGE: |
| mEvent.cec.initiator = event.cec.initiator; |
| mEvent.cec.destination = event.cec.destination; |
| mEvent.cec.length = event.cec.length; |
| std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length); |
| break; |
| case HDMI_EVENT_HOT_PLUG: |
| mEvent.hotplug.connected = event.hotplug.connected; |
| mEvent.hotplug.port_id = event.hotplug.port_id; |
| break; |
| default: |
| // TODO: add more type whenever new type is introduced. |
| break; |
| } |
| } |
| |
| const cec_message_t& cec() const { |
| return mEvent.cec; |
| } |
| |
| const hotplug_event_t& hotplug() const { |
| return mEvent.hotplug; |
| } |
| |
| virtual ~CecEventWrapper() {} |
| |
| private: |
| hdmi_event_t mEvent; |
| }; |
| |
| // Handler class to delegate incoming message to service thread. |
| class HdmiCecEventHandler : public MessageHandler { |
| public: |
| HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event) |
| : mController(controller), |
| mEventWrapper(event) { |
| } |
| |
| virtual ~HdmiCecEventHandler() {} |
| |
| void handleMessage(const Message& message) { |
| switch (message.what) { |
| case HDMI_EVENT_CEC_MESSAGE: |
| propagateCecCommand(mEventWrapper->cec()); |
| break; |
| case HDMI_EVENT_HOT_PLUG: |
| propagateHotplugEvent(mEventWrapper->hotplug()); |
| break; |
| default: |
| // TODO: add more type whenever new type is introduced. |
| break; |
| } |
| } |
| |
| private: |
| // Propagate the message up to Java layer. |
| void propagateCecCommand(const cec_message_t& message) { |
| jint srcAddr = message.initiator; |
| jint dstAddr = message.destination; |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| jbyteArray body = env->NewByteArray(message.length); |
| const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body); |
| env->SetByteArrayRegion(body, 0, message.length, bodyPtr); |
| |
| env->CallVoidMethod(mController->getCallbacksObj(), |
| gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr, |
| dstAddr, body); |
| env->DeleteLocalRef(body); |
| |
| checkAndClearExceptionFromCallback(env, __FUNCTION__); |
| } |
| |
| void propagateHotplugEvent(const hotplug_event_t& event) { |
| // Note that this method should be called in service thread. |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| jint port = event.port_id; |
| jboolean connected = (jboolean) event.connected; |
| env->CallVoidMethod(mController->getCallbacksObj(), |
| gHdmiCecControllerClassInfo.handleHotplug, port, connected); |
| |
| checkAndClearExceptionFromCallback(env, __FUNCTION__); |
| } |
| |
| // static |
| static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { |
| if (env->ExceptionCheck()) { |
| ALOGE("An exception was thrown by callback '%s'.", methodName); |
| LOGE_EX(env); |
| env->ExceptionClear(); |
| } |
| } |
| |
| HdmiCecController* mController; |
| sp<CecEventWrapper> mEventWrapper; |
| }; |
| |
| HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, |
| jobject callbacksObj, const sp<Looper>& looper) : |
| mDevice(device), |
| mCallbacksObj(callbacksObj), |
| mLooper(looper) { |
| } |
| |
| void HdmiCecController::init() { |
| mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this); |
| } |
| |
| int HdmiCecController::sendMessage(const cec_message_t& message) { |
| // TODO: propagate send_message's return value. |
| return mDevice->send_message(mDevice, &message); |
| } |
| |
| int HdmiCecController::addLogicalAddress(cec_logical_address_t address) { |
| return mDevice->add_logical_address(mDevice, address); |
| } |
| |
| void HdmiCecController::clearLogicaladdress() { |
| mDevice->clear_logical_address(mDevice); |
| } |
| |
| int HdmiCecController::getPhysicalAddress() { |
| uint16_t addr; |
| if (!mDevice->get_physical_address(mDevice, &addr)) { |
| return addr; |
| } |
| return INVALID_PHYSICAL_ADDRESS; |
| } |
| |
| int HdmiCecController::getVersion() { |
| int version = 0; |
| mDevice->get_version(mDevice, &version); |
| return version; |
| } |
| |
| uint32_t HdmiCecController::getVendorId() { |
| uint32_t vendorId = 0; |
| mDevice->get_vendor_id(mDevice, &vendorId); |
| return vendorId; |
| } |
| |
| jobjectArray HdmiCecController::getPortInfos() { |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo"); |
| if (hdmiPortInfo == NULL) { |
| return NULL; |
| } |
| jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V"); |
| if (ctor == NULL) { |
| return NULL; |
| } |
| hdmi_port_info* ports; |
| int numPorts; |
| mDevice->get_port_info(mDevice, &ports, &numPorts); |
| jobjectArray res = env->NewObjectArray(numPorts, hdmiPortInfo, NULL); |
| |
| // MHL support field will be obtained from MHL HAL. Leave it to false. |
| jboolean mhlSupported = (jboolean) 0; |
| for (int i = 0; i < numPorts; ++i) { |
| hdmi_port_info* info = &ports[i]; |
| jboolean cecSupported = (jboolean) info->cec_supported; |
| jboolean arcSupported = (jboolean) info->arc_supported; |
| jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_id, info->type, |
| info->physical_address, cecSupported, mhlSupported, arcSupported); |
| env->SetObjectArrayElement(res, i, infoObj); |
| } |
| return res; |
| } |
| |
| void HdmiCecController::setOption(int flag, int value) { |
| mDevice->set_option(mDevice, flag, value); |
| } |
| |
| // Set audio return channel status. |
| void HdmiCecController::setAudioReturnChannel(bool enabled) { |
| mDevice->set_audio_return_channel(mDevice, enabled ? 1 : 0); |
| } |
| |
| // Whether to hdmi device is connected to the given port. |
| bool HdmiCecController::isConnected(int port) { |
| return mDevice->is_connected(mDevice, port) == HDMI_CONNECTED; |
| } |
| |
| // static |
| void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) { |
| HdmiCecController* controller = static_cast<HdmiCecController*>(arg); |
| if (controller == NULL) { |
| return; |
| } |
| |
| sp<CecEventWrapper> spEvent(new CecEventWrapper(*event)); |
| sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent)); |
| controller->mLooper->sendMessage(handler, event->type); |
| } |
| |
| //------------------------------------------------------------------------------ |
| #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ |
| var = env->GetMethodID(clazz, methodName, methodDescriptor); \ |
| LOG_FATAL_IF(! var, "Unable to find method " methodName); |
| |
| static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj, |
| jobject messageQueueObj) { |
| int err; |
| hw_module_t* module; |
| err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID, |
| const_cast<const hw_module_t **>(&module)); |
| if (err != 0) { |
| ALOGE("Error acquiring hardware module: %d", err); |
| return 0; |
| } |
| |
| hw_device_t* device; |
| err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device); |
| if (err != 0) { |
| ALOGE("Error opening hardware module: %d", err); |
| return 0; |
| } |
| |
| sp<MessageQueue> messageQueue = |
| android_os_MessageQueue_getMessageQueue(env, messageQueueObj); |
| |
| HdmiCecController* controller = new HdmiCecController( |
| reinterpret_cast<hdmi_cec_device*>(device), |
| env->NewGlobalRef(callbacksObj), |
| messageQueue->getLooper()); |
| controller->init(); |
| |
| GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz, |
| "handleIncomingCecCommand", "(II[B)V"); |
| GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz, |
| "handleHotplug", "(IZ)V"); |
| |
| return reinterpret_cast<jlong>(controller); |
| } |
| |
| static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, |
| jint srcAddr, jint dstAddr, jbyteArray body) { |
| cec_message_t message; |
| message.initiator = static_cast<cec_logical_address_t>(srcAddr); |
| message.destination = static_cast<cec_logical_address_t>(dstAddr); |
| |
| jsize len = env->GetArrayLength(body); |
| message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH); |
| ScopedByteArrayRO bodyPtr(env, body); |
| std::memcpy(message.body, bodyPtr.get(), len); |
| |
| HdmiCecController* controller = |
| reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->sendMessage(message); |
| } |
| |
| static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr, |
| jint logicalAddress) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->addLogicalAddress(static_cast<cec_logical_address_t>(logicalAddress)); |
| } |
| |
| static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| controller->clearLogicaladdress(); |
| } |
| |
| static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->getPhysicalAddress(); |
| } |
| |
| static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->getVersion(); |
| } |
| |
| static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->getVendorId(); |
| } |
| |
| static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->getPortInfos(); |
| } |
| |
| static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| controller->setOption(flag, value); |
| } |
| |
| static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr, |
| jboolean enabled) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| controller->setAudioReturnChannel(enabled == JNI_TRUE); |
| } |
| |
| static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) { |
| HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); |
| return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ; |
| } |
| |
| static JNINativeMethod sMethods[] = { |
| /* name, signature, funcPtr */ |
| { "nativeInit", |
| "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J", |
| (void *) nativeInit }, |
| { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand }, |
| { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress }, |
| { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress }, |
| { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress }, |
| { "nativeGetVersion", "(J)I", (void *) nativeGetVersion }, |
| { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId }, |
| { "nativeGetPortInfos", |
| "(J)[Landroid/hardware/hdmi/HdmiPortInfo;", |
| (void *) nativeGetPortInfos }, |
| { "nativeSetOption", "(JII)V", (void *) nativeSetOption }, |
| { "nativeSetAudioReturnChannel", "(JZ)V", (void *) nativeSetAudioReturnChannel }, |
| { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected }, |
| }; |
| |
| #define CLASS_PATH "com/android/server/hdmi/HdmiCecController" |
| |
| int register_android_server_hdmi_HdmiCecController(JNIEnv* env) { |
| int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods)); |
| LOG_FATAL_IF(res < 0, "Unable to register native methods."); |
| return 0; |
| } |
| |
| } /* namespace android */ |