diff options
5 files changed, 220 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java new file mode 100644 index 0000000..68311de --- /dev/null +++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.HdmiDeviceInfo; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Buffer storage to keep incoming messages for later processing. Used to + * handle messages that arrive when the device is not ready. Useful when + * keeping the messages from a connected device which are not discovered yet. + */ +final class DelayedMessageBuffer { + private final ArrayList<HdmiCecMessage> mBuffer = new ArrayList<>(); + private final HdmiCecLocalDevice mDevice; + + DelayedMessageBuffer(HdmiCecLocalDevice device) { + mDevice = device; + } + + /** + * Add a new message to the buffer. The buffer keeps selected messages in + * the order they are received. + * + * @param message {@link HdmiCecMessage} to add + */ + void add(HdmiCecMessage message) { + boolean buffered = true; + + // Note that all the messages are not handled in the same manner. + // For <Active Source> we keep the latest one only. + // TODO: This might not be the best way to choose the active source. + // Devise a better way to pick up the best one. + switch (message.getOpcode()) { + case Constants.MESSAGE_ACTIVE_SOURCE: + removeActiveSource(); + mBuffer.add(message); + break; + case Constants.MESSAGE_INITIATE_ARC: + mBuffer.add(message); + break; + default: + buffered = false; + break; + } + if (buffered) { + HdmiLogger.debug("Buffering message:" + message); + } + } + + private void removeActiveSource() { + // Uses iterator to remove elements while looping through the list. + for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) { + HdmiCecMessage message = iter.next(); + if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { + iter.remove(); + } + } + } + + void processAllMessages() { + for (HdmiCecMessage message : mBuffer) { + mDevice.onMessage(message); + HdmiLogger.debug("Processing message:" + message); + } + mBuffer.clear(); + } + + /** + * Process messages from a given logical device. Called by + * {@link NewDeviceAction} actions when they finish adding the device + * information. + * <p><Active Source> is not processed in this method but processed + * separately via {@link #processActiveSource()}. + * + * @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); + for (Iterator<HdmiCecMessage> 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(); + } + } + } + + /** + * Process <Active Source>. + * + * <p>The message has a dependency on TV input framework. Should be invoked + * after we get the callback + * {@link android.media.tv.TvInputManager.TvInputCallback#onInputAdded(String)} + * to ensure the processing of the message takes effect when transformed + * to input change callback. + * + * @param address logical address of the device to be the active source + */ + void processActiveSource(int address) { + for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) { + HdmiCecMessage message = iter.next(); + if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE + && message.getSource() == address) { + mDevice.onMessage(message); + HdmiLogger.debug("Processing message:" + message); + iter.remove(); + } + } + } +} diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index 2ec9778..da404c4 100644 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -326,6 +326,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { Slog.v(TAG, "--------------------------------------------"); mCallback.onDeviceDiscoveryDone(result); finish(); + // Process any commands buffered while device discovery action was in progress. + tv().processAllDelayedMessages(); } private void checkAndProceedStage() { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 6536165..0cdb4d5 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -30,6 +30,8 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANAL import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; +import android.annotation.Nullable; +import android.content.Context; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiRecordSources; @@ -37,6 +39,9 @@ import android.hardware.hdmi.HdmiTimerRecordSources; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; import android.media.AudioSystem; +import android.media.tv.TvInputInfo; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputManager.TvInputCallback; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings.Global; @@ -49,6 +54,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; +import com.android.server.SystemService; import java.io.UnsupportedEncodingException; import java.util.ArrayList; @@ -123,6 +129,26 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // other CEC devices since they might not have logical address. private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>(); + // Message buffer used to buffer selected messages to process later. <Active Source> + // from a source device, for instance, needs to be buffered if the device is not + // discovered yet. The buffered commands are taken out and when they are ready to + // handle. + private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this); + + // Defines the callback invoked when TV input framework is updated with input status. + // We are interested in the notification for HDMI input addition event, in order to + // process any CEC commands that arrived before the input is added. + private final TvInputCallback mTvInputCallback = new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId); + HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo(); + if (info != null && info.isCecDevice()) { + mDelayedMessageBuffer.processActiveSource(info.getLogicalAddress()); + } + } + }; + HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; @@ -136,6 +162,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected void onAddressAllocated(int logicalAddress, int reason) { assertRunOnServiceThread(); + mService.registerTvInputCallback(mTvInputCallback); mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mService.getPhysicalAddress(), mDeviceType)); mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( @@ -407,7 +434,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); if (info == null) { - handleNewDeviceAtTheTailOfActivePath(physicalAddress); + if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { + mDelayedMessageBuffer.add(message); + } } else { ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); @@ -555,7 +584,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { activeSource.physicalAddress, deviceType)); } - private void handleNewDeviceAtTheTailOfActivePath(int path) { + private boolean handleNewDeviceAtTheTailOfActivePath(int path) { // Seq #22 if (isTailOfActivePath(path, getActivePath())) { removeAction(RoutingControlAction.class); @@ -564,7 +593,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( mAddress, getActivePath(), newPath)); addAndStartAction(new RoutingControlAction(this, newPath, false, null)); + return true; } + return false; } /** @@ -706,8 +737,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly void onNewAvrAdded(HdmiDeviceInfo avr) { assertRunOnServiceThread(); - addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); - if (isArcFeatureEnabled()) { + if (getSystemAudioModeSetting()) { + addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); + } + if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) { startArcAction(true); } } @@ -941,6 +974,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!canStartArcUpdateAction(message.getSource(), true)) { + if (getAvrDeviceInfo() == null) { + // AVR may not have been discovered yet. Delay the message processing. + mDelayedMessageBuffer.add(message); + return true; + } mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); if (!isConnectedToArcPort(message.getSource())) { displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); @@ -1436,6 +1474,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); + mService.unregisterTvInputCallback(mTvInputCallback); // Remove any repeated working actions. // HotplugDetectionAction will be reinstated during the wake up process. // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> @@ -1714,6 +1753,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); } + @ServiceThreadOnly + void processAllDelayedMessages() { + assertRunOnServiceThread(); + mDelayedMessageBuffer.processAllMessages(); + } + + @ServiceThreadOnly + void processDelayedMessages(int address) { + assertRunOnServiceThread(); + mDelayedMessageBuffer.processMessagesForDevice(address); + } + @Override protected void dump(final IndentingPrintWriter pw) { super.dump(pw); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 907a49a..8a25f62 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -48,6 +48,8 @@ import android.hardware.hdmi.IHdmiRecordListener; import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.hardware.hdmi.IHdmiVendorCommandListener; import android.media.AudioManager; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputManager.TvInputCallback; import android.net.Uri; import android.os.Build; import android.os.Handler; @@ -272,6 +274,9 @@ public final class HdmiControlService extends SystemService { @Nullable private HdmiMhlControllerStub mMhlController; + @Nullable + private TvInputManager mTvInputManager; + // Last input port before switching to the MHL port. Should switch back to this port // when the mobile device sends the request one touch play with off. // Gets invalidated if we go to other port/input. @@ -343,6 +348,28 @@ public final class HdmiControlService extends SystemService { } } + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mTvInputManager = (TvInputManager) getContext().getSystemService( + Context.TV_INPUT_SERVICE); + } + } + + TvInputManager getTvInputManager() { + return mTvInputManager; + } + + void registerTvInputCallback(TvInputCallback callback) { + if (mTvInputManager == null) return; + mTvInputManager.registerCallback(callback, mHandler); + } + + void unregisterTvInputCallback(TvInputCallback callback) { + if (mTvInputManager == null) return; + mTvInputManager.unregisterCallback(callback); + } + /** * Called when the initialization of local devices is complete. */ diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index 998889b..64f0703 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -161,6 +161,9 @@ final class NewDeviceAction extends HdmiCecFeatureAction { mDeviceType, mVendorId, mDisplayName); tv().addCecDevice(deviceInfo); + // Consume CEC messages we already got for this newly found device. + tv().processDelayedMessages(mDeviceLogicalAddress); + if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress) == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { tv().onNewAvrAdded(deviceInfo); |