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 <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