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 34 namespace android { 35 36 static struct { 37 jmethodID handleIncomingCecCommand; 38 jmethodID handleHotplug; 39 } gHdmiCecControllerClassInfo; 40 41 class HdmiCecController { 42 public: 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 73 private: 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. 85 class CecEventWrapper : public LightRefBase<CecEventWrapper> { 86 public: 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_id = event.hotplug.port_id; 99 break; 100 default: 101 // TODO: add more type whenever new type is introduced. 102 break; 103 } 104 } 105 106 const cec_message_t& cec() const { 107 return mEvent.cec; 108 } 109 110 const hotplug_event_t& hotplug() const { 111 return mEvent.hotplug; 112 } 113 114 virtual ~CecEventWrapper() {} 115 116 private: 117 hdmi_event_t mEvent; 118 }; 119 120 // Handler class to delegate incoming message to service thread. 121 class HdmiCecEventHandler : public MessageHandler { 122 public: 123 HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event) 124 : mController(controller), 125 mEventWrapper(event) { 126 } 127 128 virtual ~HdmiCecEventHandler() {} 129 130 void handleMessage(const Message& message) { 131 switch (message.what) { 132 case HDMI_EVENT_CEC_MESSAGE: 133 propagateCecCommand(mEventWrapper->cec()); 134 break; 135 case HDMI_EVENT_HOT_PLUG: 136 propagateHotplugEvent(mEventWrapper->hotplug()); 137 break; 138 default: 139 // TODO: add more type whenever new type is introduced. 140 break; 141 } 142 } 143 144 private: 145 // Propagate the message up to Java layer. 146 void propagateCecCommand(const cec_message_t& message) { 147 jint srcAddr = message.initiator; 148 jint dstAddr = message.destination; 149 JNIEnv* env = AndroidRuntime::getJNIEnv(); 150 jbyteArray body = env->NewByteArray(message.length); 151 const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body); 152 env->SetByteArrayRegion(body, 0, message.length, bodyPtr); 153 154 env->CallVoidMethod(mController->getCallbacksObj(), 155 gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr, 156 dstAddr, body); 157 env->DeleteLocalRef(body); 158 159 checkAndClearExceptionFromCallback(env, __FUNCTION__); 160 } 161 162 void propagateHotplugEvent(const hotplug_event_t& event) { 163 // Note that this method should be called in service thread. 164 JNIEnv* env = AndroidRuntime::getJNIEnv(); 165 jint port = event.port_id; 166 jboolean connected = (jboolean) event.connected; 167 env->CallVoidMethod(mController->getCallbacksObj(), 168 gHdmiCecControllerClassInfo.handleHotplug, port, connected); 169 170 checkAndClearExceptionFromCallback(env, __FUNCTION__); 171 } 172 173 // static 174 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { 175 if (env->ExceptionCheck()) { 176 ALOGE("An exception was thrown by callback '%s'.", methodName); 177 LOGE_EX(env); 178 env->ExceptionClear(); 179 } 180 } 181 182 HdmiCecController* mController; 183 sp<CecEventWrapper> mEventWrapper; 184 }; 185 186 HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, 187 jobject callbacksObj, const sp<Looper>& looper) : 188 mDevice(device), 189 mCallbacksObj(callbacksObj), 190 mLooper(looper) { 191 } 192 193 void HdmiCecController::init() { 194 mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this); 195 } 196 197 int HdmiCecController::sendMessage(const cec_message_t& message) { 198 // TODO: propagate send_message's return value. 199 return mDevice->send_message(mDevice, &message); 200 } 201 202 int HdmiCecController::addLogicalAddress(cec_logical_address_t address) { 203 return mDevice->add_logical_address(mDevice, address); 204 } 205 206 void HdmiCecController::clearLogicaladdress() { 207 mDevice->clear_logical_address(mDevice); 208 } 209 210 int HdmiCecController::getPhysicalAddress() { 211 uint16_t addr; 212 if (!mDevice->get_physical_address(mDevice, &addr)) { 213 return addr; 214 } 215 return INVALID_PHYSICAL_ADDRESS; 216 } 217 218 int HdmiCecController::getVersion() { 219 int version = 0; 220 mDevice->get_version(mDevice, &version); 221 return version; 222 } 223 224 uint32_t HdmiCecController::getVendorId() { 225 uint32_t vendorId = 0; 226 mDevice->get_vendor_id(mDevice, &vendorId); 227 return vendorId; 228 } 229 230 jobjectArray HdmiCecController::getPortInfos() { 231 JNIEnv* env = AndroidRuntime::getJNIEnv(); 232 jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo"); 233 if (hdmiPortInfo == NULL) { 234 return NULL; 235 } 236 jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V"); 237 if (ctor == NULL) { 238 return NULL; 239 } 240 hdmi_port_info* ports; 241 int numPorts; 242 mDevice->get_port_info(mDevice, &ports, &numPorts); 243 jobjectArray res = env->NewObjectArray(numPorts, hdmiPortInfo, NULL); 244 245 // MHL support field will be obtained from MHL HAL. Leave it to false. 246 jboolean mhlSupported = (jboolean) 0; 247 for (int i = 0; i < numPorts; ++i) { 248 hdmi_port_info* info = &ports[i]; 249 jboolean cecSupported = (jboolean) info->cec_supported; 250 jboolean arcSupported = (jboolean) info->arc_supported; 251 jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_id, info->type, 252 info->physical_address, cecSupported, mhlSupported, arcSupported); 253 env->SetObjectArrayElement(res, i, infoObj); 254 } 255 return res; 256 } 257 258 void HdmiCecController::setOption(int flag, int value) { 259 mDevice->set_option(mDevice, flag, value); 260 } 261 262 // Set audio return channel status. 263 void HdmiCecController::setAudioReturnChannel(bool enabled) { 264 mDevice->set_audio_return_channel(mDevice, enabled ? 1 : 0); 265 } 266 267 // Whether to hdmi device is connected to the given port. 268 bool HdmiCecController::isConnected(int port) { 269 return mDevice->is_connected(mDevice, port) == HDMI_CONNECTED; 270 } 271 272 // static 273 void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) { 274 HdmiCecController* controller = static_cast<HdmiCecController*>(arg); 275 if (controller == NULL) { 276 return; 277 } 278 279 sp<CecEventWrapper> spEvent(new CecEventWrapper(*event)); 280 sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent)); 281 controller->mLooper->sendMessage(handler, event->type); 282 } 283 284 //------------------------------------------------------------------------------ 285 #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ 286 var = env->GetMethodID(clazz, methodName, methodDescriptor); \ 287 LOG_FATAL_IF(! var, "Unable to find method " methodName); 288 289 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj, 290 jobject messageQueueObj) { 291 int err; 292 hw_module_t* module; 293 err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID, 294 const_cast<const hw_module_t **>(&module)); 295 if (err != 0) { 296 ALOGE("Error acquiring hardware module: %d", err); 297 return 0; 298 } 299 300 hw_device_t* device; 301 err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device); 302 if (err != 0) { 303 ALOGE("Error opening hardware module: %d", err); 304 return 0; 305 } 306 307 sp<MessageQueue> messageQueue = 308 android_os_MessageQueue_getMessageQueue(env, messageQueueObj); 309 310 HdmiCecController* controller = new HdmiCecController( 311 reinterpret_cast<hdmi_cec_device*>(device), 312 env->NewGlobalRef(callbacksObj), 313 messageQueue->getLooper()); 314 controller->init(); 315 316 GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz, 317 "handleIncomingCecCommand", "(II[B)V"); 318 GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz, 319 "handleHotplug", "(IZ)V"); 320 321 return reinterpret_cast<jlong>(controller); 322 } 323 324 static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, 325 jint srcAddr, jint dstAddr, jbyteArray body) { 326 cec_message_t message; 327 message.initiator = static_cast<cec_logical_address_t>(srcAddr); 328 message.destination = static_cast<cec_logical_address_t>(dstAddr); 329 330 jsize len = env->GetArrayLength(body); 331 message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH); 332 ScopedByteArrayRO bodyPtr(env, body); 333 std::memcpy(message.body, bodyPtr.get(), len); 334 335 HdmiCecController* controller = 336 reinterpret_cast<HdmiCecController*>(controllerPtr); 337 return controller->sendMessage(message); 338 } 339 340 static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr, 341 jint logicalAddress) { 342 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 343 return controller->addLogicalAddress(static_cast<cec_logical_address_t>(logicalAddress)); 344 } 345 346 static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { 347 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 348 controller->clearLogicaladdress(); 349 } 350 351 static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { 352 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 353 return controller->getPhysicalAddress(); 354 } 355 356 static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) { 357 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 358 return controller->getVersion(); 359 } 360 361 static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) { 362 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 363 return controller->getVendorId(); 364 } 365 366 static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) { 367 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 368 return controller->getPortInfos(); 369 } 370 371 static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) { 372 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 373 controller->setOption(flag, value); 374 } 375 376 static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr, 377 jboolean enabled) { 378 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 379 controller->setAudioReturnChannel(enabled == JNI_TRUE); 380 } 381 382 static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) { 383 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 384 return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ; 385 } 386 387 static JNINativeMethod sMethods[] = { 388 /* name, signature, funcPtr */ 389 { "nativeInit", 390 "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J", 391 (void *) nativeInit }, 392 { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand }, 393 { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress }, 394 { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress }, 395 { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress }, 396 { "nativeGetVersion", "(J)I", (void *) nativeGetVersion }, 397 { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId }, 398 { "nativeGetPortInfos", 399 "(J)[Landroid/hardware/hdmi/HdmiPortInfo;", 400 (void *) nativeGetPortInfos }, 401 { "nativeSetOption", "(JII)V", (void *) nativeSetOption }, 402 { "nativeSetAudioReturnChannel", "(JZ)V", (void *) nativeSetAudioReturnChannel }, 403 { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected }, 404 }; 405 406 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController" 407 408 int register_android_server_hdmi_HdmiCecController(JNIEnv* env) { 409 int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods)); 410 LOG_FATAL_IF(res < 0, "Unable to register native methods."); 411 return 0; 412 } 413 414 } /* namespace android */ 415