From 6e26f7f7b09dfd8495ec5478a7a4713dab346bc1 Mon Sep 17 00:00:00 2001 From: Jinsuk Kim Date: Wed, 7 Jan 2015 16:45:14 +0900 Subject: [PATCH] CEC: Handle arriving before TvInputCallback.onAddInput Resolves a timing-related bug that fails to process the command at TV boot up. Checks if TV input required for the command is ready to accept the request. If not, makes sure the command is buffered, and processed later when the input is ready. Bug: 18896770 Change-Id: Id17e5e8468519b17daf61c962dd718ccc56fb0ea --- .../server/hdmi/DelayedMessageBuffer.java | 20 ++++---- .../server/hdmi/HdmiCecLocalDevice.java | 11 +++++ .../server/hdmi/HdmiCecLocalDeviceTv.java | 48 ++++++++++++++++++- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java index 922a2c14c4cce..d3ecff0f188a6 100644 --- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java +++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java @@ -88,24 +88,22 @@ final class DelayedMessageBuffer { * Process messages from a given logical device. Called by * {@link NewDeviceAction} actions when they finish adding the device * information. - *

<Active Source> is not processed in this method but processed - * separately via {@link #processActiveSource()}. + *

<Active Source> is processed only when the TV input is ready. + * If not, {@link #processActiveSource()} will be invoked later to handle it. * * @param address logical address of CEC device which the messages to process * are associated with */ void processMessagesForDevice(int address) { - HdmiLogger.debug("Processing message for address:" + address); + HdmiLogger.debug("Checking message for address:" + address); for (Iterator iter = mBuffer.iterator(); iter.hasNext(); ) { HdmiCecMessage message = iter.next(); - if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { - continue; - } - if (message.getSource() == address) { - mDevice.onMessage(message); - HdmiLogger.debug("Processing message:" + message); - iter.remove(); - } + if (message.getSource() != address) continue; + if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE + && !mDevice.isInputReady(HdmiDeviceInfo.idForCecDevice(address))) continue; + mDevice.onMessage(message); + HdmiLogger.debug("Processing message:" + message); + iter.remove(); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 4f8b9fbc71f5d..1b478885e9cbf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -190,6 +190,17 @@ abstract class HdmiCecLocalDevice { */ protected abstract void setPreferredAddress(int addr); + /** + * Returns true if the TV input associated with the CEC device is ready + * to accept further processing such as input switching. This is used + * to buffer certain CEC commands and process it later if the input is not + * ready yet. For other types of local devices(non-TV), this method returns + * true by default to let the commands be processed right away. + */ + protected boolean isInputReady(int deviceId) { + return true; + } + /** * Dispatch incoming message. * diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 90dbd17652007..a8941619dc2df 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -63,6 +63,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.HashMap; /** * Represent a logical device of type TV residing in Android system. @@ -143,12 +144,44 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { public void onInputAdded(String inputId) { TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId); HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo(); - if (info != null && info.isCecDevice()) { - mDelayedMessageBuffer.processActiveSource(info.getLogicalAddress()); + if (info == null) return; + addTvInput(inputId, info.getId()); + if (info.isCecDevice()) { + processDelayedActiveSource(info.getLogicalAddress()); } } + + @Override + public void onInputRemoved(String inputId) { + removeTvInput(inputId); + } }; + // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to + // accept input switching request from HDMI devices. Requests for which the corresponding + // input ID is not yet registered by TV input framework need to be buffered for delayed + // processing. + private final HashMap mTvInputs = new HashMap<>(); + + @ServiceThreadOnly + private void addTvInput(String inputId, int deviceId) { + assertRunOnServiceThread(); + mTvInputs.put(inputId, deviceId); + } + + @ServiceThreadOnly + private void removeTvInput(String inputId) { + assertRunOnServiceThread(); + mTvInputs.remove(inputId); + } + + @Override + @ServiceThreadOnly + protected boolean isInputReady(int deviceId) { + assertRunOnServiceThread(); + return mTvInputs.containsValue(deviceId); + } + HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; @@ -168,6 +201,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( mAddress, mService.getVendorId())); mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too. + mTvInputs.clear(); mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && reason != HdmiControlService.INITIATED_BY_BOOT_UP); @@ -447,8 +481,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); if (info == null) { if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { + HdmiLogger.debug("Device info not found: %X; buffering the command", logicalAddress); mDelayedMessageBuffer.add(message); } + } else if (!isInputReady(info.getId())) { + HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); + mDelayedMessageBuffer.add(message); } else { ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); @@ -1776,6 +1814,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mDelayedMessageBuffer.processMessagesForDevice(address); } + @ServiceThreadOnly + void processDelayedActiveSource(int address) { + assertRunOnServiceThread(); + mDelayedMessageBuffer.processActiveSource(address); + } + @Override protected void dump(final IndentingPrintWriter pw) { super.dump(pw);