HdmiControlService 流程梳理

HdmiControlService是提供发送和处理HDMI控制消息, HDMI-CEC和MHL控制命令的服务。

一 HdmiControlService启动

1. SystemServer在startOtherServices过程中会启动HdmiControlService

            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
                traceBeginAndSlog("StartHdmiControlService");
                mSystemServiceManager.startService(HdmiControlService.class);
                traceEnd();
            }

SystemFeature 配置在frameworks/native/data/etc路径下,在pkms启动时会读取和解析这些xml文件,其中android.hardware.hdmi.cec.xml有这个feature。 


    

2.初始化 

确认设备的类型,并且创建了一个Settings数据库的监听器。

static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type";这个是设备的hdmi类型,

对于机顶盒 (OTT) 等 HDMI 源设备,请采用如下设置:

PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=4

对于平板电视等 HDMI 接收设备,请采用如下设置:

PRODUCT_PROPERTY_OVERRIDES += ro.hdmi.device_type=0

 

public class HdmiDeviceInfo implements Parcelable {

    /** TV device type. */
    public static final int DEVICE_TV = 0;

    /** Recording device type. */
    public static final int DEVICE_RECORDER = 1;

    /** Device type reserved for future usage. */
    public static final int DEVICE_RESERVED = 2;

    /** Tuner device type. */
    public static final int DEVICE_TUNER = 3;

    /** Playback device type. */
    public static final int DEVICE_PLAYBACK = 4;

    /** Audio system device type. */
    public static final int DEVICE_AUDIO_SYSTEM = 5;

    /** @hide Pure CEC switch device type. */
    public static final int DEVICE_PURE_CEC_SWITCH = 6;

    /** @hide Video processor device type. */
    public static final int DEVICE_VIDEO_PROCESSOR = 7;

    // Value indicating the device is not an active source.
    public static final int DEVICE_INACTIVE = -1;
    public HdmiControlService(Context context) {
        super(context);
        mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
        mSettingsObserver = new SettingsObserver(mHandler);
    }
    private class SettingsObserver extends ContentObserver {
        public SettingsObserver(Handler handler) {
            super(handler);
        }

        // onChange is set up to run in service thread.
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            String option = uri.getLastPathSegment();
            boolean enabled = readBooleanSetting(option, true);
            switch (option) {
                case Global.HDMI_CONTROL_ENABLED:
                    setControlEnabled(enabled);
                    break;
                case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
                    if (isTvDeviceEnabled()) {
                        tv().setAutoWakeup(enabled);
                    }
                    setCecOption(OptionKey.WAKEUP, enabled);
                    break;
                case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
                    for (int type : mLocalDevices) {
                        HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
                        if (localDevice != null) {
                            localDevice.setAutoDeviceOff(enabled);
                        }
                    }
                    // No need to propagate to HAL.
                    break;
                case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
                    if (isTvDeviceEnabled()) {
                        tv().setSystemAudioControlFeatureEnabled(enabled);
                    }
                    break;
                case Global.HDMI_SYSTEM_AUDIO_STATUS_ENABLED:
                    if (isTvDeviceEnabled()) {
                        tv().changeSystemAudioStatus(enabled);
                    }
                    break;
                case Global.MHL_INPUT_SWITCHING_ENABLED:
                    setMhlInputChangeEnabled(enabled);
                    break;
                case Global.MHL_POWER_CHARGE_ENABLED:
                    mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
                    break;
            }
        }
    }

3.onStart

    public void onStart() {
        mIoThread.start();
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
        mProhibitMode = false;
        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
        mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);

        mCecController = HdmiCecController.create(this);
        if (mCecController != null) {
            if (mHdmiControlEnabled) {
                initializeCec(INITIATED_BY_BOOT_UP);
            }
        } else {
            Slog.i(TAG, "Device does not support HDMI-CEC.");
            return;
        }

        mMhlController = HdmiMhlControllerStub.create(this);
        if (!mMhlController.isReady()) {
            Slog.i(TAG, "Device does not support MHL-control.");
        }
        mMhlDevices = Collections.emptyList();

        initPortInfo();
        mMessageValidator = new HdmiCecMessageValidator(this);
        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());

        if (mCecController != null) {
            // Register broadcast receiver for power state change.
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SHUTDOWN);
            filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
            getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);

            // Register ContentObserver to monitor the settings change.
            registerContentObserver();
        }
        mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
    }

 ①启动IoThread

    // A thread to handle synchronous IO of CEC and MHL control service.
    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
    // and sparse call it shares a thread to handle IO operations.
    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");

