summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java133
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java59
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java27
-rw-r--r--services/core/java/com/android/server/hdmi/NewDeviceAction.java3
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 &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();
+ }
+ }
+ }
+}
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);