From 5023f012703fbd0e1b9f104452eaac28b4c5cfa5 Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Wed, 5 Oct 2022 13:59:48 +0200 Subject: [PATCH 1/5] Block new ARC connections during eARC connection If eARC is disabled or not supported, ARC gets connected normally. Test: atest Bug: 262573690 Change-Id: I5c3d172f313c9457e3debe4d35ba6308f221b740 --- .../server/hdmi/HdmiCecLocalDeviceTv.java | 13 + .../server/hdmi/HdmiControlService.java | 10 + .../server/hdmi/HdmiEarcLocalDeviceTx.java | 4 + .../server/hdmi/HdmiCecLocalDeviceTvTest.java | 316 ++++++++++++++++-- .../server/hdmi/HdmiControlServiceTest.java | 40 +++ 5 files changed, 354 insertions(+), 29 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 96e7b03ec5a06..72ff5b55de5bf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -912,6 +912,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } return; } + if (enabled && mService.earcBlocksArcConnection()) { + Slog.i(TAG, + "ARC connection blocked because eARC connection is established or being " + + "established."); + return; + } // Terminate opposite action and start action if not exist. if (enabled) { @@ -1010,6 +1016,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { protected int handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); + if (mService.earcBlocksArcConnection()) { + Slog.i(TAG, + "ARC connection blocked because eARC connection is established or being " + + "established."); + return Constants.ABORT_NOT_IN_CORRECT_MODE; + } + if (!canStartArcUpdateAction(message.getSource(), true)) { HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo(); if (avrDeviceInfo == null) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 85bd16b2cb2f7..8c4a5cab6a29e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -26,6 +26,7 @@ import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.DISABLED; import static com.android.server.hdmi.Constants.ENABLED; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING; import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE; import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING; import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE; @@ -4540,4 +4541,13 @@ public class HdmiControlService extends SystemService { mEarcLocalDevice.handleEarcCapabilitiesReported(rawCapabilities); } } + + protected boolean earcBlocksArcConnection() { + if (mEarcLocalDevice == null) { + return false; + } + synchronized (mLock) { + return mEarcLocalDevice.mEarcStatus != HDMI_EARC_STATUS_ARC_PENDING; + } + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java index c7c7702b871a1..5caacf6d291a1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING; import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED; +import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING; import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE; import android.hardware.hdmi.HdmiDeviceInfo; @@ -69,6 +70,9 @@ public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { HdmiEarcLocalDeviceTx(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_TV); + synchronized (mLock) { + mEarcStatus = HDMI_EARC_STATUS_EARC_PENDING; + } mReportCapsHandler = new Handler(service.getServiceLooper()); mReportCapsRunnable = new ReportCapsRunnable(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 5246107685543..689fcd1ce2288 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -96,6 +96,7 @@ public class HdmiCecLocalDeviceTvTest { private int mTvPhysicalAddress; private int mTvLogicalAddress; private boolean mWokenUp; + private boolean mEarcBlocksArc; private List mDeviceEventListeners = new ArrayList<>(); private class DeviceEventListener { @@ -155,6 +156,11 @@ public class HdmiCecLocalDeviceTvTest { return false; } + @Override + boolean isPowerStandbyOrTransient() { + return false; + } + @Override AudioManager getAudioManager() { return mAudioManager; @@ -164,6 +170,11 @@ public class HdmiCecLocalDeviceTvTest { void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { mDeviceEventListeners.add(new DeviceEventListener(device, status)); } + + @Override + protected boolean earcBlocksArcConnection() { + return mEarcBlocksArc; + } }; mHdmiControlService.setIoLooper(mMyLooper); @@ -175,16 +186,18 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = - new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false); + new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false, false); hdmiPortInfos[1] = - new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true); + new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); mTvPhysicalAddress = 0x0000; + mEarcBlocksArc = false; mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress); + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED); mTestLooper.dispatchAll(); mHdmiCecLocalDeviceTv = mHdmiControlService.tv(); mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(); @@ -426,9 +439,10 @@ public class HdmiCecLocalDeviceTvTest { public void startArcAction_enable_portDoesNotSupportArc() { // Emulate Audio device on port 0x1000 (does not support ARC) mNativeWrapper.setPortConnectionStatus(1, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mHdmiCecLocalDeviceTv.startArcAction(true); HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( @@ -445,9 +459,10 @@ public class HdmiCecLocalDeviceTvTest { public void startArcAction_disable_portDoesNotSupportArc() { // Emulate Audio device on port 0x1000 (does not support ARC) mNativeWrapper.setPortConnectionStatus(1, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mHdmiCecLocalDeviceTv.startArcAction(false); HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( @@ -464,9 +479,10 @@ public class HdmiCecLocalDeviceTvTest { public void startArcAction_enable_portSupportsArc() { // Emulate Audio device on port 0x2000 (supports ARC) mNativeWrapper.setPortConnectionStatus(2, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); mHdmiCecLocalDeviceTv.startArcAction(true); @@ -485,9 +501,10 @@ public class HdmiCecLocalDeviceTvTest { public void startArcAction_disable_portSupportsArc() { // Emulate Audio device on port 0x2000 (supports ARC) mNativeWrapper.setPortConnectionStatus(2, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); mHdmiCecLocalDeviceTv.startArcAction(false); @@ -522,9 +539,10 @@ public class HdmiCecLocalDeviceTvTest { public void handleInitiateArc_portDoesNotSupportArc() { // Emulate Audio device on port 0x1000 (does not support ARC) mNativeWrapper.setPortConnectionStatus(1, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( ADDR_AUDIO_SYSTEM, @@ -544,9 +562,10 @@ public class HdmiCecLocalDeviceTvTest { public void handleInitiateArc_portSupportsArc() { // Emulate Audio device on port 0x2000 (supports ARC) mNativeWrapper.setPortConnectionStatus(2, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( @@ -574,6 +593,66 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); } + @Test + public void handleTerminateArc_noAudioDevice() { + HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(terminateArc); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated); + } + + @Test + public void handleTerminateArc_portDoesNotSupportArc() { + // Emulate Audio device on port 0x1000 (does not support ARC) + mNativeWrapper.setPortConnectionStatus(1, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + + HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(terminateArc); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated); + } + + @Test + public void handleTerminateArc_portSupportsArc() { + // Emulate Audio device on port 0x2000 (supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(terminateArc); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated); + } + @Test public void supportsRecordTvScreen() { HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_RECORDER_1, mTvLogicalAddress, @@ -595,9 +674,10 @@ public class HdmiCecLocalDeviceTvTest { HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED); // Emulate Audio device on port 0x1000 (does not support ARC) mNativeWrapper.setPortConnectionStatus(1, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); HdmiCecFeatureAction systemAudioAutoInitiationAction = @@ -636,9 +716,9 @@ public class HdmiCecLocalDeviceTvTest { public void hotplugDetectionAction_discoversDeviceAfterMessageReceived() { // Playback 1 sends a message before ACKing a poll mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildActiveSource( + HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( ADDR_PLAYBACK_1, ADDR_TV); - mNativeWrapper.onCecMessage(hdmiCecMessage); + mNativeWrapper.onCecMessage(activeSource); mTestLooper.dispatchAll(); // Playback 1 begins ACKing polls, allowing detection by HotplugDetectionAction @@ -853,11 +933,11 @@ public class HdmiCecLocalDeviceTvTest { @Test public void tvSendRequestArcTerminationOnSleep() { // Emulate Audio device on port 0x2000 (supports ARC) - mNativeWrapper.setPortConnectionStatus(2, true); - HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); - mNativeWrapper.onCecMessage(hdmiCecMessage); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); mHdmiCecLocalDeviceTv.startArcAction(true); @@ -898,4 +978,182 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); } + @Test + public void startArcAction_enable_earcBlocksArc() { + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = true; + + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.startArcAction(true); + mTestLooper.dispatchAll(); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + @Test + public void startArcAction_enable_earcDoesNotBlockArc() { + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = false; + + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.startArcAction(true); + mTestLooper.dispatchAll(); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + } + + @Test + public void startArcAction_disable_earcBlocksArc() { + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = true; + + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.startArcAction(false); + mTestLooper.dispatchAll(); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + } + + @Test + public void handleInitiateArc_earcBlocksArc() { + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = true; + + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(requestArcInitiation); + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_TV, + ADDR_AUDIO_SYSTEM, + Constants.MESSAGE_INITIATE_ARC, + Constants.ABORT_NOT_IN_CORRECT_MODE); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage); + } + + @Test + public void handleInitiateArc_earcDoesNotBlockArc() { + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = false; + + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(requestArcInitiation); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + // should only be sent after SAD querying is done + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); + + // Finish querying SADs + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + } + + @Test + public void handleTerminateArc_earcBlocksArc() { + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = true; + + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(terminateArc); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 2b555a07a50fd..ac361e1cb9fba 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -1253,6 +1253,46 @@ public class HdmiControlServiceTest { verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true); } + @Test + public void earcIdle_blocksArcConnection() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue(); + } + + @Test + public void earcPending_blocksArcConnection() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue(); + } + + @Test + public void earcEnabled_blocksArcConnection() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue(); + } + + @Test + public void arcPending_doesNotBlockArcConnection() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isFalse(); + } + protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby; From 55037ec56c822aabcfd1c473aecd421d603aac07 Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Thu, 20 Oct 2022 09:03:12 +0200 Subject: [PATCH 2/5] Add callback to startArcAction Test: atest Bug: 262573690 Change-Id: I29676ef75dd3a197129e2ad0b92487f329cb806c --- .../server/hdmi/HdmiCecLocalDeviceTv.java | 38 +- .../server/hdmi/HdmiControlService.java | 3 + .../android/server/hdmi/RequestArcAction.java | 36 +- .../hdmi/RequestArcInitiationAction.java | 38 +- .../hdmi/RequestArcTerminationAction.java | 38 +- .../server/hdmi/HdmiCecLocalDeviceTvTest.java | 360 +++++++++++++++++- 6 files changed, 466 insertions(+), 47 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 72ff5b55de5bf..6303bdc63c36b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -899,10 +899,16 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly void startArcAction(boolean enabled) { + startArcAction(enabled, null); + } + + @ServiceThreadOnly + void startArcAction(boolean enabled, IHdmiControlCallback callback) { assertRunOnServiceThread(); HdmiDeviceInfo info = getAvrDeviceInfo(); if (info == null) { Slog.w(TAG, "Failed to start arc action; No AVR device."); + invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); return; } if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) { @@ -910,25 +916,37 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) { displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); } + invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } if (enabled && mService.earcBlocksArcConnection()) { Slog.i(TAG, "ARC connection blocked because eARC connection is established or being " + "established."); + invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } - // Terminate opposite action and start action if not exist. + // Terminate opposite action and create an action with callback. if (enabled) { removeAction(RequestArcTerminationAction.class); - if (!hasAction(RequestArcInitiationAction.class)) { - addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); + if (hasAction(RequestArcInitiationAction.class)) { + RequestArcInitiationAction existingInitiationAction = + getActions(RequestArcInitiationAction.class).get(0); + existingInitiationAction.addCallback(callback); + } else { + addAndStartAction( + new RequestArcInitiationAction(this, info.getLogicalAddress(), callback)); } } else { removeAction(RequestArcInitiationAction.class); - if (!hasAction(RequestArcTerminationAction.class)) { - addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); + if (hasAction(RequestArcTerminationAction.class)) { + RequestArcTerminationAction existingTerminationAction = + getActions(RequestArcTerminationAction.class).get(0); + existingTerminationAction.addCallback(callback); + } else { + addAndStartAction( + new RequestArcTerminationAction(this, info.getLogicalAddress(), callback)); } } } @@ -1036,9 +1054,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return Constants.ABORT_REFUSED; } - // In case where is started by - // need to clean up RequestArcInitiationAction. - removeAction(RequestArcInitiationAction.class); + // In case where is started by , this message is + // handled in RequestArcInitiationAction as well. SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), true); addAndStartAction(action); @@ -1072,9 +1089,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return Constants.HANDLED; } // Do not check ARC configuration since the AVR might have been already removed. - // Clean up RequestArcTerminationAction in case was started by - // . - removeAction(RequestArcTerminationAction.class); + // In case where is started by , this + // message is handled in RequestArcTerminationAction as well. SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), false); addAndStartAction(action); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 8c4a5cab6a29e..36ed5b52039ce 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -3237,6 +3237,9 @@ public class HdmiControlService extends SystemService { } private void invokeCallback(IHdmiControlCallback callback, int result) { + if (callback == null) { + return; + } try { callback.onComplete(result); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 3d9a2905f4fbc..54c8c00b88892 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -16,7 +16,9 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; /** * Base feature action class for <Request ARC Initiation>/<Request ARC Termination>. @@ -35,41 +37,19 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { * * @param source {@link HdmiCecLocalDevice} instance * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type + * @param callback callback to inform about the status of the action * @throws IllegalArgumentException if device type of sourceAddress and avrAddress * is invalid */ - RequestArcAction(HdmiCecLocalDevice source, int avrAddress) { - super(source); + RequestArcAction(HdmiCecLocalDevice source, int avrAddress, IHdmiControlCallback callback) { + super(source, callback); HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); mAvrAddress = avrAddress; } - @Override - boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE - || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) { - return false; - } - int opcode = cmd.getOpcode(); - switch (opcode) { - // Handles only here and, both and - // are handled in HdmiControlService itself because both can be - // received without or . - case Constants.MESSAGE_FEATURE_ABORT: - int originalOpcode = cmd.getParams()[0] & 0xFF; - if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) { - disableArcTransmission(); - finish(); - return true; - } else if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) { - tv().disableArc(); - finish(); - return true; - } - return false; - } - return false; + RequestArcAction(HdmiCecLocalDevice source, int avrAddress) { + this(source, avrAddress, null); } protected final void disableArcTransmission() { @@ -86,6 +66,6 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { } HdmiLogger.debug("[T] RequestArcAction."); disableArcTransmission(); - finish(); + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); } } diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java index 3b7f1dd0d9715..db21a33ae3253 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; /** @@ -35,6 +37,16 @@ final class RequestArcInitiationAction extends RequestArcAction { super(source, avrAddress); } + /** + * @Constructor + * + * For more details look at {@link RequestArcAction#RequestArcAction}. + */ + RequestArcInitiationAction(HdmiCecLocalDevice source, int avrAddress, + IHdmiControlCallback callback) { + super(source, avrAddress, callback); + } + @Override boolean start() { // Seq #38 @@ -49,10 +61,34 @@ final class RequestArcInitiationAction extends RequestArcAction { if (error != SendMessageResult.SUCCESS) { // Turn off ARC status if fails. tv().disableArc(); - finish(); + finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); } } }); return true; } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE + || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) { + return false; + } + int opcode = cmd.getOpcode(); + switch (opcode) { + case Constants.MESSAGE_FEATURE_ABORT: + int originalOpcode = cmd.getParams()[0] & 0xFF; + if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) { + tv().disableArc(); + finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + return true; + } + return false; + case Constants.MESSAGE_INITIATE_ARC: + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + // This message still needs to be handled in HdmiCecLocalDeviceTv as well. + return false; + } + return false; + } } diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java index 8b5a293102331..85128b69b5788 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; /** @@ -35,6 +37,16 @@ final class RequestArcTerminationAction extends RequestArcAction { super(source, avrAddress); } + /** + * @Constructor + * + * @see RequestArcAction#RequestArcAction + */ + RequestArcTerminationAction(HdmiCecLocalDevice source, int avrAddress, + IHdmiControlCallback callback) { + super(source, avrAddress, callback); + } + @Override boolean start() { mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; @@ -49,10 +61,34 @@ final class RequestArcTerminationAction extends RequestArcAction { // If failed to send , start "Disabled" ARC // transmission action. disableArcTransmission(); - finish(); + finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); } } }); return true; } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE + || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) { + return false; + } + int opcode = cmd.getOpcode(); + switch (opcode) { + case Constants.MESSAGE_FEATURE_ABORT: + int originalOpcode = cmd.getParams()[0] & 0xFF; + if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) { + disableArcTransmission(); + finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + return true; + } + return false; + case Constants.MESSAGE_TERMINATE_ARC: + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + // This message still needs to be handled in HdmiCecLocalDeviceTv as well. + return false; + } + return false; + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 689fcd1ce2288..ba44cac2dc08e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -39,6 +39,7 @@ import android.content.Context; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.media.AudioManager; import android.os.Looper; @@ -209,6 +210,20 @@ public class HdmiCecLocalDeviceTvTest { mNativeWrapper.clearResultMessages(); } + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList mCallbackResult = new ArrayList(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + + private int getResult() { + assertThat(mCallbackResult.size()).isEqualTo(1); + return mCallbackResult.get(0); + } + } + @Test public void initialPowerStateIsStandby() { assertThat(mHdmiCecLocalDeviceTv.getPowerStatus()).isEqualTo( @@ -892,12 +907,12 @@ public class HdmiCecLocalDeviceTvTest { mTestLooper.dispatchAll(); // [Not in correct mode] not sent - HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( ADDR_TV, ADDR_PLAYBACK_1, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_NOT_IN_CORRECT_MODE); - assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort); // uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25] verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt()); @@ -918,12 +933,12 @@ public class HdmiCecLocalDeviceTvTest { mTestLooper.dispatchAll(); // [Not in correct mode] sent - HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( ADDR_TV, ADDR_PLAYBACK_1, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_NOT_IN_CORRECT_MODE); - assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); // AudioManager not notified of volume change verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(), @@ -1081,12 +1096,12 @@ public class HdmiCecLocalDeviceTvTest { mNativeWrapper.onCecMessage(requestArcInitiation); mTestLooper.dispatchAll(); - HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( ADDR_TV, ADDR_AUDIO_SYSTEM, Constants.MESSAGE_INITIATE_ARC, Constants.ABORT_NOT_IN_CORRECT_MODE); - assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); } @Test @@ -1156,4 +1171,337 @@ public class HdmiCecLocalDeviceTvTest { ADDR_AUDIO_SYSTEM); assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated); } + + @Test + public void startArcAction_initiation_noAvr() { + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + } + + @Test + public void startArcAction_initiation_portNotConnected() { + // Emulate Audio device on port 0x2000 (supports ARC) + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + // Emulate port disconnect + mNativeWrapper.setPortConnectionStatus(2, false); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE); + } + + @Test + public void startArcAction_initiation_portDoesNotSupportArc() { + // Emulate Audio device on port 0x1000 (Doesn´t support ARC) + mNativeWrapper.setPortConnectionStatus(1, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE); + } + + @Test + public void startArcAction_initiation_indirectPhysicalAddress() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2320, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE); + } + + @Test + public void startArcAction_initiation_earcBlocksArc() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + mEarcBlocksArc = true; + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE); + } + + @Test + public void startArcAction_initiation_messageNotAcked() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + mNativeWrapper.setMessageSendResult( + Constants.MESSAGE_REQUEST_ARC_INITIATION, SendMessageResult.NACK); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + } + + @Test + public void startArcAction_initiation_timeout() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + mTestLooper.moveTimeForward(TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT); + } + + @Test + public void startArcAction_initiation_featureAbort() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, + ADDR_TV, + Constants.MESSAGE_REQUEST_ARC_INITIATION, + Constants.ABORT_NOT_IN_CORRECT_MODE); + mNativeWrapper.onCecMessage(featureAbort); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + } + + @Test + public void startArcAction_initiation_success() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(true, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + + HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + mNativeWrapper.onCecMessage(initiateArc); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void startArcAction_termination_noAvr() { + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(false, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + } + + @Test + public void startArcAction_termination_portDoesNotSupportArc() { + // Emulate Audio device on port 0x1000 (Doesn´t support ARC) + mNativeWrapper.setPortConnectionStatus(1, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(false, callback); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE); + } + + @Test + public void startArcAction_termination_messageNotAcked() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + mNativeWrapper.setMessageSendResult( + Constants.MESSAGE_REQUEST_ARC_TERMINATION, SendMessageResult.NACK); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(false, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + } + + @Test + public void startArcAction_termination_timeout() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(false, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + mTestLooper.moveTimeForward(TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT); + } + + @Test + public void startArcAction_termination_featureAbort() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(false, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, + ADDR_TV, + Constants.MESSAGE_REQUEST_ARC_TERMINATION, + Constants.ABORT_NOT_IN_CORRECT_MODE); + mNativeWrapper.onCecMessage(featureAbort); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + } + + @Test + public void startArcAction_termination_success() { + // Emulate Audio device on port 0x2000 (Supports ARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceTv.startArcAction(false, callback); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + + HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + mNativeWrapper.onCecMessage(terminateArc); + mTestLooper.dispatchAll(); + + assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } } From c02f2fb278bacf4ff9ab1eb420908698ad5864fc Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Wed, 5 Oct 2022 16:15:07 +0200 Subject: [PATCH 3/5] Update ARC state based on eARC status updates 1. If eARC connection fails: try to initiate ARC. 2. If the eARC HAL moves to the IDLE state: terminate ARC. This is required by the HDMI 2.0 definition, Section 9.5.4.2.5. Test: atest Bug: 262573690 Change-Id: Iefc324ba054ac97fc0790729453cec18d42571cc --- .../server/hdmi/HdmiControlService.java | 9 ++++ .../server/hdmi/HdmiEarcLocalDeviceTx.java | 2 + .../server/hdmi/HdmiControlServiceTest.java | 42 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 36ed5b52039ce..503442e4a5f6f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4468,6 +4468,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private void onEnableEarc() { + // This will terminate ARC as well. initializeEarc(INITIATED_BY_ENABLE_EARC); } @@ -4553,4 +4554,12 @@ public class HdmiControlService extends SystemService { return mEarcLocalDevice.mEarcStatus != HDMI_EARC_STATUS_ARC_PENDING; } } + + protected void startArcAction(boolean enabled, IHdmiControlCallback callback) { + if (!isTvDeviceEnabled()) { + invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); + } else { + tv().startArcAction(enabled, callback); + } + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java index 5caacf6d291a1..bdc30b9f16ad2 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java @@ -87,8 +87,10 @@ public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { mReportCapsHandler.removeCallbacksAndMessages(null); if (status == HDMI_EARC_STATUS_IDLE) { notifyEarcStatusToAudioService(false, new ArrayList<>()); + mService.startArcAction(false, null); } else if (status == HDMI_EARC_STATUS_ARC_PENDING) { notifyEarcStatusToAudioService(false, new ArrayList<>()); + mService.startArcAction(true, null); } else if (status == HDMI_EARC_STATUS_EARC_CONNECTED) { mReportCapsHandler.postDelayed(mReportCapsRunnable, REPORT_CAPS_MAX_DELAY_MS); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index ac361e1cb9fba..b948fd0a590b9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -30,9 +30,11 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -1293,6 +1295,46 @@ public class HdmiControlServiceTest { assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isFalse(); } + @Test + public void earcStatusBecomesIdle_terminateArc() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE); + verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any()); + } + + @Test + public void earcStatusBecomesEnabled_doNothing() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED); + verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any()); + } + + @Test + public void earcStatusBecomesPending_doNothing() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING); + verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any()); + } + + @Test + public void earcStatusBecomesNotEnabled_initiateArc() { + mHdmiControlServiceSpy.mEarcSupported = true; + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING); + verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any()); + } + protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby; From 69c298b0e3228a7ac3e7b4bd40c050292ac9f777 Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Thu, 3 Nov 2022 11:45:00 +0100 Subject: [PATCH 4/5] Terminate ARC before enabling eARC in the HAL This is required by the eARC specification: an ARC connection and an eARC connection cannot exist simultaneously. The HAL can only initiate an eARC connection once the framework has informed it with a setEarcEnabled HAL call. The framework waits until ARC has been terminated to do this. It's enough to terminate ARC before enabling eARC in the HAL: there is no need to terminate it again once the HAL reports that eARC is connected succesfully. Reason: in all situations where the eARC HAL could report that eARC got connected, ARC has been terminated before: 1. On hotplug in: this means that there was a hotplug out before --> eARC is in the IDLE state --> ARC was disconnected when we entered the IDLE state. 2. On boot: Both ARC and eARC are disconnected before boot, and on boot, eARC gets attempted first. 3. On wake-up: ARC was terminated on the last standby. 4. When eARC gets enabled in the HAL: this CL explicitly terminates ARC. Bug: 262573690 Test: atest Change-Id: I91d20fd382c4e4e4102683ace5f2ef55a10e1048 --- .../server/hdmi/HdmiControlService.java | 45 ++++++++++++---- .../server/hdmi/HdmiCecLocalDeviceTvTest.java | 45 ++++++++++++++++ .../server/hdmi/HdmiControlServiceTest.java | 52 +++++++------------ 3 files changed, 98 insertions(+), 44 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 503442e4a5f6f..f66f8ea3ba771 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -393,7 +393,7 @@ public class HdmiControlService extends SystemService { // and the eARC HAL is present. @GuardedBy("mLock") @VisibleForTesting - protected boolean mEarcSupported; + private boolean mEarcSupported; // Set to true while the eARC feature is enabled. @GuardedBy("mLock") @@ -726,7 +726,7 @@ public class HdmiControlService extends SystemService { if (isEarcEnabled()) { initializeEarc(INITIATED_BY_BOOT_UP); } else { - setEarcEnabledInHal(false); + setEarcEnabledInHal(false, false); } } @@ -3522,7 +3522,7 @@ public class HdmiControlService extends SystemService { } initializeEarc(startReason); } else { - setEarcEnabledInHal(false); + setEarcEnabledInHal(false, false); } } // TODO: Initialize MHL local devices. @@ -4418,8 +4418,17 @@ public class HdmiControlService extends SystemService { private void initializeEarc(int initiatedBy) { Slog.i(TAG, "eARC initialized, reason = " + initiatedBy); - setEarcEnabledInHal(true); initializeEarcLocalDevice(initiatedBy); + + if (initiatedBy == INITIATED_BY_ENABLE_EARC) { + // Since ARC and eARC cannot be connected simultaneously, we need to terminate ARC + // before even enabling eARC. + setEarcEnabledInHal(true, true); + } else { + // On boot, wake-up, and hotplug in, eARC will always be attempted before ARC. + // So there is no need to explicitly terminate ARC before enabling eARC. + setEarcEnabledInHal(true, false); + } } @ServiceThreadOnly @@ -4475,7 +4484,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private void onDisableEarc() { disableEarcLocalDevice(); - setEarcEnabledInHal(false); + setEarcEnabledInHal(false, false); clearEarcLocalDevice(); } @@ -4509,12 +4518,28 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly @VisibleForTesting - protected void setEarcEnabledInHal(boolean enabled) { + protected void setEarcEnabledInHal(boolean enabled, boolean terminateArcFirst) { assertRunOnServiceThread(); - mEarcController.setEarcEnabled(enabled); - mCecController.setHpdSignalType( - enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL, - mEarcPortId); + if (terminateArcFirst) { + startArcAction(false, new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) throws RemoteException { + // Independently of the result (i.e. independently of whether the ARC RX device + // responded with or not), we always end up terminating ARC in + // the HAL. As soon as we do that, we can enable eARC in the HAL. + mEarcController.setEarcEnabled(enabled); + mCecController.setHpdSignalType( + enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT + : Constants.HDMI_HPD_TYPE_PHYSICAL, + mEarcPortId); + } + }); + } else { + mEarcController.setEarcEnabled(enabled); + mCecController.setHpdSignalType( + enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL, + mEarcPortId); + } } @ServiceThreadOnly diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index ba44cac2dc08e..5dd29fda794d0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -1504,4 +1504,49 @@ public class HdmiCecLocalDeviceTvTest { assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); } + + @Test + public void enableEarc_terminateArc() { + // Emulate Audio device on port 0x2000 (supports ARC and eARC) + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage reportPhysicalAddress = + HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(reportPhysicalAddress); + mTestLooper.dispatchAll(); + + HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + + mNativeWrapper.onCecMessage(initiateArc); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + // should only be sent after SAD querying is done + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated); + + // Finish querying SADs + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + mNativeWrapper.clearResultMessages(); + + mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED); + mTestLooper.dispatchAll(); + + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index b948fd0a590b9..1c33fb984ca04 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -145,6 +145,7 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.setPowerManager(mPowerManager); mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlServiceSpy.setAudioManager(mAudioManager); + mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); } @@ -1089,7 +1090,6 @@ public class HdmiControlServiceTest { @Test public void disableEarc_clearEarcLocalDevice() { - mHdmiControlServiceSpy.setEarcSupported(true); mHdmiControlServiceSpy.clearEarcLocalDevice(); mHdmiControlServiceSpy.addEarcLocalDevice( new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy)); @@ -1102,7 +1102,6 @@ public class HdmiControlServiceTest { @Test public void disableCec_doNotClearEarcLocalDevice() { - mHdmiControlServiceSpy.setEarcSupported(true); mHdmiControlServiceSpy.clearEarcLocalDevice(); mHdmiControlServiceSpy.addEarcLocalDevice( new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy)); @@ -1115,7 +1114,6 @@ public class HdmiControlServiceTest { @Test public void enableCec_initializeCecLocalDevices() { - mHdmiControlServiceSpy.setEarcSupported(true); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); mTestLooper.dispatchAll(); @@ -1127,7 +1125,6 @@ public class HdmiControlServiceTest { @Test public void enableEarc_initializeEarcLocalDevices() { - mHdmiControlServiceSpy.setEarcSupported(true); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED); mTestLooper.dispatchAll(); @@ -1139,7 +1136,6 @@ public class HdmiControlServiceTest { @Test public void disableCec_DoNotInformHalAboutEarc() { - mHdmiControlServiceSpy.setEarcSupported(true); mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); @@ -1149,7 +1145,7 @@ public class HdmiControlServiceTest { HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean()); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean(), anyBoolean()); } @Test @@ -1157,15 +1153,14 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_ENABLED); - mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_DISABLED); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true); + verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean()); } @Test @@ -1173,14 +1168,13 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean()); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean(), anyBoolean()); } @Test @@ -1188,15 +1182,14 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_DISABLED); - mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_ENABLED); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false); + verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, true); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean()); } @Test @@ -1204,13 +1197,15 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_ENABLED); - mHdmiControlServiceSpy.setEarcSupported(true); + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.initService(); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false); + verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, false); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean()); } @Test @@ -1218,13 +1213,12 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_DISABLED); - mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.initService(); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true); + verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean()); } @Test @@ -1232,13 +1226,12 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_ENABLED); - mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(false); + verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, false); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean()); } @Test @@ -1246,18 +1239,16 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.SETTING_NAME_EARC_ENABLED, HdmiControlManager.EARC_FEATURE_DISABLED); - mHdmiControlServiceSpy.setEarcSupported(true); mTestLooper.dispatchAll(); Mockito.clearInvocations(mHdmiControlServiceSpy); mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON); mTestLooper.dispatchAll(); - verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false); - verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(true); + verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false); + verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean()); } @Test public void earcIdle_blocksArcConnection() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE); @@ -1267,7 +1258,6 @@ public class HdmiControlServiceTest { @Test public void earcPending_blocksArcConnection() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING); @@ -1277,7 +1267,6 @@ public class HdmiControlServiceTest { @Test public void earcEnabled_blocksArcConnection() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED); @@ -1287,7 +1276,6 @@ public class HdmiControlServiceTest { @Test public void arcPending_doesNotBlockArcConnection() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING); @@ -1297,7 +1285,6 @@ public class HdmiControlServiceTest { @Test public void earcStatusBecomesIdle_terminateArc() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); @@ -1307,7 +1294,6 @@ public class HdmiControlServiceTest { @Test public void earcStatusBecomesEnabled_doNothing() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); @@ -1317,7 +1303,6 @@ public class HdmiControlServiceTest { @Test public void earcStatusBecomesPending_doNothing() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); @@ -1327,7 +1312,6 @@ public class HdmiControlServiceTest { @Test public void earcStatusBecomesNotEnabled_initiateArc() { - mHdmiControlServiceSpy.mEarcSupported = true; mHdmiControlServiceSpy.clearEarcLocalDevice(); HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); From ffa600a03a50a665f49699f6405519c02a3a6ad0 Mon Sep 17 00:00:00 2001 From: Nathalie Le Clair Date: Thu, 15 Dec 2022 17:52:17 +0100 Subject: [PATCH 5/5] Terminate ARC when eARC gets enabled on the RX device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the RX device has an active ARC connection and eARC disabled, the user may enable eARC in the RX device's settings, i.e. the Audio System's settings. In this scenario, the RX will initiate an eARC connection and should in theory send to terminate the active ARC connection. Even if the RX doesn´t send this message, the TX should send the RX a request to terminate the active ARC connection with a message. The TX should send this message when the HAL reports an EARC_PENDING or EARC_CONNECTED status after the last status update had been ARC_PENDING. Test: atest Bug: 262573690 Change-Id: I8514bcc0858ba6fa744fe2db4c81bdfbed90de22 --- .../server/hdmi/HdmiEarcLocalDeviceTx.java | 8 +++++++ .../server/hdmi/HdmiControlServiceTest.java | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java index bdc30b9f16ad2..abb8439092acd 100644 --- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java +++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java @@ -78,9 +78,11 @@ public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { } protected void handleEarcStateChange(@Constants.EarcStatus int status) { + int oldEarcStatus; synchronized (mLock) { HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus, status); + oldEarcStatus = mEarcStatus; mEarcStatus = status; } @@ -91,7 +93,13 @@ public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice { } else if (status == HDMI_EARC_STATUS_ARC_PENDING) { notifyEarcStatusToAudioService(false, new ArrayList<>()); mService.startArcAction(true, null); + } else if (status == HDMI_EARC_STATUS_EARC_PENDING + && oldEarcStatus == HDMI_EARC_STATUS_ARC_PENDING) { + mService.startArcAction(false, null); } else if (status == HDMI_EARC_STATUS_EARC_CONNECTED) { + if (oldEarcStatus == HDMI_EARC_STATUS_ARC_PENDING) { + mService.startArcAction(false, null); + } mReportCapsHandler.postDelayed(mReportCapsRunnable, REPORT_CAPS_MAX_DELAY_MS); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 1c33fb984ca04..aa49a6222390f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -1319,6 +1319,28 @@ public class HdmiControlServiceTest { verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any()); } + @Test + public void earcStateWasArcPending_becomesEarcPending_terminateArc() { + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING); + mTestLooper.dispatchAll(); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING); + verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any()); + } + + @Test + public void earcStateWasArcPending_becomesEarcEnabled_terminateArc() { + mHdmiControlServiceSpy.clearEarcLocalDevice(); + HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy); + mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING); + mTestLooper.dispatchAll(); + localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED); + verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any()); + } + protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby;