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