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