TvInputService Implementation : KeyEvents to a HDMI-CEC Device
As we know Android Tv Input Framework, a data source or Tv Input like IP-TV, Tuner, HDMI etc. is nothing else but the implementation of TvInputService. So if any of these datasource/device need KeyEvents, that should be implemented in TvInputService.
A TvInputService implementation can be found in this post.
For this following TvInputService$Session must be overridden in order to intercept key down events before they are processed by the application.
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return false;
}
If true is returned, the application will not process the event itself.
If false is returned, the normal application processing will occur as if the TV input had not seen the event at all.
We can overide these methods to control a connected HDMI-CEC device.
Following code should be added to our TvInputService implementation in order to work this.
mHdmiControlService = IHdmiControlService.Stub
.asInterface(ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
mHdmiControlService.deviceSelect(currentHdmiDeviceInfo.getDeviceId(), mHdmiControlCallbackImpl);
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(interestedToHandle(keyCode))
{
mHdmiControlService.sendKeyEvent(activeLocalDeviceType, keyCode, true);
return true;
}
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if(interestedToHandle(keyCode))
{
mHdmiControlService.sendKeyEvent(activeLocalDeviceType, keyCode, false);
return true;
}
return false;
}
So now let's see how the Android system key event will flow through HdmiControlService and mapped as CEC message, passed to hdmi_cec HAL.
Here is the call flow,
HdmiControlService.java
sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
localDevice.sendKeyEvent(keyCode, isPressed);
}
HdmiCecLocalDeviceTv.java
sendKeyEvent(int keyCode, boolean isPressed) {
action.get(0).processKeyEvent(keyCode, isPressed);
}
SendKeyAction.java
processKeyEvent(int keycode, boolean isPressed) {
if (isPressed) {
sendKeyDown(keycode);
} else {
sendKeyUp();
}
sendKeyDown(int keycode) {
byte[] cecKeycodeAndParams = HdmiCecKeycode.androidKeyToCecKey(keycode);
sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
mTargetAddress, cecKeycodeAndParams));
}
sendKeyUp() {
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
mTargetAddress));
}
HdmiCecMessageBuilder builds the HdmiCecMessage/Commands. HdmiCecKeycode class contains Android Key to CEC command mapping.
HdmiCecMessage contains source and destination address, command (or opcode) and optional params.
HdmiCecFeatureAction.java
sendCommand(HdmiCecMessage cmd) {
mService.sendCecCommand(cmd);
}
HdmiControlService.java
Transmit a CEC command to CEC bus.
sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
mCecController.sendCommand(command, callback);
}
HdmiCecController.java
sendCommand(final HdmiCecMessage cecMessage,
final HdmiControlService.SendMessageCallback callback) {
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
errorCode = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
cecMessage.getDestination(), body);
callback.onSendCompleted(finalError);
}
/jni/com_android_server_hdmi_HdmiCecController.cpp
nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
jint srcAddr, jint dstAddr, jbyteArray body) {
cec_message_t message;
return controller->sendMessage(message);
}
HdmiCecController::sendMessage(const cec_message_t& message) {
return mDevice->send_message(mDevice, &message);
}
Finally the call in hdmi_cec HAL, and if following method implemented to interface hdmi-cec driver,
the device will respond to the keys pressed on remote of hosting device.
(*send_message)() //transmits HDMI-CEC message to other HDMI device.