Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c47e26ca authored by Jungshik Jang's avatar Jungshik Jang
Browse files

DO NOT MERGE: Add cec message handler to hdmi cec jni implementation

Notification for incoming cec message can be issued
from any thread but jni can call java method
in the thread that jni knows like service thread.
So this change delegates incoming message to
jni-friendly thread, service thread by exploit
looper of service thread.

Change-Id: If3b141d917df3e209912af1778eb509777199969
parent 2c2a3017
Loading
Loading
Loading
Loading
+7 −16
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Slog;
import android.util.SparseArray;

@@ -29,7 +30,6 @@ import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
import libcore.util.EmptyArray;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
@@ -99,7 +99,7 @@ final class HdmiCecController {
     */
    static HdmiCecController create(HdmiControlService service) {
        HdmiCecController controller = new HdmiCecController();
        long nativePtr = nativeInit(controller);
        long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
        if (nativePtr == 0L) {
            controller = null;
            return null;
@@ -471,8 +471,8 @@ final class HdmiCecController {

    private void onReceiveCommand(HdmiCecMessage message) {
        assertRunOnServiceThread();
        if (isAcceptableAddress(message.getDestination()) &&
                mService.handleCecCommand(message)) {
        if (isAcceptableAddress(message.getDestination())
                && mService.handleCecCommand(message)) {
            return;
        }

@@ -517,17 +517,8 @@ final class HdmiCecController {
     * Called by native when incoming CEC message arrived.
     */
    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
        byte opcode = body[0];
        byte params[] = Arrays.copyOfRange(body, 1, body.length);
        final HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);

        // Delegate message to main handler so that it handles in main thread.
        runOnServiceThread(new Runnable() {
            @Override
            public void run() {
                onReceiveCommand(cecMessage);
            }
        });
        assertRunOnServiceThread();
        onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
    }

    /**
@@ -539,7 +530,7 @@ final class HdmiCecController {
        mService.onHotplug(0, connected);
    }

    private static native long nativeInit(HdmiCecController handler);
    private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
    private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
            int dstAddress, byte[] body);
    private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
+15 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecMessage;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * A helper class to build {@link HdmiCecMessage} from various cec commands.
@@ -38,6 +39,20 @@ public class HdmiCecMessageBuilder {

    private HdmiCecMessageBuilder() {}

    /**
     * Build {@link HdmiCecMessage} from raw data.
     *
     * @param src source address of command
     * @param dest destination address of command
     * @param body body of message. It includes opcode.
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage of(int src, int dest, byte[] body) {
        byte opcode = body[0];
        byte params[] = Arrays.copyOfRange(body, 1, body.length);
        return new HdmiCecMessage(src, dest, opcode, params);
    }

    /**
     * Build <Feature Abort> command. <Feature Abort> consists of
     * 1 byte original opcode and 1 byte reason fields with basic fields.
+147 −65
Original line number Diff line number Diff line
@@ -21,12 +21,15 @@
#include "JNIHelp.h"
#include "ScopedPrimitiveArray.h"

#include <string>
#include <cstring>

#include <android_os_MessageQueue.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <hardware/hdmi_cec.h>
#include <sys/param.h>
#include <utils/Looper.h>
#include <utils/RefBase.h>

namespace android {

@@ -37,7 +40,8 @@ static struct {

class HdmiCecController {
public:
    HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj);
    HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj,
            const sp<Looper>& looper);

    void init();

@@ -54,28 +58,89 @@ public:
    // Get vendor id used for vendor command.
    uint32_t getVendorId();

private:
    // Propagate the message up to Java layer.
    void propagateCecCommand(const cec_message_t& message);
    void propagateHotplugEvent(const hotplug_event_t& event);
    jobject getCallbacksObj() const {
        return mCallbacksObj;
    }

private:
    static void onReceived(const hdmi_event_t* event, void* arg);
    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);

    hdmi_cec_device_t* mDevice;
    jobject mCallbacksObj;
    sp<Looper> mLooper;
};

HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj) :
    mDevice(device),
    mCallbacksObj(callbacksObj) {
// RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL
// may keep its own lifetime, we need to copy it in order to delegate
// it to service thread.
class CecEventWrapper : public LightRefBase<CecEventWrapper> {
public:
    CecEventWrapper(const hdmi_event_t& event) {
        // Copy message.
        switch (event.type) {
        case HDMI_EVENT_CEC_MESSAGE:
            mEvent.cec.initiator = event.cec.initiator;
            mEvent.cec.destination = event.cec.destination;
            mEvent.cec.length = event.cec.length;
            std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length);
            break;
        case HDMI_EVENT_HOT_PLUG:
            mEvent.hotplug.connected = event.hotplug.connected;
            mEvent.hotplug.port = event.hotplug.port;
            break;
        case HDMI_EVENT_TX_STATUS:
            mEvent.tx_status.status = event.tx_status.status;
            mEvent.tx_status.opcode = event.tx_status.opcode;
            break;
        default:
            // TODO: add more type whenever new type is introduced.
            break;
        }
    }

void HdmiCecController::init() {
    mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
    const cec_message_t& cec() const {
        return mEvent.cec;
    }

    const hotplug_event_t& hotplug() const {
        return mEvent.hotplug;
    }

void HdmiCecController::propagateCecCommand(const cec_message_t& message) {
    virtual ~CecEventWrapper() {}

private:
    hdmi_event_t mEvent;
};

// Handler class to delegate incoming message to service thread.
class HdmiCecEventHandler : public MessageHandler {
public:
    HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event)
        : mController(controller),
          mEventWrapper(event) {
    }

    virtual ~HdmiCecEventHandler() {}

    void handleMessage(const Message& message) {
        switch (message.what) {
        case HDMI_EVENT_CEC_MESSAGE:
            propagateCecCommand(mEventWrapper->cec());
            break;
        case HDMI_EVENT_HOT_PLUG:
            propagateHotplugEvent(mEventWrapper->hotplug());
            break;
        case HDMI_EVENT_TX_STATUS:
            // TODO: propagate this to controller.
        default:
            // TODO: add more type whenever new type is introduced.
            break;
        }
    }

private:
    // Propagate the message up to Java layer.
    void propagateCecCommand(const cec_message_t& message) {
        jint srcAddr = message.initiator;
        jint dstAddr = message.destination;
        JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -83,22 +148,47 @@ void HdmiCecController::propagateCecCommand(const cec_message_t& message) {
        const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
        env->SetByteArrayRegion(body, 0, message.length, bodyPtr);

    env->CallVoidMethod(mCallbacksObj,
            gHdmiCecControllerClassInfo.handleIncomingCecCommand,
            srcAddr, dstAddr, body);
        env->CallVoidMethod(mController->getCallbacksObj(),
                gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
                dstAddr, body);
        env->DeleteLocalRef(body);

        checkAndClearExceptionFromCallback(env, __FUNCTION__);
    }

void HdmiCecController::propagateHotplugEvent(const hotplug_event_t& event) {
    void propagateHotplugEvent(const hotplug_event_t& event) {
        // Note that this method should be called in service thread.
        JNIEnv* env = AndroidRuntime::getJNIEnv();
    env->CallVoidMethod(mCallbacksObj,
        env->CallVoidMethod(mController->getCallbacksObj(),
                gHdmiCecControllerClassInfo.handleHotplug, event.connected);

        checkAndClearExceptionFromCallback(env, __FUNCTION__);
    }

    // static
    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
        if (env->ExceptionCheck()) {
            ALOGE("An exception was thrown by callback '%s'.", methodName);
            LOGE_EX(env);
            env->ExceptionClear();
        }
    }

    HdmiCecController* mController;
    sp<CecEventWrapper> mEventWrapper;
};

HdmiCecController::HdmiCecController(hdmi_cec_device_t* device,
        jobject callbacksObj, const sp<Looper>& looper) :
    mDevice(device),
    mCallbacksObj(callbacksObj),
    mLooper(looper) {
}

void HdmiCecController::init() {
    mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
}

int HdmiCecController::sendMessage(const cec_message_t& message) {
    // TODO: propagate send_message's return value.
    return mDevice->send_message(mDevice, &message);
@@ -132,15 +222,6 @@ uint32_t HdmiCecController::getVendorId() {
    return vendorId;
}

// static
void HdmiCecController::checkAndClearExceptionFromCallback(JNIEnv* env,
        const char* methodName) {
    if (env->ExceptionCheck()) {
        ALOGE("An exception was thrown by callback '%s'.", methodName);
        LOGE_EX(env);
        env->ExceptionClear();
    }
}

// static
void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
@@ -149,17 +230,9 @@ void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
        return;
    }

    switch (event->type) {
    case HDMI_EVENT_CEC_MESSAGE:
        controller->propagateCecCommand(event->cec);
        break;
    case HDMI_EVENT_HOT_PLUG:
        controller->propagateHotplugEvent(event->hotplug);
        break;
    default:
        ALOGE("Unsupported event type: %d", event->type);
        break;
    }
    sp<CecEventWrapper> spEvent(new CecEventWrapper(*event));
    sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent));
    controller->mLooper->sendMessage(handler, event->type);
}

//------------------------------------------------------------------------------
@@ -167,31 +240,38 @@ void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
        LOG_FATAL_IF(! var, "Unable to find method " methodName);

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
// TODO: replace above code with following once
// replace old HdmiCecService with HdmiControlService
#undef HDMI_CEC_HARDWARE_MODULE_ID
#define HDMI_CEC_HARDWARE_MODULE_ID "hdmi_cec_module"
#undef HDMI_CEC_HARDWARE_INTERFACE
#define HDMI_CEC_HARDWARE_INTERFACE "hdmi_cec_module_hw_if"

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
        jobject messageQueueObj) {
    int err;
    // If use same hardware module id between HdmiCecService and
    // HdmiControlSservice it may conflict and cause abnormal state of HAL.
    // TODO: use HDMI_CEC_HARDWARE_MODULE_ID of hdmi_cec.h for module id
    //       once migration to HdmiControlService is done.
    hw_module_t* module;
    err = hw_get_module("hdmi_cec_module",
    err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
            const_cast<const hw_module_t **>(&module));
    if (err != 0) {
        ALOGE("Error acquiring hardware module: %d", err);
        return 0;
    }

    hw_device_t* device;
    // TODO: use HDMI_CEC_HARDWARE_INTERFACE of hdmi_cec.h for interface name
    //       once migration to HdmiControlService is done.
    err = module->methods->open(module, "hdmi_cec_module_hw_if", &device);
    err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
    if (err != 0) {
        ALOGE("Error opening hardware module: %d", err);
        return 0;
    }

    sp<MessageQueue> messageQueue =
            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);

    HdmiCecController* controller = new HdmiCecController(
            reinterpret_cast<hdmi_cec_device*>(device),
            env->NewGlobalRef(callbacksObj));
            env->NewGlobalRef(callbacksObj),
            messageQueue->getLooper());
    controller->init();

    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
@@ -255,7 +335,8 @@ static jint nativeGetVendorId(JNIEnv* env, jclass clazz, jlong controllerPtr) {

static JNINativeMethod sMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J",
    { "nativeInit",
      "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
      (void *) nativeInit },
    { "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
    { "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
@@ -268,7 +349,8 @@ static JNINativeMethod sMethods[] = {
#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"

int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
    int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods,
            NELEM(sMethods));
    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
    return 0;
}