com_android_server_hdmi_HdmiCecController.cpp revision 0340bbc89f8162f9c2a298c98b03bfcdd1bc6e87
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "HdmiCecControllerJni" 18 19#define LOG_NDEBUG 1 20 21#include "JNIHelp.h" 22#include "ScopedPrimitiveArray.h" 23 24#include <cstring> 25 26#include <android_os_MessageQueue.h> 27#include <android_runtime/AndroidRuntime.h> 28#include <android_runtime/Log.h> 29#include <hardware/hdmi_cec.h> 30#include <sys/param.h> 31#include <utils/Looper.h> 32#include <utils/RefBase.h> 33 34namespace android { 35 36static struct { 37 jmethodID handleIncomingCecCommand; 38 jmethodID handleHotplug; 39} gHdmiCecControllerClassInfo; 40 41class HdmiCecController { 42public: 43 HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj, 44 const sp<Looper>& looper); 45 46 void init(); 47 48 // Send message to other device. Note that it runs in IO thread. 49 int sendMessage(const cec_message_t& message); 50 // Add a logical address to device. 51 int addLogicalAddress(cec_logical_address_t address); 52 // Clear all logical address registered to the device. 53 void clearLogicaladdress(); 54 // Get physical address of device. 55 int getPhysicalAddress(); 56 // Get CEC version from driver. 57 int getVersion(); 58 // Get vendor id used for vendor command. 59 uint32_t getVendorId(); 60 // Get Port information on all the HDMI ports. 61 jobjectArray getPortInfos(); 62 // Set a flag and its value. 63 void setOption(int flag, int value); 64 // Set audio return channel status. 65 void setAudioReturnChannel(bool flag); 66 // Whether to hdmi device is connected to the given port. 67 bool isConnected(int port); 68 69 jobject getCallbacksObj() const { 70 return mCallbacksObj; 71 } 72 73private: 74 static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 75 static void onReceived(const hdmi_event_t* event, void* arg); 76 77 hdmi_cec_device_t* mDevice; 78 jobject mCallbacksObj; 79 sp<Looper> mLooper; 80}; 81 82// RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL 83// may keep its own lifetime, we need to copy it in order to delegate 84// it to service thread. 85class CecEventWrapper : public LightRefBase<CecEventWrapper> { 86public: 87 CecEventWrapper(const hdmi_event_t& event) { 88 // Copy message. 89 switch (event.type) { 90 case HDMI_EVENT_CEC_MESSAGE: 91 mEvent.cec.initiator = event.cec.initiator; 92 mEvent.cec.destination = event.cec.destination; 93 mEvent.cec.length = event.cec.length; 94 std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length); 95 break; 96 case HDMI_EVENT_HOT_PLUG: 97 mEvent.hotplug.connected = event.hotplug.connected; 98 mEvent.hotplug.port = event.hotplug.port; 99 break; 100 case HDMI_EVENT_TX_STATUS: 101 mEvent.tx_status.status = event.tx_status.status; 102 mEvent.tx_status.opcode = event.tx_status.opcode; 103 break; 104 default: 105 // TODO: add more type whenever new type is introduced. 106 break; 107 } 108 } 109 110 const cec_message_t& cec() const { 111 return mEvent.cec; 112 } 113 114 const hotplug_event_t& hotplug() const { 115 return mEvent.hotplug; 116 } 117 118 virtual ~CecEventWrapper() {} 119 120private: 121 hdmi_event_t mEvent; 122}; 123 124// Handler class to delegate incoming message to service thread. 125class HdmiCecEventHandler : public MessageHandler { 126public: 127 HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event) 128 : mController(controller), 129 mEventWrapper(event) { 130 } 131 132 virtual ~HdmiCecEventHandler() {} 133 134 void handleMessage(const Message& message) { 135 switch (message.what) { 136 case HDMI_EVENT_CEC_MESSAGE: 137 propagateCecCommand(mEventWrapper->cec()); 138 break; 139 case HDMI_EVENT_HOT_PLUG: 140 propagateHotplugEvent(mEventWrapper->hotplug()); 141 break; 142 case HDMI_EVENT_TX_STATUS: 143 // TODO: propagate this to controller. 144 default: 145 // TODO: add more type whenever new type is introduced. 146 break; 147 } 148 } 149 150private: 151 // Propagate the message up to Java layer. 152 void propagateCecCommand(const cec_message_t& message) { 153 jint srcAddr = message.initiator; 154 jint dstAddr = message.destination; 155 JNIEnv* env = AndroidRuntime::getJNIEnv(); 156 jbyteArray body = env->NewByteArray(message.length); 157 const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body); 158 env->SetByteArrayRegion(body, 0, message.length, bodyPtr); 159 160 env->CallVoidMethod(mController->getCallbacksObj(), 161 gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr, 162 dstAddr, body); 163 env->DeleteLocalRef(body); 164 165 checkAndClearExceptionFromCallback(env, __FUNCTION__); 166 } 167 168 void propagateHotplugEvent(const hotplug_event_t& event) { 169 // Note that this method should be called in service thread. 170 JNIEnv* env = AndroidRuntime::getJNIEnv(); 171 env->CallVoidMethod(mController->getCallbacksObj(), 172 gHdmiCecControllerClassInfo.handleHotplug, event.connected); 173 174 checkAndClearExceptionFromCallback(env, __FUNCTION__); 175 } 176 177 // static 178 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { 179 if (env->ExceptionCheck()) { 180 ALOGE("An exception was thrown by callback '%s'.", methodName); 181 LOGE_EX(env); 182 env->ExceptionClear(); 183 } 184 } 185 186 HdmiCecController* mController; 187 sp<CecEventWrapper> mEventWrapper; 188}; 189 190HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, 191 jobject callbacksObj, const sp<Looper>& looper) : 192 mDevice(device), 193 mCallbacksObj(callbacksObj), 194 mLooper(looper) { 195} 196 197void HdmiCecController::init() { 198 mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this); 199} 200 201int HdmiCecController::sendMessage(const cec_message_t& message) { 202 // TODO: propagate send_message's return value. 203 return mDevice->send_message(mDevice, &message); 204} 205 206int HdmiCecController::addLogicalAddress(cec_logical_address_t address) { 207 return mDevice->add_logical_address(mDevice, address); 208} 209 210void HdmiCecController::clearLogicaladdress() { 211 mDevice->clear_logical_address(mDevice); 212} 213 214int HdmiCecController::getPhysicalAddress() { 215 uint16_t addr; 216 if (!mDevice->get_physical_address(mDevice, &addr)) { 217 return addr; 218 } 219 return INVALID_PHYSICAL_ADDRESS; 220} 221 222int HdmiCecController::getVersion() { 223 int version = 0; 224 mDevice->get_version(mDevice, &version); 225 return version; 226} 227 228uint32_t HdmiCecController::getVendorId() { 229 uint32_t vendorId = 0; 230 mDevice->get_vendor_id(mDevice, &vendorId); 231 return vendorId; 232} 233 234jobjectArray HdmiCecController::getPortInfos() { 235 JNIEnv* env = AndroidRuntime::getJNIEnv(); 236 jclass hdmiPortInfo = env->FindClass("com/android/server/hdmi/HdmiPortInfo"); 237 if (hdmiPortInfo == NULL) { 238 return NULL; 239 } 240 jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V"); 241 if (ctor == NULL) { 242 return NULL; 243 } 244 hdmi_port_info* ports; 245 int numPorts; 246 mDevice->get_port_info(mDevice, &ports, &numPorts); 247 jobjectArray res = env->NewObjectArray(numPorts, hdmiPortInfo, NULL); 248 249 // MHL support field will be obtained from MHL HAL. Leave it to false. 250 jboolean mhlSupported = (jboolean) 0; 251 for (int i = 0; i < numPorts; ++i) { 252 hdmi_port_info* info = &ports[i]; 253 jboolean cecSupported = (jboolean) info->cec_supported; 254 jboolean arcSupported = (jboolean) info->arc_supported; 255 jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_num, info->type, 256 info->physical_address, cecSupported, mhlSupported, arcSupported); 257 env->SetObjectArrayElement(res, i, infoObj); 258 } 259 return res; 260} 261 262void HdmiCecController::setOption(int flag, int value) { 263 mDevice->set_option(mDevice, flag, value); 264} 265 266// Set audio return channel status. 267void HdmiCecController::setAudioReturnChannel(bool enabled) { 268 mDevice->set_audio_return_channel(mDevice, enabled ? 1 : 0); 269} 270 271// Whether to hdmi device is connected to the given port. 272bool HdmiCecController::isConnected(int port) { 273 return mDevice->is_connected(mDevice, port) == HDMI_CONNECTED; 274} 275 276// static 277void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) { 278 HdmiCecController* controller = static_cast<HdmiCecController*>(arg); 279 if (controller == NULL) { 280 return; 281 } 282 283 sp<CecEventWrapper> spEvent(new CecEventWrapper(*event)); 284 sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent)); 285 controller->mLooper->sendMessage(handler, event->type); 286} 287 288//------------------------------------------------------------------------------ 289#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ 290 var = env->GetMethodID(clazz, methodName, methodDescriptor); \ 291 LOG_FATAL_IF(! var, "Unable to find method " methodName); 292 293static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj, 294 jobject messageQueueObj) { 295 int err; 296 hw_module_t* module; 297 err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID, 298 const_cast<const hw_module_t **>(&module)); 299 if (err != 0) { 300 ALOGE("Error acquiring hardware module: %d", err); 301 return 0; 302 } 303 304 hw_device_t* device; 305 err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device); 306 if (err != 0) { 307 ALOGE("Error opening hardware module: %d", err); 308 return 0; 309 } 310 311 sp<MessageQueue> messageQueue = 312 android_os_MessageQueue_getMessageQueue(env, messageQueueObj); 313 314 HdmiCecController* controller = new HdmiCecController( 315 reinterpret_cast<hdmi_cec_device*>(device), 316 env->NewGlobalRef(callbacksObj), 317 messageQueue->getLooper()); 318 controller->init(); 319 320 GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz, 321 "handleIncomingCecCommand", "(II[B)V"); 322 GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz, 323 "handleHotplug", "(Z)V"); 324 325 return reinterpret_cast<jlong>(controller); 326} 327 328static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, 329 jint srcAddr, jint dstAddr, jbyteArray body) { 330 cec_message_t message; 331 message.initiator = static_cast<cec_logical_address_t>(srcAddr); 332 message.destination = static_cast<cec_logical_address_t>(dstAddr); 333 334 jsize len = env->GetArrayLength(body); 335 message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH); 336 ScopedByteArrayRO bodyPtr(env, body); 337 std::memcpy(message.body, bodyPtr.get(), len); 338 339 HdmiCecController* controller = 340 reinterpret_cast<HdmiCecController*>(controllerPtr); 341 return controller->sendMessage(message); 342} 343 344static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr, 345 jint logicalAddress) { 346 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 347 return controller->addLogicalAddress(static_cast<cec_logical_address_t>(logicalAddress)); 348} 349 350static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { 351 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 352 controller->clearLogicaladdress(); 353} 354 355static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { 356 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 357 return controller->getPhysicalAddress(); 358} 359 360static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) { 361 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 362 return controller->getVersion(); 363} 364 365static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) { 366 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 367 return controller->getVendorId(); 368} 369 370static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) { 371 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 372 return controller->getPortInfos(); 373} 374 375static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) { 376 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 377 controller->setOption(flag, value); 378} 379 380static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr, 381 jboolean enabled) { 382 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 383 controller->setAudioReturnChannel(enabled == JNI_TRUE); 384} 385 386static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) { 387 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 388 return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ; 389} 390 391static JNINativeMethod sMethods[] = { 392 /* name, signature, funcPtr */ 393 { "nativeInit", 394 "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J", 395 (void *) nativeInit }, 396 { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand }, 397 { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress }, 398 { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress }, 399 { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress }, 400 { "nativeGetVersion", "(J)I", (void *) nativeGetVersion }, 401 { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId }, 402 { "nativeGetPortInfos", 403 "(J)[Lcom/android/server/hdmi/HdmiPortInfo;", 404 (void *) nativeGetPortInfos }, 405 { "nativeSetOption", "(JII)V", (void *) nativeSetOption }, 406 { "nativeSetAudioReturnChannel", "(JZ)V", (void *) nativeSetAudioReturnChannel }, 407 { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected }, 408}; 409 410#define CLASS_PATH "com/android/server/hdmi/HdmiCecController" 411 412int register_android_server_hdmi_HdmiCecController(JNIEnv* env) { 413 int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods)); 414 LOG_FATAL_IF(res < 0, "Unable to register native methods."); 415 return 0; 416} 417 418} /* namespace android */ 419