TIF 和Hdmi cec hotplug热插拔事件过程梳理一

从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的调用,下一节理清楚。

 

 

 

 


 

你可能感兴趣的:(TIF 和Hdmi cec hotplug热插拔事件过程梳理一)