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