②创建HdmiCecController并对cec进行初始化

HdmiCecController它将用户的命令转换为CEC HAL,以便将消息发送到其他设备。对于传入消息,它会转换消息并将其委托给适当的模块。主要的工作是native侧的HdmiCecController完成的,在这里会同cec hal IHdmiCec进行通信。

    static HdmiCecController create(HdmiControlService service) {
        HdmiCecController controller = new HdmiCecController(service);
        long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
        if (nativePtr == 0L) {
            controller = null;
            return null;
        }

        controller.init(nativePtr);
        return controller;
    }

Native侧 处理cec消息的线程也是HdmiControlService所在的SystemServer进程主线程。这里会拿到Hal服务IHdmiCec的客户端。 

    /**
     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
     * for tasks that are running on main service thread.
     *
     * 

Declared as package-private. */ Looper getServiceLooper() { return mHandler.getLooper(); }

frameworks\base\services\core\jni\com_android_server_hdmi_HdmiCecController.cpp

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
        jobject messageQueueObj) {
    // TODO(b/31632518)
    sp hdmiCec = IHdmiCec::getService();
    if (hdmiCec == nullptr) {
        ALOGE("Couldn't get tv.cec service.");
        return 0;
    }
    sp messageQueue =
            android_os_MessageQueue_getMessageQueue(env, messageQueueObj);

    HdmiCecController* controller = new HdmiCecController(
            hdmiCec,
            env->NewGlobalRef(callbacksObj),
            messageQueue->getLooper());

    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
            "handleIncomingCecCommand", "(II[B)V");
    GET_METHOD_ID(gHdmiCecControllerClassInfo.handleHotplug, clazz,
            "handleHotplug", "(IZ)V");

    return reinterpret_cast(controller);
}

这里也会把HdmiCecCallback设置给IHdmiCec hal服务,以此来接收消息。 

HdmiCecController::HdmiCecController(sp hdmiCec,
        jobject callbacksObj, const sp& looper)
        : mHdmiCec(hdmiCec),
          mCallbacksObj(callbacksObj),
          mLooper(looper) {
    mHdmiCecCallback = new HdmiCecCallback(this);
    Return ret = mHdmiCec->setCallback(mHdmiCecCallback);
    if (!ret.isOk()) {
        ALOGE("Failed to set a cec callback.");
    }
}

从IhdmiCec hal回调返回的cecMessage和HotplugEvent

Return HdmiCecController::HdmiCecCallback::onCecMessage(const CecMessage& message) {
    sp handler(new HdmiCecEventHandler(mController, message));
    mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::CEC_MESSAGE);
    return Void();
}

Return HdmiCecController::HdmiCecCallback::onHotplugEvent(const HotplugEvent& event) {
    sp handler(new HdmiCecEventHandler(mController, event));
    mController->mLooper->sendMessage(handler, HdmiCecEventHandler::EventType::HOT_PLUG);
    return Void();
}
    void handleMessage(const Message& message) {
        switch (message.what) {
        case EventType::CEC_MESSAGE:
            propagateCecCommand(mCecMessage);
            break;
        case EventType::HOT_PLUG:
            propagateHotplugEvent(mHotplugEvent);
            break;
        default:
            // TODO: add more type whenever new type is introduced.
            break;
        }
    }

private:
    // Propagate the message up to Java layer.
    void propagateCecCommand(const CecMessage& message) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        jint srcAddr = static_cast(message.initiator);
        jint dstAddr = static_cast(message.destination);
        jbyteArray body = env->NewByteArray(message.body.size());
        const jbyte* bodyPtr = reinterpret_cast(message.body.data());
        env->SetByteArrayRegion(body, 0, message.body.size(), bodyPtr);
        env->CallVoidMethod(mController->getCallbacksObj(),
                gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
                dstAddr, body);
        env->DeleteLocalRef(body);

        checkAndClearExceptionFromCallback(env, __FUNCTION__);
    }

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

 handleHotplug和handleIncomingCecCommand在nativeInit里面已经注册过了,对应java侧。

    /**
     * Called by native when incoming CEC message arrived.
     */
    @ServiceThreadOnly
    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
        assertRunOnServiceThread();
        HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
        HdmiLogger.debug("[R]:" + command);
        addMessageToHistory(true /* isReceived */, command);
        onReceiveCommand(command);
    }

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

 从HdmiControlController到hal侧则是正常的hal调用

