From 6613fd3fbba8bbd4315ce71d5cc530ad036d5ca9 Mon Sep 17 00:00:00 2001 From: Yan Han Date: Tue, 29 Mar 2022 15:25:08 +0200 Subject: [PATCH 1/4] Add wrapper for AudioDeviceVolumeManager in the HDMI framework Adding a wrapper interface for AudioDeviceVolumeManager allows the class to be faked. This allows tests that instantiate HdmiControlService to avoid also instantiating AudioDeviceVolumeManager, which can affect the state of AudioService on the DUT. Using an interface instead of inheritance allows constructors to be more independent, and allows the same class to "wrap" multiple managers in the future if they need to share state (e.g. AudioManager and AudioDeviceVolumeManager). Bug: 217923086 Test: atest com.android.server.hdmi Change-Id: I673ab6e3a6acbbc6af8998e2f5c048fec59ad47a --- .../hdmi/AudioDeviceVolumeManagerWrapper.java | 67 +++++++++++++++++ ...ioDeviceVolumeManagerWrapperInterface.java | 61 ++++++++++++++++ .../server/hdmi/HdmiControlService.java | 45 +++++++++--- .../server/hdmi/ActiveSourceActionTest.java | 3 +- .../hdmi/ArcInitiationActionFromAvrTest.java | 3 +- .../hdmi/ArcTerminationActionFromAvrTest.java | 3 +- ...ectTvSystemAudioModeSupportActionTest.java | 2 +- .../hdmi/DevicePowerStatusActionTest.java | 3 +- .../DeviceSelectActionFromPlaybackTest.java | 2 +- .../hdmi/DeviceSelectActionFromTvTest.java | 2 +- .../FakeAudioDeviceVolumeManagerWrapper.java | 72 +++++++++++++++++++ .../server/hdmi/HdmiCecAtomLoggingTest.java | 3 +- .../server/hdmi/HdmiCecControllerTest.java | 3 +- .../HdmiCecLocalDeviceAudioSystemTest.java | 2 +- .../hdmi/HdmiCecLocalDevicePlaybackTest.java | 2 +- .../server/hdmi/HdmiCecLocalDeviceTest.java | 3 +- .../server/hdmi/HdmiCecLocalDeviceTvTest.java | 2 +- .../hdmi/HdmiCecMessageValidatorTest.java | 3 +- .../server/hdmi/HdmiCecNetworkTest.java | 3 +- .../HdmiCecPowerStatusControllerTest.java | 3 +- .../server/hdmi/HdmiControlServiceTest.java | 3 +- .../server/hdmi/OneTouchPlayActionTest.java | 3 +- .../hdmi/PowerStatusMonitorActionTest.java | 3 +- .../server/hdmi/RequestSadActionTest.java | 4 +- .../server/hdmi/RoutingControlActionTest.java | 2 +- ...etAudioVolumeLevelDiscoveryActionTest.java | 3 +- .../SystemAudioAutoInitiationActionTest.java | 3 +- ...ystemAudioInitiationActionFromAvrTest.java | 2 +- 28 files changed, 275 insertions(+), 35 deletions(-) create mode 100644 services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java create mode 100644 services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java new file mode 100644 index 0000000000000..438c1ea01e29f --- /dev/null +++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceVolumeManager; +import android.media.VolumeInfo; + +import java.util.concurrent.Executor; + +/** + * Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly + * passes method calls to that instance. + */ +public class AudioDeviceVolumeManagerWrapper + implements AudioDeviceVolumeManagerWrapperInterface { + + private static final String TAG = "AudioDeviceVolumeManagerWrapper"; + + private final AudioDeviceVolumeManager mAudioDeviceVolumeManager; + + public AudioDeviceVolumeManagerWrapper(Context context) { + mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context); + } + + @Override + public void addOnDeviceVolumeBehaviorChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) + throws SecurityException { + mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener); + } + + @Override + public void removeOnDeviceVolumeBehaviorChangedListener( + @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) { + mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener); + } + + @Override + public void setDeviceAbsoluteVolumeBehavior( + @NonNull AudioDeviceAttributes device, + @NonNull VolumeInfo volume, + @NonNull @CallbackExecutor Executor executor, + @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener, + boolean handlesVolumeAdjustment) { + mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor, + vclistener, handlesVolumeAdjustment); + } +} diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java new file mode 100644 index 0000000000000..1a1d4c19358be --- /dev/null +++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener; +import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceVolumeManager; +import android.media.VolumeInfo; + +import java.util.concurrent.Executor; + +/** + * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework. + * Allows the class to be faked for tests. + */ +public interface AudioDeviceVolumeManagerWrapperInterface { + + /** + * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener( + * Executor, OnDeviceVolumeBehaviorChangedListener)} + */ + void addOnDeviceVolumeBehaviorChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener); + + /** + * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener( + * OnDeviceVolumeBehaviorChangedListener)} + */ + void removeOnDeviceVolumeBehaviorChangedListener( + @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener); + + /** + * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior( + * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)} + */ + void setDeviceAbsoluteVolumeBehavior( + @NonNull AudioDeviceAttributes device, + @NonNull VolumeInfo volume, + @NonNull @CallbackExecutor Executor executor, + @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener, + boolean handlesVolumeAdjustment); +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 12380abd0d380..65530e2343245 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -410,6 +410,9 @@ public class HdmiControlService extends SystemService { @Nullable private PowerManagerInternalWrapper mPowerManagerInternal; + @Nullable + private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager; + @Nullable private Looper mIoLooper; @@ -439,11 +442,21 @@ public class HdmiControlService extends SystemService { private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer(); - @VisibleForTesting HdmiControlService(Context context, List deviceTypes) { + /** + * Constructor for testing. + * + * It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated + * AudioDeviceVolumeManager can access the "real" AudioService on the DUT. + * + * @see FakeAudioDeviceVolumeManagerWrapper + */ + @VisibleForTesting HdmiControlService(Context context, List deviceTypes, + AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) { super(context); mLocalDevices = deviceTypes; mSettingsObserver = new SettingsObserver(mHandler); mHdmiCecConfig = new HdmiCecConfig(context); + mAudioDeviceVolumeManager = audioDeviceVolumeManager; } public HdmiControlService(Context context) { @@ -744,6 +757,8 @@ public class HdmiControlService extends SystemService { Context.TV_INPUT_SERVICE); mPowerManager = new PowerManagerWrapper(getContext()); mPowerManagerInternal = new PowerManagerInternalWrapper(); + mAudioDeviceVolumeManager = + new AudioDeviceVolumeManagerWrapper(getContext()); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { runOnServiceThread(this::bootCompleted); } @@ -3086,15 +3101,17 @@ public class HdmiControlService extends SystemService { private void announceHdmiCecVolumeControlFeatureChange( @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) { assertRunOnServiceThread(); - mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> { - try { - listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl); - } catch (RemoteException e) { - Slog.e(TAG, - "Failed to report HdmiControlVolumeControlStatusChange: " - + hdmiCecVolumeControl); - } - }); + synchronized (mLock) { + mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> { + try { + listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl); + } catch (RemoteException e) { + Slog.e(TAG, + "Failed to report HdmiControlVolumeControlStatusChange: " + + hdmiCecVolumeControl); + } + }); + } } public HdmiCecLocalDeviceTv tv() { @@ -3135,6 +3152,14 @@ public class HdmiControlService extends SystemService { return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); } + /** + * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. + */ + @Nullable + private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() { + return mAudioDeviceVolumeManager; + } + boolean isControlEnabled() { synchronized (mLock) { return mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index 18f264277b41d..3fc7918627123 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -61,7 +61,8 @@ public class ActiveSourceActionTest { public void setUp() throws Exception { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); - mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) { + mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index e4c5ad6769d75..e4eecc6f6fa05 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -68,7 +68,8 @@ public class ArcInitiationActionFromAvrTest { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); HdmiControlService hdmiControlService = - new HdmiControlService(mContextSpy, Collections.emptyList()) { + new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isPowerStandby() { return false; 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 d73cdb5f53b0c..5b114661fae89 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -68,7 +68,8 @@ public class ArcTerminationActionFromAvrTest { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); HdmiControlService hdmiControlService = - new HdmiControlService(mContextSpy, Collections.emptyList()) { + new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return mAudioManager; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 5cec8ad1e63d3..28ba4bb503f91 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -56,7 +56,7 @@ public class DetectTvSystemAudioModeSupportActionTest { mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234); HdmiControlService hdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override void sendCecCommand( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 52a0b6cdc2beb..59e7501f6059a 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -78,7 +78,8 @@ public class DevicePowerStatusActionTest { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); - mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) { + mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index 35432edfcf16c..a1263307e79f9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -100,7 +100,7 @@ public class DeviceSelectActionFromPlaybackTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isControlEnabled() { return true; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index e77cd91b46d84..26aa83fdf13f9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -109,7 +109,7 @@ public class DeviceSelectActionFromTvTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isControlEnabled() { return true; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java new file mode 100644 index 0000000000000..d33ef9bc88799 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener; +import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceVolumeManager; +import android.media.AudioManager; +import android.media.VolumeInfo; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executor; + +/** + * Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing. + */ +public class FakeAudioDeviceVolumeManagerWrapper implements + AudioDeviceVolumeManagerWrapperInterface { + + private final Set mVolumeBehaviorListeners; + + public FakeAudioDeviceVolumeManagerWrapper() { + mVolumeBehaviorListeners = new HashSet<>(); + } + + @Override + public void addOnDeviceVolumeBehaviorChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnDeviceVolumeBehaviorChangedListener listener) + throws SecurityException { + mVolumeBehaviorListeners.add(listener); + } + + @Override + public void removeOnDeviceVolumeBehaviorChangedListener( + @NonNull OnDeviceVolumeBehaviorChangedListener listener) { + mVolumeBehaviorListeners.remove(listener); + } + + @Override + public void setDeviceAbsoluteVolumeBehavior( + @NonNull AudioDeviceAttributes device, + @NonNull VolumeInfo volume, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnAudioDeviceVolumeChangedListener vclistener, + boolean handlesVolumeAdjustment) { + // Notify all volume behavior listeners that the device adopted absolute volume behavior + for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) { + listener.onDeviceVolumeBehaviorChanged(device, + AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java index 30bcc7e8afa10..9f744f9373ed0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java @@ -89,7 +89,8 @@ public class HdmiCecAtomLoggingTest { mContextSpy = spy(new ContextWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext())); - mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index 2dcc449e36a56..0cba10669c858 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -101,7 +101,8 @@ public class HdmiCecControllerTest { mMyLooper = mTestLooper.getLooper(); mHdmiControlServiceSpy = spy(new HdmiControlService( - InstrumentationRegistry.getTargetContext(), Collections.emptyList())); + InstrumentationRegistry.getTargetContext(), Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper())); doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper(); doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper(); doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion(); 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 70bc460411c86..91d265c810839 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -88,7 +88,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 86130daf4aac7..74761d8a97e8f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -92,7 +92,7 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override void wakeUp() { mWokenUp = true; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index fb8baa30e6b4d..f3867ec5dfc41 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -137,7 +137,8 @@ public class HdmiCecLocalDeviceTest { Context context = InstrumentationRegistry.getTargetContext(); mHdmiControlService = - new HdmiControlService(context, Collections.emptyList()) { + new HdmiControlService(context, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isControlEnabled() { return isControlEnabled; 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 df4aa5dac9df3..3ccae1ce28f52 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -125,7 +125,7 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override void wakeUp() { mWokenUp = true; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 50c9f70ccb035..a446e109c9214 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -51,7 +51,8 @@ public class HdmiCecMessageValidatorTest { @Before public void setUp() throws Exception { HdmiControlService mHdmiControlService = new HdmiControlService( - InstrumentationRegistry.getTargetContext(), Collections.emptyList()); + InstrumentationRegistry.getTargetContext(), Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()); mHdmiControlService.setIoLooper(mTestLooper.getLooper()); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java index 03532ae1cb1fc..b8a1ba3633735 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java @@ -67,7 +67,8 @@ public class HdmiCecNetworkTest { @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); - mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) { + mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { mDeviceEventListenerStatuses.add(status); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index 7a68285bc0037..0c508632cc954 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -65,7 +65,8 @@ public class HdmiCecPowerStatusControllerTest { Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); Looper myLooper = mTestLooper.getLooper(); - mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) { + mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isControlEnabled() { return true; 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 3987c32277cd4..6266571d33d45 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -91,7 +91,8 @@ public class HdmiControlServiceTest { HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index 561e6a5fec410..bd552c4b567aa 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -87,7 +87,8 @@ public class OneTouchPlayActionTest { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) { + mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index c878f99f79129..40b413f85a842 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -68,7 +68,8 @@ public class PowerStatusMonitorActionTest { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); mHdmiControlService = new HdmiControlService(mContextSpy, - Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) { + Collections.singletonList(HdmiDeviceInfo.DEVICE_TV), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 6184c2116e1d8..2b2f537adcdb4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -96,8 +96,8 @@ public class RequestSadActionTest { mMyLooper = mTestLooper.getLooper(); mHdmiControlService = - new HdmiControlService(context, - Collections.emptyList()) { + new HdmiControlService(context, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isControlEnabled() { return true; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java index 0587864eeb20a..14af7d95b95da 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -149,7 +149,7 @@ public class RoutingControlActionTest { mHdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override boolean isControlEnabled() { return true; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java index a34b55c003085..0484bd14b6f85 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -81,7 +81,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest { mContextSpy = spy(new ContextWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext())); - mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index 9d143418fd4cc..356539c3a1743 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -69,7 +69,8 @@ public class SystemAudioAutoInitiationActionTest { Looper myLooper = mTestLooper.getLooper(); - mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) { + mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(), + new FakeAudioDeviceVolumeManagerWrapper()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index 095c69c776a20..c2f706ad1220f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -69,7 +69,7 @@ public class SystemAudioInitiationActionFromAvrTest { Context context = InstrumentationRegistry.getTargetContext(); HdmiControlService hdmiControlService = new HdmiControlService(context, - Collections.emptyList()) { + Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) { @Override void sendCecCommand( HdmiCecMessage command, @Nullable SendMessageCallback callback) { From 24da93d99b18d6ce3a2501118d2cfd7203e8fbaf Mon Sep 17 00:00:00 2001 From: Yan Han Date: Tue, 29 Mar 2022 18:39:03 +0200 Subject: [PATCH 2/4] Add call to onBootPhase to tests that use HdmiControlService Some system services are accessed and cached in a call to HdmiControlService#onBootPhase(SystemService#SYSTEM_SERVICES_READY). Since a lot of the code in the HDMI framework accesses other system services, tests that use HdmiControlService may cause null pointer exceptions as the codebase changes. This CL proactively adds this call to avoid future breakages. Test: atest com.android.server.hdmi Bug: 205817707 Change-Id: I684d188e563ff9089deacd1009f511e50a08f255 --- .../src/com/android/server/hdmi/ActiveSourceActionTest.java | 2 +- .../com/android/server/hdmi/DevicePowerStatusActionTest.java | 2 ++ .../android/server/hdmi/DeviceSelectActionFromPlaybackTest.java | 2 ++ .../com/android/server/hdmi/DeviceSelectActionFromTvTest.java | 2 ++ .../com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java | 2 ++ .../src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java | 2 ++ .../src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java | 2 ++ .../android/server/hdmi/HdmiCecPowerStatusControllerTest.java | 2 ++ .../src/com/android/server/hdmi/OneTouchPlayActionTest.java | 2 ++ .../com/android/server/hdmi/PowerStatusMonitorActionTest.java | 2 ++ .../src/com/android/server/hdmi/RequestSadActionTest.java | 2 ++ .../src/com/android/server/hdmi/RoutingControlActionTest.java | 2 ++ .../server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java | 2 ++ .../server/hdmi/SystemAudioAutoInitiationActionTest.java | 2 ++ 14 files changed, 27 insertions(+), 1 deletion(-) diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index 3fc7918627123..112db7612f295 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -94,6 +94,7 @@ public class ActiveSourceActionTest { mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); mPhysicalAddress = 0x2000; @@ -154,7 +155,6 @@ public class ActiveSourceActionTest { mHdmiControlService); audioDevice.init(); mLocalDevices.add(audioDevice); - mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 59e7501f6059a..545f3183ee260 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; @@ -111,6 +112,7 @@ public class DevicePowerStatusActionTest { mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); mPhysicalAddress = 0x2000; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index a1263307e79f9..d7fef90456ab6 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON; import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY; import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3; @@ -136,6 +137,7 @@ public class DeviceSelectActionFromPlaybackTest { mLocalDevices.add(mHdmiCecLocalDevicePlayback); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mNativeWrapper.setPhysicalAddress(0x0000); mPowerManager = new FakePowerManagerWrapper(context); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index 26aa83fdf13f9..72d36b00d73da 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON; import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY; import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_TV; @@ -145,6 +146,7 @@ public class DeviceSelectActionFromTvTest { true, false, false); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 74761d8a97e8f..484b5a8dae1ad 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; @@ -151,6 +152,7 @@ public class HdmiCecLocalDevicePlaybackTest { mNativeWrapper.setPortInfo(hdmiPortInfos); mNativeWrapper.setPortConnectionStatus(1, true); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index f3867ec5dfc41..48e70fe4a60ff 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TV; @@ -191,6 +192,7 @@ public class HdmiCecLocalDeviceTest { mNativeWrapper.setPortInfo(hdmiPortInfos); mNativeWrapper.setPortConnectionStatus(1, true); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mNativeWrapper.setPhysicalAddress(0x2000); mTestLooper.dispatchAll(); 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 3ccae1ce28f52..2554118306769 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; @@ -179,6 +180,7 @@ public class HdmiCecLocalDeviceTvTest { new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index 0c508632cc954..b94deeddb8afd 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -106,6 +107,7 @@ public class HdmiCecPowerStatusControllerTest { mNativeWrapper.setPortInfo(hdmiPortInfos); mNativeWrapper.setPortConnectionStatus(1, true); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(contextSpy); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.getHdmiCecNetwork().initPortInfo(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index bd552c4b567aa..46a4e862c3002 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS; @@ -121,6 +122,7 @@ public class OneTouchPlayActionTest { mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); mPhysicalAddress = 0x2000; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index 40b413f85a842..e5058bea8d1b2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; @@ -111,6 +112,7 @@ public class PowerStatusMonitorActionTest { new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false); mNativeWrapper.setPortInfo(hdmiPortInfo); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); mPhysicalAddress = 0x0000; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 2b2f537adcdb4..f7983ca218162 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; @@ -125,6 +126,7 @@ public class RequestSadActionTest { mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java index 14af7d95b95da..566a7e0fecce8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; @@ -186,6 +187,7 @@ public class RoutingControlActionTest { true, false, false); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java index 0484bd14b6f85..087e407e314cd 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -20,6 +20,7 @@ import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -99,6 +100,7 @@ public class SetAudioVolumeLevelDiscoveryActionTest { mHdmiControlServiceSpy.setHdmiMhlController( HdmiMhlControllerStub.create(mHdmiControlServiceSpy)); mHdmiControlServiceSpy.initService(); + mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlServiceSpy.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index 356539c3a1743..1644252e57399 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT; @@ -109,6 +110,7 @@ public class SystemAudioAutoInitiationActionTest { new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true); mNativeWrapper.setPortInfo(hdmiPortInfos); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); From 8f18957e4c017ca86e86fff84f5fdc8b79235878 Mon Sep 17 00:00:00 2001 From: Yan Han Date: Tue, 8 Feb 2022 10:59:13 +0100 Subject: [PATCH 3/4] Implement support for Absolute Volume Control - Adds logic for enabling or disabling AVC. This is checked whenever one of the necessary conditions for AVC changes. (HdmiControlService#checkAbsoluteVolumeConditions) - Adds code for listening for audio status changes from AudioService when using AVC (HdmiControlService#AbsoluteVolumeChangedListener). - Adds code for notifying AudioService of volume changes on the System Audio device (HdmiControlService#notifyAvcVolumeChange, HdmiControlService#notifyAvcMuteChange) - Adds action for querying and monitoring the audio status of the System Audio device (AbsoluteVolumeAudioStatusAction). - Removes SystemAudioStatusAction: it is no longer needed because we use full volume when in System Audio Mode, and don't track audio status. Test: atest PlaybackDeviceToAudioSystemAvcTest PlaybackDeviceToTvAvcTest TvToAudioSystemAvcTest Bug: 205817429 Change-Id: I4d7772de851b12085bb2e9844a41d52245ee1d4a --- .../hdmi/AbsoluteVolumeAudioStatusAction.java | 103 ++++ .../com/android/server/hdmi/AudioStatus.java | 67 ++ .../com/android/server/hdmi/Constants.java | 3 +- .../server/hdmi/HdmiCecLocalDevice.java | 54 ++ .../server/hdmi/HdmiCecLocalDeviceSource.java | 1 + .../server/hdmi/HdmiCecLocalDeviceTv.java | 9 +- .../android/server/hdmi/HdmiCecMessage.java | 2 + .../android/server/hdmi/HdmiCecNetwork.java | 7 +- .../server/hdmi/HdmiControlService.java | 386 +++++++++++- .../android/server/hdmi/SendKeyAction.java | 15 +- .../SetAudioVolumeLevelDiscoveryAction.java | 7 + .../server/hdmi/SystemAudioAction.java | 7 +- .../server/hdmi/SystemAudioActionFromAvr.java | 4 +- .../server/hdmi/SystemAudioStatusAction.java | 112 ---- .../hdmi/BaseAbsoluteVolumeControlTest.java | 570 ++++++++++++++++++ .../PlaybackDeviceToAudioSystemAvcTest.java | 107 ++++ .../hdmi/PlaybackDeviceToTvAvcTest.java | 110 ++++ .../server/hdmi/TvToAudioSystemAvcTest.java | 66 ++ 18 files changed, 1502 insertions(+), 128 deletions(-) create mode 100644 services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java create mode 100644 services/core/java/com/android/server/hdmi/AudioStatus.java delete mode 100644 services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java create mode 100644 services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java new file mode 100644 index 0000000000000..d7563e085e614 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +/** + * Action to query and track the audio status of the System Audio device when enabling or using + * Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions: + * 1. When enabling AVC: queries the starting audio status of the System Audio device and + * enables the feature upon receiving a response. + * 2. While AVC is enabled: monitors messages from the System Audio device and + * notifies AudioService if the audio status changes. + */ +final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction { + private static final String TAG = "AbsoluteVolumeAudioStatusAction"; + + private int mInitialAudioStatusRetriesLeft = 2; + + private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1; + private static final int STATE_MONITOR_AUDIO_STATUS = 2; + + private final int mTargetAddress; + + private AudioStatus mLastAudioStatus; + + AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) { + super(source); + mTargetAddress = targetAddress; + } + + @Override + boolean start() { + mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS; + sendGiveAudioStatus(); + return true; + } + + void updateVolume(int volumeIndex) { + mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute()); + } + + private void sendGiveAudioStatus() { + addTimer(mState, HdmiConfig.TIMEOUT_MS); + sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress)); + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + switch (cmd.getOpcode()) { + case Constants.MESSAGE_REPORT_AUDIO_STATUS: + return handleReportAudioStatus(cmd); + } + + return false; + } + + private boolean handleReportAudioStatus(HdmiCecMessage cmd) { + if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) { + return false; + } + + boolean mute = HdmiUtils.isAudioStatusMute(cmd); + int volume = HdmiUtils.getAudioStatusVolume(cmd); + AudioStatus audioStatus = new AudioStatus(volume, mute); + if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) { + localDevice().getService().enableAbsoluteVolumeControl(audioStatus); + mState = STATE_MONITOR_AUDIO_STATUS; + } else if (mState == STATE_MONITOR_AUDIO_STATUS) { + if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) { + localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume()); + } + if (audioStatus.getMute() != mLastAudioStatus.getMute()) { + localDevice().getService().notifyAvcMuteChange(audioStatus.getMute()); + } + } + mLastAudioStatus = audioStatus; + + return true; + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } else if (mInitialAudioStatusRetriesLeft > 0) { + mInitialAudioStatusRetriesLeft--; + sendGiveAudioStatus(); + } + } +} diff --git a/services/core/java/com/android/server/hdmi/AudioStatus.java b/services/core/java/com/android/server/hdmi/AudioStatus.java new file mode 100644 index 0000000000000..a884ffb93a6dc --- /dev/null +++ b/services/core/java/com/android/server/hdmi/AudioStatus.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.annotation.Nullable; + +import java.util.Objects; + +/** + * Immutable representation of the information in the [Audio Status] operand: + * volume status (0 <= N <= 100) and mute status (muted or unmuted). + */ +public class AudioStatus { + public static final int MAX_VOLUME = 100; + public static final int MIN_VOLUME = 0; + + int mVolume; + boolean mMute; + + public AudioStatus(int volume, boolean mute) { + mVolume = volume; + mMute = mute; + } + + public int getVolume() { + return mVolume; + } + + public boolean getMute() { + return mMute; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof AudioStatus)) { + return false; + } + + AudioStatus other = (AudioStatus) obj; + return mVolume == other.mVolume + && mMute == other.mMute; + } + + @Override + public int hashCode() { + return Objects.hash(mVolume, mMute); + } + + @Override + public String toString() { + return "AudioStatus mVolume:" + mVolume + " mMute:" + mMute; + } +} diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 751f2db995280..157057d0e4b72 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -118,6 +118,7 @@ final class Constants { MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, MESSAGE_GIVE_AUDIO_STATUS, MESSAGE_SET_SYSTEM_AUDIO_MODE, + MESSAGE_SET_AUDIO_VOLUME_LEVEL, MESSAGE_REPORT_AUDIO_STATUS, MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS, MESSAGE_SYSTEM_AUDIO_MODE_STATUS, @@ -197,9 +198,9 @@ final class Constants { static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70; static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71; static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72; + static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73; static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A; static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D; - static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73; static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E; static final int MESSAGE_ROUTING_CHANGE = 0x80; static final int MESSAGE_ROUTING_INFORMATION = 0x81; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 26a161323e4fc..8acec128245b4 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -1002,6 +1002,57 @@ abstract class HdmiCecLocalDevice { action.start(); } + void addAvcAudioStatusAction(int targetAddress) { + if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) { + addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress)); + } + } + + void removeAvcAudioStatusAction() { + removeAction(AbsoluteVolumeAudioStatusAction.class); + } + + void updateAvcVolume(int volumeIndex) { + for (AbsoluteVolumeAudioStatusAction action : + getActions(AbsoluteVolumeAudioStatusAction.class)) { + action.updateVolume(volumeIndex); + } + } + + /** + * Determines whether {@code targetAddress} supports . Does two things + * in parallel: send (to get in response), + * and send (to see if it gets a in response). + */ + @ServiceThreadOnly + void queryAvcSupport(int targetAddress) { + assertRunOnServiceThread(); + + // Send if using CEC 2.0 or above. + if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { + synchronized (mLock) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures( + getDeviceInfo().getLogicalAddress(), targetAddress)); + } + } + + // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target + // device, start one. + List savlDiscoveryActions = + getActions(SetAudioVolumeLevelDiscoveryAction.class); + if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) { + addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress, + new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + if (result == HdmiControlManager.RESULT_SUCCESS) { + getService().checkAndUpdateAbsoluteVolumeControlState(); + } + } + })); + } + } + @ServiceThreadOnly void startQueuedActions() { assertRunOnServiceThread(); @@ -1205,6 +1256,9 @@ abstract class HdmiCecLocalDevice { */ protected void disableDevice( boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { + removeAction(AbsoluteVolumeAudioStatusAction.class); + removeAction(SetAudioVolumeLevelDiscoveryAction.class); + mPendingActionClearedCallback = new PendingActionClearedCallback() { @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 90b4f76fec106..c0c02027a7a1c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -307,6 +307,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { removeAction(OneTouchPlayAction.class); removeAction(DevicePowerStatusAction.class); + removeAction(AbsoluteVolumeAudioStatusAction.class); super.disableDevice(initiatedByCec, callback); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 9212fb6047212..2fae6dcc21c69 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1202,6 +1202,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } + /** + * Returns the audio output device used for System Audio Mode. + */ + AudioDeviceAttributes getSystemAudioOutputDevice() { + return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC; + } + @ServiceThreadOnly void handleRemoveActiveRoutingPath(int path) { @@ -1296,6 +1303,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(OneTouchRecordAction.class); removeAction(TimerRecordingAction.class); removeAction(NewDeviceAction.class); + removeAction(AbsoluteVolumeAudioStatusAction.class); disableSystemAudioIfExist(); disableArcIfExist(); @@ -1318,7 +1326,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(SystemAudioActionFromAvr.class); removeAction(SystemAudioActionFromTv.class); removeAction(SystemAudioAutoInitiationAction.class); - removeAction(SystemAudioStatusAction.class); removeAction(VolumeControlAction.class); if (!mService.isControlEnabled()) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java index 290cae50f2cb2..2b84225e7c55e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java @@ -262,6 +262,8 @@ public class HdmiCecMessage { return "Give Audio Status"; case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: return "Set System Audio Mode"; + case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL: + return "Set Audio Volume Level"; case Constants.MESSAGE_REPORT_AUDIO_STATUS: return "Report Audio Status"; case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS: diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java index 8b6d16a171f5c..caaf80073de1d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -259,6 +259,7 @@ public class HdmiCecNetwork { // The addition of a local device should not notify listeners return; } + mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { // Don't notify listeners of devices that haven't reported their physical address yet return; @@ -383,7 +384,7 @@ public class HdmiCecNetwork { final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) { assertRunOnServiceThread(); HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); - + mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); localDevice.mCecMessageCache.flushMessagesFrom(address); if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { // Don't notify listeners of devices that haven't reported their physical address yet @@ -586,6 +587,8 @@ public class HdmiCecNetwork { .build(); updateCecDevice(newDeviceInfo); + + mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); } @ServiceThreadOnly @@ -617,6 +620,8 @@ public class HdmiCecNetwork { ) .build(); updateCecDevice(newDeviceInfo); + + mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 65530e2343245..f44dc49da095f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -30,6 +30,7 @@ import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL; import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -38,6 +39,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.display.DisplayManager; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; @@ -56,7 +58,12 @@ import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.hardware.hdmi.IHdmiVendorCommandListener; import android.hardware.tv.cec.V1_0.OptionKey; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioDeviceVolumeManager; import android.media.AudioManager; +import android.media.VolumeInfo; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.tv.TvInputManager; @@ -83,6 +90,7 @@ import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.Display; +import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -101,6 +109,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -202,6 +211,27 @@ public class HdmiControlService extends SystemService { public @interface WakeReason { } + @VisibleForTesting + static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, ""); + @VisibleForTesting + static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC = + new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HDMI_ARC, ""); + static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC = + new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HDMI_EARC, ""); + + // Audio output devices used for Absolute Volume Control + private static final List AVC_AUDIO_OUTPUT_DEVICES = + Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI, + AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC)); + + // AudioAttributes for STREAM_MUSIC + @VisibleForTesting + static final AudioAttributes STREAM_MUSIC_ATTRIBUTES = + new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build(); + private final Executor mServiceThreadExecutor = new Executor() { @Override public void execute(Runnable r) { @@ -209,6 +239,10 @@ public class HdmiControlService extends SystemService { } }; + Executor getServiceThreadExecutor() { + return mServiceThreadExecutor; + } + // Logical address of the active source. @GuardedBy("mLock") protected final ActiveSource mActiveSource = new ActiveSource(); @@ -222,6 +256,13 @@ public class HdmiControlService extends SystemService { @HdmiControlManager.VolumeControl private int mHdmiCecVolumeControl; + // Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES. + @GuardedBy("mLock") + private Map mAudioDeviceVolumeBehaviors = new HashMap<>(); + + // Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services. + private int mStreamMusicMaxVolume; + // Make sure HdmiCecConfig is instantiated and the XMLs are read. private HdmiCecConfig mHdmiCecConfig; @@ -410,6 +451,9 @@ public class HdmiControlService extends SystemService { @Nullable private PowerManagerInternalWrapper mPowerManagerInternal; + @Nullable + private AudioManager mAudioManager; + @Nullable private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager; @@ -757,8 +801,14 @@ public class HdmiControlService extends SystemService { Context.TV_INPUT_SERVICE); mPowerManager = new PowerManagerWrapper(getContext()); mPowerManagerInternal = new PowerManagerInternalWrapper(); - mAudioDeviceVolumeManager = - new AudioDeviceVolumeManagerWrapper(getContext()); + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC); + if (mAudioDeviceVolumeManager == null) { + mAudioDeviceVolumeManager = + new AudioDeviceVolumeManagerWrapper(getContext()); + } + getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener( + mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { runOnServiceThread(this::bootCompleted); } @@ -2434,6 +2484,7 @@ public class HdmiControlService extends SystemService { pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus()); pw.println("mIsCecAvailable: " + mIsCecAvailable); pw.println("mCecVersion: " + mCecVersion); + pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled()); // System settings pw.println("System_settings:"); @@ -2593,6 +2644,7 @@ public class HdmiControlService extends SystemService { @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) { mHdmiCecVolumeControl = hdmiCecVolumeControl; announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl); + runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState); } // Get the source address to send out commands to devices connected to the current device @@ -3148,8 +3200,12 @@ public class HdmiControlService extends SystemService { HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } + /** + * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. + */ + @Nullable AudioManager getAudioManager() { - return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + return mAudioManager; } /** @@ -3511,6 +3567,7 @@ public class HdmiControlService extends SystemService { synchronized (mLock) { mSystemAudioActivated = on; } + runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState); } @ServiceThreadOnly @@ -3624,6 +3681,8 @@ public class HdmiControlService extends SystemService { device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress), deviceIsActiveSource, caller); } + + runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState); } // This method should only be called when the device can be the active source @@ -3816,4 +3875,325 @@ public class HdmiControlService extends SystemService { Slog.e(TAG, "Failed to report setting change", e); } } + + /** + * Listener for changes to the volume behavior of an audio output device. Caches the + * volume behavior of devices used for Absolute Volume Control. + */ + @VisibleForTesting + @ServiceThreadOnly + void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) { + assertRunOnServiceThread(); + if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) { + synchronized (mLock) { + mAudioDeviceVolumeBehaviors.put(device, volumeBehavior); + } + checkAndUpdateAbsoluteVolumeControlState(); + } + } + + /** + * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached + * results for the volume behaviors of HDMI audio devices. + */ + @AudioManager.DeviceVolumeBehavior + private int getDeviceVolumeBehavior(AudioDeviceAttributes device) { + if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) { + synchronized (mLock) { + if (mAudioDeviceVolumeBehaviors.containsKey(device)) { + return mAudioDeviceVolumeBehaviors.get(device); + } + } + } + return getAudioManager().getDeviceVolumeBehavior(device); + } + + /** + * Returns whether Absolute Volume Control is enabled or not. This is determined by the + * volume behavior of the relevant HDMI audio output device(s) for this device's type. + */ + public boolean isAbsoluteVolumeControlEnabled() { + if (!isTvDevice() && !isPlaybackDevice()) { + return false; + } + AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice(); + if (avcAudioOutputDevice == null) { + return false; + } + return getDeviceVolumeBehavior(avcAudioOutputDevice) + == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE; + } + + private AudioDeviceAttributes getAvcAudioOutputDevice() { + if (isTvDevice()) { + return tv().getSystemAudioOutputDevice(); + } else if (isPlaybackDevice()) { + return AUDIO_OUTPUT_DEVICE_HDMI; + } else { + return null; + } + } + + /** + * Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature + * if necessary. AVC is enabled precisely when a specific audio output device + * (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume + * behavior. + * + * AVC must be enabled on a Playback device or TV precisely when it is playing + * audio on an external device (the System Audio device) that supports the feature. + * This reduces to these conditions: + * + * 1. If the System Audio Device is an Audio System: System Audio Mode is active + * 2. Our HDMI audio output device is using full volume behavior + * 3. CEC volume is enabled + * 4. The System Audio device supports AVC (i.e. it supports ) + * + * If not all of these conditions are met, this method disables AVC if necessary. + * + * If all of these conditions are met, this method starts an action to query the System Audio + * device's audio status, which enables AVC upon obtaining the audio status. + */ + @ServiceThreadOnly + void checkAndUpdateAbsoluteVolumeControlState() { + assertRunOnServiceThread(); + + // Can't enable or disable AVC before we have access to system services + if (getAudioManager() == null) { + return; + } + + HdmiCecLocalDevice localCecDevice; + if (isTvDevice() && tv() != null) { + localCecDevice = tv(); + // Condition 1: TVs need System Audio Mode to be active + // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the + // TV is the System Audio Device instead.) + if (!isSystemAudioActivated()) { + disableAbsoluteVolumeControl(); + return; + } + } else if (isPlaybackDevice() && playback() != null) { + localCecDevice = playback(); + } else { + // Either this device type doesn't support AVC, or it hasn't fully initialized yet + return; + } + + HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo( + localCecDevice.findAudioReceiverAddress()); + @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior = + getDeviceVolumeBehavior(getAvcAudioOutputDevice()); + + // Condition 2: Already using full or absolute volume behavior + boolean alreadyUsingFullOrAbsoluteVolume = + currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL + || currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE; + // Condition 3: CEC volume is enabled + boolean cecVolumeEnabled = + getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED; + + if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) { + disableAbsoluteVolumeControl(); + return; + } + + // Check for safety: if the System Audio device is a candidate for AVC, we should already + // have received messages from it to trigger the other conditions. + if (systemAudioDeviceInfo == null) { + disableAbsoluteVolumeControl(); + return; + } + // Condition 4: The System Audio device supports AVC (i.e. ). + switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) { + case DeviceFeatures.FEATURE_SUPPORTED: + if (!isAbsoluteVolumeControlEnabled()) { + // Start an action that will call {@link #enableAbsoluteVolumeControl} + // once the System Audio device sends + localCecDevice.addAvcAudioStatusAction( + systemAudioDeviceInfo.getLogicalAddress()); + } + return; + case DeviceFeatures.FEATURE_NOT_SUPPORTED: + disableAbsoluteVolumeControl(); + return; + case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN: + disableAbsoluteVolumeControl(); + localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress()); + return; + default: + return; + } + } + + private void disableAbsoluteVolumeControl() { + if (isPlaybackDevice()) { + playback().removeAvcAudioStatusAction(); + } else if (isTvDevice()) { + tv().removeAvcAudioStatusAction(); + } + AudioDeviceAttributes device = getAvcAudioOutputDevice(); + if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { + getAudioManager().setDeviceVolumeBehavior(device, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + } + } + + /** + * Enables Absolute Volume Control. Should only be called when all the conditions for + * AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}). + * @param audioStatus The initial audio status to set the audio output device to + */ + void enableAbsoluteVolumeControl(AudioStatus audioStatus) { + HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv(); + HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo( + localDevice.findAudioReceiverAddress()); + VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC) + .setMuted(audioStatus.getMute()) + .setVolumeIndex(audioStatus.getVolume()) + .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) + .setMinVolumeIndex(AudioStatus.MIN_VOLUME) + .build(); + mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener( + localDevice, systemAudioDevice); + + // AudioService sets the volume of the stream and device based on the input VolumeInfo + // when enabling absolute volume behavior, but not the mute state + notifyAvcMuteChange(audioStatus.getMute()); + getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior( + getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor, + mAbsoluteVolumeChangedListener, true); + } + + private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener; + + @VisibleForTesting + AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() { + return mAbsoluteVolumeChangedListener; + } + + /** + * Listeners for changes reported by AudioService to the state of an audio output device using + * absolute volume behavior. + */ + @VisibleForTesting + class AbsoluteVolumeChangedListener implements + AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener { + private HdmiCecLocalDevice mLocalDevice; + private HdmiDeviceInfo mSystemAudioDevice; + + private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice, + HdmiDeviceInfo systemAudioDevice) { + mLocalDevice = localDevice; + mSystemAudioDevice = systemAudioDevice; + } + + /** + * Called when AudioService sets the volume level of an absolute volume audio output device + * to a numeric value. + */ + @Override + public void onAudioDeviceVolumeChanged( + @NonNull AudioDeviceAttributes audioDevice, + @NonNull VolumeInfo volumeInfo) { + int localDeviceAddress; + synchronized (mLocalDevice.mLock) { + localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress(); + } + sendCecCommand(SetAudioVolumeLevelMessage.build( + localDeviceAddress, + mSystemAudioDevice.getLogicalAddress(), + volumeInfo.getVolumeIndex()), + // If sending the message fails, ask the System Audio device for its + // audio status so that we can update AudioService + (int errorCode) -> { + if (errorCode == SendMessageResult.SUCCESS) { + // Update the volume tracked in our AbsoluteVolumeAudioStatusAction + // so it correctly processes incoming messages + HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback(); + avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex()); + } else { + sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( + localDeviceAddress, + mSystemAudioDevice.getLogicalAddress() + )); + } + }); + } + + /** + * Called when AudioService adjusts the volume or mute state of an absolute volume + * audio output device + */ + @Override + public void onAudioDeviceVolumeAdjusted( + @NonNull AudioDeviceAttributes audioDevice, + @NonNull VolumeInfo volumeInfo, + @AudioManager.VolumeAdjustment int direction, + @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode + ) { + int keyCode; + switch (direction) { + case AudioManager.ADJUST_RAISE: + keyCode = KeyEvent.KEYCODE_VOLUME_UP; + break; + case AudioManager.ADJUST_LOWER: + keyCode = KeyEvent.KEYCODE_VOLUME_DOWN; + break; + case AudioManager.ADJUST_TOGGLE_MUTE: + case AudioManager.ADJUST_MUTE: + case AudioManager.ADJUST_UNMUTE: + // Many CEC devices only support toggle mute. Therefore, we send the + // same keycode for all three mute options. + keyCode = KeyEvent.KEYCODE_VOLUME_MUTE; + break; + default: + return; + } + switch (mode) { + case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL: + mLocalDevice.sendVolumeKeyEvent(keyCode, true); + mLocalDevice.sendVolumeKeyEvent(keyCode, false); + break; + case AudioDeviceVolumeManager.ADJUST_MODE_START: + mLocalDevice.sendVolumeKeyEvent(keyCode, true); + break; + case AudioDeviceVolumeManager.ADJUST_MODE_END: + mLocalDevice.sendVolumeKeyEvent(keyCode, false); + break; + default: + return; + } + } + } + + /** + * Notifies AudioService of a change in the volume of the System Audio device. Has no effect if + * AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC + */ + void notifyAvcVolumeChange(int volume) { + if (!isAbsoluteVolumeControlEnabled()) return; + List streamMusicDevices = + getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); + if (streamMusicDevices.contains(getAvcAudioOutputDevice())) { + getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC, + volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, + AudioManager.FLAG_ABSOLUTE_VOLUME); + } + } + + /** + * Notifies AudioService of a change in the mute status of the System Audio device. Has no + * effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC + */ + void notifyAvcMuteChange(boolean mute) { + if (!isAbsoluteVolumeControlEnabled()) return; + List streamMusicDevices = + getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); + if (streamMusicDevices.contains(getAvcAudioOutputDevice())) { + int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE; + getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, + AudioManager.FLAG_ABSOLUTE_VOLUME); + } + } } diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index adcef667545f6..7daeaf19c6579 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -172,8 +172,19 @@ final class SendKeyAction extends HdmiCecFeatureAction { } private void sendKeyUp() { - sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), - mTargetAddress)); + // When using Absolute Volume Control, query audio status after a volume key is released. + // This allows us to notify AudioService of the resulting volume or mute status changes. + if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode) + && localDevice().getService().isAbsoluteVolumeControlEnabled()) { + sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), + mTargetAddress), + __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( + getSourceAddress(), + localDevice().findAudioReceiverAddress()))); + } else { + sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), + mTargetAddress)); + } } @Override diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java index 96fd003c72b48..eb3b33d8ca228 100644 --- a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java @@ -122,4 +122,11 @@ public class SetAudioVolumeLevelDiscoveryAction extends HdmiCecFeatureAction { return true; } } + + /** + * Returns the logical address of this action's target device. + */ + public int getTargetAddress() { + return mTargetAddress; + } } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index 978c25d0a8c60..e7a3db75c9d0d 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -153,7 +153,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd); if (receivedStatus == mTargetAudioStatus) { setSystemAudioMode(receivedStatus); - startAudioStatusAction(); + finish(); return true; } else { HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus); @@ -168,11 +168,6 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { } } - protected void startAudioStatusAction() { - addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallbacks)); - finish(); - } - protected void removeSystemAudioActionInProgress() { removeActionExcept(SystemAudioActionFromTv.class, this); removeActionExcept(SystemAudioActionFromAvr.class, this); diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java index 6ddff91a70f7a..99148c4ea114a 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java @@ -16,8 +16,8 @@ package com.android.server.hdmi; -import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; /** @@ -65,7 +65,7 @@ final class SystemAudioActionFromAvr extends SystemAudioAction { if (mTargetAudioStatus) { setSystemAudioMode(true); - startAudioStatusAction(); + finish(); } else { setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java deleted file mode 100644 index b4af540b96f5a..0000000000000 --- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.hdmi; - -import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.IHdmiControlCallback; -import android.hardware.tv.cec.V1_0.SendMessageResult; - -import com.android.server.hdmi.HdmiControlService.SendMessageCallback; - -import java.util.List; - -/** - * Action to update audio status (volume or mute) of audio amplifier - */ -final class SystemAudioStatusAction extends HdmiCecFeatureAction { - private static final String TAG = "SystemAudioStatusAction"; - - // State that waits for . - private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1; - - private final int mAvrAddress; - - SystemAudioStatusAction( - HdmiCecLocalDevice source, int avrAddress, List callbacks) { - super(source, callbacks); - mAvrAddress = avrAddress; - } - - SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress, - IHdmiControlCallback callback) { - super(source, callback); - mAvrAddress = avrAddress; - } - - @Override - boolean start() { - mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS; - addTimer(mState, HdmiConfig.TIMEOUT_MS); - sendGiveAudioStatus(); - return true; - } - - private void sendGiveAudioStatus() { - sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress), - new SendMessageCallback() { - @Override - public void onSendCompleted(int error) { - if (error != SendMessageResult.SUCCESS) { - handleSendGiveAudioStatusFailure(); - } - } - }); - } - - private void handleSendGiveAudioStatusFailure() { - - // Still return SUCCESS to callback. - finishWithCallback(HdmiControlManager.RESULT_SUCCESS); - } - - @Override - boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) { - return false; - } - - switch (cmd.getOpcode()) { - case Constants.MESSAGE_REPORT_AUDIO_STATUS: - handleReportAudioStatus(cmd); - return true; - } - - return false; - } - - private void handleReportAudioStatus(HdmiCecMessage cmd) { - byte[] params = cmd.getParams(); - boolean mute = HdmiUtils.isAudioStatusMute(cmd); - int volume = HdmiUtils.getAudioStatusVolume(cmd); - tv().setAudioStatus(mute, volume); - - if (!(tv().isSystemAudioActivated() ^ mute)) { - // Toggle AVR's mute status to match with the system audio status. - sendUserControlPressedAndReleased(mAvrAddress, HdmiCecKeycode.CEC_KEYCODE_MUTE); - } - finishWithCallback(HdmiControlManager.RESULT_SUCCESS); - } - - @Override - void handleTimerEvent(int state) { - if (mState != state) { - return; - } - - handleSendGiveAudioStatusFailure(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java new file mode 100644 index 0000000000000..e06877f9144eb --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; + +import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.TruthJUnit.assume; + +import static org.mockito.AdditionalMatchers.not; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceVolumeManager; +import android.media.AudioManager; +import android.media.VolumeInfo; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; + +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +/** + * Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that + * the device responds correctly to incoming messages and API calls + * from AudioService when AVC is active. + * + * This is an abstract base class. Concrete subclasses specify the type of the local device, and the + * type of the System Audio device. This allows each test to be run for multiple setups. + * + * We test the following pairs of (local device, System Audio device): + * (Playback, TV): {@link PlaybackDeviceToTvAvcTest} + * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest} + * (TV, Audio System): {@link TvToAudioSystemAvcTest} + */ +public abstract class BaseAbsoluteVolumeControlTest { + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevice mHdmiCecLocalDevice; + private FakeHdmiCecConfig mHdmiCecConfig; + private FakePowerManagerWrapper mPowerManager; + private Looper mLooper; + private Context mContextSpy; + private ArrayList mLocalDevices = new ArrayList<>(); + + @Mock protected AudioManager mAudioManager; + protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager; + + protected TestLooper mTestLooper = new TestLooper(); + protected FakeNativeWrapper mNativeWrapper; + + // Audio Status given by the System Audio device in its initial that + // triggers AVC being enabled + private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS = + new AudioStatus(50, false); + + // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC + private static final VolumeInfo ENABLE_AVC_VOLUME_INFO = + new VolumeInfo.Builder(AudioManager.STREAM_MUSIC) + .setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()) + .setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume()) + .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) + .setMinVolumeIndex(AudioStatus.MIN_VOLUME) + .build(); + + protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService); + + protected abstract int getPhysicalAddress(); + protected abstract int getDeviceType(); + protected abstract AudioDeviceAttributes getAudioOutputDevice(); + + protected abstract int getSystemAudioDeviceLogicalAddress(); + protected abstract int getSystemAudioDeviceType(); + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + + mContextSpy = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + + mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper()); + + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext(), + Collections.singletonList(getDeviceType()), + mAudioDeviceVolumeManager) { + @Override + AudioManager getAudioManager() { + return mAudioManager; + } + + @Override + protected void writeStringSystemProperty(String key, String value) { + // do nothing + } + }; + + mLooper = mTestLooper.getLooper(); + mHdmiControlService.setIoLooper(mLooper); + + mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); + mHdmiCecConfig.setIntValue( + HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, + HdmiControlManager.HDMI_CEC_VERSION_2_0); + mHdmiCecConfig.setIntValue( + HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, + HdmiControlManager.VOLUME_CONTROL_DISABLED); + mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig); + + mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(getPhysicalAddress()); + mNativeWrapper.setPollAddressResponse(Constants.ADDR_TV, SendMessageResult.SUCCESS); + + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController( + HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.initService(); + mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mHdmiControlService.setPowerManager(mPowerManager); + + mHdmiCecLocalDevice = createLocalDevice(mHdmiControlService); + mHdmiCecLocalDevice.init(); + mLocalDevices.add(mHdmiCecLocalDevice); + + HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; + hdmiPortInfos[0] = + new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false); + mNativeWrapper.setPortInfo(hdmiPortInfos); + mNativeWrapper.setPortConnectionStatus(1, true); + + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP); + mTestLooper.dispatchAll(); + + // Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called + doAnswer(invocation -> { + setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1)); + return null; + }).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt()); + + // Set starting volume behavior + doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE) + .when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice())); + + // Audio service always plays STREAM_MUSIC on the device we need + doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager) + .getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES); + + // Max volume of STREAM_MUSIC + doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC); + + // Receive messages from devices to make sure they're registered in HdmiCecNetwork + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + Constants.ADDR_TV, getLogicalAddress())); + if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) { + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + Constants.ADDR_AUDIO_SYSTEM, getLogicalAddress())); + } + + mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + mHdmiControlService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + mTestLooper.dispatchAll(); + } + + protected int getLogicalAddress() { + synchronized (mHdmiCecLocalDevice.mLock) { + return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress(); + } + } + + /** + * Simulates the volume behavior of {@code device} being set to {@code behavior}. + */ + protected void setDeviceVolumeBehavior(AudioDeviceAttributes device, + @AudioManager.DeviceVolumeBehavior int behavior) { + doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device)); + mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior); + mTestLooper.dispatchAll(); + } + + /** + * Changes the setting for CEC volume. + */ + protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) { + mHdmiControlService.getHdmiCecConfig().setIntValue( + HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, setting); + mTestLooper.dispatchAll(); + } + + /** + * Has the device receive a message from the System Audio device specifying + * whether is supported or not. + */ + protected void receiveSetAudioVolumeLevelSupport( + @DeviceFeatures.FeatureSupportStatus int featureSupportStatus) { + // can't specify an unknown feature support status + if (featureSupportStatus != DeviceFeatures.FEATURE_SUPPORT_UNKNOWN) { + mNativeWrapper.onCecMessage(ReportFeaturesMessage.build( + getSystemAudioDeviceLogicalAddress(), HdmiControlManager.HDMI_CEC_VERSION_2_0, + Arrays.asList(getSystemAudioDeviceType()), Constants.RC_PROFILE_SOURCE, + Collections.emptyList(), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(featureSupportStatus) + .build())); + mTestLooper.dispatchAll(); + } + } + + /** + * Enables System Audio mode if the System Audio device is an Audio System. + */ + protected void enableSystemAudioModeIfNeeded() { + if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) { + receiveSetSystemAudioMode(true); + } + } + + /** + * Sets System Audio Mode by having the device receive + * from the Audio System. + */ + protected void receiveSetSystemAudioMode(boolean status) { + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode( + Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, status)); + mTestLooper.dispatchAll(); + } + + /** + * Has the device receive a reporting the status in + * {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS} + */ + protected void receiveInitialReportAudioStatus() { + receiveReportAudioStatus( + INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(), + INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()); + } + + /** + * Has the device receive a message from the System Audio Device. + */ + protected void receiveReportAudioStatus(int volume, boolean mute) { + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus( + getSystemAudioDeviceLogicalAddress(), + getLogicalAddress(), + volume, + mute)); + mTestLooper.dispatchAll(); + } + + /** + * Triggers all the conditions required to enable Absolute Volume Control. + */ + protected void enableAbsoluteVolumeControl() { + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + enableSystemAudioModeIfNeeded(); + receiveInitialReportAudioStatus(); + + verifyAbsoluteVolumeEnabled(); + } + + /** + * Verifies that AVC was enabled - that is the audio output device's volume behavior was last + * set to absolute volume behavior. + */ + protected void verifyAbsoluteVolumeEnabled() { + InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager); + inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior( + eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean()); + inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior( + eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE))); + } + + /** + * Verifies that AVC was disabled - that is, the audio output device's volume behavior was + * last set to something other than absolute volume behavior. + */ + protected void verifyAbsoluteVolumeDisabled() { + InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager); + inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior( + eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE))); + inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior( + eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean()); + } + + protected void verifyGiveAudioStatusNeverSent() { + assertThat(mNativeWrapper.getResultMessages()).doesNotContain( + HdmiCecMessageBuilder.buildGiveAudioStatus( + getLogicalAddress(), getSystemAudioDeviceLogicalAddress())); + } + + protected void verifyGiveAudioStatusSent() { + assertThat(mNativeWrapper.getResultMessages()).contains( + HdmiCecMessageBuilder.buildGiveAudioStatus( + getLogicalAddress(), getSystemAudioDeviceLogicalAddress())); + } + + @Test + public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() { + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + enableSystemAudioModeIfNeeded(); + + assertThat(mNativeWrapper.getResultMessages()).contains( + SetAudioVolumeLevelMessage.build( + getLogicalAddress(), getSystemAudioDeviceLogicalAddress(), + Constants.AUDIO_VOLUME_STATUS_UNKNOWN)); + assertThat(mNativeWrapper.getResultMessages()).contains( + HdmiCecMessageBuilder.buildGiveFeatures( + getLogicalAddress(), getSystemAudioDeviceLogicalAddress())); + } + + @Test + public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() { + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + enableSystemAudioModeIfNeeded(); + verifyGiveAudioStatusNeverSent(); + + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + verifyGiveAudioStatusSent(); + } + + @Test + public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() { + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + enableSystemAudioModeIfNeeded(); + verifyGiveAudioStatusNeverSent(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + verifyGiveAudioStatusSent(); + } + + @Test + public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() { + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + enableSystemAudioModeIfNeeded(); + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + verifyGiveAudioStatusNeverSent(); + + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + verifyGiveAudioStatusSent(); + } + + @Test + public void allConditionsMet_fullVolumeBehaviorLast_giveAudioStatusSent() { + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + enableSystemAudioModeIfNeeded(); + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + verifyGiveAudioStatusNeverSent(); + + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + verifyGiveAudioStatusSent(); + } + + @Test + public void allConditionsMet_systemAudioModeEnabledLast_giveAudioStatusSent() { + // Only run when the System Audio device is an Audio System. + assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + verifyGiveAudioStatusNeverSent(); + + receiveSetSystemAudioMode(true); + verifyGiveAudioStatusSent(); + } + + @Test + public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() { + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED); + enableSystemAudioModeIfNeeded(); + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); + + // Verify that AVC was never enabled + verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior( + eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean()); + receiveInitialReportAudioStatus(); + + verifyAbsoluteVolumeEnabled(); + } + + @Test + public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() { + enableAbsoluteVolumeControl(); + + setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED); + verifyAbsoluteVolumeDisabled(); + } + + @Test + public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() { + enableAbsoluteVolumeControl(); + + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED); + verifyAbsoluteVolumeDisabled(); + } + + @Test + public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() { + enableAbsoluteVolumeControl(); + + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + getSystemAudioDeviceLogicalAddress(), getLogicalAddress(), + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE)); + mTestLooper.dispatchAll(); + verifyAbsoluteVolumeDisabled(); + } + + @Test + public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() { + // Only run when the System Audio device is an Audio System. + assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + + enableAbsoluteVolumeControl(); + + receiveSetSystemAudioMode(false); + verifyAbsoluteVolumeDisabled(); + } + + @Test + public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() { + // Initial has volume=50 and mute=false + enableAbsoluteVolumeControl(); + + // New volume and mute status: sets both + receiveReportAudioStatus(20, true); + verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), + anyInt()); + verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), + eq(AudioManager.ADJUST_MUTE), anyInt()); + clearInvocations(mAudioManager); + + // New volume only: sets volume only + receiveReportAudioStatus(32, true); + verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8), + anyInt()); + verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), + eq(AudioManager.ADJUST_MUTE), anyInt()); + clearInvocations(mAudioManager); + + // New mute status only: sets mute only + receiveReportAudioStatus(32, false); + verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8), + anyInt()); + verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), + eq(AudioManager.ADJUST_UNMUTE), anyInt()); + clearInvocations(mAudioManager); + + // Repeat of earlier message: sets neither volume nor mute + receiveReportAudioStatus(32, false); + verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8), + anyInt()); + verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), + eq(AudioManager.ADJUST_UNMUTE), anyInt()); + clearInvocations(mAudioManager); + + // If AudioService causes us to send , the System Audio device's + // volume changes. Afterward, a duplicate of an earlier should + // still cause us to call setStreamVolume() + mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged( + getAudioOutputDevice(), + new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO) + .setVolumeIndex(20) + .build() + ); + mTestLooper.dispatchAll(); + receiveReportAudioStatus(32, false); + verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8), + anyInt()); + verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), + eq(AudioManager.ADJUST_UNMUTE), anyInt()); + } + + @Test + public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() { + enableAbsoluteVolumeControl(); + mNativeWrapper.clearResultMessages(); + + mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted( + getAudioOutputDevice(), + ENABLE_AVC_VOLUME_INFO, + AudioManager.ADJUST_RAISE, + AudioDeviceVolumeManager.ADJUST_MODE_NORMAL + ); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains( + HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(), + getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP)); + assertThat(mNativeWrapper.getResultMessages()).contains( + HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(), + getSystemAudioDeviceLogicalAddress())); + assertThat(mNativeWrapper.getResultMessages()).contains( + HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(), + getSystemAudioDeviceLogicalAddress())); + } + + @Test + public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() { + enableAbsoluteVolumeControl(); + mNativeWrapper.clearResultMessages(); + + mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged( + getAudioOutputDevice(), + new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO) + .setVolumeIndex(20) + .build() + ); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains( + SetAudioVolumeLevelMessage.build(getLogicalAddress(), + getSystemAudioDeviceLogicalAddress(), 20)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java new file mode 100644 index 0000000000000..64186028e6ed2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.media.AudioDeviceAttributes; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.google.android.collect.Lists; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +/** + * Tests for Absolute Volume Control where the local device is a Playback device and the + * System Audio device is an Audio System. + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest { + + @Override + protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) { + return new HdmiCecLocalDevicePlayback(hdmiControlService); + } + + @Override + protected int getPhysicalAddress() { + return 0x1100; + } + + @Override + protected int getDeviceType() { + return HdmiDeviceInfo.DEVICE_PLAYBACK; + } + + @Override + protected AudioDeviceAttributes getAudioOutputDevice() { + return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI; + } + + @Override + protected int getSystemAudioDeviceLogicalAddress() { + return Constants.ADDR_AUDIO_SYSTEM; + } + + @Override + protected int getSystemAudioDeviceType() { + return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; + } + + /** + * AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown + * support for . It is enabled once the TV confirms support for + * and sends . + */ + @Test + public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() { + enableAbsoluteVolumeControl(); + + // Audio System disables System Audio Mode. AVC should be disabled. + receiveSetSystemAudioMode(false); + verifyAbsoluteVolumeDisabled(); + + // TV reports support for + mNativeWrapper.onCecMessage(ReportFeaturesMessage.build( + Constants.ADDR_TV, HdmiControlManager.HDMI_CEC_VERSION_2_0, + Arrays.asList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED) + .build())); + mTestLooper.dispatchAll(); + + // TV reports its initial audio status + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus( + Constants.ADDR_TV, + getLogicalAddress(), + 30, + false)); + mTestLooper.dispatchAll(); + + verifyAbsoluteVolumeEnabled(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java new file mode 100644 index 0000000000000..504c3bc2626a9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import static org.mockito.Mockito.clearInvocations; + +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.media.AudioDeviceAttributes; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.Collections; + +/** + * Tests for Absolute Volume Control where the local device is a Playback device and the + * System Audio device is a TV. + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest { + + @Override + protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) { + return new HdmiCecLocalDevicePlayback(hdmiControlService); + } + + @Override + protected int getPhysicalAddress() { + return 0x1100; + } + + @Override + protected int getDeviceType() { + return HdmiDeviceInfo.DEVICE_PLAYBACK; + } + + @Override + protected AudioDeviceAttributes getAudioOutputDevice() { + return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI; + } + + @Override + protected int getSystemAudioDeviceLogicalAddress() { + return Constants.ADDR_TV; + } + + @Override + protected int getSystemAudioDeviceType() { + return HdmiDeviceInfo.DEVICE_TV; + } + + /** + * AVC is disabled when an Audio System with unknown support for + * becomes the System Audio device. It is enabled once the Audio System reports that it + * supports and sends . + */ + @Test + public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() { + enableAbsoluteVolumeControl(); + + // Audio System enables System Audio Mode. AVC should be disabled. + receiveSetSystemAudioMode(true); + verifyAbsoluteVolumeDisabled(); + + clearInvocations(mAudioManager, mAudioDeviceVolumeManager); + + // Audio System reports support for + mNativeWrapper.onCecMessage(ReportFeaturesMessage.build( + Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0, + Arrays.asList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE, + Collections.emptyList(), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED) + .build())); + mTestLooper.dispatchAll(); + + // Audio system reports its initial audio status + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus( + Constants.ADDR_AUDIO_SYSTEM, + getLogicalAddress(), + 30, + false)); + mTestLooper.dispatchAll(); + + verifyAbsoluteVolumeEnabled(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java new file mode 100644 index 0000000000000..41c0e0d29879e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.HdmiDeviceInfo; +import android.media.AudioDeviceAttributes; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for Absolute Volume Control where the local device is a TV and the System Audio device + * is an Audio System. Assumes that the TV uses ARC (rather than eARC). + */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest { + + @Override + protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) { + return new HdmiCecLocalDeviceTv(hdmiControlService); + } + + @Override + protected int getPhysicalAddress() { + return 0x0000; + } + + @Override + protected int getDeviceType() { + return HdmiDeviceInfo.DEVICE_TV; + } + + @Override + protected AudioDeviceAttributes getAudioOutputDevice() { + return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC; + } + + @Override + protected int getSystemAudioDeviceLogicalAddress() { + return Constants.ADDR_AUDIO_SYSTEM; + } + + @Override + protected int getSystemAudioDeviceType() { + return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; + } +} From 73dee58455507ec5eb8770872e101ca18b7bbc5f Mon Sep 17 00:00:00 2001 From: Yan Han Date: Thu, 17 Mar 2022 16:15:16 +0100 Subject: [PATCH 4/4] Add Absolute Volume Control support for a TV as the System Audio device A TV will react to messages by setting the volume of STREAM_MUSIC, unless System Audio Mode is enabled. In this case, the TV does nothing and responds with [not in correct mode to respond], as messages should only be send to the System Audio device. Test: atest HdmiCecLocalDeviceTvTest Bug: 223131113 Change-Id: I1aac3926ffe1280344e97efcbd1dce7f9c3c3ffd --- .../server/hdmi/HdmiCecLocalDevice.java | 12 +++++ .../server/hdmi/HdmiCecLocalDeviceTv.java | 14 +++++ .../server/hdmi/HdmiControlService.java | 13 +++-- .../server/hdmi/HdmiCecLocalDeviceTvTest.java | 52 ++++++++++++++++++- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 8acec128245b4..fb2d2ee08cbda 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -303,6 +303,13 @@ abstract class HdmiCecLocalDevice { if (dispatchMessageToAction(message)) { return Constants.HANDLED; } + + // If a message type has its own class, all valid messages of that type + // will be represented by an instance of that class. + if (message instanceof SetAudioVolumeLevelMessage) { + return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message); + } + switch (message.getOpcode()) { case Constants.MESSAGE_ACTIVE_SOURCE: return handleActiveSource(message); @@ -637,6 +644,11 @@ abstract class HdmiCecLocalDevice { return Constants.NOT_HANDLED; } + @Constants.HandleMessageResult + protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) { + return Constants.NOT_HANDLED; + } + @Constants.RcProfile protected abstract int getRcProfile(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 2fae6dcc21c69..1ea1457439ecf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1166,6 +1166,19 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return Constants.HANDLED; } + @Override + @Constants.HandleMessageResult + protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) { + // should only be sent to the System Audio device, so we don't + // handle it when System Audio Mode is enabled. + if (mService.isSystemAudioActivated()) { + return Constants.ABORT_NOT_IN_CORRECT_MODE; + } else { + mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0); + return Constants.HANDLED; + } + } + void announceOneTouchRecordResult(int recorderAddress, int result) { mService.invokeOneTouchRecordResult(recorderAddress, result); } @@ -1596,6 +1609,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() .setRecordTvScreenSupport(FEATURE_SUPPORTED) .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED) .build(); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index f44dc49da095f..9824b4e6c43ae 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4176,9 +4176,7 @@ public class HdmiControlService extends SystemService { List streamMusicDevices = getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); if (streamMusicDevices.contains(getAvcAudioOutputDevice())) { - getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC, - volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, - AudioManager.FLAG_ABSOLUTE_VOLUME); + setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME); } } @@ -4196,4 +4194,13 @@ public class HdmiControlService extends SystemService { AudioManager.FLAG_ABSOLUTE_VOLUME); } } + + /** + * Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index + * from HDMI-CEC volume range to STREAM_MUSIC's. + */ + void setStreamMusicVolume(int volume, int flags) { + getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC, + volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags); + } } 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 2554118306769..f27b8c2f4b3a3 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -30,8 +30,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.hdmi.HdmiControlManager; @@ -770,7 +772,7 @@ public class HdmiCecLocalDeviceTvTest { // When the device reports its physical address, the listener eventually is invoked. HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK); + ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK); mNativeWrapper.onCecMessage(reportPhysicalAddress); mTestLooper.dispatchAll(); @@ -779,6 +781,54 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mDeviceEventListeners.size()).isEqualTo(1); assertThat(mDeviceEventListeners.get(0).getStatus()) .isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); + } + @Test + public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() { + when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25); + + // Max volume of STREAM_MUSIC is retrieved on boot + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build( + ADDR_PLAYBACK_1, + ADDR_TV, + 20)); + mTestLooper.dispatchAll(); + + // [Not in correct mode] not sent + HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_TV, + ADDR_PLAYBACK_1, + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + Constants.ABORT_NOT_IN_CORRECT_MODE); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage); + + // uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25] + verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt()); + } + + @Test + public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() { + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_TV, true)); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build( + ADDR_PLAYBACK_1, ADDR_TV, 50)); + mTestLooper.dispatchAll(); + + // [Not in correct mode] sent + HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_TV, + ADDR_PLAYBACK_1, + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + Constants.ABORT_NOT_IN_CORRECT_MODE); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage); + + // AudioManager not notified of volume change + verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(), + anyInt()); } }