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

这次梳理下Hdmi设备增删的事件是如何传递到TvInputService,以及TvInputManagerService的处理差异。

1.HdmiCecLocalDeviceTv

HdmiControlService在收到HdmiCecController的onHotplug回调后,会再依次调用设备列表中的各个HdmiCecLocalDevice的onHotplug方法。以Tv为例,它会发起HotplugDetectionAction来轮询cec总线上的每一个逻辑地址,来判断是否需要添加或者删除设备。

Z:\p\frameworks\base\services\core\java\com\android\server\hdmi\HdmiCecLocalDeviceTv.java

    @Override
    @ServiceThreadOnly
    void onHotplug(int portId, boolean connected) {
        assertRunOnServiceThread();
        if (!connected) {
            removeCecSwitches(portId);
        }
        // Tv device will have permanent HotplugDetectionAction.
        List hotplugActions = getActions(HotplugDetectionAction.class);
        if (!hotplugActions.isEmpty()) {
            // Note that hotplug action is single action running on a machine.
            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
            // It covers seq #40, #43.
            hotplugActions.get(0).pollAllDevicesNow();
        } else {
            HdmiLogger.debug("start poll devices for hotplug");
            addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
        }
    }

2.HotplugDetectionAction

Z:\p\frameworks\base\services\core\java\com\android\server\hdmi\HotplugDetectionAction.java

    private void pollAllDevices() {
        Slog.v(TAG, "Poll all devices.");

        pollDevices(new DevicePollingCallback() {
            @Override
            public void onPollingFinished(List ackedAddress) {
                checkHotplug(ackedAddress, false);
            }
        }, Constants.POLL_ITERATION_IN_ORDER
                | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY);
    }

addDevice是通过发送GET_PHSYCAL_ADDRESS message收到响应后再添加的。

    private void addDevice(int addedAddress) {
        // Sending  will initiate new device action.
        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
                addedAddress));
    }

tv handleReportPhysicalAddress 

        HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
        addCecDevice(deviceInfo);

下面就是关键addCecDevice,这里会调用注册在HdmiControlService里面的IHdmiDeviceEventListener来触发DEVICE_EVENT_ADD_DEVICE和DEVICE_EVENT_REMOVE_DEVICE事件。

    /**
     * Called when a device is newly added or a new device is detected or
     * existing device is updated.
     *
     * @param info device info of a new device.
     */
    @ServiceThreadOnly
    final void addCecDevice(HdmiDeviceInfo info) {
        assertRunOnServiceThread();
        Slog.d(TAG, "addCecDevice " + info);
        HdmiDeviceInfo old = addDeviceInfo(info);
        if (info.getLogicalAddress() == mAddress) {
            // The addition of TV device itself should not be notified.
            return;
        }
        if (old == null) {
            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
        } else if (!old.equals(info)) {
            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
        }
    }

3.TvInputHardwareManager

和hotplug一样,都是在TvInputManagerService创建的TvInputHardwareManager里面初始化时创建的监听。

    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);
                    hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);

以add为例,首先会将这个HdmiDeviceInfo添加到保存好的列表里面,并发送 ListenerHandler.HDMI_DEVICE_ADDED继续处理。

    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
        @Override
        public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
            Slog.w(TAG, "onStatusChanged " + deviceInfo + " status " + status);
            if (!deviceInfo.isSourceType()) return;
            synchronized (mLock) {
                int messageType = 0;
                Object obj = null;
                switch (status) {
                    case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
                        if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
                            mHdmiDeviceList.add(deviceInfo);
                        } else {
                            Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
                            return;
                        }
                        messageType = ListenerHandler.HDMI_DEVICE_ADDED;
                        obj = deviceInfo;
                        break;
                    }
                    case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
                            return;
                        }
                        messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
                        obj = deviceInfo;
                        break;
                    }
                    case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
                            return;
                        }
                        mHdmiDeviceList.add(deviceInfo);
                        messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
                        obj = deviceInfo;
                        break;
                    }
                }

                Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
                if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
                    msg.sendToTarget();
                } else {
                    mPendingHdmiDeviceEvents.add(msg);
                }
            }
        }

