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 <Report Audio Status> 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