1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.hdmi; 18 19 /** 20 * Action to query and track the audio status of the System Audio device when using 21 * absolute volume behavior, or adjust-only absolute volume behavior. Must be removed when 22 * neither behavior is used. 23 * 24 * Performs two main functions: 25 * 1. When enabling AVB: queries the starting audio status of the System Audio device and 26 * adopts the appropriate volume behavior upon receiving a response. 27 * 2. While AVB is enabled: monitors <Report Audio Status> messages from the System Audio device and 28 * notifies AudioService if the audio status changes. 29 */ 30 final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction { 31 private static final String TAG = "AbsoluteVolumeAudioStatusAction"; 32 33 private int mInitialAudioStatusRetriesLeft = 2; 34 35 private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1; 36 private static final int STATE_MONITOR_AUDIO_STATUS = 2; 37 38 private final int mTargetAddress; 39 40 private AudioStatus mLastAudioStatus; 41 AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress)42 AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) { 43 super(source); 44 mTargetAddress = targetAddress; 45 } 46 47 @Override start()48 boolean start() { 49 mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS; 50 sendGiveAudioStatus(); 51 return true; 52 } 53 updateVolume(int volumeIndex)54 void updateVolume(int volumeIndex) { 55 mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute()); 56 } 57 sendGiveAudioStatus()58 private void sendGiveAudioStatus() { 59 addTimer(mState, HdmiConfig.TIMEOUT_MS); 60 sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress)); 61 } 62 63 @Override processCommand(HdmiCecMessage cmd)64 boolean processCommand(HdmiCecMessage cmd) { 65 switch (cmd.getOpcode()) { 66 case Constants.MESSAGE_REPORT_AUDIO_STATUS: 67 return handleReportAudioStatus(cmd); 68 } 69 70 return false; 71 } 72 73 /** 74 * If AVB has been enabled, send <Give Audio Status> and notify AudioService of the response. 75 */ requestAndUpdateAudioStatus()76 void requestAndUpdateAudioStatus() { 77 if (mState == STATE_MONITOR_AUDIO_STATUS) { 78 sendGiveAudioStatus(); 79 } 80 } 81 handleReportAudioStatus(HdmiCecMessage cmd)82 private boolean handleReportAudioStatus(HdmiCecMessage cmd) { 83 if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) { 84 return false; 85 } 86 87 boolean mute = HdmiUtils.isAudioStatusMute(cmd); 88 int volume = HdmiUtils.getAudioStatusVolume(cmd); 89 90 // If the volume is out of range, report it as handled and ignore the message. 91 // According to the spec, such values are either reserved or indicate an unknown volume. 92 if (volume == Constants.UNKNOWN_VOLUME) { 93 return true; 94 } 95 96 AudioStatus audioStatus = new AudioStatus(volume, mute); 97 if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) { 98 localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus); 99 mState = STATE_MONITOR_AUDIO_STATUS; 100 } else if (mState == STATE_MONITOR_AUDIO_STATUS) { 101 // Update volume in AudioService if it has changed since the last <Report Audio Status> 102 boolean updateVolume = audioStatus.getVolume() != mLastAudioStatus.getVolume(); 103 if (updateVolume) { 104 localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume()); 105 } 106 107 // Update mute in AudioService if any of the following conditions are met: 108 // - The mute status changed 109 // - The volume changed - we need to make sure mute is set correctly afterwards, since 110 // setting volume can affect mute status as well as a side effect. 111 // - We're a TV panel - we want to trigger volume UI on TV panels, so that the user 112 // always gets visual feedback when they attempt to adjust the AVR's volume/mute. 113 if ((audioStatus.getMute() != mLastAudioStatus.getMute()) 114 || updateVolume 115 || localDevice().getService().isTvDevice()) { 116 localDevice().getService().notifyAvbMuteChange(audioStatus.getMute()); 117 } 118 } 119 mLastAudioStatus = audioStatus; 120 121 return true; 122 } 123 124 @Override handleTimerEvent(int state)125 void handleTimerEvent(int state) { 126 if (mState != state) { 127 return; 128 } else if (mInitialAudioStatusRetriesLeft > 0) { 129 mInitialAudioStatusRetriesLeft--; 130 sendGiveAudioStatus(); 131 } 132 } 133 } 134