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
17package com.android.server.hdmi;
18
19import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT;
20import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS;
21import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED;
22import static com.android.server.hdmi.HdmiConfig.IRT_MS;
23
24import 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 */
35final 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     */
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     */
69    public static int scaleToCustomVolume(int cecVolume, int scale) {
70        return (cecVolume * scale) / MAX_VOLUME;
71    }
72
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
84    private void updateLastKeyUpdateTime() {
85        mLastKeyUpdateTime = System.currentTimeMillis();
86    }
87
88    @Override
89    boolean start() {
90        mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS;
91        sendVolumeKeyPressed();
92        resetTimer();
93        return true;
94    }
95
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
103    private void resetTimer() {
104        mActionTimer.clearTimerMessage();
105        addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS);
106    }
107
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
119    private void sendVolumeKeyReleased() {
120        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
121                getSourceAddress(), mAvrAddress));
122        mSentKeyPressed = false;
123    }
124
125    @Override
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
140    private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
141        byte params[] = cmd.getParams();
142        boolean mute = (params[0] & 0x80) == 0x80;
143        int volume = params[0] & 0x7F;
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
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
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
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
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