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 17 package com.android.server.hdmi; 18 19 import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT; 20 import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS; 21 import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED; 22 import static com.android.server.hdmi.HdmiConfig.IRT_MS; 23 24 import android.media.AudioManager; 25 26 /** 27 * Feature action that transmits volume change to Audio Receiver. 28 * <p> 29 * This action is created when a user pressed volume up/down. However, Android only provides a 30 * listener for delta of some volume change instead of individual key event. Also it's hard to know 31 * Audio Receiver's number of volume steps for a single volume control key. Because of this, it 32 * sends key-down event until IRT timeout happens, and it will send key-up event if no additional 33 * volume change happens; otherwise, it will send again key-down as press and hold feature does. 34 */ 35 final class VolumeControlAction extends HdmiCecFeatureAction { 36 private static final String TAG = "VolumeControlAction"; 37 38 // State that wait for next volume press. 39 private static final int STATE_WAIT_FOR_NEXT_VOLUME_PRESS = 1; 40 private static final int MAX_VOLUME = 100; 41 42 private static final int UNKNOWN_AVR_VOLUME = -1; 43 44 private final int mAvrAddress; 45 private boolean mIsVolumeUp; 46 private long mLastKeyUpdateTime; 47 private int mLastAvrVolume; 48 private boolean mLastAvrMute; 49 private boolean mSentKeyPressed; 50 51 /** 52 * Scale a custom volume value to cec volume scale. 53 * 54 * @param volume volume value in custom scale 55 * @param scale scale of volume (max volume) 56 * @return a volume scaled to cec volume range 57 */ scaleToCecVolume(int volume, int scale)58 public static int scaleToCecVolume(int volume, int scale) { 59 return (volume * MAX_VOLUME) / scale; 60 } 61 62 /** 63 * Scale a cec volume which is in range of 0 to 100 to custom volume level. 64 * 65 * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100] 66 * @param scale scale of custom volume (max volume) 67 * @return a volume scaled to custom volume range 68 */ scaleToCustomVolume(int cecVolume, int scale)69 public static int scaleToCustomVolume(int cecVolume, int scale) { 70 return (cecVolume * scale) / MAX_VOLUME; 71 } 72 VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp)73 VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp) { 74 super(source); 75 mAvrAddress = avrAddress; 76 mIsVolumeUp = isVolumeUp; 77 mLastAvrVolume = UNKNOWN_AVR_VOLUME; 78 mLastAvrMute = false; 79 mSentKeyPressed = false; 80 81 updateLastKeyUpdateTime(); 82 } 83 updateLastKeyUpdateTime()84 private void updateLastKeyUpdateTime() { 85 mLastKeyUpdateTime = System.currentTimeMillis(); 86 } 87 88 @Override start()89 boolean start() { 90 mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS; 91 sendVolumeKeyPressed(); 92 resetTimer(); 93 return true; 94 } 95 sendVolumeKeyPressed()96 private void sendVolumeKeyPressed() { 97 sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress, 98 mIsVolumeUp ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP 99 : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); 100 mSentKeyPressed = true; 101 } 102 resetTimer()103 private void resetTimer() { 104 mActionTimer.clearTimerMessage(); 105 addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS); 106 } 107 handleVolumeChange(boolean isVolumeUp)108 void handleVolumeChange(boolean isVolumeUp) { 109 if (mIsVolumeUp != isVolumeUp) { 110 HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp); 111 sendVolumeKeyReleased(); 112 mIsVolumeUp = isVolumeUp; 113 sendVolumeKeyPressed(); 114 resetTimer(); 115 } 116 updateLastKeyUpdateTime(); 117 } 118 sendVolumeKeyReleased()119 private void sendVolumeKeyReleased() { 120 sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( 121 getSourceAddress(), mAvrAddress)); 122 mSentKeyPressed = false; 123 } 124 125 @Override processCommand(HdmiCecMessage cmd)126 boolean processCommand(HdmiCecMessage cmd) { 127 if (mState != STATE_WAIT_FOR_NEXT_VOLUME_PRESS || cmd.getSource() != mAvrAddress) { 128 return false; 129 } 130 131 switch (cmd.getOpcode()) { 132 case MESSAGE_REPORT_AUDIO_STATUS: 133 return handleReportAudioStatus(cmd); 134 case MESSAGE_FEATURE_ABORT: 135 return handleFeatureAbort(cmd); 136 } 137 return false; 138 } 139 handleReportAudioStatus(HdmiCecMessage cmd)140 private boolean handleReportAudioStatus(HdmiCecMessage cmd) { 141 byte params[] = cmd.getParams(); 142 boolean mute = HdmiUtils.isAudioStatusMute(cmd); 143 int volume = HdmiUtils.getAudioStatusVolume(cmd); 144 mLastAvrVolume = volume; 145 mLastAvrMute = mute; 146 if (shouldUpdateAudioVolume(mute)) { 147 HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume); 148 tv().setAudioStatus(mute, volume); 149 mLastAvrVolume = UNKNOWN_AVR_VOLUME; 150 mLastAvrMute = false; 151 } 152 return true; 153 } 154 shouldUpdateAudioVolume(boolean mute)155 private boolean shouldUpdateAudioVolume(boolean mute) { 156 // Do nothing if in mute. 157 if (mute) { 158 return true; 159 } 160 161 // Update audio status if current volume position is edge of volume bar, 162 // i.e max or min volume. 163 AudioManager audioManager = tv().getService().getAudioManager(); 164 int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 165 if (mIsVolumeUp) { 166 int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 167 return currentVolume == maxVolume; 168 } else { 169 return currentVolume == 0; 170 } 171 } 172 handleFeatureAbort(HdmiCecMessage cmd)173 private boolean handleFeatureAbort(HdmiCecMessage cmd) { 174 int originalOpcode = cmd.getParams()[0] & 0xFF; 175 // Since it sends <User Control Released> only when it finishes this action, 176 // it takes care of <User Control Pressed> only here. 177 if (originalOpcode == MESSAGE_USER_CONTROL_PRESSED) { 178 finish(); 179 return true; 180 } 181 return false; 182 } 183 184 @Override clear()185 protected void clear() { 186 super.clear(); 187 if (mSentKeyPressed) { 188 sendVolumeKeyReleased(); 189 } 190 if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) { 191 tv().setAudioStatus(mLastAvrMute, mLastAvrVolume); 192 mLastAvrVolume = UNKNOWN_AVR_VOLUME; 193 mLastAvrMute = false; 194 } 195 } 196 197 @Override handleTimerEvent(int state)198 void handleTimerEvent(int state) { 199 if (state != STATE_WAIT_FOR_NEXT_VOLUME_PRESS) { 200 return; 201 } 202 203 if (System.currentTimeMillis() - mLastKeyUpdateTime >= IRT_MS) { 204 finish(); 205 } else { 206 sendVolumeKeyPressed(); 207 resetTimer(); 208 } 209 } 210 } 211