/* * 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 #include #include #include #include #include #include #include #include #include namespace android { static struct { jmethodID handleIncomingCecCommand; jmethodID handleHotplug; } gHdmiCecControllerClassInfo; class HdmiCecController { public: HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj, const sp& 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(int port, 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 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 { 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& 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(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 mEventWrapper; }; HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj, const sp& 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, "", "(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(int port, bool enabled) { mDevice->set_audio_return_channel(mDevice, port, 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(arg); if (controller == NULL) { return; } sp spEvent(new CecEventWrapper(*event)); sp 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(&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 = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); HdmiCecController* controller = new HdmiCecController( reinterpret_cast(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(controller); } static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, jint srcAddr, jint dstAddr, jbyteArray body) { cec_message_t message; message.initiator = static_cast(srcAddr); message.destination = static_cast(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(controllerPtr); return controller->sendMessage(message); } static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr, jint logicalAddress) { HdmiCecController* controller = reinterpret_cast(controllerPtr); return controller->addLogicalAddress(static_cast(logicalAddress)); } static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { HdmiCecController* controller = reinterpret_cast(controllerPtr); controller->clearLogicaladdress(); } static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { HdmiCecController* controller = reinterpret_cast(controllerPtr); return controller->getPhysicalAddress(); } static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) { HdmiCecController* controller = reinterpret_cast(controllerPtr); return controller->getVersion(); } static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) { HdmiCecController* controller = reinterpret_cast(controllerPtr); return controller->getVendorId(); } static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) { HdmiCecController* controller = reinterpret_cast(controllerPtr); return controller->getPortInfos(); } static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) { HdmiCecController* controller = reinterpret_cast(controllerPtr); controller->setOption(flag, value); } static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port, jboolean enabled) { HdmiCecController* controller = reinterpret_cast(controllerPtr); controller->setAudioReturnChannel(port, enabled == JNI_TRUE); } static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) { HdmiCecController* controller = reinterpret_cast(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", "(JIZ)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."); (void)res; // Don't scream about unused variable in the LOG_NDEBUG case return 0; } } /* namespace android */