diff options
author | Jungshik Jang <jayjang@google.com> | 2014-05-02 00:32:44 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-05-02 00:32:44 +0000 |
commit | 615d1337fcb4b7e16706e615b19305eea7b689bb (patch) | |
tree | bc7c712572f5d4c11fbc2b7b57fea1d3aabba253 /services | |
parent | 27e12e72926a552bb5f7d9dc57e9ad562fe4f3b0 (diff) | |
parent | e9c77c88ea34a66f83a94f960547275c0ff6bd07 (diff) | |
download | frameworks_base-615d1337fcb4b7e16706e615b19305eea7b689bb.zip frameworks_base-615d1337fcb4b7e16706e615b19305eea7b689bb.tar.gz frameworks_base-615d1337fcb4b7e16706e615b19305eea7b689bb.tar.bz2 |
Merge "Implement native send and receive logic for HdmiCecController."
Diffstat (limited to 'services')
3 files changed, 262 insertions, 38 deletions
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 5f07108..8d4341d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -16,9 +16,14 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiCec; +import android.hardware.hdmi.HdmiCecMessage; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.Slog; + +import java.util.Arrays; /** * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command @@ -32,12 +37,27 @@ import android.os.Message; class HdmiCecController { private static final String TAG = "HdmiCecController"; + // A message to pass cec send command to IO looper. + private static final int MSG_SEND_CEC_COMMAND = 1; + + // Message types to handle incoming message in main service looper. + private final static int MSG_RECEIVE_CEC_COMMAND = 1; + + // TODO: move these values to HdmiCec.java once make it internal constant class. + // CEC's ABORT reason values. + private static final int ABORT_UNRECOGNIZED_MODE = 0; + private static final int ABORT_NOT_IN_CORRECT_MODE = 1; + private static final int ABORT_CANNOT_PROVIDE_SOURCE = 2; + private static final int ABORT_INVALID_OPERAND = 3; + private static final int ABORT_REFUSED = 4; + private static final int ABORT_UNABLE_TO_DETERMINE = 5; + // Handler instance to process synchronous I/O (mainly send) message. private Handler mIoHandler; // Handler instance to process various messages coming from other CEC // device or issued by internal state change. - private Handler mMessageHandler; + private Handler mControlHandler; // Stores the pointer to the native implementation of the service that // interacts with HAL. @@ -52,14 +72,12 @@ class HdmiCecController { * inner device or has no device it will return {@code null}. * * <p>Declared as package-private, accessed by {@link HdmiControlService} only. - * - * @param ioLooper a Looper instance to handle IO (mainly send message) operation. - * @param messageHandler a message handler that processes a message coming from other - * CEC compatible device or callback of internal state change. + * @param service {@link HdmiControlService} instance used to create internal handler + * and to pass callback for incoming message or event. * @return {@link HdmiCecController} if device is initialized successfully. Otherwise, * returns {@code null}. */ - static HdmiCecController create(Looper ioLooper, Handler messageHandler) { + static HdmiCecController create(HdmiControlService service) { HdmiCecController handler = new HdmiCecController(); long nativePtr = nativeInit(handler); if (nativePtr == 0L) { @@ -67,28 +85,108 @@ class HdmiCecController { return null; } - handler.init(ioLooper, messageHandler, nativePtr); + handler.init(service, nativePtr); return handler; } - private void init(Looper ioLooper, Handler messageHandler, long nativePtr) { - mIoHandler = new Handler(ioLooper) { - @Override - public void handleMessage(Message msg) { - // TODO: Call native sendMessage. + private static byte[] buildBody(int opcode, byte[] params) { + byte[] body = new byte[params.length + 1]; + body[0] = (byte) opcode; + System.arraycopy(params, 0, body, 1, params.length); + return body; + } + + private final class IoHandler extends Handler { + private IoHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SEND_CEC_COMMAND: + HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj; + byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); + nativeSendCecCommand(mNativePtr, cecMessage.getSource(), + cecMessage.getDestination(), body); + break; + default: + Slog.w(TAG, "Unsupported CEC Io request:" + msg.what); + break; + } + } + } + + private final class ControlHandler extends Handler { + private ControlHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_RECEIVE_CEC_COMMAND: + // TODO: delegate it to HdmiControl service. + onReceiveCommand((HdmiCecMessage) msg.obj); + break; + default: + Slog.i(TAG, "Unsupported message type:" + msg.what); + break; } - }; + } + } - mMessageHandler = messageHandler; + private void init(HdmiControlService service, long nativePtr) { + mIoHandler = new IoHandler(service.getServiceLooper()); + mControlHandler = new ControlHandler(service.getServiceLooper()); mNativePtr = nativePtr; } + private void onReceiveCommand(HdmiCecMessage message) { + // TODO: Handle message according to opcode type. + + // TODO: Use device's source address for broadcast message. + int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ? + message.getDestination() : 0; + // Reply <Feature Abort> to initiator (source) for all requests. + sendFeatureAbort(sourceAddress, message.getSource(), message.getOpcode(), + ABORT_REFUSED); + } + + private void sendFeatureAbort(int srcAddress, int destAddress, int originalOpcode, + int reason) { + byte[] params = new byte[2]; + params[0] = (byte) originalOpcode; + params[1] = (byte) reason; + + HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, destAddress, + HdmiCec.MESSAGE_FEATURE_ABORT, params); + Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage); + mIoHandler.sendMessage(message); + } + + /** + * 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); + HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params); + + // Delegate message to main handler so that it handles in main thread. + Message message = mControlHandler.obtainMessage( + MSG_RECEIVE_CEC_COMMAND, cecMessage); + mControlHandler.sendMessage(message); + } + /** - * Called by native when an HDMI-CEC message arrived. + * Called by native when a hotplug event issues. */ - private void handleMessage(int srcAddress, int dstAddres, int opcode, byte[] params) { - // TODO: Translate message and delegate it to main message handler. + private void handleHotplug(boolean connected) { + // TODO: Delegate event to main message handler. } private static native long nativeInit(HdmiCecController handler); + private static native int nativeSendCecCommand(long contollerPtr, int srcAddress, + int dstAddress, byte[] body); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 56c5b49..7c1995e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -18,9 +18,8 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.content.Context; -import android.os.Handler; import android.os.HandlerThread; -import android.os.Message; +import android.os.Looper; import android.util.Slog; import com.android.server.SystemService; @@ -37,14 +36,6 @@ public final class HdmiControlService extends SystemService { // and sparse call it shares a thread to handle IO operations. private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); - // Main handler class to handle incoming message from each controller. - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - // TODO: Add handler for each message type. - } - }; - @Nullable private HdmiCecController mCecController; @@ -57,14 +48,33 @@ public final class HdmiControlService extends SystemService { @Override public void onStart() { - mCecController = HdmiCecController.create(mIoThread.getLooper(), mHandler); + mCecController = HdmiCecController.create(this); if (mCecController == null) { Slog.i(TAG, "Device does not support HDMI-CEC."); } - mMhlController = HdmiMhlController.create(mIoThread.getLooper(), mHandler); + mMhlController = HdmiMhlController.create(this); if (mMhlController == null) { Slog.i(TAG, "Device does not support MHL-control."); } } + + /** + * Returns {@link Looper} for IO operation. + * + * <p>Declared as package-private. + */ + Looper getIoLooper() { + return mIoThread.getLooper(); + } + + /** + * Returns {@link Looper} of main thread. Use this {@link Looper} instance + * for tasks that are running on main service thread. + * + * <p>Declared as package-private. + */ + Looper getServiceLooper() { + return Looper.myLooper(); + } } diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp index f3e8f3c..cfe74ed 100644 --- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp +++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp @@ -19,57 +19,173 @@ #define LOG_NDEBUG 1 #include "JNIHelp.h" +#include "ScopedPrimitiveArray.h" + +#include <string> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> #include <hardware/hdmi_cec.h> +#include <sys/param.h> namespace android { static struct { - jmethodID handleMessage; + jmethodID handleIncomingCecCommand; + jmethodID handleHotplug; } gHdmiCecControllerClassInfo; - class HdmiCecController { public: - HdmiCecController(jobject callbacksObj); + HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj); + + void init(); + + // Send message to other device. Note that it runs in IO thread. + int sendMessage(const cec_message_t& message); private: + // Propagate the message up to Java layer. + void propagateCecCommand(const cec_message_t& message); + void propagateHotplugEvent(const hotplug_event_t& event); + 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; }; -HdmiCecController::HdmiCecController(jobject callbacksObj) : +HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj) : + mDevice(device), mCallbacksObj(callbacksObj) { } +void HdmiCecController::init() { + mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this); +} + +void HdmiCecController::propagateCecCommand(const cec_message_t& message) { + jint srcAddr = message.initiator; + jint dstAddr = message.destination; + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jbyteArray body = env->NewByteArray(message.length); + 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->DeleteLocalRef(body); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +void HdmiCecController::propagateHotplugEvent(const hotplug_event_t& event) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mCallbacksObj, + gHdmiCecControllerClassInfo.handleHotplug, event.connected); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +int HdmiCecController::sendMessage(const cec_message_t& message) { + // TODO: propagate send_message's return value. + return mDevice->send_message(mDevice, &message); +} + +// 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) { - HdmiCecController* handler = static_cast<HdmiCecController*>(arg); - if (handler == NULL) { + HdmiCecController* controller = static_cast<HdmiCecController*>(arg); + if (controller == NULL) { return; } - // TODO: propagate message to Java layer. + 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; + } } - //------------------------------------------------------------------------------ +#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + 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: initialize hal and pass it to controller if ready. + 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", + 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); + if (err != 0) { + ALOGE("Error opening hardware module: %d", err); + return 0; + } HdmiCecController* controller = new HdmiCecController( + reinterpret_cast<hdmi_cec_device*>(device), env->NewGlobalRef(callbacksObj)); + controller->init(); + + GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz, + "handleIncomingCecCommand", "(II[B)V"); + GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz, + "handleHotplug", "(Z)V"); return reinterpret_cast<jlong>(controller); } +static jint nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr, + jint srcAddr, jint dstAddr, jbyteArray body) { + cec_message_t message; + message.initiator = static_cast<cec_logical_address_t>(srcAddr); + message.destination = static_cast<cec_logical_address_t>(dstAddr); + + jsize len = env->GetArrayLength(body); + message.length = MIN(len, CEC_MESSAGE_BODY_MAX_LENGTH); + ScopedByteArrayRO bodyPtr(env, body); + std::memcpy(message.body, bodyPtr.get(), len); + + HdmiCecController* controller = + reinterpret_cast<HdmiCecController*>(controllerPtr); + return controller->sendMessage(message); +} + static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J", (void *) nativeInit }, + { "nativeSendCommand", "(JII[B)I", + (void *) nativeSendCecCommand }, }; #define CLASS_PATH "com/android/server/hdmi/HdmiCecController" |