Saturday, August 29, 2015

TvInputService Implementation : KeyEvents to a HDMI-CEC Device

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.




TvInputService Implementation : Displaying Video from Connected HDMI Device

TvInputService Implementation : Displaying Video from Connected HDMI 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.

As in the post we implemented a TvInputService for HDMI input source. In order to get the video from device connected to HDMI input, we need to override following method from TvInputService correctly.

public boolean onSetSurface(Surface surface) { }

Let's see the call flow from application and implement this method.

An application uses TvView and calls it's setSurface() method to show content from selected TvInput source.

TvView.java
surfaceCreated(SurfaceHolder holder) {
   mSurface = holder.getSurface();
   setSessionSurface(mSurface);
}

private void setSessionSurface(Surface surface) {
    mSession.setSurface(surface);
}

TvInputManager.java
setSurface(Surface surface) {
    mService.setSurface(mToken, surface, mUserId);
}

TvInputManagerService.java
setSurface(IBinder sessionToken, Surface surface, int userId) {
     getSessionLocked(sessionState.hardwareSessionToken,
                 Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
}

TvInputService.java
setSurface(Surface surface) {
    onSetSurface(surface);
}

Now we this need to be overriden. For HDMI-Input, or any other Hardware device, it can be done like below.

private final TvInputManager.HardwareCallback mHardwareCallback = new TvInputManager.HardwareCallback() {
    @Override
        public void onReleased() 
    {
    }
    @Override
        public void onStreamConfigChanged(TvStreamConfig[] configs)
                {
        mTvStreamConfig = configs;
                }

    };

    mHardware = mTvInputManager.acquireTvInputHardware(mDeviceId, mHardwareCallback, mTvInputInfo);

onSetSurface(Surface surface)
{
    mHardware.setSurface(mSurface, mTvStreamConfig[0]);
    return true;
}

Now this call will be directed to

TvInputManager.java
setSurface(Surface surface, TvStreamConfig config) {
      return mInterface.setSurface(surface, config);
}


TvInputHardwareManager.java
A TvInputHardwareImpl object holds only one active session. Therefore, if a client
attempts to call setSurface with different TvStreamConfig objects, the last call will prevail.

setSurface(Surface surface, TvStreamConfig config)

      if (surface == null) {
          result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
          mActiveConfig = null;
      } else {

          result = mHal.addStream(mInfo.getDeviceId(), surface, config);
}

TvInputHal.java
addStream(int deviceId, Surface surface, TvStreamConfig streamConfig) {

    if (nativeAddStream(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) {
          return SUCCESS;
}

jni/com_android_server_tv_TvInputHal.cpp
JTvInputHal::addStream(int deviceId, int streamId, const sp<Surface>& surface) {

   if (mDevice->get_stream_configurations(
           mDevice, deviceId, &numConfigs, &configs) != 0) {
       return UNKNOWN_ERROR;
   }

   if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) {
       ALOGE("Couldn't add stream");
       return UNKNOWN_ERROR;
   }

}

And finally in tv_input HAL implementation, here put your code for HDMI kernel driver calls to get the video stream.

tv_input_open_stream(struct tv_input_device*, int device id , tv_stream_t* stream)
{
//This is the place, the HDMI driver functions call related to opening stream should be made.
    return 0;
}

Similarly if surface is null, then close_stream will be called,

tv_input_close_stream(struct tv_input_device*, int device id , int)
{
//This is the place, the HDMI driver functions call related to closing stream should be made.
    return 0;
}

For a dummy tv_input HAL implementation, refer to post.
                     



TvInputService Implementation : Creating TvInputInfo

TvInputService Implementation : Creating TvInputInfo 


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.

TvInputManagerService provides the info of Hardware/HDMI devices available in the system, to all concrete classes of TvInputService by following callbcaks,

    @SystemApi
    public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
        return null;
    }

    @SystemApi
    public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
        return null;
    }

While implementing TvInputService for any Hardware or HDMI-CEC, we need to override these methods carefuly, so a create correct TvInputInfo which will be returned back to TvInputManagerService for storing it.

