summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/hdmi/CecMessageBuffer.java22
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java15
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java11
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java19
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java127
6 files changed, 186 insertions, 19 deletions
diff --git a/services/core/java/com/android/server/hdmi/CecMessageBuffer.java b/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
index 8f971fd7db07..0c73582e66cb 100644
--- a/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
@@ -48,6 +48,12 @@ final class CecMessageBuffer {
case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
bufferSystemAudioModeRequest(message);
return true;
+ case Constants.MESSAGE_ROUTING_CHANGE:
+ bufferRoutingChange(message);
+ return true;
+ case Constants.MESSAGE_SET_STREAM_PATH:
+ bufferSetStreamPath(message);
+ return true;
// Add here if new message that needs to buffer
default:
// Do not need to buffer messages other than above
@@ -89,6 +95,22 @@ final class CecMessageBuffer {
}
}
+ private void bufferRoutingChange(HdmiCecMessage message) {
+ if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ROUTING_CHANGE)) {
+ mBuffer.add(message);
+ }
+ }
+
+ private void bufferSetStreamPath(HdmiCecMessage message) {
+ if (!replaceMessageIfBuffered(message, Constants.MESSAGE_SET_STREAM_PATH)) {
+ mBuffer.add(message);
+ }
+ }
+
+ public List<HdmiCecMessage> getBuffer() {
+ return new ArrayList<>(mBuffer);
+ }
+
// Returns true if the message is replaced
private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
for (int i = 0; i < mBuffer.size(); i++) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 97e9c6458376..5aa3fa4db4f1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -147,6 +147,9 @@ final class HdmiCecController {
private final HdmiCecAtomWriter mHdmiCecAtomWriter;
+ // This variable is used for testing, in order to delay the logical address allocation.
+ private long mLogicalAddressAllocationDelay = 0;
+
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(
HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -215,12 +218,12 @@ final class HdmiCecController {
final AllocateAddressCallback callback) {
assertRunOnServiceThread();
- runOnIoThread(new Runnable() {
+ mIoHandler.postDelayed(new Runnable() {
@Override
public void run() {
handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
}
- });
+ }, mLogicalAddressAllocationDelay);
}
/**
@@ -386,6 +389,14 @@ final class HdmiCecController {
}
/**
+ * This method is used for testing, in order to delay the logical address allocation.
+ */
+ @VisibleForTesting
+ void setLogicalAddressAllocationDelay(long delay) {
+ mLogicalAddressAllocationDelay = delay;
+ }
+
+ /**
* Returns true if the language code is well-formed.
*/
@VisibleForTesting static boolean isLanguage(String language) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 1de1a7a5e1e5..2622cef32241 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -649,6 +649,13 @@ abstract class HdmiCecLocalDevice {
return Constants.NOT_HANDLED;
}
+ /**
+ * Called after logical address allocation is finished, allowing a local device to react to
+ * messages in the buffer before they are processed. This method may be used to cancel deferred
+ * actions.
+ */
+ protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {}
+
@Constants.RcProfile
protected abstract int getRcProfile();
@@ -963,8 +970,10 @@ abstract class HdmiCecLocalDevice {
}
@ServiceThreadOnly
- final void handleAddressAllocated(int logicalAddress, int reason) {
+ final void handleAddressAllocated(
+ int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) {
assertRunOnServiceThread();
+ preprocessBufferedMessages(bufferedMessages);
mPreferredAddress = logicalAddress;
updateDeviceFeatures();
if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 5cfe27a62e1b..ea54b3001163 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -530,6 +530,25 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
}
}
+ /**
+ * Called after logical address allocation is finished, allowing a local device to react to
+ * messages in the buffer before they are processed. This method may be used to cancel deferred
+ * actions.
+ */
+ @Override
+ protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {
+ for (HdmiCecMessage message: bufferedMessages) {
+ // Prevent the device from broadcasting <Active Source> message if the active path
+ // changed during address allocation.
+ if (message.getOpcode() == Constants.MESSAGE_ROUTING_CHANGE
+ || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH
+ || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
+ removeAction(ActiveSourceAction.class);
+ return;
+ }
+ }
+ }
+
@Override
protected int findKeyReceiverAddress() {
return Constants.ADDR_TV;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6c37bf1bb9e2..fa8b5c1b8086 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1079,9 +1079,10 @@ public class HdmiControlService extends SystemService {
@ServiceThreadOnly
private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
assertRunOnServiceThread();
+ List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer();
for (HdmiCecLocalDevice device : devices) {
int address = device.getDeviceInfo().getLogicalAddress();
- device.handleAddressAllocated(address, initiatedBy);
+ device.handleAddressAllocated(address, bufferedMessages, initiatedBy);
}
if (isTvDeviceEnabled()) {
tv().setSelectRequestBuffer(mSelectRequestBuffer);
@@ -1361,8 +1362,9 @@ public class HdmiControlService extends SystemService {
@Constants.HandleMessageResult int handleMessageResult =
dispatchMessageToLocalDevice(message);
- if (handleMessageResult == Constants.NOT_HANDLED
- && !mAddressAllocated
+ // mAddressAllocated is false during address allocation, meaning there is no device to
+ // handle the message, so it should be buffered, if possible.
+ if (!mAddressAllocated
&& mCecMessageBuffer.bufferMessage(message)) {
return Constants.HANDLED;
}
@@ -3286,7 +3288,8 @@ public class HdmiControlService extends SystemService {
}
@ServiceThreadOnly
- private void onWakeUp(@WakeReason final int wakeUpAction) {
+ @VisibleForTesting
+ protected void onWakeUp(@WakeReason final int wakeUpAction) {
assertRunOnServiceThread();
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
false);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 0f6addb452a1..fe9e0b6a2d07 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -47,7 +47,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.concurrent.TimeUnit;
@SmallTest
@@ -79,6 +78,7 @@ public class HdmiCecLocalDevicePlaybackTest {
private TestLooper mTestLooper = new TestLooper();
private FakePowerManagerWrapper mPowerManager;
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
private int mPlaybackPhysicalAddress;
private int mPlaybackLogicalAddress;
private boolean mWokenUp;
@@ -91,9 +91,11 @@ public class HdmiCecLocalDevicePlaybackTest {
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ mLocalDeviceTypes, new FakeAudioDeviceVolumeManagerWrapper()) {
+
@Override
void wakeUp() {
mWokenUp = true;
@@ -121,16 +123,6 @@ public class HdmiCecLocalDevicePlaybackTest {
}
@Override
- boolean isPowerStandby() {
- return false;
- }
-
- @Override
- boolean isPowerStandbyOrTransient() {
- return false;
- }
-
- @Override
boolean canGoToStandby() {
return true;
}
@@ -165,6 +157,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mNativeWrapper.clearResultMessages();
mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
HdmiProperties.playback_device_action_on_routing_control_values.NONE;
+ mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
}
@Test
@@ -1199,6 +1192,9 @@ public class HdmiCecLocalDevicePlaybackTest {
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
mHdmiCecLocalDevicePlayback.dispatchMessage(setStreamPath);
+ // The ActiveSourceAction created from the message above is deferred until the device wakes
+ // up.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
HdmiCecMessage activeSource =
HdmiCecMessageBuilder.buildActiveSource(
@@ -2088,4 +2084,111 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
.isEmpty();
}
+
+ @Test
+ public void handleRoutingChange_addressNotAllocated_removeActiveSourceAction() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
+ HdmiProperties
+ .playback_device_action_on_routing_control_values
+ .WAKE_UP_AND_SEND_ACTIVE_SOURCE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ HdmiCecMessage routingChangeToPlayback =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ mPlaybackPhysicalAddress);
+ HdmiCecMessage routingChangeToTv =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, mPlaybackPhysicalAddress,
+ 0x0000);
+ HdmiCecMessage unexpectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ // 1. DUT goes to sleep.
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ // Delay allocate logical address in order to trigger message buffering.
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mNativeWrapper.onCecMessage(routingChangeToPlayback);
+ mTestLooper.dispatchAll();
+ // 2. DUT wakes up and defer ActiveSourceAction.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+ // 3. DUT buffers <Routing Change> message to TV.
+ mNativeWrapper.onCecMessage(routingChangeToTv);
+ mTestLooper.dispatchAll();
+ // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+ // No <Active Source> message is sent by the DUT.
+ mTestLooper.moveTimeForward(allocationDelay);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+ }
+
+ @Test
+ public void handleSetStreamPath_addressNotAllocated_removeActiveSourceAction() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ HdmiCecMessage setStreamPathToPlayback =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+ HdmiCecMessage setStreamPathToTv =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x0000);
+ HdmiCecMessage unexpectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ // 1. DUT goes to sleep.
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ // Delay allocate logical address in order to trigger message buffering.
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mNativeWrapper.onCecMessage(setStreamPathToPlayback);
+ mTestLooper.dispatchAll();
+ // 2. DUT wakes up and defer ActiveSourceAction.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+ // 3. DUT buffers <Set Stream Path> message to TV.
+ mNativeWrapper.onCecMessage(setStreamPathToTv);
+ mTestLooper.dispatchAll();
+ // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+ // No <Active Source> message is sent by the DUT.
+ mTestLooper.moveTimeForward(allocationDelay);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+ }
+
+ @Test
+ public void handleActiveSource_addressNotAllocated_removeActiveSourceAction() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+
+ HdmiCecMessage setStreamPathToPlayback =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage unexpectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ // 1. DUT goes to sleep.
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ // Delay allocate logical address in order to trigger message buffering.
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mNativeWrapper.onCecMessage(setStreamPathToPlayback);
+ mTestLooper.dispatchAll();
+ // 2. DUT wakes up and defer ActiveSourceAction.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+ // 3. DUT buffers <Active Source> message from TV.
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.dispatchAll();
+ // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+ // No <Active Source> message is sent by the DUT.
+ mTestLooper.moveTimeForward(allocationDelay);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+ }
}