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 <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 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