Merge "Implement native send and receive logic for HdmiCecController."
This commit is contained in:
committed by
Android (Google) Code Review
commit
615d1337fc
@@ -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;
|
||||
}
|
||||
|
||||
mMessageHandler = messageHandler;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 an HDMI-CEC message arrived.
|
||||
* Called by native when incoming CEC message arrived.
|
||||
*/
|
||||
private void handleMessage(int srcAddress, int dstAddres, int opcode, byte[] params) {
|
||||
// TODO: Translate message and delegate it to main message handler.
|
||||
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 a hotplug event issues.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
static void onReceived(const hdmi_event_t* event, void* arg);
|
||||
// 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"
|
||||
|
||||
Reference in New Issue
Block a user