| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.hdmi; |
| |
| import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT; |
| import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS; |
| import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED; |
| import static com.android.server.hdmi.HdmiConfig.IRT_MS; |
| |
| import android.media.AudioManager; |
| |
| /** |
| * Feature action that transmits volume change to Audio Receiver. |
| * <p> |
| * This action is created when a user pressed volume up/down. However, Android only provides a |
| * listener for delta of some volume change instead of individual key event. Also it's hard to know |
| * Audio Receiver's number of volume steps for a single volume control key. Because of this, it |
| * sends key-down event until IRT timeout happens, and it will send key-up event if no additional |
| * volume change happens; otherwise, it will send again key-down as press and hold feature does. |
| */ |
| final class VolumeControlAction extends HdmiCecFeatureAction { |
| private static final String TAG = "VolumeControlAction"; |
| |
| // State that wait for next volume press. |
| private static final int STATE_WAIT_FOR_NEXT_VOLUME_PRESS = 1; |
| private static final int MAX_VOLUME = 100; |
| |
| private static final int UNKNOWN_AVR_VOLUME = -1; |
| |
| private final int mAvrAddress; |
| private boolean mIsVolumeUp; |
| private long mLastKeyUpdateTime; |
| private int mLastAvrVolume; |
| private boolean mLastAvrMute; |
| private boolean mSentKeyPressed; |
| |
| /** |
| * Scale a custom volume value to cec volume scale. |
| * |
| * @param volume volume value in custom scale |
| * @param scale scale of volume (max volume) |
| * @return a volume scaled to cec volume range |
| */ |
| public static int scaleToCecVolume(int volume, int scale) { |
| return (volume * MAX_VOLUME) / scale; |
| } |
| |
| /** |
| * Scale a cec volume which is in range of 0 to 100 to custom volume level. |
| * |
| * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100] |
| * @param scale scale of custom volume (max volume) |
| * @return a volume scaled to custom volume range |
| */ |
| public static int scaleToCustomVolume(int cecVolume, int scale) { |
| return (cecVolume * scale) / MAX_VOLUME; |
| } |
| |
| VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, boolean isVolumeUp) { |
| super(source); |
| mAvrAddress = avrAddress; |
| mIsVolumeUp = isVolumeUp; |
| mLastAvrVolume = UNKNOWN_AVR_VOLUME; |
| mLastAvrMute = false; |
| mSentKeyPressed = false; |
| |
| updateLastKeyUpdateTime(); |
| } |
| |
| private void updateLastKeyUpdateTime() { |
| mLastKeyUpdateTime = System.currentTimeMillis(); |
| } |
| |
| @Override |
| boolean start() { |
| mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS; |
| sendVolumeKeyPressed(); |
| resetTimer(); |
| return true; |
| } |
| |
| private void sendVolumeKeyPressed() { |
| sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress, |
| mIsVolumeUp ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP |
| : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); |
| mSentKeyPressed = true; |
| } |
| |
| private void resetTimer() { |
| mActionTimer.clearTimerMessage(); |
| addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS); |
| } |
| |
| void handleVolumeChange(boolean isVolumeUp) { |
| if (mIsVolumeUp != isVolumeUp) { |
| HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp); |
| sendVolumeKeyReleased(); |
| mIsVolumeUp = isVolumeUp; |
| sendVolumeKeyPressed(); |
| resetTimer(); |
| } |
| updateLastKeyUpdateTime(); |
| } |
| |
| private void sendVolumeKeyReleased() { |
| sendCommand(HdmiCecMessageBuilder.buildUserControlReleased( |
| getSourceAddress(), mAvrAddress)); |
| mSentKeyPressed = false; |
| } |
| |
| @Override |
| boolean processCommand(HdmiCecMessage cmd) { |
| if (mState != STATE_WAIT_FOR_NEXT_VOLUME_PRESS || cmd.getSource() != mAvrAddress) { |
| return false; |
| } |
| |
| switch (cmd.getOpcode()) { |
| case MESSAGE_REPORT_AUDIO_STATUS: |
| return handleReportAudioStatus(cmd); |
| case MESSAGE_FEATURE_ABORT: |
| return handleFeatureAbort(cmd); |
| } |
| return false; |
| } |
| |
| private boolean handleReportAudioStatus(HdmiCecMessage cmd) { |
| boolean mute = HdmiUtils.isAudioStatusMute(cmd); |
| int volume = HdmiUtils.getAudioStatusVolume(cmd); |
| mLastAvrVolume = volume; |
| mLastAvrMute = mute; |
| if (shouldUpdateAudioVolume(mute)) { |
| HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume); |
| tv().setAudioStatus(mute, volume); |
| mLastAvrVolume = UNKNOWN_AVR_VOLUME; |
| mLastAvrMute = false; |
| } |
| return true; |
| } |
| |
| private boolean shouldUpdateAudioVolume(boolean mute) { |
| // Do nothing if in mute. |
| if (mute) { |
| return true; |
| } |
| |
| // Update audio status if current volume position is edge of volume bar, |
| // i.e max or min volume. |
| AudioManagerWrapper audioManager = tv().getService().getAudioManager(); |
| int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); |
| if (mIsVolumeUp) { |
| int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); |
| return currentVolume == maxVolume; |
| } else { |
| return currentVolume == 0; |
| } |
| } |
| |
| private boolean handleFeatureAbort(HdmiCecMessage cmd) { |
| int originalOpcode = cmd.getParams()[0] & 0xFF; |
| // Since it sends <User Control Released> only when it finishes this action, |
| // it takes care of <User Control Pressed> only here. |
| if (originalOpcode == MESSAGE_USER_CONTROL_PRESSED) { |
| finish(); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| protected void clear() { |
| super.clear(); |
| if (mSentKeyPressed) { |
| sendVolumeKeyReleased(); |
| } |
| if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) { |
| tv().setAudioStatus(mLastAvrMute, mLastAvrVolume); |
| mLastAvrVolume = UNKNOWN_AVR_VOLUME; |
| mLastAvrMute = false; |
| } |
| } |
| |
| @Override |
| void handleTimerEvent(int state) { |
| if (state != STATE_WAIT_FOR_NEXT_VOLUME_PRESS) { |
| return; |
| } |
| |
| if (System.currentTimeMillis() - mLastKeyUpdateTime >= IRT_MS) { |
| finish(); |
| } else { |
| sendVolumeKeyPressed(); |
| resetTimer(); |
| } |
| } |
| } |