TvInputInfo.java
private final String mId; // a string representation(example at end) 
private final String mParentId; //relevant in case of HDMI-CEC

private int mType = TYPE_TUNER;
private HdmiDeviceInfo mHdmiDeviceInfo; //relevant in case of HDMI-CEC

For creating TvInputInfo we will use one of the overloaded static methods createTvInputInfo::createTvInputInfo,

1. For Built-in Tuner, createTvInputInfo(Context context, ResolveInfo service)
2. For HDMI-CEC, createTvInputInfo(Context, ResolveInfo service, HdmiDeviceInfo, String parentId, label, iconUri)
3. For other hardware like HDMI-Input, createTvInputInfo(Context, ResolveInfo service, TvInputHardwareInfo, label, iconUri)

Here is an example of method implementation,
public class MyTvInputService extends TvInputService
{
    private final Context context;
    private final ResolveInfo service;

    public void onCreate( 
    {
context = getApplicationContext();
service = context.getPackageManager().
resolveService(new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_INTENT_FILTERS | PackageManager.GET_META_DATA);

    }

    public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo)
    {

        TvInputInfo info;
        try    
        {
        String lebel = new StringBuilder("HDMI").append(hardwareInfo.getDeviceId()).toString();
        info = TvInputInfo.createTvInputInfo(context, service, hardwareInfo, lebel, iconUri);
        } catch (Exception ex)   {        }
        return info;
    }

    public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) 
    {
        TvInputInfo info;
        try    
        {
String lebel = deviceInfo.getDisplayName();
info = TvInputInfo.createTvInputInfo(context, service, deviceInfo, parentId, lebel, iconUri);
} catch (Exception ex)   {        }
        return info;
    }
}

In case of onHdmiDeviceAdded, parentId is ID of this TV input's parent input, this is the Id of previously registered TvInput's, on same port-id as this HdmiDeviceInfo's port.

In "adb shell dumpsys tv_input" we can see all the TvInputs available in system,
For example,
Id will be "com.xyz.android.tv/.MyTvInputService/HW1" for 1st HW of MyTvInputService in package com.xyz.android.tv



Wednesday, August 26, 2015

Android TV Framework : HDMI Events Call Flow to TvInputService

HDMI Events Call Flow to TvInputService

In previous post we understood how HdmiControlManager.DEVICE_EVENT_ADD_DEVICE event is generated in Hdmi framework, now we will check further how this event is reached in TvInputFramework and finaly to a TvInputService implementation.

There are 3 categories of events notified from HdmiControlService to TvInputFramework,
1.HotplugEvent and
2.DeviceEvent
3.SystemAudioModeChange

TvInputManagerService registers for these as EventListener in TvInputHardwareManager class.
TvInputHardwareManager manages the hardwares either of TvInputHal(tv_input_device_t) type implemented in tv_input.cpp or hdmi_cec_device Type implemented in HDMI-CEC hal.

public TvInputManagerService(){
   mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
}

//TvInputHardwareManager.java
public void onBootPhase(int phase) {
   mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
   mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
   mHdmiControlService.addSystemAudioModeChangeListener(
mHdmiSystemAudioModeChangeListener);
   mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices());

}

HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
   public void onReceived(HdmiHotplugEvent event) {
      mHdmiStateMap.put(event.getPort(), event.isConnected());

      mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
         convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
}

HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
  public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
    switch (status) {
         case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
            mHdmiDeviceList.add(deviceInfo);
            messageType = ListenerHandler.HDMI_DEVICE_ADDED;
         }
         case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
            if (!mHdmiDeviceList.remove(originalDeviceInfo)) 
            messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
         }
         case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
            if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
            mHdmiDeviceList.add(deviceInfo);
            messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
         }
         Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
msg.sendToTarget();
}

