从Hal开始,具体的code不贴了,只贴下接口。
1.注册接口
hardware\libhardware\include\hardware\hdmi_cec.h
/*
* (*register_event_callback)() registers a callback that HDMI-CEC HAL
* can later use for incoming CEC messages or internal HDMI events.
* When calling from C++, use the argument arg to pass the calling object.
* It will be passed back when the callback is invoked so that the context
* can be retrieved.
*/
void (*register_event_callback)(const struct hdmi_cec_device* dev,
event_callback_t callback, void* arg);
2.IHdmiCec callback接口回调实现
Z:\p\hardware\interfaces\tv\cec\1.0\default\HdmiCec.h
如果是HDMI_EVENT_HOT_PLUG,则会带上connected boolean值和对应的portId。如果是盒子的话,portId就是1,TV的话就是从1-4.
static void eventCallback(const hdmi_event_t* event, void* /* arg */) {
if (mCallback != nullptr && event != nullptr) {
if (event->type == HDMI_EVENT_CEC_MESSAGE) {
size_t length = std::min(event->cec.length,
static_cast(MaxLength::MESSAGE_BODY));
CecMessage cecMessage {
.initiator = static_cast(event->cec.initiator),
.destination = static_cast(event->cec.destination),
};
cecMessage.body.resize(length);
for (size_t i = 0; i < length; ++i) {
cecMessage.body[i] = static_cast(event->cec.body[i]);
}
mCallback->onCecMessage(cecMessage);
} else if (event->type == HDMI_EVENT_HOT_PLUG) {
HotplugEvent hotplugEvent {
.connected = event->hotplug.connected > 0,
.portId = static_cast(event->hotplug.port_id)
};
mCallback->onHotplugEvent(hotplugEvent);
}
}
}
3.android framework jni
会访问到java侧的HdmiCecController的handleHotplug方法。
Z:\p\frameworks\base\services\core\jni\com_android_server_hdmi_HdmiCecController.cpp
Return HdmiCecController::HdmiCecCallback::onHotplugEvent(const HotplugEvent& event) {
sp handler(new HdmiCecEventHandler(mController, event));
mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::HOT_PLUG);
return Void();
}
void propagateHotplugEvent(const HotplugEvent& event) {
// Note that this method should be called in service thread.
JNIEnv* env = AndroidRuntime::getJNIEnv();
jint port = static_cast(event.portId);
jboolean connected = (jboolean) event.connected;
env->CallVoidMethod(mController->getCallbacksObj(),
gHdmiCecControllerClassInfo.handleHotplug, port, connected);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
4. HdmiCecController
Z:\p\frameworks\base\services\core\java\com\android\server\hdmi\HdmiCecController.java
在打开log.tag.HDMI=DEBUG开关以后,会有port和connected的打印。
/**
* Called by native when a hotplug event issues.
*/
@ServiceThreadOnly
private void handleHotplug(int port, boolean connected) {
assertRunOnServiceThread();
HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
mService.onHotplug(port, connected);
}
5.HdmiControlService
①如果不是tv,就重新分配逻辑地址。
②调用HdmiCecLocalDevice的onHotplug回调,tv会调用HotplugDetectionAction进行设备列表的处理,其他类型的设备则可能会进行唤醒的处理。
③广播HotplugEvent。
广播HotplugEvent是后续事件传输过程的关键,因为下面会交给TIF来处理,更新TvInputManagerService里面Hdmi和Hardware相关设备的更新和对应接口add、remove的回调。
/**
* Called when a new hotplug event is issued.
*
* @param portId hdmi port number where hot plug event issued.
* @param connected whether to be plugged in or not
*/
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
if (connected && !isTvDevice()) {
ArrayList localDevices = new ArrayList<>();
for (int type : mLocalDevices) {
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
if (localDevice == null) {
localDevice = HdmiCecLocalDevice.create(this, type);
localDevice.init();
}
localDevices.add(localDevice);
}
allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
}
for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
device.onHotplug(portId, connected);
}
announceHotplugEvent(portId, connected);
}
private void announceHotplugEvent(int portId, boolean connected) {
HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
synchronized (mLock) {
for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
invokeHotplugEventListenerLocked(record.mListener, event);
}
}
}
@Override
public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
enforceAccessPermission();
HdmiControlService.this.addHotplugEventListener(listener);
}
private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
try {
listener.asBinder().linkToDeath(record, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Listener already died");
return;
}
synchronized (mLock) {
mHotplugEventListenerRecords.add(record);
}
// Inform the listener of the initial state of each HDMI port by generating
// hotplug events.
runOnServiceThread(new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (!mHotplugEventListenerRecords.contains(record)) return;
}
for (HdmiPortInfo port : mPortInfo) {
HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
mCecController.isConnected(port.getId()));
synchronized (mLock) {
invokeHotplugEventListenerLocked(listener, event);
}
}
}
});
}
6.TvInputHardwareManager添加回调以及回调实现
Z:\p\frameworks\base\services\core\java\com\android\server\tv\TvInputHardwareManager.java
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface(
ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
if (hdmiControlService != null) {
try {
hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
......
}
}
}
如果这个Hdmi设备有对应的TVInputHardwareInfo,一般都是有的啦。dumpsys tv_input就会看到从hdmi_port, 就能找到对应的deviceId,再从deviceId找到InputId,这样就完成HDMIDeviceInfo《-》TVInputInfo的过渡了。
mHardwareList:
TvInputHardwareInfo {id=1, type=3, audio_type=-2147450880, audio_addr=, cable_connection_status=0}
TvInputHardwareInfo {id=5, type=9, audio_type=-2147483616, audio_addr=, hdmi_port=1, cable_connection_status=0}
TvInputHardwareInfo {id=6, type=9, audio_type=-2147483616, audio_addr=, hdmi_port=2, cable_connection_status=0}
TvInputHardwareInfo {id=7, type=9, audio_type=-2147483616, audio_addr=, hdmi_port=3, cable_connection_status=0}
TvInputHardwareInfo {id=16, type=2, audio_type=-2147467264, audio_addr=, cable_connection_status=0}
mHardwareInputIdMap: deviceId -> inputId
1: com.droidlogic.tvinput/.services.AV1InputService/HW1
5: com.droidlogic.tvinput/.services.Hdmi1InputService/HW5
6: com.droidlogic.tvinput/.services.Hdmi2InputService/HW6
7: com.droidlogic.tvinput/.services.Hdmi3InputService/HW7
16: com.droidlogic.tvinput/.services.ADTVInputService/HW16
如果能找到对应的InputId,那么就开始TvInputHardware 状态变更的处理。
private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
@Override
public void onReceived(HdmiHotplugEvent event) {
synchronized (mLock) {
mHdmiStateMap.put(event.getPort(), event.isConnected());
TvInputHardwareInfo hardwareInfo =
findHardwareInfoForHdmiPortLocked(event.getPort());
Slog.w(TAG, "onReceived HdmiHotplugEvent " + event + " hardwareInfo " + hardwareInfo);
if (hardwareInfo == null) {
return;
}
String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Slog.w(TAG, "onReceived inputId " + inputId);
if (inputId == null) {
return;
}
// No HDMI hotplug does not necessarily mean disconnected, as old devices may
// not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to
// denote unknown state.
int state = event.isConnected()
? INPUT_STATE_CONNECTED
: INPUT_STATE_CONNECTED_STANDBY;
mHandler.obtainMessage(
ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget();
}
}
}
interface Listener {
void onStateChanged(String inputId, int state);
void onHardwareDeviceAdded(TvInputHardwareInfo info);
void onHardwareDeviceRemoved(TvInputHardwareInfo info);
void onHdmiDeviceAdded(HdmiDeviceInfo device);
void onHdmiDeviceRemoved(HdmiDeviceInfo device);
void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
}
private class ListenerHandler extends Handler {
private static final int STATE_CHANGED = 1;
private static final int HARDWARE_DEVICE_ADDED = 2;
private static final int HARDWARE_DEVICE_REMOVED = 3;
private static final int HDMI_DEVICE_ADDED = 4;
private static final int HDMI_DEVICE_REMOVED = 5;
private static final int HDMI_DEVICE_UPDATED = 6;
@Override
public final void handleMessage(Message msg) {
switch (msg.what) {
case STATE_CHANGED: {
String inputId = (String) msg.obj;
int state = msg.arg1;
mListener.onStateChanged(inputId, state);
break;
}
case HARDWARE_DEVICE_ADDED: {
TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
mListener.onHardwareDeviceAdded(info);
break;
}
case HARDWARE_DEVICE_REMOVED: {
TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
mListener.onHardwareDeviceRemoved(info);
break;
}
case HDMI_DEVICE_ADDED: {
HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
mListener.onHdmiDeviceAdded(info);
break;
}
case HDMI_DEVICE_REMOVED: {
HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
mListener.onHdmiDeviceRemoved(info);
break;
}
case HDMI_DEVICE_UPDATED: {
HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
String inputId;
synchronized (mLock) {
inputId = mHdmiInputIdMap.get(info.getId());
}
if (inputId != null) {
mListener.onHdmiDeviceUpdated(inputId, info);
} else {
Slog.w(TAG, "Could not resolve input ID matching the device info; "
+ "ignoring.");
}
break;
}
default: {
Slog.w(TAG, "Unhandled message: " + msg);
break;
}
}
mListener是在TvInputHardwareManager初始化的时候由TvInputManagerServive创建的。
public TvInputHardwareManager(Context context, Listener listener) {
mContext = context;
mListener = listener;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mHal.init();
}
7. TvInputManagerService
Z:\p\frameworks\base\services\core\java\com\android\server\tv\TvInputManagerService.java
private final class HardwareListener implements TvInputHardwareManager.Listener {
@Override
public void onStateChanged(String inputId, int state) {
synchronized (mLock) {
setStateLocked(inputId, state, mCurrentUserId);
}
}
@Override
public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
synchronized (mLock) {
Slog.d(TAG, "onHardwareDeviceAdded " + info);
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
if (!serviceState.isHardware || serviceState.service == null) continue;
try {
serviceState.service.notifyHardwareAdded(info);
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHardwareAdded", e);
}
}
}
}
@Override
public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
synchronized (mLock) {
Slog.d(TAG, "onHardwareDeviceRemoved " + info);
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
if (!serviceState.isHardware || serviceState.service == null) continue;
try {
serviceState.service.notifyHardwareRemoved(info);
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHardwareRemoved", e);
}
}
}
}
@Override
public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
if (!serviceState.isHardware || serviceState.service == null) continue;
try {
serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
}
}
}
}
@Override
public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
if (!serviceState.isHardware || serviceState.service == null) continue;
try {
serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
}
}
}
}
@Override
public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
synchronized (mLock) {
Integer state;
switch (deviceInfo.getDevicePowerStatus()) {
case HdmiControlManager.POWER_STATUS_ON:
state = INPUT_STATE_CONNECTED;
break;
case HdmiControlManager.POWER_STATUS_STANDBY:
case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
state = INPUT_STATE_CONNECTED_STANDBY;
break;
case HdmiControlManager.POWER_STATUS_UNKNOWN:
default:
state = null;
break;
}
if (state != null) {
setStateLocked(inputId, state, mCurrentUserId);
}
}
}
}
private void setStateLocked(String inputId, int state, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
TvInputState inputState = userState.inputMap.get(inputId);
if (inputState == null ||inputState.info == null) {
Slog.d(TAG,"Can't get input info,exit!!!!!");
return;
}
ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
int oldState = inputState.state;
inputState.state = state;
Slog.d(TAG,"setStateLocked oldState " + oldState + " new " + inputState.state);
if (serviceState != null && serviceState.service == null
&& (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
// We don't notify state change while reconnecting. It should remain disconnected.
Slog.d(TAG,"Can't get input info when disconnected!");
return;
}
if (oldState != state) {
notifyInputStateChangedLocked(userState, inputId, state, null);
}
}
最终会调用注册在HdmiControlService里面的ITvInputManagerCallback的onInputStateChanged回调方法,ITvInputManagerCallback则是可以通过TvInputManager注册的回调。
private void notifyInputStateChangedLocked(UserState userState, String inputId,
int state, ITvInputManagerCallback targetCallback) {
if (DEBUG) {
Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
+ ", state=" + state + ")");
}
if (targetCallback == null) {
for (ITvInputManagerCallback callback : userState.callbackSet) {
try {
callback.onInputStateChanged(inputId, state);
} catch (RemoteException e) {
Slog.e(TAG, "failed to report state change to callback", e);
}
}
} else {
try {
targetCallback.onInputStateChanged(inputId, state);
} catch (RemoteException e) {
Slog.e(TAG, "failed to report state change to callback", e);
}
}
}
参考log:
130|console:/ # logcat -c
ce -s TvInputHardwareManager -s TvInputManagerService <
--------- beginning of system
--------- beginning of main
01-06 21:14:11.978 3640 3640 D TvInputManagerService: setStateLocked oldState 0 new 1
01-06 21:14:11.978 3640 3640 D TvInputManagerService: notifyInputStateChangedLocked(inputId=com.droidlogic.tvinput/.services.Hdmi1InputService/HW5, state=1)
01-06 21:14:12.182 3640 3640 W TvInputHardwareManager: onReceived HdmiHotplugEvent android.hardware.hdmi.HdmiHotplugEvent@6a58b2b hardwareInfo TvInputHardwareInfo {id=5, type=9, audio_type=-2147483616, audio_addr=, hdmi_port=1, cable_connection_status=0}
01-06 21:14:12.182 3640 3640 W TvInputHardwareManager: onReceived inputId com.droidlogic.tvinput/.services.Hdmi1InputService/HW5
01-06 21:14:12.183 3640 3640 D TvInputManagerService: setStateLocked oldState 1 new 1
01-06 21:14:12.631 3640 3640 W TvInputHardwareManager: onStatusChanged CEC: id: 8 logical_address: 0x08 device_type: 4 vendor_id: 32837 display_name: Playback_2 power_status: 0 physical_address: 0x1000 port_id: 1 status 2
这一节并没有看到onHdmiDeviceAdded和onHardwareDeviceAdded的调用,下一节理清楚。