/* * 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. *

* 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) { byte params[] = cmd.getParams(); boolean mute = (params[0] & 0x80) == 0x80; int volume = params[0] & 0x7F; 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. AudioManager 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 only when it finishes this action, // it takes care of 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(); } } }