Merge "CEC: Buffer messages coming from unknown device" into lmp-mr1-dev
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user