This handler notifies to TvInputManagerService.
ListenerHandler extends Handler {
  public final void handleMessage(Message msg) {
      switch (msg.what) {
          case STATE_CHANGED: {
              mListener.onStateChanged(inputId, state);
              break;
          }
          case HDMI_DEVICE_ADDED: {
              mListener.onHdmiDeviceAdded(info);
              break;
          }
          case HDMI_DEVICE_REMOVED: {
              mListener.onHdmiDeviceRemoved(info);
              break;
          }

TvInputManagerService broadcasts this event to all services currently available in system.

public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
   // Broadcast the event to all hardware inputs.
   serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
}

TvInputService gets the event and posts to its handler which will call the onHdmiDeviceAdded.

TvInputService.java
void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
   mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_TV_INPUT,
          deviceInfo).sendToTarget();
}

ServiceHandler extends Handler {
   case DO_ADD_HDMI_TV_INPUT: {
        HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
        TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
        if (inputInfo != null) {
              broadcastAddHdmiTvInput(deviceInfo.getId(), inputInfo);
        }
}

Note here that a custom TvInputService must override to modify default behavior of ignoring all HDMI logical input device i.e. build a TvInputInfo using this HdmiDeviceInfo and return it.

public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
     return null;
}
(How to override this? link TvInputService Implementation : Creating TvInputInfo )

With this TvInputInfo, TvInputService broadcasts to TvInputManagerService so that it can add/update this new TvInputInfo in TvInputList.
broadcastAddHdmiTvInput(int id, TvInputInfo inputInfo) {
                  mCallbacks.getBroadcastItem(i).addHdmiTvInput(id, inputInfo);
}

In next post we will use this HdmiDeviceInfo/TvInputInfo for CEC communication implementation.


Sunday, August 23, 2015

Android TV Framework : HDMI CEC Introduction

Android HDMI-CEC and Tv Input Framework

HDMI CEC feature enables a user to command different electronics devices, CEC enabled and connected through HDMI, with any of their remote control.
Android TV framework provides support for HDMI-CEC features like One Touch Play, Audio Control, Remote Control Pass-through etc.

This is accomplished by HdmiControlService, a system service which interacts with TvInput Framework and AudioSystem.

Full design and integration with TIF can be found on Android official site.

A HDMI input device connected to Android TV, can be controlled by the TV-remote.
The prebuilt TV application will get the all keyEvents and will pass to currently active Session(which is TvInputService). If TvInput Framework has already mapped the corresponding HDMI-ports to CEC logical addresses and these things are stored in corresponding TvInputHardwareInfo, currently active Session on a HDMI port, will dispatch the keys to same port via HdmiControlManager  to hdmi_cec HAL and finally CEC device will respond to command.

Like TvInputService and tv_input HAL dummy implementation, will implement a hdmi_cec HAL and modify our TvInputService to get the keyEvents and pass it as CEC command.

First let's see HDMI CEC service, it's initialization, interaction to HAL and TvInputFramework.

HdmiControlService is a SystemService which creates the HdmiCecController in it's onStart() call and enables OPTION_CEC_SERVICE_CONTROL option.

HdmiControlService::onStart() {

            if (mHdmiControlEnabled) {
                initializeCec(INITIATED_BY_BOOT_UP);

        mCecController = HdmiCecController.create(this);
            mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
        initPortInfo();
    }

HdmiCecController is the class which interacts to HDMI_CEC Hal through com_android_server_hdmi_HdmiCecController jni.
HdmiCecController::init() registers the HdmiCecController::onReceived callback for device events.

HdmiCecController.java
    static HdmiCecController create(HdmiControlService service) {
        HdmiCecController controller = new HdmiCecController(service);
        long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
        controller.init(nativePtr);
    }

    private void init(long nativePtr) {
        mNativePtr = nativePtr;
    }

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

Now in HdmiControlService::onStart(), initializeCec is called,

HdmiControlService.java
private void initializeCec(int initiatedBy) {
        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
        initializeLocalDevices(initiatedBy);
    }

private void initializeLocalDevices(final int initiatedBy) {
                localDevice = HdmiCecLocalDevice.create(this, type);
        allocateLogicalAddress(localDevices, initiatedBy);
    }

private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
            final int initiatedBy) {

            mCecController.allocateLogicalAddress(localDevice.getType(),//call to HAL
                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
                @Override
                public void onAllocated(int deviceType, int logicalAddress) {//Callback from controller

                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
                                HdmiControlManager.POWER_STATUS_ON);
                        localDevice.setDeviceInfo(deviceInfo);
                        mCecController.addLocalDevice(deviceType, localDevice);
                        mCecController.addLogicalAddress(logicalAddress);
                        allocatedDevices.add(localDevice);
                    }

                        notifyAddressAllocated(allocatedDevices, initiatedBy);
                    }
                }
            });
        }
    }

    private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
        for (HdmiCecLocalDevice device : devices) {
            int address = device.getDeviceInfo().getLogicalAddress();
            device.handleAddressAllocated(address, initiatedBy);
        }
    }

