From 753fe4ea909dd0f5fd91334b3ddf005cabc3a00d Mon Sep 17 00:00:00 2001 From: Paul Colta Date: Mon, 28 Nov 2022 13:54:07 +0100 Subject: [PATCH] HDMI: More reliable DSM setting disabling Wait for ArcTerminationActionFromAvr to finish before giving up the LA 5 during Dynamic Soundbar mode disabling process. Add callback inside ArcTerminationActionFromAvr. Transfer the callback in HdmiCecLocalDeviceAudioSystem#handleRequestArcTermination so it doesn't get lost. Callback is not transfered in the following situation, in HdmiCecLocalDeviceAudioSystem#initArcOnFromAvr ARC termination is interrupted by ARC initialization from AVR. This scenario would occur if user toggles DSM off then on before ARC termination process is completed, then the local device audio system tries establishing ARC connection when logical address allocation finished. In this case, DSM would be enabled and the callback from ARC termination action that retriggers logical address allocation wouldn't be necessary. Bug: 260322563 Test: make && atest com.android.server.hdmi.HdmiControlServiceTest Change-Id: Ib0a8b3a7da79da8cf3f9724a685a55c80760a755 --- .../hdmi/ArcTerminationActionFromAvr.java | 21 +++++++-- .../hdmi/HdmiCecLocalDeviceAudioSystem.java | 12 ++++- .../server/hdmi/HdmiControlService.java | 24 ++++++++-- .../hdmi/ArcTerminationActionFromAvrTest.java | 46 ++++++++++++++++++- .../HdmiCecLocalDeviceAudioSystemTest.java | 28 +++++++++++ .../server/hdmi/HdmiControlServiceTest.java | 6 +++ 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java index 4855be68e6761..ccb263362abbb 100644 --- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java @@ -15,6 +15,8 @@ */ package com.android.server.hdmi; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; /** @@ -33,6 +35,10 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction { super(source); } + ArcTerminationActionFromAvr(HdmiCecLocalDevice source, IHdmiControlCallback callback) { + super(source, callback); + } + @Override boolean start() { mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE; @@ -47,10 +53,19 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction { return false; } switch (cmd.getOpcode()) { + case Constants.MESSAGE_FEATURE_ABORT: + int originalOpcode = cmd.getParams()[0] & 0xFF; + if (originalOpcode == Constants.MESSAGE_TERMINATE_ARC) { + mState = STATE_ARC_TERMINATED; + audioSystem().processArcTermination(); + finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + return true; + } + return false; case Constants.MESSAGE_REPORT_ARC_TERMINATED: mState = STATE_ARC_TERMINATED; audioSystem().processArcTermination(); - finish(); + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); return true; } return false; @@ -79,7 +94,7 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction { audioSystem().setArcStatus(false); } HdmiLogger.debug("Terminate ARC was not successfully sent."); - finish(); + finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); } }); } @@ -88,6 +103,6 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction { // Disable ARC if TV didn't respond with in time. audioSystem().setArcStatus(false); HdmiLogger.debug("handleTerminateArcTimeout"); - finish(); + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index ccaa9255dbd44..a026c4b59ec5d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -458,8 +458,16 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { HdmiLogger.debug("ARC is not established between TV and AVR device"); return Constants.ABORT_NOT_IN_CORRECT_MODE; } else { - removeAction(ArcTerminationActionFromAvr.class); - addAndStartAction(new ArcTerminationActionFromAvr(this)); + if (!getActions(ArcTerminationActionFromAvr.class).isEmpty() + && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) { + IHdmiControlCallback callback = + getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0); + removeAction(ArcTerminationActionFromAvr.class); + addAndStartAction(new ArcTerminationActionFromAvr(this, callback)); + } else { + removeAction(ArcTerminationActionFromAvr.class); + addAndStartAction(new ArcTerminationActionFromAvr(this)); + } return Constants.HANDLED; } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 43cd71ae519c2..2f15e579b2149 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -878,16 +878,30 @@ public class HdmiControlService extends SystemService { Slog.w(TAG, "Device type doesn't support ARC."); return; } + boolean isArcEnabled = false; if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) { - if (audioSystem.isArcEnabled()) { - audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem)); - } + isArcEnabled = audioSystem.isArcEnabled(); if (isSystemAudioActivated()) { audioSystem.terminateSystemAudioMode(); } + if (isArcEnabled) { + if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) { + audioSystem.removeAction(ArcTerminationActionFromAvr.class); + } + audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem, + new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + mAddressAllocated = false; + initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE); + } + })); + } + } + if (!isArcEnabled) { + mAddressAllocated = false; + initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE); } - mAddressAllocated = false; - initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE); } /** diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 09cd47a99cba4..2cb46da9d2073 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -24,6 +24,8 @@ import static org.mockito.Mockito.spy; import android.content.Context; import android.content.ContextWrapper; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.media.AudioManager; import android.os.Looper; @@ -52,6 +54,7 @@ public class ArcTerminationActionFromAvrTest { private Context mContextSpy; private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; private FakePowerManagerWrapper mPowerManager; + private TestCallback mCallback; private ArcTerminationActionFromAvr mAction; private FakeNativeWrapper mNativeWrapper; @@ -112,7 +115,9 @@ public class ArcTerminationActionFromAvrTest { } }; mHdmiCecLocalDeviceAudioSystem.init(); - mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); + mCallback = new TestCallback(); + mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, + mCallback); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); @@ -121,6 +126,20 @@ public class ArcTerminationActionFromAvrTest { mTestLooper.dispatchAll(); } + 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 testSendMessage_sendFailed() { mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC, @@ -133,6 +152,7 @@ public class ArcTerminationActionFromAvrTest { assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc); assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); + assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); } @Test @@ -149,6 +169,7 @@ public class ArcTerminationActionFromAvrTest { mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); + assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT); } @Test @@ -167,5 +188,28 @@ public class ArcTerminationActionFromAvrTest { mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); + assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void testReportArcTerminated_featureAbort() { + mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc( + Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV); + + assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc); + + HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_TV, + Constants.ADDR_AUDIO_SYSTEM, + Constants.MESSAGE_TERMINATE_ARC, + Constants.ABORT_REFUSED); + + mNativeWrapper.onCecMessage(arcTerminatedResponse); + mTestLooper.dispatchAll(); + + assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); + assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 7c6c990500f3f..de2c2181d3abc 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -534,6 +534,25 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } + @Test + public void handleRequestArcTerminate_callbackIsPreserved() throws Exception { + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); + assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue(); + mHdmiCecLocalDeviceAudioSystem.addAndStartAction( + new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback)); + + HdmiCecMessage message = + HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isEqualTo(Constants.HANDLED); + + mTestLooper.dispatchAll(); + assertThat(mHdmiCecLocalDeviceAudioSystem.getActions( + ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback); + } + @Test public void handleRequestArcInit_arcIsNotSupported() throws Exception { HdmiCecMessage message = @@ -880,4 +899,13 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain( systemAudioModeRequest_fromAudioSystem); } + + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList mCallbackResult = new ArrayList(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + } } 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 ef2b21211297c..49a0a9a52c47d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -1036,6 +1036,7 @@ public class HdmiControlServiceTest { @Test public void setSoundbarMode_enabled_addAudioSystemLocalDevice() { + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); // Initialize the local devices excluding the audio system. mHdmiControlServiceSpy.clearCecLocalDevices(); mLocalDevices.remove(mAudioSystemDeviceSpy); @@ -1053,6 +1054,7 @@ public class HdmiControlServiceTest { @Test public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() { + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); // Initialize the local devices excluding the audio system. mHdmiControlServiceSpy.clearCecLocalDevices(); mLocalDevices.remove(mAudioSystemDeviceSpy); @@ -1073,6 +1075,10 @@ public class HdmiControlServiceTest { HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE, HdmiControlManager.SOUNDBAR_MODE_DISABLED); mTestLooper.dispatchAll(); + + // Wait for ArcTerminationActionFromAvr timeout for the logical address allocation to start. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); assertThat(mHdmiControlServiceSpy.audioSystem()).isNull(); }