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 <nativehelper/JNIHelp.h>
22#include <nativehelper/ScopedPrimitiveArray.h>
23
24#include <android/hardware/tv/cec/1.0/IHdmiCec.h>
25#include <android/hardware/tv/cec/1.0/IHdmiCecCallback.h>
26#include <android/hardware/tv/cec/1.0/types.h>
27#include <android_os_MessageQueue.h>
28#include <android_runtime/AndroidRuntime.h>
29#include <android_runtime/Log.h>
30#include <sys/param.h>
31#include <utils/Errors.h>
32#include <utils/Looper.h>
33#include <utils/RefBase.h>
34
35using ::android::hardware::tv::cec::V1_0::CecLogicalAddress;
36using ::android::hardware::tv::cec::V1_0::CecMessage;
37using ::android::hardware::tv::cec::V1_0::HdmiPortInfo;
38using ::android::hardware::tv::cec::V1_0::HotplugEvent;
39using ::android::hardware::tv::cec::V1_0::IHdmiCec;
40using ::android::hardware::tv::cec::V1_0::IHdmiCecCallback;
41using ::android::hardware::tv::cec::V1_0::MaxLength;
42using ::android::hardware::tv::cec::V1_0::OptionKey;
43using ::android::hardware::tv::cec::V1_0::Result;
44using ::android::hardware::tv::cec::V1_0::SendMessageResult;
45using ::android::hardware::Return;
46using ::android::hardware::Void;
47using ::android::hardware::hidl_vec;
48using ::android::hardware::hidl_string;
49
50namespace android {
51
52static struct {
53    jmethodID handleIncomingCecCommand;
54    jmethodID handleHotplug;
55} gHdmiCecControllerClassInfo;
56
57class HdmiCecController {
58public:
59    HdmiCecController(sp<IHdmiCec> hdmiCec, jobject callbacksObj, const sp<Looper>& looper);
60    ~HdmiCecController();
61
62    // Send message to other device. Note that it runs in IO thread.
63    int sendMessage(const CecMessage& message);
64    // Add a logical address to device.
65    int addLogicalAddress(CecLogicalAddress address);
66    // Clear all logical address registered to the device.
67    void clearLogicaladdress();
68    // Get physical address of device.
69    int getPhysicalAddress();
70    // Get CEC version from driver.
71    int getVersion();
72    // Get vendor id used for vendor command.
73    uint32_t getVendorId();
74    // Get Port information on all the HDMI ports.
75    jobjectArray getPortInfos();
76    // Set an option to CEC HAL.
77    void setOption(OptionKey key, bool enabled);
78    // Informs CEC HAL about the current system language.
79    void setLanguage(hidl_string language);
80    // Enable audio return channel.
81    void enableAudioReturnChannel(int port, bool flag);
82    // Whether to hdmi device is connected to the given port.
83    bool isConnected(int port);
84
85    jobject getCallbacksObj() const {
86        return mCallbacksObj;
87    }
88
89private:
90    class HdmiCecCallback : public IHdmiCecCallback {
91    public:
92        HdmiCecCallback(HdmiCecController* controller) : mController(controller) {};
93        Return<void> onCecMessage(const CecMessage& event)  override;
94        Return<void> onHotplugEvent(const HotplugEvent& event)  override;
95    private:
96        HdmiCecController* mController;
97    };
98
99    static const int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
100
101    sp<IHdmiCec> mHdmiCec;
102    jobject mCallbacksObj;
103    sp<IHdmiCecCallback> mHdmiCecCallback;
104    sp<Looper> mLooper;
105};
106
107// Handler class to delegate incoming message to service thread.
108class HdmiCecEventHandler : public MessageHandler {
109public:
110    enum EventType {
111        CEC_MESSAGE,
112        HOT_PLUG
113    };
114
115    HdmiCecEventHandler(HdmiCecController* controller, const CecMessage& cecMessage)
116            : mController(controller),
117              mCecMessage(cecMessage) {}
118
119    HdmiCecEventHandler(HdmiCecController* controller, const HotplugEvent& hotplugEvent)
120            : mController(controller),
121              mHotplugEvent(hotplugEvent) {}
122
123    virtual ~HdmiCecEventHandler() {}
124
125    void handleMessage(const Message& message) {
126        switch (message.what) {
127        case EventType::CEC_MESSAGE:
128            propagateCecCommand(mCecMessage);
129            break;
130        case EventType::HOT_PLUG:
131            propagateHotplugEvent(mHotplugEvent);
132            break;
133        default:
134            // TODO: add more type whenever new type is introduced.
135            break;
136        }
137    }
138
139private:
140    // Propagate the message up to Java layer.
141    void propagateCecCommand(const CecMessage& message) {
142        JNIEnv* env = AndroidRuntime::getJNIEnv();
143        jint srcAddr = static_cast<jint>(message.initiator);
144        jint dstAddr = static_cast<jint>(message.destination);
145        jbyteArray body = env->NewByteArray(message.body.size());
146        const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body.data());
147        env->SetByteArrayRegion(body, 0, message.body.size(), bodyPtr);
148        env->CallVoidMethod(mController->getCallbacksObj(),
149                gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
150                dstAddr, body);
151        env->DeleteLocalRef(body);
152
153        checkAndClearExceptionFromCallback(env, __FUNCTION__);
154    }
155
156    void propagateHotplugEvent(const HotplugEvent& event) {
157        // Note that this method should be called in service thread.
158        JNIEnv* env = AndroidRuntime::getJNIEnv();
159        jint port = static_cast<jint>(event.portId);
160        jboolean connected = (jboolean) event.connected;
161        env->CallVoidMethod(mController->getCallbacksObj(),
162                gHdmiCecControllerClassInfo.handleHotplug, port, connected);
163
164        checkAndClearExceptionFromCallback(env, __FUNCTION__);
165    }
166
167    // static
168    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
169        if (env->ExceptionCheck()) {
170            ALOGE("An exception was thrown by callback '%s'.", methodName);
171            LOGE_EX(env);
172            env->ExceptionClear();
173        }
174    }
175
176    HdmiCecController* mController;
177    CecMessage mCecMessage;
178    HotplugEvent mHotplugEvent;
179};
180
181HdmiCecController::HdmiCecController(sp<IHdmiCec> hdmiCec,
182        jobject callbacksObj, const sp<Looper>& looper)
183        : mHdmiCec(hdmiCec),
184          mCallbacksObj(callbacksObj),
185          mLooper(looper) {
186    mHdmiCecCallback = new HdmiCecCallback(this);
187    Return<void> ret = mHdmiCec->setCallback(mHdmiCecCallback);
188    if (!ret.isOk()) {
189        ALOGE("Failed to set a cec callback.");
190    }
191}
192
193HdmiCecController::~HdmiCecController() {
194    Return<void> ret = mHdmiCec->setCallback(nullptr);
195    if (!ret.isOk()) {
196        ALOGE("Failed to set a cec callback.");
197    }
198}
199
200int HdmiCecController::sendMessage(const CecMessage& message) {
201    // TODO: propagate send_message's return value.
202    Return<SendMessageResult> ret = mHdmiCec->sendMessage(message);
203    if (!ret.isOk()) {
204        ALOGE("Failed to send CEC message.");
205        return static_cast<int>(SendMessageResult::FAIL);
206    }
207    return static_cast<int>((SendMessageResult) ret);
208}
209
210int HdmiCecController::addLogicalAddress(CecLogicalAddress address) {
211    Return<Result> ret = mHdmiCec->addLogicalAddress(address);
212    if (!ret.isOk()) {
213        ALOGE("Failed to add a logical address.");
214        return static_cast<int>(Result::FAILURE_UNKNOWN);
215    }
216    return static_cast<int>((Result) ret);
217}
218
219void HdmiCecController::clearLogicaladdress() {
220    Return<void> ret = mHdmiCec->clearLogicalAddress();
221    if (!ret.isOk()) {
222        ALOGE("Failed to clear logical address.");
223    }
224}
225
226int HdmiCecController::getPhysicalAddress() {
227    Result result;
228    uint16_t addr;
229    Return<void> ret = mHdmiCec->getPhysicalAddress([&result, &addr](Result res, uint16_t paddr) {
230            result = res;
231            addr = paddr;
232        });
233    if (!ret.isOk()) {
234        ALOGE("Failed to get physical address.");
235        return INVALID_PHYSICAL_ADDRESS;
236    }
237    return result == Result::SUCCESS ? addr : INVALID_PHYSICAL_ADDRESS;
238}
239
240int HdmiCecController::getVersion() {
241    Return<int32_t> ret = mHdmiCec->getCecVersion();
242    if (!ret.isOk()) {
243        ALOGE("Failed to get cec version.");
244    }
245    return ret;
246}
247
248uint32_t HdmiCecController::getVendorId() {
249    Return<uint32_t> ret = mHdmiCec->getVendorId();
250    if (!ret.isOk()) {
251        ALOGE("Failed to get vendor id.");
252    }
253    return ret;
254}
255
256jobjectArray HdmiCecController::getPortInfos() {
257    JNIEnv* env = AndroidRuntime::getJNIEnv();
258    jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo");
259    if (hdmiPortInfo == NULL) {
260        return NULL;
261    }
262    jmethodID ctor = env->GetMethodID(hdmiPortInfo, "<init>", "(IIIZZZ)V");
263    if (ctor == NULL) {
264        return NULL;
265    }
266    hidl_vec<HdmiPortInfo> ports;
267    Return<void> ret = mHdmiCec->getPortInfo([&ports](hidl_vec<HdmiPortInfo> list) {
268            ports = list;
269        });
270    if (!ret.isOk()) {
271        ALOGE("Failed to get port information.");
272        return NULL;
273    }
274    jobjectArray res = env->NewObjectArray(ports.size(), hdmiPortInfo, NULL);
275
276    // MHL support field will be obtained from MHL HAL. Leave it to false.
277    jboolean mhlSupported = (jboolean) 0;
278    for (size_t i = 0; i < ports.size(); ++i) {
279        jboolean cecSupported = (jboolean) ports[i].cecSupported;
280        jboolean arcSupported = (jboolean) ports[i].arcSupported;
281        jobject infoObj = env->NewObject(hdmiPortInfo, ctor, ports[i].portId, ports[i].type,
282                ports[i].physicalAddress, cecSupported, mhlSupported, arcSupported);
283        env->SetObjectArrayElement(res, i, infoObj);
284    }
285    return res;
286}
287
288void HdmiCecController::setOption(OptionKey key, bool enabled) {
289    Return<void> ret = mHdmiCec->setOption(key, enabled);
290    if (!ret.isOk()) {
291        ALOGE("Failed to set option.");
292    }
293}
294
295void HdmiCecController::setLanguage(hidl_string language) {
296    Return<void> ret = mHdmiCec->setLanguage(language);
297    if (!ret.isOk()) {
298        ALOGE("Failed to set language.");
299    }
300}
301
302// Enable audio return channel.
303void HdmiCecController::enableAudioReturnChannel(int port, bool enabled) {
304    Return<void> ret = mHdmiCec->enableAudioReturnChannel(port, enabled);
305    if (!ret.isOk()) {
306        ALOGE("Failed to enable/disable ARC.");
307    }
308}
309
310// Whether to hdmi device is connected to the given port.
311bool HdmiCecController::isConnected(int port) {
312    Return<bool> ret = mHdmiCec->isConnected(port);
313    if (!ret.isOk()) {
314        ALOGE("Failed to get connection info.");
315    }
316    return ret;
317}
318
319Return<void> HdmiCecController::HdmiCecCallback::onCecMessage(const CecMessage& message) {
320    sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(mController, message));
321    mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::CEC_MESSAGE);
322    return Void();
323}
324
325Return<void> HdmiCecController::HdmiCecCallback::onHotplugEvent(const HotplugEvent& event) {
326    sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(mController, event));
327    mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::HOT_PLUG);
328    return Void();
329}
330
331//------------------------------------------------------------------------------
332#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
333        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
334        LOG_FATAL_IF(! (var), "Unable to find method " methodName);
335
336static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
337        jobject messageQueueObj) {
338    // TODO(b/31632518)
339    sp<IHdmiCec> hdmiCec = IHdmiCec::getService();
340    if (hdmiCec == nullptr) {
341        ALOGE("Couldn't get tv.cec service.");
342        return 0;
343    }
344    sp<MessageQueue> messageQueue =
345            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
346
347    HdmiCecController* controller = new HdmiCecController(
348            hdmiCec,
349            env->NewGlobalRef(callbacksObj),
350            messageQueue->getLooper());
351
352    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
353            "handleIncomingCecCommand", "(II[B)V");
354    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
355            "handleHotplug", "(IZ)V");
356
357    return reinterpret_cast<jlong>(controller);
358}
359
360static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
361        jint srcAddr, jint dstAddr, jbyteArray body) {
362    CecMessage message;
363    message.initiator = static_cast<CecLogicalAddress>(srcAddr);
364    message.destination = static_cast<CecLogicalAddress>(dstAddr);
365
366    jsize len = env->GetArrayLength(body);
367    ScopedByteArrayRO bodyPtr(env, body);
368    size_t bodyLength = MIN(static_cast<size_t>(len),
369            static_cast<size_t>(MaxLength::MESSAGE_BODY));
370    message.body.resize(bodyLength);
371    for (size_t i = 0; i < bodyLength; ++i) {
372        message.body[i] = static_cast<uint8_t>(bodyPtr[i]);
373    }
374
375    HdmiCecController* controller =
376            reinterpret_cast<HdmiCecController*>(controllerPtr);
377    return controller->sendMessage(message);
378}
379
380static jint nativeAddLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr,
381        jint logicalAddress) {
382    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
383    return controller->addLogicalAddress(static_cast<CecLogicalAddress>(logicalAddress));
384}
385
386static void nativeClearLogicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
387    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
388    controller->clearLogicaladdress();
389}
390
391static jint nativeGetPhysicalAddress(JNIEnv* env, jclass clazz, jlong controllerPtr) {
392    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
393    return controller->getPhysicalAddress();
394}
395
396static jint nativeGetVersion(JNIEnv* env, jclass clazz, jlong controllerPtr) {
397    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
398    return controller->getVersion();
399}
400
401static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {
402    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
403    return controller->getVendorId();
404}
405
406static jobjectArray nativeGetPortInfos(JNIEnv* env, jclass clazz, jlong controllerPtr) {
407    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
408    return controller->getPortInfos();
409}
410
411static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag, jint value) {
412    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
413    controller->setOption(static_cast<OptionKey>(flag), value > 0 ? true : false);
414}
415
416static void nativeSetLanguage(JNIEnv* env, jclass clazz, jlong controllerPtr, jstring language) {
417    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
418    const char *languageStr = env->GetStringUTFChars(language, NULL);
419    controller->setLanguage(languageStr);
420    env->ReleaseStringUTFChars(language, languageStr);
421}
422
423static void nativeEnableAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr,
424        jint port, jboolean enabled) {
425    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
426    controller->enableAudioReturnChannel(port, enabled == JNI_TRUE);
427}
428
429static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) {
430    HdmiCecController* controller = reinterpret_cast<HdmiCecController*>(controllerPtr);
431    return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ;
432}
433
434static const JNINativeMethod sMethods[] = {
435    /* name, signature, funcPtr */
436    { "nativeInit",
437      "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
438      (void *) nativeInit },
439    { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
440    { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
441    { "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
442    { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
443    { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
444    { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
445    { "nativeGetPortInfos",
446      "(J)[Landroid/hardware/hdmi/HdmiPortInfo;",
447      (void *) nativeGetPortInfos },
448    { "nativeSetOption", "(JIZ)V", (void *) nativeSetOption },
449    { "nativeSetLanguage", "(JLjava/lang/String;)V", (void *) nativeSetLanguage },
450    { "nativeEnableAudioReturnChannel", "(JIZ)V", (void *) nativeEnableAudioReturnChannel },
451    { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected },
452};
453
454#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
455
456int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
457    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
458    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
459    (void)res; // Don't scream about unused variable in the LOG_NDEBUG case
460    return 0;
461}
462
463}  /* namespace android */
464