com_android_server_hdmi_HdmiCecController.cpp revision 0340bbc89f8162f9c2a298c98b03bfcdd1bc6e87
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    // 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
73private:
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.
85class CecEventWrapper : public LightRefBase<CecEventWrapper> {
86public:
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 = event.hotplug.port;
99            break;
100        case HDMI_EVENT_TX_STATUS:
101            mEvent.tx_status.status = event.tx_status.status;
102            mEvent.tx_status.opcode = event.tx_status.opcode;
103            break;
104        default:
105            // TODO: add more type whenever new type is introduced.
106            break;
107        }
108    }
109
110    const cec_message_t& cec() const {
111        return mEvent.cec;
112    }
113
114    const hotplug_event_t& hotplug() const {
115        return mEvent.hotplug;
116    }
117
118    virtual ~CecEventWrapper() {}
119
120private:
121    hdmi_event_t mEvent;
122};
123
124// Handler class to delegate incoming message to service thread.
125class HdmiCecEventHandler : public MessageHandler {
126public:
127    HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event)
128        : mController(controller),
129          mEventWrapper(event) {
130    }
131
132    virtual ~HdmiCecEventHandler() {}
133
134    void handleMessage(const Message& message) {
135        switch (message.what) {
136        case HDMI_EVENT_CEC_MESSAGE:
137            propagateCecCommand(mEventWrapper->cec());
138            break;
139        case HDMI_EVENT_HOT_PLUG:
140            propagateHotplugEvent(mEventWrapper->hotplug());
141            break;
142        case HDMI_EVENT_TX_STATUS:
143            // TODO: propagate this to controller.
144        default:
145            // TODO: add more type whenever new type is introduced.
146            break;
147        }
148    }
149
150private:
151    // Propagate the message up to Java layer.
152    void propagateCecCommand(const cec_message_t& message) {
153        jint srcAddr = message.initiator;
154        jint dstAddr = message.destination;
155        JNIEnv* env = AndroidRuntime::getJNIEnv();
156        jbyteArray body = env->NewByteArray(message.length);
157        const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
158        env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
159
160        env->CallVoidMethod(mController->getCallbacksObj(),
161                gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
162                dstAddr, body);
163        env->DeleteLocalRef(body);
164
165        checkAndClearExceptionFromCallback(env, __FUNCTION__);
166    }
167
168    void propagateHotplugEvent(const hotplug_event_t& event) {
169        // Note that this method should be called in service thread.
170        JNIEnv* env = AndroidRuntime::getJNIEnv();
171        env->CallVoidMethod(mController->getCallbacksObj(),
172                gHdmiCecControllerClassInfo.handleHotplug, event.connected);
173
174        checkAndClearExceptionFromCallback(env, __FUNCTION__);
175    }
176
177    // static
178    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
179        if (env->ExceptionCheck()) {
180            ALOGE("An exception was thrown by callback '%s'.", methodName);
181            LOGE_EX(env);
182            env->ExceptionClear();
183        }
184    }
185
186    HdmiCecController* mController;
187    sp<CecEventWrapper> mEventWrapper;
188};
189
190HdmiCecController::HdmiCecController(hdmi_cec_device_t* device,
191        jobject callbacksObj, const sp<Looper>& looper) :
192    mDevice(device),
193    mCallbacksObj(callbacksObj),
194    mLooper(looper) {
195}
196
197void HdmiCecController::init() {
198    mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
199}
200
201int HdmiCecController::sendMessage(const cec_message_t& message) {
202    // TODO: propagate send_message's return value.
203    return mDevice->send_message(mDevice, &message);
204}
205
206int HdmiCecController::addLogicalAddress(cec_logical_address_t address) {
207    return mDevice->add_logical_address(mDevice, address);
208}
209
210void HdmiCecController::clearLogicaladdress() {
211    mDevice->clear_logical_address(mDevice);
212}
213
214int HdmiCecController::getPhysicalAddress() {
215    uint16_t addr;
216    if (!mDevice->get_physical_address(mDevice, &addr)) {
217        return addr;
218    }
219    return INVALID_PHYSICAL_ADDRESS;
220}
221
222int HdmiCecController::getVersion() {
223    int version = 0;
224    mDevice->get_version(mDevice, &version);
225    return version;
226}
227
228uint32_t HdmiCecController::getVendorId() {
229    uint32_t vendorId = 0;
230    mDevice->get_vendor_id(mDevice, &vendorId);
231    return vendorId;
232}
233
234jobjectArray HdmiCecController::getPortInfos() {
235    JNIEnv* env = AndroidRuntime::getJNIEnv();
236    jclass hdmiPortInfo = env->FindClass("com/android/server/hdmi/HdmiPortInfo");
237    if (hdmiPortInfo == NULL) {
238        return NULL;
239    }
240    jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V");
241    if (ctor == NULL) {
242        return NULL;
243    }
244    hdmi_port_info* ports;
245    int numPorts;
246    mDevice->get_port_info(mDevice, &ports, &numPorts);
247    jobjectArray res = env->NewObjectArray(numPorts, hdmiPortInfo, NULL);
248
249    // MHL support field will be obtained from MHL HAL. Leave it to false.
250    jboolean mhlSupported = (jboolean) 0;
251    for (int i = 0; i < numPorts; ++i) {
252        hdmi_port_info* info = &ports[i];
253        jboolean cecSupported = (jboolean) info->cec_supported;
254        jboolean arcSupported = (jboolean) info->arc_supported;
255        jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_num, info->type,
256                info->physical_address, cecSupported, mhlSupported, arcSupported);
257        env->SetObjectArrayElement(res, i, infoObj);
258    }
259    return res;
260}
261
262void HdmiCecController::setOption(int flag, int value) {
263    mDevice->set_option(mDevice, flag, value);
264}
265
266// Set audio return channel status.
267void HdmiCecController::setAudioReturnChannel(bool enabled) {
268    mDevice->set_audio_return_channel(mDevice, enabled ? 1 : 0);
269}
270
271// Whether to hdmi device is connected to the given port.
272bool HdmiCecController::isConnected(int port) {
273    return mDevice->is_connected(mDevice, port) == HDMI_CONNECTED;
274}
275
276// static
277void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
278    HdmiCecController* controller = static_cast<HdmiCecController*>(arg);
279    if (controller == NULL) {
280        return;
281    }
282
283    sp<CecEventWrapper> spEvent(new CecEventWrapper(*event));
284    sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent));
285    controller->mLooper->sendMessage(handler, event->type);
286}
287
288//------------------------------------------------------------------------------
289#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
290        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
291        LOG_FATAL_IF(! var, "Unable to find method " methodName);
292
293static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
294        jobject messageQueueObj) {
295    int err;
296    hw_module_t* module;
297    err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
298            const_cast<const hw_module_t **>(&module));
299    if (err != 0) {
300        ALOGE("Error acquiring hardware module: %d", err);
301        return 0;
302    }
303
304    hw_device_t* device;
305    err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
306    if (err != 0) {
307        ALOGE("Error opening hardware module: %d", err);
308        return 0;
309    }
310
311    sp<MessageQueue> messageQueue =
312            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
313
314    HdmiCecController* controller = new HdmiCecController(
315            reinterpret_cast<hdmi_cec_device*>(device),
316            env->NewGlobalRef(callbacksObj),
317            messageQueue->getLooper());
318    controller->init();
319
320    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
321            "handleIncomingCecCommand", "(II[B)V");
322    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
323            "handleHotplug", "(Z)V");
324
325    return reinterpret_cast<jlong>(controller);
326}
327
328static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
329        jint srcAddr, jint dstAddr, jbyteArray body) {
330    cec_message_t message;
331    message.initiator = static_cast<cec_logical_address_t>(srcAddr);
332    message.destination = static_cast<cec_logical_address_t>(dstAddr);
333
334    jsize len = env->GetArrayLength(body);
335    message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH);
336    ScopedByteArrayRO bodyPtr(env, body);
337    std::memcpy(message.body, bodyPtr.get(), len);
338
339    HdmiCecController* controller =
340            reinterpret_cast<HdmiCecController*>(controllerPtr);
341    return controller->sendMessage(message);
342}
343
344static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr,
345        jint logicalAddress) {
346    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
347    return controller->addLogicalAddress(static_cast<cec_logical_address_t>(logicalAddress));
348}
349
350static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
351    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
352    controller->clearLogicaladdress();
353}
354
355static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
356    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
357    return controller->getPhysicalAddress();
358}
359
360static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) {
361    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
362    return controller->getVersion();
363}
364
365static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
366    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
367    return controller->getVendorId();
368}
369
370static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) {
371    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
372    return controller->getPortInfos();
373}
374
375static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) {
376    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
377    controller->setOption(flag, value);
378}
379
380static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr,
381        jboolean enabled) {
382    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
383    controller->setAudioReturnChannel(enabled == JNI_TRUE);
384}
385
386static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) {
387    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
388    return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ;
389}
390
391static JNINativeMethod sMethods[] = {
392    /* name, signature, funcPtr */
393    { "nativeInit",
394      "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
395      (void *) nativeInit },
396    { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
397    { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
398    { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
399    { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
400    { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
401    { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
402    { "nativeGetPortInfos",
403      "(J)[Lcom/android/server/hdmi/HdmiPortInfo;",
404      (void *) nativeGetPortInfos },
405    { "nativeSetOption", "(JII)V", (void *) nativeSetOption },
406    { "nativeSetAudioReturnChannel", "(JZ)V", (void *) nativeSetAudioReturnChannel },
407    { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected },
408};
409
410#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
411
412int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
413    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
414    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
415    return 0;
416}
417
418}  /* namespace android */
419