com_android_server_hdmi_HdmiCecController.cpp revision 4085d0ef62554c7e139b82bca0f97161bb159f8c
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
34namespace android {
35
36static struct {
37    jmethodID handleIncomingCecCommand;
38    jmethodID handleHotplug;
39} gHdmiCecControllerClassInfo;
40
41class HdmiCecController {
42public:
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
61    jobject getCallbacksObj() const {
62        return mCallbacksObj;
63    }
64
65private:
66    static void onReceived(const hdmi_event_t* event, void* arg);
67
68    hdmi_cec_device_t* mDevice;
69    jobject mCallbacksObj;
70    sp<Looper> mLooper;
71};
72
73// RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL
74// may keep its own lifetime, we need to copy it in order to delegate
75// it to service thread.
76class CecEventWrapper : public LightRefBase<CecEventWrapper> {
77public:
78    CecEventWrapper(const hdmi_event_t& event) {
79        // Copy message.
80        switch (event.type) {
81        case HDMI_EVENT_CEC_MESSAGE:
82            mEvent.cec.initiator = event.cec.initiator;
83            mEvent.cec.destination = event.cec.destination;
84            mEvent.cec.length = event.cec.length;
85            std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length);
86            break;
87        case HDMI_EVENT_HOT_PLUG:
88            mEvent.hotplug.connected = event.hotplug.connected;
89            mEvent.hotplug.port = event.hotplug.port;
90            break;
91        case HDMI_EVENT_TX_STATUS:
92            mEvent.tx_status.status = event.tx_status.status;
93            mEvent.tx_status.opcode = event.tx_status.opcode;
94            break;
95        default:
96            // TODO: add more type whenever new type is introduced.
97            break;
98        }
99    }
100
101    const cec_message_t& cec() const {
102        return mEvent.cec;
103    }
104
105    const hotplug_event_t& hotplug() const {
106        return mEvent.hotplug;
107    }
108
109    virtual ~CecEventWrapper() {}
110
111private:
112    hdmi_event_t mEvent;
113};
114
115// Handler class to delegate incoming message to service thread.
116class HdmiCecEventHandler : public MessageHandler {
117public:
118    HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event)
119        : mController(controller),
120          mEventWrapper(event) {
121    }
122
123    virtual ~HdmiCecEventHandler() {}
124
125    void handleMessage(const Message& message) {
126        switch (message.what) {
127        case HDMI_EVENT_CEC_MESSAGE:
128            propagateCecCommand(mEventWrapper->cec());
129            break;
130        case HDMI_EVENT_HOT_PLUG:
131            propagateHotplugEvent(mEventWrapper->hotplug());
132            break;
133        case HDMI_EVENT_TX_STATUS:
134            // TODO: propagate this to controller.
135        default:
136            // TODO: add more type whenever new type is introduced.
137            break;
138        }
139    }
140
141private:
142    // Propagate the message up to Java layer.
143    void propagateCecCommand(const cec_message_t& message) {
144        jint srcAddr = message.initiator;
145        jint dstAddr = message.destination;
146        JNIEnv* env = AndroidRuntime::getJNIEnv();
147        jbyteArray body = env->NewByteArray(message.length);
148        const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
149        env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
150
151        env->CallVoidMethod(mController->getCallbacksObj(),
152                gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
153                dstAddr, body);
154        env->DeleteLocalRef(body);
155
156        checkAndClearExceptionFromCallback(env, __FUNCTION__);
157    }
158
159    void propagateHotplugEvent(const hotplug_event_t& event) {
160        // Note that this method should be called in service thread.
161        JNIEnv* env = AndroidRuntime::getJNIEnv();
162        env->CallVoidMethod(mController->getCallbacksObj(),
163                gHdmiCecControllerClassInfo.handleHotplug, event.connected);
164
165        checkAndClearExceptionFromCallback(env, __FUNCTION__);
166    }
167
168    // static
169    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
170        if (env->ExceptionCheck()) {
171            ALOGE("An exception was thrown by callback '%s'.", methodName);
172            LOGE_EX(env);
173            env->ExceptionClear();
174        }
175    }
176
177    HdmiCecController* mController;
178    sp<CecEventWrapper> mEventWrapper;
179};
180
181HdmiCecController::HdmiCecController(hdmi_cec_device_t* device,
182        jobject callbacksObj, const sp<Looper>& looper) :
183    mDevice(device),
184    mCallbacksObj(callbacksObj),
185    mLooper(looper) {
186}
187
188void HdmiCecController::init() {
189    mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
190}
191
192int HdmiCecController::sendMessage(const cec_message_t& message) {
193    // TODO: propagate send_message's return value.
194    return mDevice->send_message(mDevice, &message);
195}
196
197int HdmiCecController::addLogicalAddress(cec_logical_address_t address) {
198    return mDevice->add_logical_address(mDevice, address);
199}
200
201void HdmiCecController::clearLogicaladdress() {
202    mDevice->clear_logical_address(mDevice);
203}
204
205int HdmiCecController::getPhysicalAddress() {
206    uint16_t physicalAddress = 0xFFFF;
207    if (mDevice->get_physical_address(mDevice, &physicalAddress) == 0) {
208        return physicalAddress;
209    }
210    return -1;
211}
212
213int HdmiCecController::getVersion() {
214    int version = 0;
215    mDevice->get_version(mDevice, &version);
216    return version;
217}
218
219uint32_t HdmiCecController::getVendorId() {
220    uint32_t vendorId = 0;
221    mDevice->get_vendor_id(mDevice, &vendorId);
222    return vendorId;
223}
224
225
226// static
227void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
228    HdmiCecController* controller = static_cast<HdmiCecController*>(arg);
229    if (controller == NULL) {
230        return;
231    }
232
233    sp<CecEventWrapper> spEvent(new CecEventWrapper(*event));
234    sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent));
235    controller->mLooper->sendMessage(handler, event->type);
236}
237
238//------------------------------------------------------------------------------
239#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
240        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
241        LOG_FATAL_IF(! var, "Unable to find method " methodName);
242
243// TODO: replace above code with following once
244// replace old HdmiCecService with HdmiControlService
245#undef HDMI_CEC_HARDWARE_MODULE_ID
246#define HDMI_CEC_HARDWARE_MODULE_ID "hdmi_cec_module"
247#undef HDMI_CEC_HARDWARE_INTERFACE
248#define HDMI_CEC_HARDWARE_INTERFACE "hdmi_cec_module_hw_if"
249
250static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
251        jobject messageQueueObj) {
252    int err;
253    hw_module_t* module;
254    err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
255            const_cast<const hw_module_t **>(&module));
256    if (err != 0) {
257        ALOGE("Error acquiring hardware module: %d", err);
258        return 0;
259    }
260
261    hw_device_t* device;
262    err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
263    if (err != 0) {
264        ALOGE("Error opening hardware module: %d", err);
265        return 0;
266    }
267
268    sp<MessageQueue> messageQueue =
269            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
270
271    HdmiCecController* controller = new HdmiCecController(
272            reinterpret_cast<hdmi_cec_device*>(device),
273            env->NewGlobalRef(callbacksObj),
274            messageQueue->getLooper());
275    controller->init();
276
277    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
278            "handleIncomingCecCommand", "(II[B)V");
279    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
280            "handleHotplug", "(Z)V");
281
282    return reinterpret_cast<jlong>(controller);
283}
284
285static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
286        jint srcAddr, jint dstAddr, jbyteArray body) {
287    cec_message_t message;
288    message.initiator = static_cast<cec_logical_address_t>(srcAddr);
289    message.destination = static_cast<cec_logical_address_t>(dstAddr);
290
291    jsize len = env->GetArrayLength(body);
292    message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH);
293    ScopedByteArrayRO bodyPtr(env, body);
294    std::memcpy(message.body, bodyPtr.get(), len);
295
296    HdmiCecController* controller =
297            reinterpret_cast<HdmiCecController*>(controllerPtr);
298    return controller->sendMessage(message);
299}
300
301static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz,
302        jlong controllerPtr, jint logicalAddress) {
303    HdmiCecController* controller =
304            reinterpret_cast<HdmiCecController*>(controllerPtr);
305    return controller->addLogicalAddress(
306            static_cast<cec_logical_address_t>(logicalAddress));
307}
308
309static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz,
310        jlong controllerPtr) {
311    HdmiCecController* controller =
312            reinterpret_cast<HdmiCecController*>(controllerPtr);
313    controller->clearLogicaladdress();
314}
315
316static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz,
317        jlong controllerPtr) {
318    HdmiCecController* controller =
319            reinterpret_cast<HdmiCecController*>(controllerPtr);
320    return controller->getPhysicalAddress();
321}
322
323static jint nativeGetVersion(JNIEnv* env, jclass clazz,
324        jlong controllerPtr) {
325    HdmiCecController* controller =
326            reinterpret_cast<HdmiCecController*>(controllerPtr);
327    return controller->getVersion();
328}
329
330static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
331    HdmiCecController* controller =
332            reinterpret_cast<HdmiCecController*>(controllerPtr);
333    return controller->getVendorId();
334}
335
336static JNINativeMethod sMethods[] = {
337    /* name, signature, funcPtr */
338    { "nativeInit",
339      "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
340      (void *) nativeInit },
341    { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
342    { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
343    { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
344    { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
345    { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
346    { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
347};
348
349#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
350
351int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
352    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods,
353            NELEM(sMethods));
354    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
355    return 0;
356}
357
358}  /* namespace android */
359