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