blob: c8235d08d83117fe9071e30a060feb75331a37dc [file] [log] [blame]
/*
* 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 android.hdmicec.cts;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
import org.junit.Before;
import org.junit.Test;
/**
* Abstract base class for tests for Absolute Volume Control (AVC). Subclasses must call
* this class's constructor to specify the device type of the DUT and the System Audio device.
* The three valid pairs of (DUT type, System Audio device type) for AVC are as follows:
* (Playback, TV); (Playback, Audio System); (TV, Audio System).
*
* Currently, it is only feasible to test the case where the DUT is a playback device and the
* System Audio device is a TV. This is because the CEC adapter responds <Feature Abort> to
* <Set Audio Volume Level> when it is started as an Audio System.
*/
public abstract class BaseHdmiCecAbsoluteVolumeControlTest extends BaseHdmiCecCtsTest {
/**
* Constructor. The test device type and client params (determining the client's device type)
* passed in here determine the behavior of the tests.
*/
public BaseHdmiCecAbsoluteVolumeControlTest(@HdmiCecConstants.CecDeviceType int testDeviceType,
String... clientParams) {
super(testDeviceType, clientParams);
}
/**
* Returns the audio output device being used.
*/
public int getAudioOutputDevice() {
if (mTestDeviceType == HdmiCecConstants.CEC_DEVICE_TYPE_TV) {
return HdmiCecConstants.DEVICE_OUT_HDMI_ARC;
} else {
return HdmiCecConstants.DEVICE_OUT_HDMI;
}
}
@Before
@Override
public void setUp() throws Exception {
super.setUp();
// This setting must be enabled to use AVC. Start with it disabled to ensure that we can
// control when the AVC initiation process starts.
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_DISABLED);
// Disable and enable CEC on the DUT to clear its knowledge of device feature support.
// If the DUT isn't a TV, simulate a connected sink as well.
if (mTestDeviceType == HdmiCecConstants.CEC_DEVICE_TYPE_TV) {
getDevice().executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0");
waitForCondition(() -> !isCecAvailable(getDevice()), "Could not disable CEC");
getDevice().executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1");
waitForCondition(() -> isCecAvailable(getDevice()), "Could not enable CEC");
} else {
simulateCecSinkConnected(getDevice(), getTargetLogicalAddress());
}
// Full volume behavior is a prerequisite for AVC. However, we cannot control this
// condition from CTS tests or shell due to missing permissions. Therefore, we run these
// tests only if it is already true.
assumeTrue(isFullVolumeDevice(getAudioOutputDevice()));
}
/**
* Requires the device to be able to adopt CEC 2.0 so that it sends <Give Features>.
*
* Tests that the DUT enables and disables AVC in response to changes in the System Audio
* device's support for <Set Audio Volume Level>. In this test, this support status is
* communicated through <Report Features> messages.
*/
@Test
public void testEnableDisableAvc_cec20_triggeredByReportFeatures() throws Exception {
// Enable CEC 2.0
setCec20();
// Enable CEC volume
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_ENABLED);
// Enable System Audio Mode if the System Audio device is an Audio System
enableSystemAudioModeIfApplicable();
// Since CEC 2.0 is enabled, DUT should also use <Give Features> to query AVC support
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_FEATURES);
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.SET_AUDIO_VOLUME_LEVEL);
// System Audio device reports support for <Set Audio Volume Level> via <Report Features>
sendReportFeatures(true);
// DUT queries audio status
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
checkAbsoluteVolumeControlStatus(false);
// DUT receives audio status
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(50));
checkAbsoluteVolumeControlStatus(true);
// System Audio device reports no support for <Set Audio Volume Level>
sendReportFeatures(false);
checkAbsoluteVolumeControlStatus(false);
}
/**
* Tests that the DUT enables and disables AVC in response to changes in the System Audio
* device's support for <Set Audio Volume Level>. In this test, this support status is
* communicated through (the lack of) <Feature Abort> responses to <Set Audio Volume Level>.
*/
@Test
public void testEnableDisableAvc_triggeredByAvcSupportChanged() throws Exception {
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfApplicable();
// DUT queries AVC support by sending <Set Audio Volume Level>
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.SET_AUDIO_VOLUME_LEVEL);
// System Audio device does not respond with <Feature Abort>. DUT queries audio status
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
checkAbsoluteVolumeControlStatus(false);
// DUT receives audio status
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(50));
checkAbsoluteVolumeControlStatus(true);
// System Audio device responds to <Set Audio Volume Level> with
// <Feature Abort>[Unrecognized opcode]
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.FEATURE_ABORT,
CecMessage.formatParams(CecOperand.SET_AUDIO_VOLUME_LEVEL + "00"));
checkAbsoluteVolumeControlStatus(false);
}
/**
* Tests that the DUT enables and disables AVC in response to CEC volume control being
* enabled or disabled.
*/
@Test
public void testEnableAndDisableAVC_triggeredByVolumeControlSettingChange() throws Exception {
enableSystemAudioModeIfApplicable();
// System audio device reports support for <Set Audio Volume Level>
sendReportFeatures(true);
// Enable CEC volume
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_ENABLED);
// DUT queries audio status
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
checkAbsoluteVolumeControlStatus(false);
// DUT receives audio status
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(50));
checkAbsoluteVolumeControlStatus(true);
// CEC volume control is disabled on the DUT
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_DISABLED);
checkAbsoluteVolumeControlStatus(false);
}
/**
* Tests that the DUT enables and disables AVC in response to System Audio mode being
* enabled or disabled.
*
* Only valid when the System Audio device is an Audio System.
*/
@Test
public void testEnableDisableAvc_triggeredBySystemAudioModeChange() throws Exception {
assumeTrue("Skipping this test for this setup because the System Audio device "
+ "is not an Audio System.",
hdmiCecClient.getSelfDevice().getDeviceType()
== HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM);
// System audio device reports support for <Set Audio Volume Level>
sendReportFeatures(true);
// CEC volume control is enabled on the DUT
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_ENABLED);
// Enable System Audio Mode
broadcastSystemAudioModeMessage(true);
// DUT queries audio status
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
checkAbsoluteVolumeControlStatus(false);
// DUT receives audio status
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(50));
checkAbsoluteVolumeControlStatus(true);
// System Audio Mode is disabled
broadcastSystemAudioModeMessage(false);
checkAbsoluteVolumeControlStatus(false);
}
/**
* Tests that the DUT sends the correct CEC messages when AVC is enabled and Android
* initiates volume changes.
*/
@Test
public void testOutgoingVolumeUpdates() throws Exception {
// Enable AVC
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfApplicable();
sendReportFeatures(true);
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(50));
checkAbsoluteVolumeControlStatus(true);
// Calling AudioManager#setStreamVolume should cause the DUT to send
// <Set Audio Volume Level> with the new volume level as a parameter
AudioManagerHelper.setDeviceVolume(getDevice(), 80);
String setAudioVolumeLevelMessage = hdmiCecClient.checkExpectedOutput(
hdmiCecClient.getSelfDevice(), CecOperand.SET_AUDIO_VOLUME_LEVEL);
assertThat(CecMessage.getParams(setAudioVolumeLevelMessage)).isEqualTo(80);
// Calling AudioManager#adjustStreamVolume should cause the DUT to send
// <User Control Pressed>, <User Control Released>, and <Give Audio Status>
AudioManagerHelper.raiseVolume(getDevice());
String userControlPressedMessage = hdmiCecClient.checkExpectedOutput(
hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
assertThat(CecMessage.getParams(userControlPressedMessage))
.isEqualTo(HdmiCecConstants.CEC_KEYCODE_VOLUME_UP);
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.USER_CONTROL_RELEASED);
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
}
/**
* Tests that the DUT notifies AudioManager when it receives <Report Audio Status> from the
* System Audio device.
*/
@Test
public void testIncomingVolumeUpdates() throws Exception {
// Enable AVC
setSettingsValue(HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
HdmiCecConstants.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfApplicable();
sendReportFeatures(true);
hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
CecOperand.GIVE_AUDIO_STATUS);
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(50)); // Volume 50, mute off
checkAbsoluteVolumeControlStatus(true);
// Volume and mute status should match the initial <Report Audio Status>
assertApproximateDeviceVolumeAndMute(50, false);
// Test an incoming <Report Audio Status> that does not mute the device
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(90)); // Volume 90, mute off
assertApproximateDeviceVolumeAndMute(90, false);
// Test an incoming <Report Audio Status> that mutes the device
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_AUDIO_STATUS,
CecMessage.formatParams(70 + 0b1000_0000)); // Volume 70, mute on
assertApproximateDeviceVolumeAndMute(0, true);
}
/**
* Enables System Audio Mode if the System Audio device is an Audio System.
*/
private void enableSystemAudioModeIfApplicable() throws Exception {
if (hdmiCecClient.getSelfDevice() == LogicalAddress.AUDIO_SYSTEM) {
broadcastSystemAudioModeMessage(true);
}
}
/**
* Has the CEC client broadcast a message enabling or disabling System Audio Mode.
*/
private void broadcastSystemAudioModeMessage(boolean val) throws Exception {
hdmiCecClient.sendCecMessage(
hdmiCecClient.getSelfDevice(),
LogicalAddress.BROADCAST,
CecOperand.SET_SYSTEM_AUDIO_MODE,
CecMessage.formatParams(val ? 1 : 0));
}
/**
* Has the CEC client send a <Report Features> message expressing support or lack of support for
* <Set Audio Volume Level>.
*/
private void sendReportFeatures(boolean setAudioVolumeLevelSupport) throws Exception {
String deviceTypeNibble = hdmiCecClient.getSelfDevice() == LogicalAddress.TV
? "80" : "08";
String featureSupportNibble = setAudioVolumeLevelSupport ? "01" : "00";
hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.REPORT_FEATURES,
"06:" + deviceTypeNibble + ":00:" + featureSupportNibble);
}
/**
* Checks that the status of Absolute Volume Control on the DUT to match the expected status.
* Waits and rechecks a limited number of times if the status does not currently match.
*/
private void checkAbsoluteVolumeControlStatus(boolean enabledStatus) throws Exception {
String expectedStatus = enabledStatus ? "enabled" : "disabled";
waitForCondition(() -> getAbsoluteVolumeControlStatus() == enabledStatus,
"Absolute Volume Control was not " + expectedStatus + " when expected");
}
/**
* Returns the state of Absolute Volume Control on the DUT. This is determined by the
* volume behavior of the audio output device being used for HDMI audio.
*/
private boolean getAbsoluteVolumeControlStatus() throws Exception {
return getDevice()
.executeShellCommand("dumpsys hdmi_control | grep mIsAbsoluteVolumeControlEnabled:")
.replace("mIsAbsoluteVolumeControlEnabled:", "").trim()
.equals("true");
}
/**
* Asserts that the DUT's volume (scale: [0, 100]) is within 5 points of an expected volume.
* This accounts for small differences due to rounding when converting between volume scales.
* Also asserts that the DUT's mute status is equal to {@code expectedMute}.
*
* Asserting both volume and mute at the same time saves a shell command because both are
* conveyed in a single log message.
*/
private void assertApproximateDeviceVolumeAndMute(int expectedVolume, boolean expectedMute)
throws Exception {
// Raw output is equal to volume out of 100, plus 128 if muted
// In practice, if the stream is muted, volume equals 0, so this will be at most 128
int rawOutput = AudioManagerHelper.getDutAudioVolume(getDevice());
int actualVolume = rawOutput % 128;
assertWithMessage("Expected DUT to have volume " + expectedVolume
+ " but was actually " + actualVolume)
.that(Math.abs(expectedVolume - actualVolume) <= 5)
.isTrue();
boolean actualMute = rawOutput >= 128;
String expectedMuteString = expectedMute ? "muted" : "unmuted";
String actualMuteString = actualMute ? "muted" : "unmuted";
assertWithMessage("Expected DUT to be " + expectedMuteString
+ "but was actually " + actualMuteString)
.that(expectedMute)
.isEqualTo(actualMute);
}
}