HdmiCecLocalDevice.java
   final void handleAddressAllocated(int logicalAddress, int reason) {
       onAddressAllocated(logicalAddress, reason);
   }

HdmiCecLocalDevice has two type of specialization
1.HdmiCecLocalDeviceTv for local TV type
2.HdmiCecLocalDevicePlayback for a MediaPlayer(CEC enabled) device connected to HDMI input.
And based on type, respective onAddressAllocated callback will be made.
For now let's follow Tv,

HdmiCecLocalDeviceTv::onAddressAllocated(int logicalAddress, int reason) {

       mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
               mAddress, mService.getPhysicalAddress(), mDeviceType));
       mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
               mAddress, mService.getVendorId()));
   
       launchDeviceDiscovery();
   }

HdmiControlService will again notify to  CecController addreses and vendor-id as CECMessage,
HdmiControlService.java
    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
            mCecController.sendCommand(command, callback);
            }

The CEC features are are encapsulated in multiple type of actions like OneTouchPlayAction,  OneTouchRecordAction, SystemAudioAction, VolumeControlAction, DeviceDiscoveryAction etc. which are started as needed, here first DeviceDiscoveryAction will be started.

HdmiCecLocalDeviceTv.java 
 void launchDeviceDiscovery() { 

       DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
               new DeviceDiscoveryCallback() {
                   @Override
                   public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
                       for (HdmiDeviceInfo info : deviceInfos) {
                           addCecDevice(info);
                       }
                       addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));

   }

    final void addCecDevice(HdmiDeviceInfo info) {
        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
    }


    private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {

        if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
            mService.invokeDeviceEventListeners(info, status);
        }
    }

And finally HdmiControlService will invoke it's listeners, which is Tv Input Framework.

HdmiControlService::invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                    record.mListener.onStatusChanged(device, status);
    }

In next post, we will see how HdmiControlManager.DEVICE_EVENT_ADD_DEVICE event is reached to a TvInputService implementation(like our implementation).

Saturday, August 15, 2015

Develop a HDMI TV Input using Android TV Framework - Part 2

Implement TvInput HAL for HDMI Input

This part deals with Android TV Input HAL implementation for HDMI. By end of this part a dummy input HAL will be implemented which can interact to any TVInput service like one of ours in part 1.
We will be able to see the interaction in logs as well as our input/session and service in "dumpsys tv_input"
If one has further lower level HDMI kernel driver implementation, it can be called here as mentioned at various placeholders and  will be able to play the videos from HDMI media player connected to Android TV, using an TV application.

As like other Android HALs, tv_input hal is located in /hardware/libhardware/modules/tv_input and default lib will be built as "tv_input.default.so",

So a basic dummy tv_input hal can be implemented like below :



typedef struct tv_input_private {
    tv_input_device_t device;

    // Callback related data
    const tv_input_callback_ops_t* callback;
    void* callback_data;
} tv_input_private_t;

static int tv_input_device_open(const struct hw_module_t* module,
        const char* name, struct hw_device_t** device);

static struct hw_module_methods_t tv_input_module_methods = {
    open: tv_input_device_open
};

