From da665a67f1a60895c9df3ae9a88b9209a5aa08b7 Mon Sep 17 00:00:00 2001 From: Marvin Ramin Date: Mon, 16 Mar 2020 14:17:58 +0100 Subject: [PATCH] CEC: Make HDMI CEC volume control configurable Add API (backed by Setting) to enable/disable volume changes via HDMI CEC for HDMI CEC source devices. This state is persisted via Settings. If volume control is disabled, no incoming HDMI CEC commands related to volume will be processed. If disabled, no HDMI CEC volume control messages will be sent by the device. Test: atest com.android.server.hdmi Bug: 149800547 Merged-In: I83ae9b423122b540b9adb156fb1c6f5964dd6105 Change-Id: I83ae9b423122b540b9adb156fb1c6f5964dd6105 --- .../hardware/hdmi/HdmiControlManager.java | 62 ++++++++++ .../hardware/hdmi/IHdmiControlService.aidl | 2 + core/java/android/provider/Settings.java | 37 ++++++ .../hdmi/HdmiAudioSystemClientTest.java | 9 ++ .../android/provider/SettingsBackupTest.java | 1 + .../server/hdmi/HdmiCecLocalDevice.java | 19 ++- .../hdmi/HdmiCecLocalDeviceAudioSystem.java | 5 +- .../server/hdmi/HdmiCecLocalDeviceTv.java | 9 +- .../server/hdmi/HdmiControlService.java | 60 +++++++++- .../HdmiCecLocalDeviceAudioSystemTest.java | 112 ++++++++++++++++++ .../hdmi/HdmiCecLocalDevicePlaybackTest.java | 72 +++++++++++ .../server/hdmi/HdmiCecLocalDeviceTest.java | 61 ++++++++++ .../server/hdmi/HdmiControlServiceTest.java | 9 ++ 13 files changed, 452 insertions(+), 6 deletions(-) diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 65a8e15ba340d..6bc962b675764 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -649,6 +649,68 @@ public final class HdmiControlManager { } } + /** + * Controls whether volume control commands via HDMI CEC are enabled. + * + *

When disabled: + *

+ * + *

Effects on different device types: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
HDMI CEC device typeenableddisabled
TV (type: 0)Per CEC specification.TV changes system volume. TV no longer reacts to incoming volume changes via + * {@code }. TV no longer handles {@code } + * .
Playback device (type: 4)Device sends volume commands to TV/Audio system via {@code }Device does not send volume commands via {@code }.
Audio device (type: 5)Full "System Audio Control" capabilities.Audio device no longer reacts to incoming {@code } + * volume commands. Audio device no longer reports volume changes via {@code }.
+ * + *

Due to the resulting behavior, usage on TV and Audio devices is discouraged. + * + * @param isHdmiCecVolumeControlEnabled target state of HDMI CEC volume control. + * @see Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED + * @hide + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) { + try { + mService.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns whether volume changes via HDMI CEC are enabled. + * @hide + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public boolean isHdmiCecVolumeControlEnabled() { + try { + return mService.isHdmiCecVolumeControlEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Gets whether the system is in system audio mode. * diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index a8fed2b03cfcc..3582a927ff469 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -80,6 +80,8 @@ interface IHdmiControlService { void sendMhlVendorCommand(int portId, int offset, int length, in byte[] data); void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener); void setStandbyMode(boolean isStandbyModeOn); + void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled); + boolean isHdmiCecVolumeControlEnabled(); void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute); void setSystemAudioModeOnForAudioOnlySource(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fe340c46e3c46..b88d350a05c4f 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9604,6 +9604,43 @@ public final class Settings { */ public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled"; + /** + * Controls whether volume control commands via HDMI CEC are enabled. (0 = false, 1 = + * true). + * + *

Effects on different device types: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
HDMI CEC device type0: disabled1: enabled
TV (type: 0)Per CEC specification.TV changes system volume. TV no longer reacts to incoming volume changes + * via {@code }. TV no longer handles {@code }.
Playback device (type: 4)Device sends volume commands to TV/Audio system via {@code }Device does not send volume commands via {@code }.
Audio device (type: 5)Full "System Audio Control" capabilities.Audio device no longer reacts to incoming {@code } + * volume commands. Audio device no longer reports volume changes via {@code + * }.
+ * + *

