Home | History | Annotate | Download | only in hdmi
      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      */
     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      */
     69     public static int scaleToCustomVolume(int cecVolume, int scale) {
     70         return (cecVolume * scale) / MAX_VOLUME;
     71     }
     72 
     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 
     84     private void updateLastKeyUpdateTime() {
     85         mLastKeyUpdateTime = System.currentTimeMillis();
     86     }
     87 
     88     @Override
     89     boolean start() {
     90         mState = STATE_WAIT_FOR_NEXT_VOLUME_PRESS;
     91         sendVolumeKeyPressed();
     92         resetTimer();
     93         return true;
     94     }
     95 
     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 
    103     private void resetTimer() {
    104         mActionTimer.clearTimerMessage();
    105         addTimer(STATE_WAIT_FOR_NEXT_VOLUME_PRESS, IRT_MS);
    106     }
    107 
    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 
    119     private void sendVolumeKeyReleased() {
    120         sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
    121                 getSourceAddress(), mAvrAddress));
    122         mSentKeyPressed = false;
    123     }
    124 
    125     @Override
    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 
    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 
    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 
    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
    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
    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