1 /*
2  * Copyright (C) 2014 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 import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT;
20 import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS;
21 import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED;
22 import static com.android.server.hdmi.HdmiConfig.IRT_MS;
23 
24 import android.media.AudioManager;
25 
26 /**
27  * Feature action that transmits volume change to Audio Receiver.
28  * <p>
29  * This action is created when a user pressed volume up/down. However, Android only provides a
30  * listener for delta of some volume change instead of individual key event. Also it's hard to know
31  * Audio Receiver's number of volume steps for a single volume control key. Because of this, it
32  * sends key-down event until IRT timeout happens, and it will send key-up event if no additional
33  * volume change happens; otherwise, it will send again key-down as press and hold feature does.
34  */
35 final class VolumeControlAction extends HdmiCecFeatureAction {
36     private static final String TAG = "VolumeControlAction";
37 
38     // State that wait for next volume press.
39     private static final int STATE_WAIT_FOR_NEXT_VOLUME_PRESS = 1;
40     private static final int MAX_VOLUME = 100;
41 
42     private static final int UNKNOWN_AVR_VOLUME = -1;
43 
44     private final int mAvrAddress;
45     private boolean mIsVolumeUp;
46     private long mLastKeyUpdateTime;
47     private int mLastAvrVolume;
48     private boolean mLastAvrMute;
49     private boolean mSentKeyPressed;
50 
51     /**
52      * Scale a custom volume value to cec volume scale.
53      *
54      * @param volume volume value in custom scale
55      * @param scale scale of volume (max volume)
56      * @return a volume scaled to cec volume range
57      */
scaleToCecVolume(int volume, int scale)58     public static int scaleToCecVolume(int volume, int scale) {
59         return (volume * MAX_VOLUME) / scale;
60     }
61 
62     /**
63      * Scale a cec volume which is in range of 0 to 100 to custom volume level.
64      *
65      * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100]
66      * @param scale scale of custom volume (max volume)
67      * @return a volume scaled to custom volume range
68      */
scaleToCustomVolume(int cecVolume, int scale)69     public static int scaleToCustomVolume(int cecVolume, int scale) {
70         return (cecVolume * scale) / MAX_VOLUME;
71     }
72 
VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp)73     VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp) {
74         super(source);
75         mAvrAddress = avrAddress;
76         mIsVolumeUp = isVolumeUp;
77         mLastAvrVolume = UNKNOWN_AVR_VOLUME;
78         mLastAvrMute = false;
79         mSentKeyPressed = false;
80 
81         updateLastKeyUpdateTime();
82     }
83 
updateLastKeyUpdateTime()84     private void updateLastKeyUpdateTime() {
85         mLastKeyUpdateTime = System.currentTimeMillis();
86     }
87 
88     @Override
start()89     boolean start() {
90         mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS;
91         sendVolumeKeyPressed();
92         resetTimer();
93         return true;
94     }
95 
sendVolumeKeyPressed()96     private void sendVolumeKeyPressed() {
97         sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
98                 mIsVolumeUp ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
99                         : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
100         mSentKeyPressed = true;
101     }
102 
resetTimer()103     private void resetTimer() {
104         mActionTimer.clearTimerMessage();
105         addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS);
106     }
107 
handleVolumeChange(boolean isVolumeUp)108     void handleVolumeChange(boolean isVolumeUp) {
109         if (mIsVolumeUp != isVolumeUp) {
110             HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp);
111             sendVolumeKeyReleased();
112             mIsVolumeUp = isVolumeUp;
113             sendVolumeKeyPressed();
114             resetTimer();
115         }
116         updateLastKeyUpdateTime();
117     }
118 
sendVolumeKeyReleased()119     private void sendVolumeKeyReleased() {
120         sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
121                 getSourceAddress(), mAvrAddress));
122         mSentKeyPressed = false;
123     }
124 
125     @Override
processCommand(HdmiCecMessage cmd)126     boolean processCommand(HdmiCecMessage cmd) {
127         if (mState != STATE_WAIT_FOR_NEXT_VOLUME_PRESS || cmd.getSource() != mAvrAddress) {
128             return false;
129         }
130 
131         switch (cmd.getOpcode()) {
132             case MESSAGE_REPORT_AUDIO_STATUS:
133                 return handleReportAudioStatus(cmd);
134             case MESSAGE_FEATURE_ABORT:
135                 return handleFeatureAbort(cmd);
136         }
137         return false;
138     }
139 
handleReportAudioStatus(HdmiCecMessage cmd)140     private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
141         byte params[] = cmd.getParams();
142         boolean mute = HdmiUtils.isAudioStatusMute(cmd);
143         int volume = HdmiUtils.getAudioStatusVolume(cmd);
144         mLastAvrVolume = volume;
145         mLastAvrMute = mute;
146         if (shouldUpdateAudioVolume(mute)) {
147             HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume);
148             tv().setAudioStatus(mute, volume);
149             mLastAvrVolume = UNKNOWN_AVR_VOLUME;
150             mLastAvrMute = false;
151         }
152         return true;
153     }
154 
shouldUpdateAudioVolume(boolean mute)155     private boolean shouldUpdateAudioVolume(boolean mute) {
156         // Do nothing if in mute.
157         if (mute) {
158             return true;
159         }
160 
161         // Update audio status if current volume position is edge of volume bar,
162         // i.e max or min volume.
163         AudioManager audioManager = tv().getService().getAudioManager();
164         int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
165         if (mIsVolumeUp) {
166             int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
167             return currentVolume == maxVolume;
168         } else {
169             return currentVolume == 0;
170         }
171     }
172 
handleFeatureAbort(HdmiCecMessage cmd)173     private boolean handleFeatureAbort(HdmiCecMessage cmd) {
174         int originalOpcode = cmd.getParams()[0] & 0xFF;
175         // Since it sends <User Control Released> only when it finishes this action,
176         // it takes care of <User Control Pressed> only here.
177         if (originalOpcode == MESSAGE_USER_CONTROL_PRESSED) {
178             finish();
179             return true;
180         }
181         return false;
182     }
183 
184     @Override
clear()185     protected void clear() {
186         super.clear();
187         if (mSentKeyPressed) {
188             sendVolumeKeyReleased();
189         }
190         if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) {
191             tv().setAudioStatus(mLastAvrMute, mLastAvrVolume);
192             mLastAvrVolume = UNKNOWN_AVR_VOLUME;
193             mLastAvrMute = false;
194         }
195     }
196 
197     @Override
handleTimerEvent(int state)198     void handleTimerEvent(int state) {
199         if (state != STATE_WAIT_FOR_NEXT_VOLUME_PRESS) {
200             return;
201         }
202 
203         if (System.currentTimeMillis() - mLastKeyUpdateTime >= IRT_MS) {
204             finish();
205         } else {
206             sendVolumeKeyPressed();
207             resetTimer();
208         }
209     }
210 }
211