Home | History | Annotate | Download | only in jni
      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