和hotplug调用InputManagerService的 onStateChanged方法一样,这里会调用onHdmiDeviceAdded方法。需要注意的一点是,实际上add和remove这两个接口的调用相当频繁,在每一次HdmiDeviceInfo发生变更时基本上就会触发一次add和remove,比如原来没拿到vendorId现在拿到了,就更新下。所以这并不能对应实际上的热插拔。在这里进行热插拔的处理是不合适的。

    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;
                }
            }
        }
    }

4.TvInputManagerService

同样也是HardwareListener,但是onHdmiDeviceAdded,后续不是交给TvInputManager去回调注册的TvInputManagerCallback,而是会调用保存的serviceState.service.notifyHdmiDeviceAdded(deviceInfo);serviceState.service是客户端比如TvView 在createSession时会和对应的TvInputService建立联系,TvInputManagerService会通过bind的方式连接TvInputService,也就是说下面就是通知TvInputService hdmi 设备的状态发生了变化。

    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);
                }
            }
        }
    }

在连接TvInputService的InputServiceConnection实现的onServiceConnected方法里面,会将TvInputService的BpBinder保存起来,并注册ServiceCallback。

    private final class InputServiceConnection implements ServiceConnection {
        private final ComponentName mComponent;
        private final int mUserId;

        private InputServiceConnection(ComponentName component, int userId) {
            mComponent = component;
            mUserId = userId;
        }

        @Override
        public void onServiceConnected(ComponentName component, IBinder service) {
            if (DEBUG) {
                Slog.d(TAG, "onServiceConnected(component=" + component + ")");
            }
            synchronized (mLock) {
                UserState userState = mUserStates.get(mUserId);
                if (userState == null) {
                    // The user was removed while connecting.
                    mContext.unbindService(this);
                    return;
                }
                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
                serviceState.service = ITvInputService.Stub.asInterface(service);

                // Register a callback, if we need to.
                if (serviceState.isHardware && serviceState.callback == null) {
                    serviceState.callback = new ServiceCallback(mComponent, mUserId);
                    try {
                        serviceState.service.registerCallback(serviceState.callback);
                    } catch (RemoteException e) {
                        Slog.e(TAG, "error in registerCallback", e);
                    }
                }

ServiceCallback的作用是,在TvInputManagerService通过onHdmiDeviceAdded调用到TvInputService以后,再回调给TvInputManagerService继续处理。

    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
        private final ComponentName mComponent;
        private final int mUserId;

        ServiceCallback(ComponentName component, int userId) {
            mComponent = component;
            mUserId = userId;
        }

        private void ensureHardwarePermission() {
            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException("The caller does not have hardware permission");
            }
        }

        private void ensureValidInput(TvInputInfo inputInfo) {
            if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
                throw new IllegalArgumentException("Invalid TvInputInfo");
            }
        }

        private void addHardwareInputLocked(TvInputInfo inputInfo) {
            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
            serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
            buildTvInputListLocked(mUserId, null);
        }

        public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
            ensureHardwarePermission();
            Slog.d(TAG, "addHardwareInput " + deviceId);
            Exception ex = new Exception("addHardwareInput");
            ex.printStackTrace();
            ensureValidInput(inputInfo);
            synchronized (mLock) {
                mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
                addHardwareInputLocked(inputInfo);
            }
        }

        public void addHdmiInput(int id, TvInputInfo inputInfo) {
            ensureHardwarePermission();
            ensureValidInput(inputInfo);
            synchronized (mLock) {
                mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                addHardwareInputLocked(inputInfo);
            }
        }

        public void removeHardwareInput(String inputId) {
            ensureHardwarePermission();
            Slog.d(TAG, "removeHardwareInput " + inputId);
            Exception ex = new Exception("removeHardwareInput");
            ex.printStackTrace();
            synchronized (mLock) {
                ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
                boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
                if (removed) {
                    buildTvInputListLocked(mUserId, null);
                    mTvInputHardwareManager.removeHardwareInput(inputId);
                } else {
                    Slog.e(TAG, "failed to remove input " + inputId);
                }
            }
        }
    }

