com_android_server_hdmi_HdmiCecController.cpp revision 8692fc68a413c7aca75f094bb02bcbabcc277c73
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
243static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
244        jobject messageQueueObj) {
245    int err;
246    hw_module_t* module;
247    err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
248            const_cast<const hw_module_t **>(&module));
249    if (err != 0) {
250        ALOGE("Error acquiring hardware module: %d", err);
251        return 0;
252    }
253
254    hw_device_t* device;
255    err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
256    if (err != 0) {
257        ALOGE("Error opening hardware module: %d", err);
258        return 0;
259    }
260
261    sp<MessageQueue> messageQueue =
262            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
263
264    HdmiCecController* controller = new HdmiCecController(
265            reinterpret_cast<hdmi_cec_device*>(device),
266            env->NewGlobalRef(callbacksObj),
267            messageQueue->getLooper());
268    controller->init();
269
270    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
271            "handleIncomingCecCommand", "(II[B)V");
272    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
273            "handleHotplug", "(Z)V");
274
275    return reinterpret_cast<jlong>(controller);
276}
277
278static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
279        jint srcAddr, jint dstAddr, jbyteArray body) {
280    cec_message_t message;
281    message.initiator = static_cast<cec_logical_address_t>(srcAddr);
282    message.destination = static_cast<cec_logical_address_t>(dstAddr);
283
284    jsize len = env->GetArrayLength(body);
285    message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH);
286    ScopedByteArrayRO bodyPtr(env, body);
287    std::memcpy(message.body, bodyPtr.get(), len);
288
289    HdmiCecController* controller =
290            reinterpret_cast<HdmiCecController*>(controllerPtr);
291    return controller->sendMessage(message);
292}
293
294static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz,
295        jlong controllerPtr, jint logicalAddress) {
296    HdmiCecController* controller =
297            reinterpret_cast<HdmiCecController*>(controllerPtr);
298    return controller->addLogicalAddress(
299            static_cast<cec_logical_address_t>(logicalAddress));
300}
301
302static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz,
303        jlong controllerPtr) {
304    HdmiCecController* controller =
305            reinterpret_cast<HdmiCecController*>(controllerPtr);
306    controller->clearLogicaladdress();
307}
308
309static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz,
310        jlong controllerPtr) {
311    HdmiCecController* controller =
312            reinterpret_cast<HdmiCecController*>(controllerPtr);
313    return controller->getPhysicalAddress();
314}
315
316static jint nativeGetVersion(JNIEnv* env, jclass clazz,
317        jlong controllerPtr) {
318    HdmiCecController* controller =
319            reinterpret_cast<HdmiCecController*>(controllerPtr);
320    return controller->getVersion();
321}
322
323static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
324    HdmiCecController* controller =
325            reinterpret_cast<HdmiCecController*>(controllerPtr);
326    return controller->getVendorId();
327}
328
329static JNINativeMethod sMethods[] = {
330    /* name, signature, funcPtr */
331    { "nativeInit",
332      "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
333      (void *) nativeInit },
334    { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
335    { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
336    { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
337    { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
338    { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
339    { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
340};
341
342#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
343
344int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
345    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods,
346            NELEM(sMethods));
347    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
348    return 0;
349}
350
351}  /* namespace android */
352