Due to the resulting behavior, usage on TV and Audio devices is discouraged. + * + * @hide + * @see android.hardware.hdmi.HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean) + */ + public static final String HDMI_CONTROL_VOLUME_CONTROL_ENABLED = + "hdmi_control_volume_control_enabled"; + /** * Whether HDMI System Audio Control feature is enabled. If enabled, TV will try to turn on * system audio mode if there's a connected CEC-enabled AV Receiver. Then audio stream will diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java index 2141b815fb3bb..7cd2f3b4c2ab2 100644 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java @@ -354,6 +354,15 @@ public class HdmiAudioSystemClientTest { @Override public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) { } + + @Override + public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) { + } + + @Override + public boolean isHdmiCecVolumeControlEnabled() { + return true; + } } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 03f6df0d5d2dc..0dd7fb80379d0 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -296,6 +296,7 @@ public class SettingsBackupTest { Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Settings.Global.HDMI_CONTROL_ENABLED, + Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, Settings.Global.HIDDEN_API_POLICY, diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index a358707a43463..3ff6ec1afa41a 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -32,6 +32,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.Constants.LocalActivePort; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; @@ -144,7 +145,8 @@ abstract class HdmiCecLocalDevice { // A collection of FeatureAction. // Note that access to this collection should happen in service thread. - private final ArrayList mActions = new ArrayList<>(); + @VisibleForTesting + final ArrayList mActions = new ArrayList<>(); private final Handler mHandler = new Handler() { @@ -544,6 +546,8 @@ abstract class HdmiCecLocalDevice { } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { mService.wakeUp(); return true; + } else if (!mService.isHdmiCecVolumeControlEnabled() && isVolumeOrMuteCommand(message)) { + return false; } final long downTime = SystemClock.uptimeMillis(); @@ -618,6 +622,16 @@ abstract class HdmiCecLocalDevice { || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); } + static boolean isVolumeOrMuteCommand(HdmiCecMessage message) { + byte[] params = message.getParams(); + return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED + && (params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN + || params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP + || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE + || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION + || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); + } + protected boolean handleTextViewOn(HdmiCecMessage message) { return false; } @@ -1038,6 +1052,9 @@ abstract class HdmiCecLocalDevice { @ServiceThreadOnly protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) { assertRunOnServiceThread(); + if (!mService.isHdmiCecVolumeControlEnabled()) { + return; + } if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) { Slog.w(TAG, "Not a volume key: " + keyCode); return; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index e5a08d3e79aa1..611b8c69077db 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -575,7 +575,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @ServiceThreadOnly protected boolean handleGiveAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); - if (isSystemAudioControlFeatureEnabled()) { + if (isSystemAudioControlFeatureEnabled() && mService.isHdmiCecVolumeControlEnabled()) { reportAudioStatus(message.getSource()); } else { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); @@ -930,6 +930,9 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { void reportAudioStatus(int source) { assertRunOnServiceThread(); + if (!mService.isHdmiCecVolumeControlEnabled()) { + return; + } int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC); boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index a702ce517f404..0ac4f9ee4c6d7 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -678,6 +678,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleReportAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); + if (!mService.isHdmiCecVolumeControlEnabled()) { + return false; + } boolean mute = HdmiUtils.isAudioStatusMute(message); int volume = HdmiUtils.getAudioStatusVolume(message); @@ -987,7 +990,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } void setAudioStatus(boolean mute, int volume) { - if (!isSystemAudioActivated()) { + if (!isSystemAudioActivated() || !mService.isHdmiCecVolumeControlEnabled()) { return; } synchronized (mLock) { @@ -1009,7 +1012,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // On initialization process, getAvrDeviceInfo() may return null and cause exception return; } - if (delta == 0 || !isSystemAudioActivated()) { + if (delta == 0 || !isSystemAudioActivated() || !mService.isHdmiCecVolumeControlEnabled()) { return; } @@ -1038,7 +1041,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly void changeMute(boolean mute) { assertRunOnServiceThread(); - if (getAvrDeviceInfo() == null) { + if (getAvrDeviceInfo() == null || !mService.isHdmiCecVolumeControlEnabled()) { // On initialization process, getAvrDeviceInfo() may return null and cause exception return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index d9e30250ba2d0..53f9ebcbd8ddf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -177,6 +177,10 @@ public class HdmiControlService extends SystemService { @GuardedBy("mLock") protected final ActiveSource mActiveSource = new ActiveSource(); + // Whether HDMI CEC volume control is enabled or not. + @GuardedBy("mLock") + private boolean mHdmiCecVolumeControlEnabled; + // Whether System Audio Mode is activated or not. @GuardedBy("mLock") private boolean mSystemAudioActivated = false; @@ -497,6 +501,8 @@ public class HdmiControlService extends SystemService { mPowerStatus = getInitialPowerStatus(); mProhibitMode = false; mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true); + mHdmiCecVolumeControlEnabled = readBooleanSetting( + Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true); mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); if (mCecController == null) { @@ -646,6 +652,7 @@ public class HdmiControlService extends SystemService { ContentResolver resolver = getContext().getContentResolver(); String[] settings = new String[] { Global.HDMI_CONTROL_ENABLED, + Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, @@ -674,6 +681,9 @@ public class HdmiControlService extends SystemService { case Global.HDMI_CONTROL_ENABLED: setControlEnabled(enabled); break; + case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED: + setHdmiCecVolumeControlEnabled(enabled); + break; case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED: if (isTvDeviceEnabled()) { tv().setAutoWakeup(enabled); @@ -1273,7 +1283,9 @@ public class HdmiControlService extends SystemService { } void setAudioStatus(boolean mute, int volume) { - if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) { + if (!isTvDeviceEnabled() + || !tv().isSystemAudioActivated() + || !isHdmiCecVolumeControlEnabled()) { return; } AudioManager audioManager = getAudioManager(); @@ -2186,6 +2198,24 @@ public class HdmiControlService extends SystemService { }); } + @Override + public boolean isHdmiCecVolumeControlEnabled() { + enforceAccessPermission(); + return HdmiControlService.this.isHdmiCecVolumeControlEnabled(); + } + + @Override + public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) { + enforceAccessPermission(); + runOnServiceThread(new Runnable() { + @Override + public void run() { + HdmiControlService.this.setHdmiCecVolumeControlEnabled( + isHdmiCecVolumeControlEnabled); + } + }); + } + @Override public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume, final boolean isMute) { @@ -2250,6 +2280,7 @@ public class HdmiControlService extends SystemService { pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled); pw.println("mSystemAudioActivated: " + isSystemAudioActivated()); + pw.println("mHdmiCecVolumeControlEnabled " + mHdmiCecVolumeControlEnabled); pw.decreaseIndent(); pw.println("mMhlController: "); @@ -2982,6 +3013,29 @@ public class HdmiControlService extends SystemService { } } + void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) { + assertRunOnServiceThread(); + synchronized (mLock) { + mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled; + + boolean storedValue = readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, + true); + if (storedValue != isHdmiCecVolumeControlEnabled) { + HdmiLogger.debug("Changing HDMI CEC volume control feature state: %s", + isHdmiCecVolumeControlEnabled); + writeBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, + isHdmiCecVolumeControlEnabled); + } + } + } + + boolean isHdmiCecVolumeControlEnabled() { + assertRunOnServiceThread(); + synchronized (mLock) { + return mHdmiCecVolumeControlEnabled; + } + } + boolean isProhibitMode() { synchronized (mLock) { return mProhibitMode; @@ -3022,8 +3076,12 @@ public class HdmiControlService extends SystemService { if (enabled) { enableHdmiControlService(); + setHdmiCecVolumeControlEnabled( + readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true)); return; } + + setHdmiCecVolumeControlEnabled(false); // Call the vendor handler before the service is disabled. invokeVendorCommandListenersOnControlStateChanged(false, HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); 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 a587029914a74..28887fdab00d6 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -24,6 +24,7 @@ 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_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; @@ -47,6 +48,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.ArrayList; + @SmallTest @RunWith(JUnit4.class) /** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */ @@ -167,6 +169,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { } }; + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mMyLooper = mTestLooper.getLooper(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService); mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) { @@ -717,4 +721,112 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.onHotplug(0, false); assertThat(mWokenUp).isFalse(); } + + @Test + public void giveAudioStatus_volumeEnabled() { + mMusicVolume = 50; + mMusicMaxVolume = 100; + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true); + + int volume = mHdmiControlService.getAudioManager() + .getStreamVolume(AudioManager.STREAM_MUSIC); + boolean mute = mHdmiControlService.getAudioManager() + .isStreamMute(AudioManager.STREAM_MUSIC); + int maxVolume = mHdmiControlService.getAudioManager() + .getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); + HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, + ADDR_TV, scaledVolume, mute); + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED); + + HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, + ADDR_AUDIO_SYSTEM); + mNativeWrapper.clearResultMessages(); + boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(expected); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort); + assertThat(handled).isTrue(); + } + + @Test + public void giveAudioStatus_volumeDisabled() { + mMusicVolume = 50; + mMusicMaxVolume = 100; + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true); + + int volume = mHdmiControlService.getAudioManager() + .getStreamVolume(AudioManager.STREAM_MUSIC); + boolean mute = mHdmiControlService.getAudioManager() + .isStreamMute(AudioManager.STREAM_MUSIC); + int maxVolume = mHdmiControlService.getAudioManager() + .getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); + HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, + ADDR_TV, scaledVolume, mute); + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED); + + HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, + ADDR_AUDIO_SYSTEM); + mNativeWrapper.clearResultMessages(); + boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected); + assertThat(handled).isTrue(); + } + + @Test + public void reportAudioStatus_volumeEnabled() { + mMusicVolume = 50; + mMusicMaxVolume = 100; + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true); + + int volume = mHdmiControlService.getAudioManager() + .getStreamVolume(AudioManager.STREAM_MUSIC); + boolean mute = mHdmiControlService.getAudioManager() + .isStreamMute(AudioManager.STREAM_MUSIC); + int maxVolume = mHdmiControlService.getAudioManager() + .getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); + HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, + ADDR_TV, scaledVolume, mute); + + mNativeWrapper.clearResultMessages(); + mHdmiCecLocalDeviceAudioSystem.reportAudioStatus(ADDR_TV); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(expected); + } + + @Test + public void reportAudioStatus_volumeDisabled() { + mMusicVolume = 50; + mMusicMaxVolume = 100; + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true); + + int volume = mHdmiControlService.getAudioManager() + .getStreamVolume(AudioManager.STREAM_MUSIC); + boolean mute = mHdmiControlService.getAudioManager() + .isStreamMute(AudioManager.STREAM_MUSIC); + int maxVolume = mHdmiControlService.getAudioManager() + .getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); + HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, + ADDR_TV, scaledVolume, mute); + + mNativeWrapper.clearResultMessages(); + mHdmiCecLocalDeviceAudioSystem.reportAudioStatus(ADDR_TV); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected); + } } 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 f72d622c00c35..b8394e3c16434 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import android.os.Looper; import android.os.test.TestLooper; +import android.view.KeyEvent; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -153,4 +154,75 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiCecLocalDevicePlayback.onHotplug(0, false); assertThat(mWokenUp).isFalse(); } + + @Test + @Ignore("b/151147315") + public void sendVolumeKeyEvent_up_volumeEnabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false); + + assertThat(hasSendKeyAction()).isTrue(); + } + + @Test + @Ignore("b/151147315") + public void sendVolumeKeyEvent_down_volumeEnabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false); + + assertThat(hasSendKeyAction()).isTrue(); + } + + @Test + @Ignore("b/151147315") + public void sendVolumeKeyEvent_mute_volumeEnabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, false); + + assertThat(hasSendKeyAction()).isTrue(); + } + + @Test + @Ignore("b/151147315") + public void sendVolumeKeyEvent_up_volumeDisabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, false); + + assertThat(hasSendKeyAction()).isFalse(); + } + + @Test + @Ignore("b/151147315") + public void sendVolumeKeyEvent_down_volumeDisabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false); + + assertThat(hasSendKeyAction()).isFalse(); + } + + @Test + @Ignore("b/151147315") + public void sendVolumeKeyEvent_mute_volumeDisabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, true); + mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE, false); + + assertThat(hasSendKeyAction()).isFalse(); + } + + private boolean hasSendKeyAction() { + boolean match = false; + for (HdmiCecFeatureAction action : mHdmiCecLocalDevicePlayback.mActions) { + if (action instanceof SendKeyAction) { + match = true; + break; + } + } + return match; + } } 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 039b904293957..e0bada3138e06 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -19,6 +19,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; 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; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.MESSAGE_DEVICE_VENDOR_ID; @@ -185,4 +186,64 @@ public class HdmiCecLocalDeviceTest { HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_AUDIO_SYSTEM)); assertTrue(mStandbyMessageReceived); } + + @Test + public void handleUserControlPressed_volumeUp() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + boolean result = mHdmiLocalDevice.handleUserControlPressed( + HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, + HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP)); + + assertTrue(result); + } + + @Test + public void handleUserControlPressed_volumeDown() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + boolean result = mHdmiLocalDevice.handleUserControlPressed( + HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, + HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); + + assertTrue(result); + } + + @Test + public void handleUserControlPressed_volumeMute() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + boolean result = mHdmiLocalDevice.handleUserControlPressed( + HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, + HdmiCecKeycode.CEC_KEYCODE_MUTE)); + + assertTrue(result); + } + + @Test + public void handleUserControlPressed_volumeUp_disabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + boolean result = mHdmiLocalDevice.handleUserControlPressed( + HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, + HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP)); + + assertFalse(result); + } + + @Test + public void handleUserControlPressed_volumeDown_disabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + boolean result = mHdmiLocalDevice.handleUserControlPressed( + HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, + HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); + + assertFalse(result); + } + + @Test + public void handleUserControlPressed_volumeMute_disabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + boolean result = mHdmiLocalDevice.handleUserControlPressed( + HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, + HdmiCecKeycode.CEC_KEYCODE_MUTE)); + + assertFalse(result); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index fa19814f401fa..7af7a23b1ef63 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -252,4 +252,13 @@ public class HdmiControlServiceTest { assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_STANDBY); } + + @Test + public void setAndGetCecVolumeControlEnabled_isApi() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse(); + + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isTrue(); + } }