com_android_server_hdmi_HdmiCecController.cpp revision e9c77c88ea34a66f83a94f960547275c0ff6bd07
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 <string>
25
26#include <android_runtime/AndroidRuntime.h>
27#include <android_runtime/Log.h>
28#include <hardware/hdmi_cec.h>
29#include <sys/param.h>
30
31namespace android {
32
33static struct {
34    jmethodID handleIncomingCecCommand;
35    jmethodID handleHotplug;
36} gHdmiCecControllerClassInfo;
37
38class HdmiCecController {
39public:
40    HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj);
41
42    void init();
43
44    // Send message to other device. Note that it runs in IO thread.
45    int sendMessage(const cec_message_t& message);
46
47private:
48    // Propagate the message up to Java layer.
49    void propagateCecCommand(const cec_message_t& message);
50    void propagateHotplugEvent(const hotplug_event_t& event);
51
52    static void onReceived(const hdmi_event_t* event, void* arg);
53    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
54
55    hdmi_cec_device_t* mDevice;
56    jobject mCallbacksObj;
57};
58
59HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj) :
60    mDevice(device),
61    mCallbacksObj(callbacksObj) {
62}
63
64void HdmiCecController::init() {
65    mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
66}
67
68void HdmiCecController::propagateCecCommand(const cec_message_t& message) {
69    jint srcAddr = message.initiator;
70    jint dstAddr = message.destination;
71    JNIEnv* env = AndroidRuntime::getJNIEnv();
72    jbyteArray body = env->NewByteArray(message.length);
73    const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
74    env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
75
76    env->CallVoidMethod(mCallbacksObj,
77            gHdmiCecControllerClassInfo.handleIncomingCecCommand,
78            srcAddr, dstAddr, body);
79    env->DeleteLocalRef(body);
80
81    checkAndClearExceptionFromCallback(env, __FUNCTION__);
82}
83
84void HdmiCecController::propagateHotplugEvent(const hotplug_event_t& event) {
85    JNIEnv* env = AndroidRuntime::getJNIEnv();
86    env->CallVoidMethod(mCallbacksObj,
87            gHdmiCecControllerClassInfo.handleHotplug, event.connected);
88
89    checkAndClearExceptionFromCallback(env, __FUNCTION__);
90}
91
92int HdmiCecController::sendMessage(const cec_message_t& message) {
93    // TODO: propagate send_message's return value.
94    return mDevice->send_message(mDevice, &message);
95}
96
97// static
98void HdmiCecController::checkAndClearExceptionFromCallback(JNIEnv* env,
99        const char* methodName) {
100    if (env->ExceptionCheck()) {
101        ALOGE("An exception was thrown by callback '%s'.", methodName);
102        LOGE_EX(env);
103        env->ExceptionClear();
104    }
105}
106
107// static
108void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
109    HdmiCecController* controller = static_cast<HdmiCecController*>(arg);
110    if (controller == NULL) {
111        return;
112    }
113
114    switch (event->type) {
115    case HDMI_EVENT_CEC_MESSAGE:
116        controller->propagateCecCommand(event->cec);
117        break;
118    case HDMI_EVENT_HOT_PLUG:
119        controller->propagateHotplugEvent(event->hotplug);
120        break;
121    default:
122        ALOGE("Unsupported event type: %d", event->type);
123        break;
124    }
125}
126
127//------------------------------------------------------------------------------
128#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
129        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
130        LOG_FATAL_IF(! var, "Unable to find method " methodName);
131
132static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
133    int err;
134    // If use same hardware module id between HdmiCecService and
135    // HdmiControlSservice it may conflict and cause abnormal state of HAL.
136    // TODO: use HDMI_CEC_HARDWARE_MODULE_ID of hdmi_cec.h for module id
137    //       once migration to HdmiControlService is done.
138    hw_module_t* module;
139    err = hw_get_module("hdmi_cec_module",
140            const_cast<const hw_module_t **>(&module));
141    if (err != 0) {
142        ALOGE("Error acquiring hardware module: %d", err);
143        return 0;
144    }
145    hw_device_t* device;
146    // TODO: use HDMI_CEC_HARDWARE_INTERFACE of hdmi_cec.h for interface name
147    //       once migration to HdmiControlService is done.
148    err = module->methods->open(module, "hdmi_cec_module_hw_if", &device);
149    if (err != 0) {
150        ALOGE("Error opening hardware module: %d", err);
151        return 0;
152    }
153
154    HdmiCecController* controller = new HdmiCecController(
155            reinterpret_cast<hdmi_cec_device*>(device),
156            env->NewGlobalRef(callbacksObj));
157    controller->init();
158
159    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
160            "handleIncomingCecCommand", "(II[B)V");
161    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
162            "handleHotplug", "(Z)V");
163
164    return reinterpret_cast<jlong>(controller);
165}
166
167static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
168        jint srcAddr, jint dstAddr, jbyteArray body) {
169    cec_message_t message;
170    message.initiator = static_cast<cec_logical_address_t>(srcAddr);
171    message.destination = static_cast<cec_logical_address_t>(dstAddr);
172
173    jsize len = env->GetArrayLength(body);
174    message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH);
175    ScopedByteArrayRO bodyPtr(env, body);
176    std::memcpy(message.body, bodyPtr.get(), len);
177
178    HdmiCecController* controller =
179            reinterpret_cast<HdmiCecController*>(controllerPtr);
180    return controller->sendMessage(message);
181}
182
183static JNINativeMethod sMethods[] = {
184    /* name, signature, funcPtr */
185    { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J",
186            (void *) nativeInit },
187    { "nativeSendCommand", "(JII[B)I",
188            (void *) nativeSendCecCommand },
189};
190
191#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
192
193int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
194    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
195    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
196    return 0;
197}
198
199}  /* namespace android */
200