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 <nativehelper/JNIHelp.h> 22 #include <nativehelper/ScopedPrimitiveArray.h> 23 24 #include <android/hardware/tv/cec/1.0/IHdmiCec.h> 25 #include <android/hardware/tv/cec/1.0/IHdmiCecCallback.h> 26 #include <android/hardware/tv/cec/1.0/types.h> 27 #include <android_os_MessageQueue.h> 28 #include <android_runtime/AndroidRuntime.h> 29 #include <android_runtime/Log.h> 30 #include <sys/param.h> 31 #include <utils/Errors.h> 32 #include <utils/Looper.h> 33 #include <utils/RefBase.h> 34 35 using ::android::hardware::tv::cec::V1_0::CecLogicalAddress; 36 using ::android::hardware::tv::cec::V1_0::CecMessage; 37 using ::android::hardware::tv::cec::V1_0::HdmiPortInfo; 38 using ::android::hardware::tv::cec::V1_0::HotplugEvent; 39 using ::android::hardware::tv::cec::V1_0::IHdmiCec; 40 using ::android::hardware::tv::cec::V1_0::IHdmiCecCallback; 41 using ::android::hardware::tv::cec::V1_0::MaxLength; 42 using ::android::hardware::tv::cec::V1_0::OptionKey; 43 using ::android::hardware::tv::cec::V1_0::Result; 44 using ::android::hardware::tv::cec::V1_0::SendMessageResult; 45 using ::android::hardware::Return; 46 using ::android::hardware::Void; 47 using ::android::hardware::hidl_vec; 48 using ::android::hardware::hidl_string; 49 50 namespace android { 51 52 static struct { 53 jmethodID handleIncomingCecCommand; 54 jmethodID handleHotplug; 55 } gHdmiCecControllerClassInfo; 56 57 class HdmiCecController { 58 public: 59 HdmiCecController(sp<IHdmiCec> hdmiCec, jobject callbacksObj, const sp<Looper>& looper); 60 ~HdmiCecController(); 61 62 // Send message to other device. Note that it runs in IO thread. 63 int sendMessage(const CecMessage& message); 64 // Add a logical address to device. 65 int addLogicalAddress(CecLogicalAddress address); 66 // Clear all logical address registered to the device. 67 void clearLogicaladdress(); 68 // Get physical address of device. 69 int getPhysicalAddress(); 70 // Get CEC version from driver. 71 int getVersion(); 72 // Get vendor id used for vendor command. 73 uint32_t getVendorId(); 74 // Get Port information on all the HDMI ports. 75 jobjectArray getPortInfos(); 76 // Set an option to CEC HAL. 77 void setOption(OptionKey key, bool enabled); 78 // Informs CEC HAL about the current system language. 79 void setLanguage(hidl_string language); 80 // Enable audio return channel. 81 void enableAudioReturnChannel(int port, bool flag); 82 // Whether to hdmi device is connected to the given port. 83 bool isConnected(int port); 84 85 jobject getCallbacksObj() const { 86 return mCallbacksObj; 87 } 88 89 private: 90 class HdmiCecCallback : public IHdmiCecCallback { 91 public: 92 HdmiCecCallback(HdmiCecController* controller) : mController(controller) {}; 93 Return<void> onCecMessage(const CecMessage& event) override; 94 Return<void> onHotplugEvent(const HotplugEvent& event) override; 95 private: 96 HdmiCecController* mController; 97 }; 98 99 static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 100 101 sp<IHdmiCec> mHdmiCec; 102 jobject mCallbacksObj; 103 sp<IHdmiCecCallback> mHdmiCecCallback; 104 sp<Looper> mLooper; 105 }; 106 107 // Handler class to delegate incoming message to service thread. 108 class HdmiCecEventHandler : public MessageHandler { 109 public: 110 enum EventType { 111 CEC_MESSAGE, 112 HOT_PLUG 113 }; 114 115 HdmiCecEventHandler(HdmiCecController* controller, const CecMessage& cecMessage) 116 : mController(controller), 117 mCecMessage(cecMessage) {} 118 119 HdmiCecEventHandler(HdmiCecController* controller, const HotplugEvent& hotplugEvent) 120 : mController(controller), 121 mHotplugEvent(hotplugEvent) {} 122 123 virtual ~HdmiCecEventHandler() {} 124 125 void handleMessage(const Message& message) { 126 switch (message.what) { 127 case EventType::CEC_MESSAGE: 128 propagateCecCommand(mCecMessage); 129 break; 130 case EventType::HOT_PLUG: 131 propagateHotplugEvent(mHotplugEvent); 132 break; 133 default: 134 // TODO: add more type whenever new type is introduced. 135 break; 136 } 137 } 138 139 private: 140 // Propagate the message up to Java layer. 141 void propagateCecCommand(const CecMessage& message) { 142 JNIEnv* env = AndroidRuntime::getJNIEnv(); 143 jint srcAddr = static_cast<jint>(message.initiator); 144 jint dstAddr = static_cast<jint>(message.destination); 145 jbyteArray body = env->NewByteArray(message.body.size()); 146 const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body.data()); 147 env->SetByteArrayRegion(body, 0, message.body.size(), bodyPtr); 148 env->CallVoidMethod(mController->getCallbacksObj(), 149 gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr, 150 dstAddr, body); 151 env->DeleteLocalRef(body); 152 153 checkAndClearExceptionFromCallback(env, __FUNCTION__); 154 } 155 156 void propagateHotplugEvent(const HotplugEvent& event) { 157 // Note that this method should be called in service thread. 158 JNIEnv* env = AndroidRuntime::getJNIEnv(); 159 jint port = static_cast<jint>(event.portId); 160 jboolean connected = (jboolean) event.connected; 161 env->CallVoidMethod(mController->getCallbacksObj(), 162 gHdmiCecControllerClassInfo.handleHotplug, port, connected); 163 164 checkAndClearExceptionFromCallback(env, __FUNCTION__); 165 } 166 167 // static 168 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { 169 if (env->ExceptionCheck()) { 170 ALOGE("An exception was thrown by callback '%s'.", methodName); 171 LOGE_EX(env); 172 env->ExceptionClear(); 173 } 174 } 175 176 HdmiCecController* mController; 177 CecMessage mCecMessage; 178 HotplugEvent mHotplugEvent; 179 }; 180 181 HdmiCecController::HdmiCecController(sp<IHdmiCec> hdmiCec, 182 jobject callbacksObj, const sp<Looper>& looper) 183 : mHdmiCec(hdmiCec), 184 mCallbacksObj(callbacksObj), 185 mLooper(looper) { 186 mHdmiCecCallback = new HdmiCecCallback(this); 187 Return<void> ret = mHdmiCec->setCallback(mHdmiCecCallback); 188 if (!ret.isOk()) { 189 ALOGE("Failed to set a cec callback."); 190 } 191 } 192 193 HdmiCecController::~HdmiCecController() { 194 Return<void> ret = mHdmiCec->setCallback(nullptr); 195 if (!ret.isOk()) { 196 ALOGE("Failed to set a cec callback."); 197 } 198 } 199 200 int HdmiCecController::sendMessage(const CecMessage& message) { 201 // TODO: propagate send_message's return value. 202 Return<SendMessageResult> ret = mHdmiCec->sendMessage(message); 203 if (!ret.isOk()) { 204 ALOGE("Failed to send CEC message."); 205 return static_cast<int>(SendMessageResult::FAIL); 206 } 207 return static_cast<int>((SendMessageResult) ret); 208 } 209 210 int HdmiCecController::addLogicalAddress(CecLogicalAddress address) { 211 Return<Result> ret = mHdmiCec->addLogicalAddress(address); 212 if (!ret.isOk()) { 213 ALOGE("Failed to add a logical address."); 214 return static_cast<int>(Result::FAILURE_UNKNOWN); 215 } 216 return static_cast<int>((Result) ret); 217 } 218 219 void HdmiCecController::clearLogicaladdress() { 220 Return<void> ret = mHdmiCec->clearLogicalAddress(); 221 if (!ret.isOk()) { 222 ALOGE("Failed to clear logical address."); 223 } 224 } 225 226 int HdmiCecController::getPhysicalAddress() { 227 Result result; 228 uint16_t addr; 229 Return<void> ret = mHdmiCec->getPhysicalAddress([&result, &addr](Result res, uint16_t paddr) { 230 result = res; 231 addr = paddr; 232 }); 233 if (!ret.isOk()) { 234 ALOGE("Failed to get physical address."); 235 return INVALID_PHYSICAL_ADDRESS; 236 } 237 return result == Result::SUCCESS ? addr : INVALID_PHYSICAL_ADDRESS; 238 } 239 240 int HdmiCecController::getVersion() { 241 Return<int32_t> ret = mHdmiCec->getCecVersion(); 242 if (!ret.isOk()) { 243 ALOGE("Failed to get cec version."); 244 } 245 return ret; 246 } 247 248 uint32_t HdmiCecController::getVendorId() { 249 Return<uint32_t> ret = mHdmiCec->getVendorId(); 250 if (!ret.isOk()) { 251 ALOGE("Failed to get vendor id."); 252 } 253 return ret; 254 } 255 256 jobjectArray HdmiCecController::getPortInfos() { 257 JNIEnv* env = AndroidRuntime::getJNIEnv(); 258 jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo"); 259 if (hdmiPortInfo == NULL) { 260 return NULL; 261 } 262 jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V"); 263 if (ctor == NULL) { 264 return NULL; 265 } 266 hidl_vec<HdmiPortInfo> ports; 267 Return<void> ret = mHdmiCec->getPortInfo([&ports](hidl_vec<HdmiPortInfo> list) { 268 ports = list; 269 }); 270 if (!ret.isOk()) { 271 ALOGE("Failed to get port information."); 272 return NULL; 273 } 274 jobjectArray res = env->NewObjectArray(ports.size(), hdmiPortInfo, NULL); 275 276 // MHL support field will be obtained from MHL HAL. Leave it to false. 277 jboolean mhlSupported = (jboolean) 0; 278 for (size_t i = 0; i < ports.size(); ++i) { 279 jboolean cecSupported = (jboolean) ports[i].cecSupported; 280 jboolean arcSupported = (jboolean) ports[i].arcSupported; 281 jobject infoObj = env->NewObject(hdmiPortInfo, ctor, ports[i].portId, ports[i].type, 282 ports[i].physicalAddress, cecSupported, mhlSupported, arcSupported); 283 env->SetObjectArrayElement(res, i, infoObj); 284 } 285 return res; 286 } 287 288 void HdmiCecController::setOption(OptionKey key, bool enabled) { 289 Return<void> ret = mHdmiCec->setOption(key, enabled); 290 if (!ret.isOk()) { 291 ALOGE("Failed to set option."); 292 } 293 } 294 295 void HdmiCecController::setLanguage(hidl_string language) { 296 Return<void> ret = mHdmiCec->setLanguage(language); 297 if (!ret.isOk()) { 298 ALOGE("Failed to set language."); 299 } 300 } 301 302 // Enable audio return channel. 303 void HdmiCecController::enableAudioReturnChannel(int port, bool enabled) { 304 Return<void> ret = mHdmiCec->enableAudioReturnChannel(port, enabled); 305 if (!ret.isOk()) { 306 ALOGE("Failed to enable/disable ARC."); 307 } 308 } 309 310 // Whether to hdmi device is connected to the given port. 311 bool HdmiCecController::isConnected(int port) { 312 Return<bool> ret = mHdmiCec->isConnected(port); 313 if (!ret.isOk()) { 314 ALOGE("Failed to get connection info."); 315 } 316 return ret; 317 } 318 319 Return<void> HdmiCecController::HdmiCecCallback::onCecMessage(const CecMessage& message) { 320 sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(mController, message)); 321 mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::CEC_MESSAGE); 322 return Void(); 323 } 324 325 Return<void> HdmiCecController::HdmiCecCallback::onHotplugEvent(const HotplugEvent& event) { 326 sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(mController, event)); 327 mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::HOT_PLUG); 328 return Void(); 329 } 330 331 //------------------------------------------------------------------------------ 332 #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ 333 var = env->GetMethodID(clazz, methodName, methodDescriptor); \ 334 LOG_FATAL_IF(! (var), "Unable to find method " methodName); 335 336 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj, 337 jobject messageQueueObj) { 338 // TODO(b/31632518) 339 sp<IHdmiCec> hdmiCec = IHdmiCec::getService(); 340 if (hdmiCec == nullptr) { 341 ALOGE("Couldn't get tv.cec service."); 342 return 0; 343 } 344 sp<MessageQueue> messageQueue = 345 android_os_MessageQueue_getMessageQueue(env, messageQueueObj); 346 347 HdmiCecController* controller = new HdmiCecController( 348 hdmiCec, 349 env->NewGlobalRef(callbacksObj), 350 messageQueue->getLooper()); 351 352 GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz, 353 "handleIncomingCecCommand", "(II[B)V"); 354 GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz, 355 "handleHotplug", "(IZ)V"); 356 357 return reinterpret_cast<jlong>(controller); 358 } 359 360 static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, 361 jint srcAddr, jint dstAddr, jbyteArray body) { 362 CecMessage message; 363 message.initiator = static_cast<CecLogicalAddress>(srcAddr); 364 message.destination = static_cast<CecLogicalAddress>(dstAddr); 365 366 jsize len = env->GetArrayLength(body); 367 ScopedByteArrayRO bodyPtr(env, body); 368 size_t bodyLength = MIN(static_cast<size_t>(len), 369 static_cast<size_t>(MaxLength::MESSAGE_BODY)); 370 message.body.resize(bodyLength); 371 for (size_t i = 0; i < bodyLength; ++i) { 372 message.body[i] = static_cast<uint8_t>(bodyPtr[i]); 373 } 374 375 HdmiCecController* controller = 376 reinterpret_cast<HdmiCecController*>(controllerPtr); 377 return controller->sendMessage(message); 378 } 379 380 static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr, 381 jint logicalAddress) { 382 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 383 return controller->addLogicalAddress(static_cast<CecLogicalAddress>(logicalAddress)); 384 } 385 386 static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { 387 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 388 controller->clearLogicaladdress(); 389 } 390 391 static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) { 392 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 393 return controller->getPhysicalAddress(); 394 } 395 396 static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) { 397 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 398 return controller->getVersion(); 399 } 400 401 static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) { 402 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 403 return controller->getVendorId(); 404 } 405 406 static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) { 407 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 408 return controller->getPortInfos(); 409 } 410 411 static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) { 412 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 413 controller->setOption(static_cast<OptionKey>(flag), value > 0 ? true : false); 414 } 415 416 static void nativeSetLanguage(JNIEnv* env, jclass clazz, jlong controllerPtr, jstring language) { 417 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 418 const char *languageStr = env->GetStringUTFChars(language, NULL); 419 controller->setLanguage(languageStr); 420 env->ReleaseStringUTFChars(language, languageStr); 421 } 422 423 static void nativeEnableAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr, 424 jint port, jboolean enabled) { 425 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 426 controller->enableAudioReturnChannel(port, enabled == JNI_TRUE); 427 } 428 429 static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) { 430 HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr); 431 return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ; 432 } 433 434 static const JNINativeMethod sMethods[] = { 435 /* name, signature, funcPtr */ 436 { "nativeInit", 437 "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J", 438 (void *) nativeInit }, 439 { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand }, 440 { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress }, 441 { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress }, 442 { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress }, 443 { "nativeGetVersion", "(J)I", (void *) nativeGetVersion }, 444 { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId }, 445 { "nativeGetPortInfos", 446 "(J)[Landroid/hardware/hdmi/HdmiPortInfo;", 447 (void *) nativeGetPortInfos }, 448 { "nativeSetOption", "(JIZ)V", (void *) nativeSetOption }, 449 { "nativeSetLanguage", "(JLjava/lang/String;)V", (void *) nativeSetLanguage }, 450 { "nativeEnableAudioReturnChannel", "(JIZ)V", (void *) nativeEnableAudioReturnChannel }, 451 { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected }, 452 }; 453 454 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController" 455 456 int register_android_server_hdmi_HdmiCecController(JNIEnv* env) { 457 int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods)); 458 LOG_FATAL_IF(res < 0, "Unable to register native methods."); 459 (void)res; // Don't scream about unused variable in the LOG_NDEBUG case 460 return 0; 461 } 462 463 } /* namespace android */ 464