int HdmiCecController::getPhysicalAddress() {
    Result result;
    uint16_t addr;
    Return ret = mHdmiCec->getPhysicalAddress([&result, &addr](Result res, uint16_t paddr) {
            result = res;
            addr = paddr;
        });
    if (!ret.isOk()) {
        ALOGE("Failed to get physical address.");
        return INVALID_PHYSICAL_ADDRESS;
    }
    return result == Result::SUCCESS ? addr : INVALID_PHYSICAL_ADDRESS;
}

 ③HdmiMhlControllerStub

HdmiMhlControllerStub和HdmiCecController类似,处理MHL command,目前还是个空壳。

④初始化Hdmi端口信息

    private void initPortInfo() {
        assertRunOnServiceThread();
        HdmiPortInfo[] cecPortInfo = null;

        synchronized (mLock) {
            mPhysicalAddress = getPhysicalAddress();
        }

        // CEC HAL provides majority of the info while MHL does only MHL support flag for
        // each port. Return empty array if CEC HAL didn't provide the info.
        if (mCecController != null) {
            cecPortInfo = mCecController.getPortInfos();
        }
        if (cecPortInfo == null) {
            return;
        }

        SparseArray portInfoMap = new SparseArray<>();
        SparseIntArray portIdMap = new SparseIntArray();
        SparseArray portDeviceMap = new SparseArray<>();
        for (HdmiPortInfo info : cecPortInfo) {
            portIdMap.put(info.getAddress(), info.getId());
            portInfoMap.put(info.getId(), info);
            portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
        }
        mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
        mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
        mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);

        HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
        ArraySet mhlSupportedPorts = new ArraySet(mhlPortInfo.length);
        for (HdmiPortInfo info : mhlPortInfo) {
            if (info.isMhlSupported()) {
                mhlSupportedPorts.add(info.getId());
            }
        }

        // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
        // cec port info if we do not have have port that supports MHL.
        if (mhlSupportedPorts.isEmpty()) {
            mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
            return;
        }
        ArrayList result = new ArrayList<>(cecPortInfo.length);
        for (HdmiPortInfo info : cecPortInfo) {
            if (mhlSupportedPorts.contains(info.getId())) {
                result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
                        info.isCecSupported(), true, info.isArcSupported()));
            } else {
                result.add(info);
            }
        }
        mPortInfo = Collections.unmodifiableList(result);
    }

 getPortInfos和getPhysicalAddress一样,都是通过和IHdmiCec hal服务通信并取得的结果。具体hal侧是如何工作的后面再看。

jobjectArray HdmiCecController::getPortInfos() {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    jclass hdmiPortInfo = env->FindClass("android/hardware/hdmi/HdmiPortInfo");
    if (hdmiPortInfo == NULL) {
        return NULL;
    }
    jmethodID ctor = env->GetMethodID(hdmiPortInfo, "", "(IIIZZZ)V");
    if (ctor == NULL) {
        return NULL;
    }
    hidl_vec ports;
    Return ret = mHdmiCec->getPortInfo([&ports](hidl_vec list) {
            ports = list;
        });
    if (!ret.isOk()) {
        ALOGE("Failed to get port information.");
        return NULL;
    }
    jobjectArray res = env->NewObjectArray(ports.size(), hdmiPortInfo, NULL);

    // MHL support field will be obtained from MHL HAL. Leave it to false.
    jboolean mhlSupported = (jboolean) 0;
    for (size_t i = 0; i < ports.size(); ++i) {
        jboolean cecSupported = (jboolean) ports[i].cecSupported;
        jboolean arcSupported = (jboolean) ports[i].arcSupported;
        jobject infoObj = env->NewObject(hdmiPortInfo, ctor, ports[i].portId, ports[i].type,
                ports[i].physicalAddress, cecSupported, mhlSupported, arcSupported);
        env->SetObjectArrayElement(res, i, infoObj);
    }
    return res;
}

 ④HdmiCecMessageValidator 

HdmiCecMessageValidator是个验证信息有效性的辅助类

⑤publishBinderService

HdmiControlService在servicemanager中注册的名字是hdmi_control,这里是添加专用的BinderService代理类来处理binder业务。

    protected final void publishBinderService(String name, IBinder service) {
        publishBinderService(name, service, false);
    }
    /**
     * Use with {@link #getSystemService(String)} to retrieve a
     * {@link android.hardware.hdmi.HdmiControlManager} for controlling and managing
     * HDMI-CEC protocol.
     *
     * @see #getSystemService(String)
     * @see android.hardware.hdmi.HdmiControlManager
     * @hide
     */
    @SystemApi
    public static final String HDMI_CONTROL_SERVICE = "hdmi_control";

 ⑥mHdmiControlBroadcastReceiver

