Merge "CEC: Buffer messages coming from unknown device" into lmp-mr1-dev

This commit is contained in:
Jinsuk Kim
2014-11-14 22:52:55 +00:00
committed by Android (Google) Code Review
5 changed files with 220 additions and 4 deletions

View File

@@ -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 &lt;Active Source&gt; 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>&lt;Active Source&gt; 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 &lt;Active Source&gt;.
*
* <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();
}
}
}
}

View File

@@ -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() {

View File

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

View File

@@ -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.
*/

View File

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