VolumeControlAction.java revision 8fa36b110be29d92a9aba070fa4666eefb14b584
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.HdmiConstants.IRT_MS;
20
21import android.hardware.hdmi.HdmiCec;
22import android.hardware.hdmi.HdmiCecMessage;
23import android.util.Slog;
24
25import com.android.internal.util.Preconditions;
26
27/**
28 * Feature action that transmits volume change to Audio Receiver.
29 * <p>
30 * This action is created when a user pressed volume up/down. However, Since Android only provides a
31 * listener for delta of some volume change, we will set a target volume, and check reported volume
32 * from Audio Receiver(AVR). If TV receives no &lt;Report Audio Status&gt; from AVR, this action
33 * will be finished in {@link #IRT_MS} * {@link #VOLUME_CHANGE_TIMEOUT_MAX_COUNT} (ms).
34 */
35final class VolumeControlAction extends FeatureAction {
36    private static final String TAG = "VolumeControlAction";
37
38    private static final int VOLUME_MUTE = 101;
39    private static final int VOLUME_RESTORE = 102;
40    private static final int MAX_VOLUME = 100;
41    private static final int MIN_VOLUME = 0;
42
43    // State where to wait for <Report Audio Status>
44    private static final int STATE_WAIT_FOR_REPORT_VOLUME_STATUS = 1;
45
46    // Maximum count of time out used to finish volume action.
47    private static final int VOLUME_CHANGE_TIMEOUT_MAX_COUNT = 2;
48
49    private final int mAvrAddress;
50    private final int mTargetVolume;
51    private final boolean mIsVolumeUp;
52    private int mTimeoutCount;
53
54    /**
55     * Create a {@link VolumeControlAction} for mute/restore change
56     *
57     * @param source source device sending volume change
58     * @param avrAddress address of audio receiver
59     * @param mute whether to mute sound or not. {@code true} for mute on; {@code false} for mute
60     *            off, i.e restore volume
61     * @return newly created {@link VolumeControlAction}
62     */
63    public static VolumeControlAction ofMute(HdmiCecLocalDevice source, int avrAddress,
64            boolean mute) {
65        return new VolumeControlAction(source, avrAddress, mute ? VOLUME_MUTE : VOLUME_RESTORE,
66                false);
67    }
68
69    /**
70     * Create a {@link VolumeControlAction} for volume up/down change
71     *
72     * @param source source device sending volume change
73     * @param avrAddress address of audio receiver
74     * @param targetVolume target volume to be set to AVR. It should be in range of [0-100]
75     * @param isVolumeUp whether to volume up or not. {@code true} for volume up; {@code false} for
76     *            volume down
77     * @return newly created {@link VolumeControlAction}
78     */
79    public static VolumeControlAction ofVolumeChange(HdmiCecLocalDevice source, int avrAddress,
80            int targetVolume, boolean isVolumeUp) {
81        Preconditions.checkArgumentInRange(targetVolume, MIN_VOLUME, MAX_VOLUME, "volume");
82        return new VolumeControlAction(source, avrAddress, targetVolume, isVolumeUp);
83    }
84
85    /**
86     * Scale a custom volume value to cec volume scale.
87     *
88     * @param volume volume value in custom scale
89     * @param scale scale of volume (max volume)
90     * @return a volume scaled to cec volume range
91     */
92    public static int scaleToCecVolume(int volume, int scale) {
93        return (volume * MAX_VOLUME) / scale;
94    }
95
96    /**
97     * Scale a cec volume which is in range of 0 to 100 to custom volume level.
98     *
99     * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100]
100     * @param scale scale of custom volume (max volume)
101     * @return a volume value scaled to custom volume range
102     */
103    public static int scaleToCustomVolume(int cecVolume, int scale) {
104        return (cecVolume * scale) / MAX_VOLUME;
105    }
106
107    private VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, int targetVolume,
108            boolean isVolumeUp) {
109        super(source);
110
111        mAvrAddress = avrAddress;
112        mTargetVolume = targetVolume;
113        mIsVolumeUp = isVolumeUp;
114    }
115
116    @Override
117    boolean start() {
118        if (isForMute()) {
119            sendMuteChange(mTargetVolume == VOLUME_MUTE);
120            finish();
121            return true;
122        }
123
124        startVolumeChange();
125        return true;
126    }
127
128
129    private boolean isForMute() {
130        return mTargetVolume == VOLUME_MUTE || mTargetVolume == VOLUME_RESTORE;
131    }
132
133
134    private void startVolumeChange() {
135        mTimeoutCount = 0;
136        sendVolumeChange(mIsVolumeUp);
137        mState = STATE_WAIT_FOR_REPORT_VOLUME_STATUS;
138        addTimer(mState, IRT_MS);
139    }
140
141    private void sendVolumeChange(boolean up) {
142        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
143                up ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
144                        : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
145    }
146
147    private void sendMuteChange(boolean mute) {
148        sendUserControlPressedAndReleased(mAvrAddress,
149                mute ? HdmiConstants.UI_COMMAND_MUTE_FUNCTION :
150                        HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION);
151    }
152
153    @Override
154    boolean processCommand(HdmiCecMessage cmd) {
155        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
156            return false;
157        }
158
159        switch (cmd.getOpcode()) {
160            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
161                handleReportAudioStatus(cmd);
162                return true;
163            case HdmiCec.MESSAGE_FEATURE_ABORT:
164                // TODO: handle feature abort.
165                finish();
166                return true;
167            default:
168                return false;
169        }
170    }
171
172    private void handleReportAudioStatus(HdmiCecMessage cmd) {
173        byte[] params = cmd.getParams();
174        if (params.length != 1) {
175            Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
176            return;
177        }
178
179        int volume = params[0] & 0x7F;
180        // Update volume with new value.
181        // Note that it will affect system volume change.
182        tv().setAudioStatus(false, volume);
183        if (mIsVolumeUp) {
184            if (mTargetVolume <= volume) {
185                finishWithVolumeChangeRelease();
186                return;
187            }
188        } else {
189            if (mTargetVolume >= volume) {
190                finishWithVolumeChangeRelease();
191                return;
192            }
193        }
194
195        // Clear action status and send another volume change command.
196        clear();
197        startVolumeChange();
198    }
199
200    private void finishWithVolumeChangeRelease() {
201        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
202                getSourceAddress(), mAvrAddress));
203        finish();
204    }
205
206    @Override
207    void handleTimerEvent(int state) {
208        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
209            return;
210        }
211
212        // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume
213        // action.
214        if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) {
215            finishWithVolumeChangeRelease();
216            return;
217        }
218
219        sendVolumeChange(mIsVolumeUp);
220        addTimer(mState, IRT_MS);
221    }
222}
223