VolumeControlAction.java revision 2e8f1b6399089626b4f0249427626ba6e63a62ef
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; 20import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT; 21import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS; 22import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED; 23 24import 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 */ 35final 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 mSentKeyPressed; 49 50 /** 51 * Scale a custom volume value to cec volume scale. 52 * 53 * @param volume volume value in custom scale 54 * @param scale scale of volume (max volume) 55 * @return a volume scaled to cec volume range 56 */ 57 public static int scaleToCecVolume(int volume, int scale) { 58 return (volume * MAX_VOLUME) / scale; 59 } 60 61 /** 62 * Scale a cec volume which is in range of 0 to 100 to custom volume level. 63 * 64 * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100] 65 * @param scale scale of custom volume (max volume) 66 * @return a volume scaled to custom volume range 67 */ 68 public static int scaleToCustomVolume(int cecVolume, int scale) { 69 return (cecVolume * scale) / MAX_VOLUME; 70 } 71 72 VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp) { 73 super(source); 74 mAvrAddress = avrAddress; 75 mIsVolumeUp = isVolumeUp; 76 mLastAvrVolume = UNKNOWN_AVR_VOLUME; 77 mSentKeyPressed = false; 78 79 updateLastKeyUpdateTime(); 80 } 81 82 private void updateLastKeyUpdateTime() { 83 mLastKeyUpdateTime = System.currentTimeMillis(); 84 } 85 86 @Override 87 boolean start() { 88 mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS; 89 sendVolumeKeyPressed(); 90 resetTimer(); 91 return true; 92 } 93 94 private void sendVolumeKeyPressed() { 95 sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress, 96 mIsVolumeUp ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP 97 : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); 98 mSentKeyPressed = true; 99 } 100 101 private void resetTimer() { 102 mActionTimer.clearTimerMessage(); 103 addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS); 104 } 105 106 void handleVolumeChange(boolean isVolumeUp) { 107 if (mIsVolumeUp != isVolumeUp) { 108 HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp); 109 sendVolumeKeyReleased(); 110 mIsVolumeUp = isVolumeUp; 111 } 112 updateLastKeyUpdateTime(); 113 } 114 115 private void sendVolumeKeyReleased() { 116 sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( 117 getSourceAddress(), mAvrAddress)); 118 mSentKeyPressed = false; 119 } 120 121 @Override 122 boolean processCommand(HdmiCecMessage cmd) { 123 if (mState != STATE_WAIT_FOR_NEXT_VOLUME_PRESS || cmd.getSource() != mAvrAddress) { 124 return false; 125 } 126 127 switch (cmd.getOpcode()) { 128 case MESSAGE_REPORT_AUDIO_STATUS: 129 return handleReportAudioStatus(cmd); 130 case MESSAGE_FEATURE_ABORT: 131 return handleFeatureAbort(cmd); 132 default: 133 return false; 134 } 135 } 136 137 private boolean handleReportAudioStatus(HdmiCecMessage cmd) { 138 byte params[] = cmd.getParams(); 139 boolean mute = (params[0] & 0x80) == 0x80; 140 int volume = params[0] & 0x7F; 141 mLastAvrVolume = volume; 142 if (shouldUpdateAudioVolume(mute)) { 143 HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume); 144 tv().setAudioStatus(mute, volume); 145 } 146 return true; 147 } 148 149 private boolean shouldUpdateAudioVolume(boolean mute) { 150 // Do nothing if in mute. 151 if (mute) { 152 return true; 153 } 154 155 // Update audio status if current volume position is edge of volume bar, 156 // i.e max or min volume. 157 AudioManager audioManager = tv().getService().getAudioManager(); 158 int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 159 if (mIsVolumeUp) { 160 int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 161 return currentVolume == maxVolume; 162 } else { 163 return currentVolume == 0; 164 } 165 } 166 167 private boolean handleFeatureAbort(HdmiCecMessage cmd) { 168 int originalOpcode = cmd.getParams()[0] & 0xFF; 169 // Since it sends <User Control Released> only when it finishes this action, 170 // it takes care of <User Control Pressed> only here. 171 if (originalOpcode == MESSAGE_USER_CONTROL_PRESSED) { 172 finish(); 173 return true; 174 } 175 return false; 176 } 177 178 @Override 179 protected void clear() { 180 super.clear(); 181 if (mSentKeyPressed) { 182 sendVolumeKeyReleased(); 183 } 184 if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) { 185 tv().setAudioStatus(false, mLastAvrVolume); 186 mLastAvrVolume = UNKNOWN_AVR_VOLUME; 187 } 188 } 189 190 @Override 191 void handleTimerEvent(int state) { 192 if (state != STATE_WAIT_FOR_NEXT_VOLUME_PRESS) { 193 return; 194 } 195 196 if (System.currentTimeMillis() - mLastKeyUpdateTime >= IRT_MS) { 197 finish(); 198 } else { 199 sendVolumeKeyPressed(); 200 resetTimer(); 201 } 202 } 203} 204