summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJungshik Jang <jayjang@google.com>2014-04-24 20:30:09 +0900
committerJungshik Jang <jayjang@google.com>2014-04-30 14:29:04 +0900
commite9c77c88ea34a66f83a94f960547275c0ff6bd07 (patch)
treeb4ddacedf69a811881426a35f71fd224d96fe6c2
parentaa2112f671e4f14555e25a6d765ce4bad454a43f (diff)
downloadframeworks_base-e9c77c88ea34a66f83a94f960547275c0ff6bd07.zip
frameworks_base-e9c77c88ea34a66f83a94f960547275c0ff6bd07.tar.gz
frameworks_base-e9c77c88ea34a66f83a94f960547275c0ff6bd07.tar.bz2
Implement native send and receive logic for HdmiCecController.
This change includes native jni implementation for incoming and outgoing message of CEC. For incoming message, native layer converts it into three pieces, source address (initiator), destination address (follower) and data body which includes opcode. In Java layer, it is delegated to main io thread. For now all messages are rejected by sending <Feature Abort> to initiator. For outoging message, all messages are sent to io thread and it delegates it into native layer. Native logic converts it into cec_message and pass it to HAL so that HAL performs sending message. In order to handle <Feature Abort> message, added [Abort reason] which is defined in hdmi_cec.h. Change-Id: If9fd74745f476105e5cfae964e39c78bae69d3e2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java132
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java34
-rw-r--r--services/core/jni/com_android_server_hdmi_HdmiCecController.cpp134
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"