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