注册HdmiControlBroadcatReceiver,对系统广播     亮屏、灭屏、关机和系统配置变化4个广播进行监听。       

           filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SHUTDOWN);
            filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);

    private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
        @ServiceThreadOnly
        @Override
        public void onReceive(Context context, Intent intent) {
            assertRunOnServiceThread();
            boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
            switch (intent.getAction()) {
                case Intent.ACTION_SCREEN_OFF:
                    if (isPowerOnOrTransient()) {
                        onStandby(STANDBY_SCREEN_OFF);
                        if (readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true)) {
                            if (mWakeLock != null) {
                                mWakeLock.release();
                                mWakeLock = null;
                            }
                            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
                            mWakeLock.acquire();
                        }
                    }
                    break;
                case Intent.ACTION_SCREEN_ON:
                    if (isPowerStandbyOrTransient()) {
                        onWakeUp();
                    }
                    break;
                case Intent.ACTION_CONFIGURATION_CHANGED:
                    String language = getMenuLanguage();
                    if (!mLanguage.equals(language)) {
                        onLanguageChanged(language);
                    }
                    break;
                case Intent.ACTION_SHUTDOWN:
                    if (isPowerOnOrTransient() && !isReboot) {
                        onStandby(STANDBY_SHUTDOWN);
                    }
                    break;
            }
        }

比如系统在唤醒后腰进行HdmiCecLocalService相关的准备工作

    @ServiceThreadOnly
    private void onWakeUp() {
        assertRunOnServiceThread();
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
        if (mCecController != null) {
            if (mHdmiControlEnabled) {
                int startReason = INITIATED_BY_SCREEN_ON;
                if (mWakeUpMessageReceived) {
                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
                }
                initializeCec(startReason);
            }
        } else {
            Slog.i(TAG, "Device does not support HDMI-CEC.");
        }
        // TODO: Initialize MHL local devices.
    }

 重新初始化cec。对应type的HDMICECLocalDevice没创建的就创建下,并对其进行allocateLogicalAddress分配逻辑地址。活是在IoThread上面做的。

    private void initializeCec(int initiatedBy) {
        mAddressAllocated = false;
        mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
        mCecController.setLanguage(mLanguage);
        initializeLocalDevices(initiatedBy);
    }
    @ServiceThreadOnly
    private void initializeLocalDevices(final int initiatedBy) {
        assertRunOnServiceThread();
        // A container for [Device type, Local device info].
        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);
        }
        // It's now safe to flush existing local devices from mCecController since they were
        // already moved to 'localDevices'.
        clearLocalDevices();
        allocateLogicalAddress(localDevices, initiatedBy);
    }

在AllocateAddressCallback里面也会在logicalAddress分配以后对HdmiCecLocalDevice设置HdmiCecDeviceInfo ,并将其保存到cec controller的列表里面,并通过 cec hal服务添加logical address。

    @ServiceThreadOnly
    private void allocateLogicalAddress(final ArrayList allocatingDevices,
            final int initiatedBy) {
        assertRunOnServiceThread();
        mCecController.clearLogicalAddress();
        final ArrayList allocatedDevices = new ArrayList<>();
        final int[] finished = new int[1];
        mAddressAllocated = allocatingDevices.isEmpty();

        // For TV device, select request can be invoked while address allocation or device
        // discovery is in progress. Initialize the request here at the start of allocation,
        // and process the collected requests later when the allocation and device discovery
        // is all completed.
        mSelectRequestBuffer.clear();

        for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
            mCecController.allocateLogicalAddress(localDevice.getType(),
                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
                @Override
                public void onAllocated(int deviceType, int logicalAddress) {
                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
                    } else {
                        // Set POWER_STATUS_ON to all local devices because they share lifetime
                        // with system.
                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
                                HdmiControlManager.POWER_STATUS_ON);
                        localDevice.setDeviceInfo(deviceInfo);
                        mCecController.addLocalDevice(deviceType, localDevice);
                        mCecController.addLogicalAddress(logicalAddress);
                        allocatedDevices.add(localDevice);
                    }

                    // Address allocation completed for all devices. Notify each device.
                    if (allocatingDevices.size() == ++finished[0]) {
                        mAddressAllocated = true;
                        if (initiatedBy != INITIATED_BY_HOTPLUG) {
                            // In case of the hotplug we don't call onInitializeCecComplete()
                            // since we reallocate the logical address only.
                            onInitializeCecComplete(initiatedBy);
                        }
                        notifyAddressAllocated(allocatedDevices, initiatedBy);
                        mCecMessageBuffer.processMessages();
                    }
                }
            });
        }
    }
    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
        // TODO: find better name instead of model name.
        String displayName = Build.MODEL;
        return new HdmiDeviceInfo(logicalAddress,
                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
                getVendorId(), displayName);
    }

 

