VolumeControlAction.java revision b509c2ecd99619248b7a07fb0fa978bb27f25cc3
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; 20 21import 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 <Report Audio Status> from AVR, this action 29 * will be finished in {@link #IRT_MS} * {@link #VOLUME_CHANGE_TIMEOUT_MAX_COUNT} (ms). 30 */ 31final class VolumeControlAction extends HdmiCecFeatureAction { 32 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 129 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, 144 mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION : 145 HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); 146 } 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()) { 155 case Constants.MESSAGE_REPORT_AUDIO_STATUS: 156 handleReportAudioStatus(cmd); 157 return true; 158 case Constants.MESSAGE_FEATURE_ABORT: 159 // TODO: handle feature abort. 160 finish(); 161 return true; 162 default: 163 return false; 164 } 165 } 166 167 private void handleReportAudioStatus(HdmiCecMessage cmd) { 168 byte[] params = cmd.getParams(); 169 int volume = params[0] & 0x7F; 170 // Update volume with new value. 171 // Note that it will affect system volume change. 172 tv().setAudioStatus(false, volume); 173 if (mIsVolumeUp) { 174 if (mTargetVolume <= volume) { 175 finishWithVolumeChangeRelease(); 176 return; 177 } 178 } else { 179 if (mTargetVolume >= volume) { 180 finishWithVolumeChangeRelease(); 181 return; 182 } 183 } 184 185 // Clear action status and send another volume change command. 186 clear(); 187 startVolumeChange(); 188 } 189 190 private void finishWithVolumeChangeRelease() { 191 sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( 192 getSourceAddress(), mAvrAddress)); 193 finish(); 194 } 195 196 @Override 197 void handleTimerEvent(int state) { 198 if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) { 199 return; 200 } 201 202 // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume 203 // action. 204 if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) { 205 finishWithVolumeChangeRelease(); 206 return; 207 } 208 209 sendVolumeChange(mIsVolumeUp); 210 addTimer(mState, IRT_MS); 211 } 212} 213