连接TvInputService的位置,在TvInputManagerService createSession和removeSession时,都可能会重新建立或者移除和TvInputService的连接。

    private void updateServiceConnectionLocked(ComponentName component, int userId) {
        UserState userState = getOrCreateUserStateLocked(userId);
        ServiceState serviceState = userState.serviceStateMap.get(component);
        if (serviceState == null) {
            return;
        }
        if (serviceState.reconnecting) {
            if (!serviceState.sessionTokens.isEmpty()) {
                // wait until all the sessions are removed.
                return;
            }
            serviceState.reconnecting = false;
        }

        boolean shouldBind;
        if (userId == mCurrentUserId) {
            shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
        } else {
            // For a non-current user,
            // if sessionTokens is not empty, it contains recording sessions only
            // because other sessions must have been removed while switching user
            // and non-recording sessions are not created by createSession().
            shouldBind = !serviceState.sessionTokens.isEmpty();
        }

        if (serviceState.service == null && shouldBind) {
            // This means that the service is not yet connected but its state indicates that we
            // have pending requests. Then, connect the service.
            if (serviceState.bound) {
                // We have already bound to the service so we don't try to bind again until after we
                // unbind later on.
                return;
            }
            if (DEBUG) {
                Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
            }

            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
            serviceState.bound = mContext.bindServiceAsUser(
                    i, serviceState.connection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    new UserHandle(userId));
        } else if (serviceState.service != null && !shouldBind) {
            // This means that the service is already connected but its state indicates that we have
            // nothing to do with it. Then, disconnect the service.
            if (DEBUG) {
                Slog.d(TAG, "unbindService(service=" + component + ")");
            }
            mContext.unbindService(serviceState.connection);
            userState.serviceStateMap.remove(component);
        }
    }

下面看看TvInputService的处理。

5.TvInputService

Z:\p\frameworks\base\media\java\android\media\tv\TvInputService.java

    @Override
    public final IBinder onBind(Intent intent) {
        return new ITvInputService.Stub() {
            @Override
            public void registerCallback(ITvInputServiceCallback cb) {
                if (cb != null) {
                    mCallbacks.register(cb);
                }
            }

            @Override
            public void unregisterCallback(ITvInputServiceCallback cb) {
                if (cb != null) {
                    mCallbacks.unregister(cb);
                }
            }

            @Override
            public void createSession(InputChannel channel, ITvInputSessionCallback cb,
                    String inputId) {
                if (channel == null) {
                    Log.w(TAG, "Creating session without input channel");
                }
                if (cb == null) {
                    return;
                }
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = channel;
                args.arg2 = cb;
                args.arg3 = inputId;
                mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
            }

            @Override
            public void createRecordingSession(ITvInputSessionCallback cb, String inputId) {
                if (cb == null) {
                    return;
                }
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = cb;
                args.arg2 = inputId;
                mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args)
                        .sendToTarget();
            }

            @Override
            public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
                Log.d(TAG, "notifyHardwareAdded " + hardwareInfo);
                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_INPUT,
                        hardwareInfo).sendToTarget();
            }

            @Override
            public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
                Log.d(TAG, "notifyHardwareRemoved " + hardwareInfo);
                mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_INPUT,
                        hardwareInfo).sendToTarget();
            }

            @Override
            public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
                Log.d(TAG, "notifyHdmiDeviceAdded " + deviceInfo);
                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_INPUT,
                        deviceInfo).sendToTarget();
            }

            @Override
            public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
                Log.d(TAG, "notifyHdmiDeviceRemoved " + deviceInfo);
                mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT,
                        deviceInfo).sendToTarget();
            }
        };
    }

首先是TvInputService具体的实现类重写onHdmiDeviceAdded方法,然后再是进行广播这个添加的TvInputInfo。 

                case DO_ADD_HDMI_INPUT: {
                    Log.d(TAG, "DO_ADD_HDMI_INPUT");
                    HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
                    TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
                    if (inputInfo != null) {
                        broadcastAddHdmiInput(deviceInfo.getId(), inputInfo);
                    }
                    return;
                }

 ITvInputServiceCallback在TvInputManagerService建立和TvInputService的连接时就注册好了,所以下面其实是又交给TvInputManagerService进行后续的TvInputInfo相关的处理。 

        private void broadcastAddHdmiInput(int id, TvInputInfo inputInfo) {
            int n = mCallbacks.beginBroadcast();
            for (int i = 0; i < n; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).addHdmiInput(id, inputInfo);
                } catch (RemoteException e) {
                    Log.e(TAG, "error in broadcastAddHdmiInput", e);
                }
            }
            mCallbacks.finishBroadcast();
        }

6.回到TvInputManagerService

首先是添加TvInputhardwareManager内部的设备信息,然后是再添加hardwareInput。