int HdmiCecController::addLogicalAddress(CecLogicalAddress address) {
    Return ret = mHdmiCec->addLogicalAddress(address);
    if (!ret.isOk()) {
        ALOGE("Failed to add a logical address.");
        return static_cast(Result::FAILURE_UNKNOWN);
    }
    return static_cast((Result) ret);
}

最后会通知新增加logical address 的设备进行更新信息。

    @ServiceThreadOnly
    private void notifyAddressAllocated(ArrayList devices, int initiatedBy) {
        assertRunOnServiceThread();
        for (HdmiCecLocalDevice device : devices) {
            int address = device.getDeviceInfo().getLogicalAddress();
            device.handleAddressAllocated(address, initiatedBy);
        }
        if (isTvDeviceEnabled()) {
            tv().setSelectRequestBuffer(mSelectRequestBuffer);
        }
    }

最后是处理在cec 分配logical 地址时发送过来的cec命令。

        public void processMessages() {
            for (final HdmiCecMessage message : mBuffer) {
                runOnServiceThread(new Runnable() {
                    @Override
                    public void run() {
                        handleCecCommand(message);
                    }
                });
            }
            mBuffer.clear();
        }
    @ServiceThreadOnly
    final void handleAddressAllocated(int logicalAddress, int reason) {
        assertRunOnServiceThread();
        mAddress = mPreferredAddress = logicalAddress;
        onAddressAllocated(logicalAddress, reason);
        setPreferredAddress(logicalAddress);
    }

当然,上面只是在分配logical address成功之后的动作,下面是分配logical address。

    @ServiceThreadOnly
    void allocateLogicalAddress(final int deviceType, final int preferredAddress,
            final AllocateAddressCallback callback) {
        assertRunOnServiceThread();

        runOnIoThread(new Runnable() {
            @Override
            public void run() {
                handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
            }
        });
    }

在发送cec poll message确定分配地址ok之后,就会通过前面的AllocateAddressCallback onAllocated函数把地址送回去。

    @IoThreadOnly
    private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
            final AllocateAddressCallback callback) {
        assertRunOnIoThread();
        int startAddress = preferredAddress;
        // If preferred address is "unregistered", start address will be the smallest
        // address matched with the given device type.
        if (preferredAddress == Constants.ADDR_UNREGISTERED) {
            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
                if (deviceType == HdmiUtils.getTypeFromAddress(i)) {
                    startAddress = i;
                    break;
                }
            }
        }

        int logicalAddress = Constants.ADDR_UNREGISTERED;
        // Iterates all possible addresses which has the same device type.
        for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
            int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
            if (curAddress != Constants.ADDR_UNREGISTERED
                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
                boolean acked = false;
                for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
                    if (sendPollMessage(curAddress, curAddress, 1)) {
                        acked = true;
                        break;
                    }
                }
                // If sending  failed, it becomes new logical address for the
                // device because no device uses it as logical address of the device.
                if (!acked) {
                    logicalAddress = curAddress;
                    break;
                }
            }
        }

        final int assignedAddress = logicalAddress;
        HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]",
                        deviceType, preferredAddress, assignedAddress);
        if (callback != null) {
            runOnServiceThread(new Runnable() {
                @Override
                public void run() {
                    callback.onAllocated(deviceType, assignedAddress);
                }
            });
        }
    }

 

 

⑦registerContentObserver

对settings关于系统HDMI开关等和HDMI、MHL相关的配置变量进行监控。

    private void registerContentObserver() {
        ContentResolver resolver = getContext().getContentResolver();
        String[] settings = new String[] {
                Global.HDMI_CONTROL_ENABLED,
                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                Global.HDMI_SYSTEM_AUDIO_STATUS_ENABLED,
                Global.MHL_INPUT_SWITCHING_ENABLED,
                Global.MHL_POWER_CHARGE_ENABLED
        };
        for (String s : settings) {
            resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
                    UserHandle.USER_ALL);
        }
    }

 

 

你可能感兴趣的:(HdmiControlService 流程梳理)