VolumeControlAction.java revision 210d73df0b77b4c8be67ecc92afb238dc8c7ccfa
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    private void startVolumeChange() {
134        mTimeoutCount = 0;
135        sendVolumeChange(mIsVolumeUp);
136        mState = STATE_WAIT_FOR_REPORT_VOLUME_STATUS;
137        addTimer(mState, IRT_MS);
138    }
139
140    private void sendVolumeChange(boolean up) {
141        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
142                up ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
143                        : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
144    }
145
146    private void sendMuteChange(boolean mute) {
147        sendUserControlPressedAndReleased(mAvrAddress,
148                mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
149                        HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
150    }
151
152    @Override
153    boolean processCommand(HdmiCecMessage cmd) {
154        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
155            return false;
156        }
157
158        switch (cmd.getOpcode()) {
159            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
160                handleReportAudioStatus(cmd);
161                return true;
162            case HdmiCec.MESSAGE_FEATURE_ABORT:
163                // TODO: handle feature abort.
164                finish();
165                return true;
166            default:
167                return false;
168        }
169    }
170
171    private void handleReportAudioStatus(HdmiCecMessage cmd) {
172        byte[] params = cmd.getParams();
173        if (params.length != 1) {
174            Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
175            return;
176        }
177
178        int volume = params[0] & 0x7F;
179        // Update volume with new value.
180        // Note that it will affect system volume change.
181        tv().setAudioStatus(false, volume);
182        if (mIsVolumeUp) {
183            if (mTargetVolume <= volume) {
184                finishWithVolumeChangeRelease();
185                return;
186            }
187        } else {
188            if (mTargetVolume >= volume) {
189                finishWithVolumeChangeRelease();
190                return;
191            }
192        }
193
194        // Clear action status and send another volume change command.
195        clear();
196        startVolumeChange();
197    }
198
199    private void finishWithVolumeChangeRelease() {
200        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
201                getSourceAddress(), mAvrAddress));
202        finish();
203    }
204
205    @Override
206    void handleTimerEvent(int state) {
207        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
208            return;
209        }
210
211        // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume
212        // action.
213        if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) {
214            finishWithVolumeChangeRelease();
215            return;
216        }
217
218        sendVolumeChange(mIsVolumeUp);
219        addTimer(mState, IRT_MS);
220    }
221}
222