Implement soundbar volume UI for TV panels.

This is done by adopting adjust-only absolute volume behavior when the
connected amplifier is able to send <Report Audio Status>. Adjust-only
absolute volume behavior supports volume tracking and automatically
displays volume UI when the volume level is updated in AudioService.

In HdmiControlService, adoption of this behavior is implemented as a
special case in the logic for adopting absolute volume behavior:
- Absolute volume behavior is currently adopted when a TV panel or
  playback device is able to track AND set the percentage volume level
  of the System Audio device.
- Adjust-only absolute volume behavior will be adopted when a TV panel
  can track, but not set, the volume level of the System Audio device.

The only behavioral difference in HdmiControlService when using
adjust-only AVB, compared to regular AVB, is that it does not send <Set
Audio Volume Level> to the System Audio device.

Bug: 240663266
Test: atest TvToAudioSystemAvbTest PlaybackDeviceToTvAvbTest PlaybackDeviceToAudioSystemAvbTest
Change-Id: I05465cd0eab2f3c3464bf41c1194cdc3be7af4bf
This commit is contained in:
Yan Han
2023-04-13 14:39:21 +02:00
parent 71afeaa6f3
commit 556aa155a0
12 changed files with 423 additions and 90 deletions

View File

@@ -17,10 +17,13 @@
package com.android.server.hdmi;
/**
* Action to query and track the audio status of the System Audio device when enabling or using
* absolute volume behavior. Must be removed when AVB is disabled. Performs two main functions:
* Action to query and track the audio status of the System Audio device when using
* absolute volume behavior, or adjust-only absolute volume behavior. Must be removed when
* neither behavior is used.
*
* Performs two main functions:
* 1. When enabling AVB: queries the starting audio status of the System Audio device and
* enables the feature upon receiving a response.
* adopts the appropriate volume behavior upon receiving a response.
* 2. While AVB is enabled: monitors <Report Audio Status> messages from the System Audio device and
* notifies AudioService if the audio status changes.
*/

View File

@@ -61,4 +61,15 @@ public interface AudioDeviceVolumeManagerWrapper {
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment);
/**
* Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior(
* AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
*/
void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment);
}

View File

@@ -67,4 +67,15 @@ public class DefaultAudioDeviceVolumeManagerWrapper
mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
vclistener, handlesVolumeAdjustment);
}
@Override
public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeAdjustOnlyBehavior(device, volume,
executor, vclistener, handlesVolumeAdjustment);
}
}

View File