private final class ServiceCallback extends ITvInputServiceCallback.Stub {       
 public void addHdmiInput(int id, TvInputInfo inputInfo) {
            ensureHardwarePermission();
            ensureValidInput(inputInfo);
            synchronized (mLock) {
                mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                addHardwareInputLocked(inputInfo);
            }
        }

这里一样在添加完信息会可能会产生StateChange的回调,交给之前通过TvInputManager 注册的TvInputManagerCallback处理。

    public void addHardwareInput(int deviceId, TvInputInfo info) {
        synchronized (mLock) {
            String oldInputId = mHardwareInputIdMap.get(deviceId);
            if (oldInputId != null) {
                Slog.w(TAG, "Trying to override previous registration: old = "
                        + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
                        + info + ":" + deviceId);
            }
            mHardwareInputIdMap.put(deviceId, info.getId());
            mInputMap.put(info.getId(), info);

            // Process pending state changes

            // For logical HDMI devices, they have information from HDMI CEC signals.
            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
                TvInputHardwareInfo hardwareInfo =
                        findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
                if (hardwareInfo == null) {
                    continue;
                }
                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
                if (inputId != null && inputId.equals(info.getId())) {
                    // 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 = mHdmiStateMap.valueAt(i)
                            ? INPUT_STATE_CONNECTED
                            : INPUT_STATE_CONNECTED_STANDBY;
                    mHandler.obtainMessage(
                        ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget();
                    return;
                }
            }
            // For the rest of the devices, we can tell by the cable connection status.
            Connection connection = mConnections.get(deviceId);
            if (connection != null) {
                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
                    connection.getInputStateLocked(), 0, info.getId()).sendToTarget();
            }
        }
    }

 TvInputManagerService内部则是会更新对应的ServiceState里面的TVInputInfo,并通过关键函数buildTvInputListLocked重建TvInput列表。

        private void addHardwareInputLocked(TvInputInfo inputInfo) {
            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
            serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
            buildTvInputListLocked(mUserId, null);
        }

不管是因为hdmi设备的增加,还是移除,都是完全重新的通过PackageManager重新查询所有的创建的TvInputService,如果有新添加的,如果没有就updateServiceConnectionLocked创建对应的ServiceState建立和它的binder连接就创建之,并且添加到保存的InputIdMap中。反之如果原来有现在没了,就移除之。最后触发notifyInputAddedLocked来通知观察者。

    private void buildTvInputListLocked(int userId, String[] updatedPackages) {
        UserState userState = getOrCreateUserStateLocked(userId);
        userState.packageSet.clear();

        if (DEBUG) Slog.d(TAG, "buildTvInputList");
        Exception ex = new Exception("buildTvInputListLocked");
        ex.printStackTrace();
        PackageManager pm = mContext.getPackageManager();
        List services = pm.queryIntentServicesAsUser(
                new Intent(TvInputService.SERVICE_INTERFACE),
                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                userId);
        List inputList = new ArrayList<>();
        for (ResolveInfo ri : services) {
            ServiceInfo si = ri.serviceInfo;
            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
                Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
                        + android.Manifest.permission.BIND_TV_INPUT);
                continue;
            }

            ComponentName component = new ComponentName(si.packageName, si.name);
            if (hasHardwarePermission(pm, component)) {
                ServiceState serviceState = userState.serviceStateMap.get(component);
                if (serviceState == null) {
                    // New hardware input found. Create a new ServiceState and connect to the
                    // service to populate the hardware list.
                    serviceState = new ServiceState(component, userId);
                    userState.serviceStateMap.put(component, serviceState);
                    updateServiceConnectionLocked(component, userId);
                } else {
                    inputList.addAll(serviceState.hardwareInputMap.values());
                }
            } else {
                try {
                    TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
                    inputList.add(info);
                } catch (Exception e) {
                    Slog.e(TAG, "failed to load TV input " + si.name, e);
                    continue;
                }
            }
            userState.packageSet.add(si.packageName);
        }

        Map inputMap = new HashMap<>();
        for (TvInputInfo info : inputList) {
            if (DEBUG) {
                Slog.d(TAG, "add " + info.getId());
            }
            TvInputState inputState = userState.inputMap.get(info.getId());
            if (inputState == null) {
                inputState = new TvInputState();
            }
            inputState.info = info;
            inputMap.put(info.getId(), inputState);
        }

        for (String inputId : inputMap.keySet()) {
            if (!userState.inputMap.containsKey(inputId)) {
                notifyInputAddedLocked(userState, inputId);
            } else if (updatedPackages != null) {
                // Notify the package updates
                ComponentName component = inputMap.get(inputId).info.getComponent();
                for (String updatedPackage : updatedPackages) {
                    if (component.getPackageName().equals(updatedPackage)) {
                        updateServiceConnectionLocked(component, userId);
                        notifyInputUpdatedLocked(userState, inputId);
                        break;
                    }
                }
            }
        }

        for (String inputId : userState.inputMap.keySet()) {
            if (!inputMap.containsKey(inputId)) {
                TvInputInfo info = userState.inputMap.get(inputId).info;
                ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
                if (serviceState != null) {
                    abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
                }
                notifyInputRemovedLocked(userState, inputId);
            }
        }

        userState.inputMap.clear();
        userState.inputMap = inputMap;
    }