tv_input_module_t HAL_MODULE_INFO_SYM = {
    common: {
        tag: HARDWARE_MODULE_TAG,
        version_major: 0,
        version_minor: 1,
        id: TV_INPUT_HARDWARE_MODULE_ID,
        name: "Sample TV input module",
        author: "The Android Open Source Project",
        methods: &tv_input_module_methods,
    }
};

#define HDMI_DEV_ID 1
#define HDMI_PORT_ID 1

void notify_hdmi_device_available()
{
    tv_input_event_t event;
    event.device_info.device_id =HDMI_DEV_ID;
    event.device_info.type = TV_INPUT_TYPE_HDMI;
    event.type = TV_INPUT_EVENT_DEVICE_AVAILABLE;
    event.device_info.audio_type = AUDIO_DEVICE_NONE;
    event.device_info.hdmi.port_id = HDMI_PORT_ID;
    callback->notify(dev, &event, data);
}

tv_stream_config_t* get_stream_configs()
{
    tv_stream_config_t* config = (tv_stream_config_t*)malloc(sizeof(config));
    config->stream_id=0;
    config->type =TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE ;
    config->max_video_width = 1280;
    config->max_video_height = 1080;
    return config;
}


static int tv_input_initialize(struct tv_input_device* dev,
        const tv_input_callback_ops_t* callback, void* data)
{
    if (dev == NULL || callback == NULL) {
        return -EINVAL;
    }
    tv_input_private_t* priv = (tv_input_private_t*)dev;
    if (priv->callback != NULL) {
        return -EEXIST;
    }
    priv->callback = callback;
    priv->callback_data = data;
    notify_hdmi_device_available(); 
    return 0;
}

static int tv_input_get_stream_configurations(
        const struct tv_input_device*, int, int*, const tv_stream_config_t**)
{
    *num_configurations = 1;
    *config = get_stream_configs();
    return 0;
}

static int tv_input_open_stream(struct tv_input_device*, int, tv_stream_t*)
{
    return 0;
}

static int tv_input_close_stream(struct tv_input_device*, int, int)
{
    return 0;
}

static int tv_input_request_capture(
        struct tv_input_device*, int, int, buffer_handle_t, uint32_t)
{
    return 0;
}

static int tv_input_cancel_capture(struct tv_input_device*, int, int, uint32_t)
{
    return 0;
}

static int tv_input_device_close(struct hw_device_t *dev)
{
    tv_input_private_t* priv = (tv_input_private_t*)dev;
    if (priv) {
        free(priv);
    }
    return 0;
}


static int tv_input_device_open(const struct hw_module_t* module,
        const char* name, struct hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name, TV_INPUT_DEFAULT_DEVICE)) {
        tv_input_private_t* dev = (tv_input_private_t*)malloc(sizeof(*dev));

        /* initialize our state here */
        memset(dev, 0, sizeof(*dev));

        /* initialize the procs */
        dev->device.common.tag = HARDWARE_DEVICE_TAG;
        dev->device.common.version = TV_INPUT_DEVICE_API_VERSION_0_1;
        dev->device.common.module = const_cast<hw_module_t*>(module);
        dev->device.common.close = tv_input_device_close;

        dev->device.initialize = tv_input_initialize;
        dev->device.get_stream_configurations =
                tv_input_get_stream_configurations;
        dev->device.open_stream = tv_input_open_stream;
        dev->device.close_stream = tv_input_close_stream;
        dev->device.request_capture = tv_input_request_capture;
        dev->device.cancel_capture = tv_input_cancel_capture;

        *device = &dev->device.common;
        status = 0;
    }
    return status;
}

This HAL will interact to "TvInputManagerService", a SystemService which will call tv_input_initialize at the time of system bootup.

TvInputManagerService uses TvInputHardwareManager/TvInputHal, further jni binding, source code location is as below.

/frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java

/frameworks/base/services/core/jni/com_android_server_tv_TvInputHal.cpp