@@ -1012,17 +1012,23 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
action.start();
}
@ServiceThreadOnly
void addAvbAudioStatusAction(int targetAddress) {
assertRunOnServiceThread();
if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
}
}
@ServiceThreadOnly
void removeAvbAudioStatusAction() {
assertRunOnServiceThread();
removeAction(AbsoluteVolumeAudioStatusAction.class);
}
@ServiceThreadOnly
void updateAvbVolume(int volumeIndex) {
assertRunOnServiceThread();
for (AbsoluteVolumeAudioStatusAction action :
getActions(AbsoluteVolumeAudioStatusAction.class)) {
action.updateVolume(volumeIndex);

View File

@@ -66,7 +66,7 @@ import java.util.stream.Collectors;
/**
* Represent a logical device of type TV residing in Android system.
*/
final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDeviceTv";
// Whether ARC is available or not. "true" means that ARC is established between TV and

View File

@@ -4230,14 +4230,18 @@ public class HdmiControlService extends SystemService {
if (avbAudioOutputDevice == null) {
return false;
}
return getDeviceVolumeBehavior(avbAudioOutputDevice)
== AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
@AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior =
getDeviceVolumeBehavior(avbAudioOutputDevice);
return deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
|| deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY;
}
private AudioDeviceAttributes getAvbAudioOutputDevice() {
if (isTvDevice()) {
if (tv() != null) {
return tv().getSystemAudioOutputDevice();
} else if (isPlaybackDevice()) {
} else if (playback() != null) {
return AUDIO_OUTPUT_DEVICE_HDMI;
} else {
return null;
@@ -4245,24 +4249,24 @@ public class HdmiControlService extends SystemService {
}
/**
* Checks the conditions for Absolute Volume Behavior (AVB), and enables or disables the feature
* if necessary. AVB 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.
* This method is responsible for adopting or disabling absolute volume behavior and
* adjust-only absolute volume behavior in AudioService. These volume behaviors are adopted on
* specific audio output devices: HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs.
*
* AVB 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:
* This method enables absolute volume behavior on a Playback device or TV panel when it is
* playing audio on an external device (the System Audio device) that supports the feature.
* This allows the volume level of the System Audio device to be tracked and set by Android.
*
* Absolute volume behavior requires the following 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 AVB (i.e. it supports <Set Audio Volume Level>)
* 4. The System Audio device supports the <Set Audio Volume Level> message
*
* If not all of these conditions are met, this method disables AVB 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 AVB upon obtaining the audio status.
* This method enables adjust-only absolute volume behavior on TV panels when conditions
* 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of
* the System Audio device and display numeric volume UI for it, even if the System Audio device
* does not support <Set Audio Volume Level>.
*/
@ServiceThreadOnly
void checkAndUpdateAbsoluteVolumeBehavior() {
@@ -4280,7 +4284,7 @@ public class HdmiControlService extends SystemService {
// (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
// TV is the System Audio Device instead.)
if (!isSystemAudioActivated()) {
disableAbsoluteVolumeBehavior();
switchToFullVolumeBehavior();
return;
}
} else if (isPlaybackDevice() && playback() != null) {
@@ -4290,75 +4294,100 @@ public class HdmiControlService extends SystemService {
return;
}
HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo(
localCecDevice.findAudioReceiverAddress());
@AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
getDeviceVolumeBehavior(getAvbAudioOutputDevice());
// Condition 2: Already using full or absolute volume behavior
boolean alreadyUsingFullOrAbsoluteVolume =
currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
|| currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
(currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL)
|| (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)
|| (currentVolumeBehavior
== AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
// Condition 3: CEC volume is enabled
boolean cecVolumeEnabled =
getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
disableAbsoluteVolumeBehavior();
switchToFullVolumeBehavior();
return;
}
// Check for safety: if the System Audio device is a candidate for AVB, we should already
// have received messages from it to trigger the other conditions.
if (systemAudioDeviceInfo == null) {
disableAbsoluteVolumeBehavior();
switchToFullVolumeBehavior();
return;
}
// Condition 4: The System Audio device supports AVB (i.e. <Set Audio Volume Level>).
// Condition 4: The System Audio device supports <Set Audio Volume Level>
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
case DeviceFeatures.FEATURE_SUPPORTED:
if (!isAbsoluteVolumeBehaviorEnabled()) {
// Start an action that will call {@link #enableAbsoluteVolumeBehavior}
if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
// If we're currently using adjust-only absolute volume behavior, switch to
// full volume behavior until we successfully adopt absolute volume behavior
switchToFullVolumeBehavior();
// Start an action that will call enableAbsoluteVolumeBehavior
// once the System Audio device sends <Report Audio Status>
localCecDevice.addAvbAudioStatusAction(
systemAudioDeviceInfo.getLogicalAddress());
}
return;
case DeviceFeatures.FEATURE_NOT_SUPPORTED:
disableAbsoluteVolumeBehavior();
// TVs may adopt adjust-only absolute volume behavior if condition 4 isn't met.
// This allows the device to display numeric volume UI for the System Audio device.
if (tv() != null) {
if (currentVolumeBehavior
!= AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
// If we're currently using absolute volume behavior, switch to full volume
// behavior until we successfully adopt adjust-only absolute volume behavior
switchToFullVolumeBehavior();
// Start an action that will call enableAbsoluteVolumeBehavior
// once the System Audio device sends <Report Audio Status>
localCecDevice.addAvbAudioStatusAction(
systemAudioDeviceInfo.getLogicalAddress());
}
} else {
switchToFullVolumeBehavior();
}
return;
case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
disableAbsoluteVolumeBehavior();
switchToFullVolumeBehavior();
localCecDevice.querySetAudioVolumeLevelSupport(
systemAudioDeviceInfo.getLogicalAddress());
return;
default:
return;
}
}
private void disableAbsoluteVolumeBehavior() {
if (isPlaybackDevice()) {
/**
* Switches to full volume behavior, if either absolute or adjust-only absolute volume behavior
* are currently used. Removes the action for handling volume updates for these behaviors.
*/
private void switchToFullVolumeBehavior() {
if (playback() != null) {
playback().removeAvbAudioStatusAction();
} else if (isTvDevice()) {
} else if (tv() != null) {
tv().removeAvbAudioStatusAction();
}
AudioDeviceAttributes device = getAvbAudioOutputDevice();
if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
int volumeBehavior = getDeviceVolumeBehavior(device);
if (volumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
|| volumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
getAudioManager().setDeviceVolumeBehavior(device,
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
}
/**
* Enables absolute volume behavior. Should only be called when all the conditions for
* AVB are met (see {@link #checkAndUpdateAbsoluteVolumeBehavior}).
* Enables absolute volume behavior or adjust-only absolute volume behavior. Should only be
* called when the conditions for one of these behaviors is met -
* see {@link #checkAndUpdateAbsoluteVolumeBehavior}.
*
* @param audioStatus The initial audio status to set the audio output device to
*/
void enableAbsoluteVolumeBehavior(AudioStatus audioStatus) {
HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
localDevice.findAudioReceiverAddress());
HdmiDeviceInfo systemAudioDevice = getDeviceInfo(localDevice.findAudioReceiverAddress());
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setMuted(audioStatus.getMute())
.setVolumeIndex(audioStatus.getVolume())
@@ -4371,9 +4400,20 @@ public class HdmiControlService extends SystemService {
// AudioService sets the volume of the stream and device based on the input VolumeInfo
// when enabling absolute volume behavior, but not the mute state
notifyAvbMuteChange(audioStatus.getMute());
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
mAbsoluteVolumeChangedListener, true);
// If <Set Audio Volume Level> is supported, enable absolute volume behavior.
// Otherwise, enable adjust-only AVB on TVs only.
if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
== DeviceFeatures.FEATURE_SUPPORTED) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
mAbsoluteVolumeChangedListener, true);
} else if (tv() != null) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
mAbsoluteVolumeChangedListener, true);
}
}
private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
@@ -4407,6 +4447,14 @@ public class HdmiControlService extends SystemService {
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
// Do nothing if the System Audio device does not support <Set Audio Volume Level>
if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
!= DeviceFeatures.FEATURE_SUPPORTED) {
return;
}
// Send <Set Audio Volume Level> to notify the System Audio device of the volume change
int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,

View File

@@ -74,7 +74,7 @@ import java.util.Collections;
* (TV, Audio System): {@link TvToAudioSystemAvbTest}
*/
public abstract class BaseAbsoluteVolumeBehaviorTest {
private HdmiControlService mHdmiControlService;
protected HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDevice mHdmiCecLocalDevice;
private FakeHdmiCecConfig mHdmiCecConfig;
@@ -282,6 +282,20 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
protected void enableAdjustOnlyAbsoluteVolumeBehavior() {
mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
receiveReportAudioStatus(
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
}
protected void verifyGiveAudioStatusNeverSent() {
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
HdmiCecMessageBuilder.buildGiveAudioStatus(
@@ -462,6 +476,7 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
@Test
public void avbEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
// Initial <Report Audio Status> has volume=50 and mute=false

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 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.media.AudioManager;
import org.junit.Test;
/**
* Base class for tests for absolute volume behavior on Playback devices. Contains tests that are
* relevant to Playback devices but not to TVs.
*
* Subclasses contain tests for the following pairs of (local device, System Audio device):
* (Playback, TV): {@link PlaybackDeviceToTvAvbTest}
* (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvbTest}
*/
public abstract class BasePlaybackDeviceAvbTest extends BaseAbsoluteVolumeBehaviorTest {
@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;
}
/**
* Unlike TVs, Playback devices don't start the process for adopting adjust-only AVB
* if the System Audio device doesn't support <Set Audio Volume Level>
*/
@Test
public void savlNotSupported_allOtherConditionsMet_giveAudioStatusNotSent() {
mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
verifyGiveAudioStatusNeverSent();
}
}

View File

@@ -188,6 +188,17 @@ public class FakeAudioFramework {
boolean handlesVolumeAdjustment) {
setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
@Override
public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
setVolumeBehaviorHelper(device,
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
}
}
/**

View File

@@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.platform.test.annotations.Presubmit;
@@ -42,27 +41,7 @@ import java.util.Arrays;
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class PlaybackDeviceToAudioSystemAvbTest extends BaseAbsoluteVolumeBehaviorTest {
@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;
}
public class PlaybackDeviceToAudioSystemAvbTest extends BasePlaybackDeviceAvbTest {
@Override
protected int getSystemAudioDeviceLogicalAddress() {

View File

@@ -23,7 +23,6 @@ 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.media.AudioManager;
import android.platform.test.annotations.Presubmit;
@@ -43,27 +42,7 @@ import java.util.Collections;
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class PlaybackDeviceToTvAvbTest extends BaseAbsoluteVolumeBehaviorTest {
@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;
}
public class PlaybackDeviceToTvAvbTest extends BasePlaybackDeviceAvbTest {
@Override
protected int getSystemAudioDeviceLogicalAddress() {

View File

@@ -16,12 +16,30 @@
package com.android.server.hdmi;
import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
import android.media.VolumeInfo;
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;
@@ -63,4 +81,185 @@ public class TvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehaviorTest {
protected int getSystemAudioDeviceType() {
return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
}
/**
* TVs start the process for adopting adjust-only AVB if the System Audio device doesn't
* support <Set Audio Volume Level>
*/
@Test
public void savlNotSupported_allOtherConditionsMet_giveAudioStatusSent() {
mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
verifyGiveAudioStatusNeverSent();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
verifyGiveAudioStatusSent();
}
@Test
public void savlNotSupported_systemAudioDeviceSendsReportAudioStatus_adjustOnlyAvbEnabled() {
mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
// Adjust-only AVB should not be enabled before receiving <Report Audio Status>
assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
receiveReportAudioStatus(20, false);
assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
eq(getAudioOutputDevice()),
eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setVolumeIndex(20)
.setMuted(false)
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
any(), any(), anyBoolean());
}
@Test
public void avbEnabled_savlNotSupported_receiveReportAudioStatus_switchToAdjustOnlyAvb() {
enableAbsoluteVolumeBehavior();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
receiveReportAudioStatus(40, true);
assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
eq(getAudioOutputDevice()),
eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setVolumeIndex(40)
.setMuted(true)
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
any(), any(), anyBoolean());
}
@Test
public void avbEnabled_savlFeatureAborted_receiveReportAudioStatus_switchToAdjustOnlyAvb() {
enableAbsoluteVolumeBehavior();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
mTestLooper.dispatchAll();
receiveReportAudioStatus(40, true);
assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
eq(getAudioOutputDevice()),
eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setVolumeIndex(40)
.setMuted(true)
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()),
any(), any(), anyBoolean());
}
@Test
public void adjustOnlyAvbEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
enableAdjustOnlyAbsoluteVolumeBehavior();
// 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());
// Volume not within range [0, 100]: sets neither volume nor mute
receiveReportAudioStatus(127, true);
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
anyInt());
verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
anyInt());
}
@Test
public void adjustOnlyAvbEnabled_audioDeviceVolumeAdjusted_sendsUcpAndGiveAudioStatus() {
enableAdjustOnlyAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
getAudioOutputDevice(),
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build(),
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 adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
enableAdjustOnlyAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
getAudioOutputDevice(),
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setVolumeIndex(20)
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()
);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).isEmpty();
}
}