VolumeControlAction.java revision 2e8f1b6399089626b4f0249427626ba6e63a62ef
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.IRT_MS;
20import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT;
21import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS;
22import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED;
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 mSentKeyPressed;
49
50    /**
51     * Scale a custom volume value to cec volume scale.
52     *
53     * @param volume volume value in custom scale
54     * @param scale scale of volume (max volume)
55     * @return a volume scaled to cec volume range
56     */
57    public static int scaleToCecVolume(int volume, int scale) {
58        return (volume * MAX_VOLUME) / scale;
59    }
60
61    /**
62     * Scale a cec volume which is in range of 0 to 100 to custom volume level.
63     *
64     * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100]
65     * @param scale scale of custom volume (max volume)
66     * @return a volume scaled to custom volume range
67     */
68    public static int scaleToCustomVolume(int cecVolume, int scale) {
69        return (cecVolume * scale) / MAX_VOLUME;
70    }
71
72    VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp) {
73        super(source);
74        mAvrAddress = avrAddress;
75        mIsVolumeUp = isVolumeUp;
76        mLastAvrVolume = UNKNOWN_AVR_VOLUME;
77        mSentKeyPressed = false;
78
79        updateLastKeyUpdateTime();
80    }
81
82    private void updateLastKeyUpdateTime() {
83        mLastKeyUpdateTime = System.currentTimeMillis();
84    }
85
86    @Override
87    boolean start() {
88        mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS;
89        sendVolumeKeyPressed();
90        resetTimer();
91        return true;
92    }
93
94    private void sendVolumeKeyPressed() {
95        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
96                mIsVolumeUp ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
97                        : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
98        mSentKeyPressed = true;
99    }
100
101    private void resetTimer() {
102        mActionTimer.clearTimerMessage();
103        addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS);
104    }
105
106    void handleVolumeChange(boolean isVolumeUp) {
107        if (mIsVolumeUp != isVolumeUp) {
108            HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp);
109            sendVolumeKeyReleased();
110            mIsVolumeUp = isVolumeUp;
111        }
112        updateLastKeyUpdateTime();
113    }
114
115    private void sendVolumeKeyReleased() {
116        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
117                getSourceAddress(), mAvrAddress));
118        mSentKeyPressed = false;
119    }
120
121    @Override
122    boolean processCommand(HdmiCecMessage cmd) {
123        if (mState != STATE_WAIT_FOR_NEXT_VOLUME_PRESS || cmd.getSource() != mAvrAddress) {
124            return false;
125        }
126
127        switch (cmd.getOpcode()) {
128            case MESSAGE_REPORT_AUDIO_STATUS:
129                return handleReportAudioStatus(cmd);
130            case MESSAGE_FEATURE_ABORT:
131                return handleFeatureAbort(cmd);
132            default:
133                return false;
134        }
135    }
136
137    private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
138        byte params[] = cmd.getParams();
139        boolean mute = (params[0] & 0x80) == 0x80;
140        int volume = params[0] & 0x7F;
141        mLastAvrVolume = volume;
142        if (shouldUpdateAudioVolume(mute)) {
143            HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume);
144            tv().setAudioStatus(mute, volume);
145        }
146        return true;
147    }
148
149    private boolean shouldUpdateAudioVolume(boolean mute) {
150        // Do nothing if in mute.
151        if (mute) {
152            return true;
153        }
154
155        // Update audio status if current volume position is edge of volume bar,
156        // i.e max or min volume.
157        AudioManager audioManager = tv().getService().getAudioManager();
158        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
159        if (mIsVolumeUp) {
160            int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
161            return currentVolume == maxVolume;
162        } else {
163            return currentVolume == 0;
164        }
165    }
166
167    private boolean handleFeatureAbort(HdmiCecMessage cmd) {
168        int originalOpcode = cmd.getParams()[0] & 0xFF;
169        // Since it sends <User Control Released> only when it finishes this action,
170        // it takes care of <User Control Pressed> only here.
171        if (originalOpcode == MESSAGE_USER_CONTROL_PRESSED) {
172            finish();
173            return true;
174        }
175        return false;
176    }
177
178    @Override
179    protected void clear() {
180        super.clear();
181        if (mSentKeyPressed) {
182            sendVolumeKeyReleased();
183        }
184        if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) {
185            tv().setAudioStatus(false, mLastAvrVolume);
186            mLastAvrVolume = UNKNOWN_AVR_VOLUME;
187        }
188    }
189
190    @Override
191    void handleTimerEvent(int state) {
192        if (state != STATE_WAIT_FOR_NEXT_VOLUME_PRESS) {
193            return;
194        }
195
196        if (System.currentTimeMillis() - mLastKeyUpdateTime >= IRT_MS) {
197            finish();
198        } else {
199            sendVolumeKeyPressed();
200            resetTimer();
201        }
202    }
203}
204