这里就是通知新的InputId的添加和移除,也是ITvInputManagerCallback。 

    private void notifyInputAddedLocked(UserState userState, String inputId) {
        if (DEBUG) {
            Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
        }

        for (ITvInputManagerCallback callback : userState.callbackSet) {
            try {
                callback.onInputAdded(inputId);
            } catch (RemoteException e) {
                Slog.e(TAG, "failed to report added input to callback", e);
            }
        }
    }

    private void notifyInputRemovedLocked(UserState userState, String inputId) {
        if (DEBUG) {
            Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
        }

        for (ITvInputManagerCallback callback : userState.callbackSet) {
            try {
                callback.onInputRemoved(inputId);
            } catch (RemoteException e) {
                Slog.e(TAG, "failed to report removed input to callback", e);
            }
        }
    }

比如Input移除通知的栈

01-06 03:55:26.632  3640  4395 W System.err: java.lang.Exception: notifyInputRemovedLocked onInputRemoved com.droidlogic.tvinput/.services.Hdmi2InputService/HDMI240008
01-06 03:55:26.632  3640  4395 W System.err: 	at com.android.server.tv.TvInputManagerService.notifyInputRemovedLocked(TvInputManagerService.java:771)
01-06 03:55:26.632  3640  4395 W System.err: 	at com.android.server.tv.TvInputManagerService.buildTvInputListLocked(TvInputManagerService.java:344)
01-06 03:55:26.632  3640  4395 W System.err: 	at com.android.server.tv.TvInputManagerService.access$400(TvInputManagerService.java:102)
01-06 03:55:26.632  3640  4395 W System.err: 	at com.android.server.tv.TvInputManagerService$ServiceCallback.removeHardwareInput(TvInputManagerService.java:2426)
01-06 03:55:26.633  3640  4395 W System.err: 	at android.media.tv.ITvInputServiceCallback$Stub.onTransact(ITvInputServiceCallback.java:86)
01-06 03:55:26.633  3640  4395 W System.err: 	at android.os.Binder.execTransact(Binder.java:731)

添加的栈

01-07 00:04:04.584  3640  5236 W System.err: java.lang.Exception: notifyInputAddedLocked onInputAdded com.droidlogic.tvinput/.services.Hdmi1InputService/HDMI100008
01-07 00:04:04.584  3640  5236 W System.err: 	at com.android.server.tv.TvInputManagerService.notifyInputAddedLocked(TvInputManagerService.java:756)
01-07 00:04:04.584  3640  5236 W System.err: 	at com.android.server.tv.TvInputManagerService.buildTvInputListLocked(TvInputManagerService.java:323)
01-07 00:04:04.584  3640  5236 W System.err: 	at com.android.server.tv.TvInputManagerService.access$400(TvInputManagerService.java:102)
01-07 00:04:04.584  3640  5236 W System.err: 	at com.android.server.tv.TvInputManagerService$ServiceCallback.addHardwareInputLocked(TvInputManagerService.java:2393)
01-07 00:04:04.584  3640  5236 W System.err: 	at com.android.server.tv.TvInputManagerService$ServiceCallback.addHdmiInput(TvInputManagerService.java:2413)
01-07 00:04:04.584  3640  5236 W System.err: 	at android.media.tv.ITvInputServiceCallback$Stub.onTransact(ITvInputServiceCallback.java:78)
01-07 00:04:04.585  3640  5236 W System.err: 	at android.os.Binder.execTransact(Binder.java:731)

 

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