Now if HDMI source is selected from TV application, it will interact to TvInputService(implemented in part1) and TvInputService will be able to further talk to our above  tv_input hal.

Also $adb shell dumpsys tv_input shall now list our input in "inputmap" and if session is running, it will show sessionStateMap as well.


Develop a HDMI TV Input using Android TV Framework - Part 1

This basic article is for System Developer for Android TV, provides background on what and how to develop so that a TV application(which is based on Android TV framework) can render video from a HDMI input(like Nexus Player or HDMI Media Player etc.) plugged in TV.

We can further divide this task in 2 sub-task -

[1]Develop TvInputService for HDMI Input

[2]Implement TvInput HAL for HDMI Input


Also this development is done in a SOC vendor side, i.e.

  • First sub-task will be part of System-Apps, it uses System APIs,
  • and second HAL implementation sub-task is a typical Android HAL implementation which relies on lower layer HDMI layer. 



Before this following Android Developer pages should be gone through



And obviously one must have installed a TV application(like "Live Channels" by Google) which can detect and interact to our HDMI input implementation.

Now let's start 1st part, 

Develop TvInputService for HDMI Input


Android TV framework base classes/APIs can be found in following path
/platform/frameworks/base/media/java/android/media/tv/

A TV input Service should be extended from TvInputService and hence implements all the abstract methods of it and it's inner class Session, also overrides some of methods.

public class HDMIInputService extends TvInputService
{

    private int HDMI_HW_ID = 1;
    private TvInputInfo mTvInputInfo;

    @Override
    public Session onCreateSession(String inputId)
    {
        return new HDMISession(this, inputId);
    }

    @Override
    public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo)
    {
        Context context = getApplicationContext();
        ResolveInfo ri = context.getPackageManager().resolveService(new Intent("android.media.tv.TvInputService"),          PackageManager.GET_INTENT_FILTERS | PackageManager.GET_META_DATA);
        mTvInputInfo = TvInputInfo.createTvInputInfo(context, ri, hardwareInfo, null, TvContract.buildChannelUriForPassthroughInput(Id));
        return mTvInputInfo;
    }
    @Override
    public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo)
    {
        return null;
    }

    private class  HDMISession extends Session
    {

        private final Context mContext;
        private final String mInputId;
        private Surface mSurface;
        private float mVolume;
        private boolean mCaptionEnabled;
        private TvInputManager mTvInputManager;
        private TvInputManager.Hardware mHardware;
        private TvStreamConfig[] mTvStreamConfig;

        private final TvInputManager.HardwareCallback mHardwareCallback = new TvInputManager.HardwareCallback() {

            @Override
            public void onReleased() { }

            @Override
            public void onStreamConfigChanged(TvStreamConfig[] configs)
            {
                mTvStreamConfig = configs;
            }

        };

        HDMISession (Context context, String inputId)
        {
            super(context);
            mContext = context;
            mInputId = inputId;
            mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
            mHardware = mTvInputManager.acquireTvInputHardware(HDMI_HW_ID, mHardwareCallback, mTvInputInfo);
        }
        @Override
        public void onRelease() {
            mTvInputManager.releaseTvInputHardware(HDMI_HW_ID, mHardware);
        }
        @Override
        public boolean onSetSurface(Surface surface)
        {
            mSurface = surface;
            mHardware.setSurface(mSurface, mTvStreamConfig[0]);
            return true;
        }
        @Override
        public void onSetStreamVolume(float volume)
        {
            mVolume = volume;
        }
        @Override
        public boolean onTune(Uri channeluri)
        {
            notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
            notifyVideoAvailable();
            return true;
        }
        @Override
        public void onSetCaptionEnabled(boolean enabled)
        {
            mCaptionEnabled = enabled;
        }        
        @Override
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            return false; //if not handling, otherwise return true
        }

    }

}

To keep things simple, only minimal code is written here. This can be build as independent apk and installed. Reference can be taken from SampleTvInput here.
Now in second part, HDMI tv_input HAL will be implemented.