欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-11 23:00:58
...

这次梳理下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<HotplugDetectionAction> 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<Integer> 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 <Give Physical Address> 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<ResolveInfo> services = pm.queryIntentServicesAsUser(
                new Intent(TvInputService.SERVICE_INTERFACE),
                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                userId);
        List<TvInputInfo> 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<String, TvInputState> 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)

 

相关标签: hdmi cec