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 android.annotation.Nullable;
     20 import android.hardware.hdmi.HdmiControlManager;
     21 import android.hardware.hdmi.HdmiDeviceInfo;
     22 import android.hardware.hdmi.IHdmiControlCallback;
     23 import android.hardware.tv.cec.V1_0.SendMessageResult;
     24 import android.os.RemoteException;
     25 import android.util.Slog;
     26 import java.util.List;
     27 
     28 /**
     29  * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr.
     30  */
     31 abstract class SystemAudioAction extends HdmiCecFeatureAction {
     32     private static final String TAG = "SystemAudioAction";
     33 
     34     // Transient state to differentiate with STATE_NONE where the on-finished callback
     35     // will not be called.
     36     private static final int STATE_CHECK_ROUTING_IN_PRGRESS = 1;
     37 
     38     // State in which waits for <SetSystemAudioMode>.
     39     private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 2;
     40 
     41     private static final int MAX_SEND_RETRY_COUNT = 2;
     42 
     43     private static final int ON_TIMEOUT_MS = 5000;
     44     private static final int OFF_TIMEOUT_MS = HdmiConfig.TIMEOUT_MS;
     45 
     46     // Logical address of AV Receiver.
     47     protected final int mAvrLogicalAddress;
     48 
     49     // The target audio status of the action, whether to enable the system audio mode or not.
     50     protected boolean mTargetAudioStatus;
     51 
     52     @Nullable private final IHdmiControlCallback mCallback;
     53 
     54     private int mSendRetryCount = 0;
     55 
     56     /**
     57      * Constructor
     58      *
     59      * @param source {@link HdmiCecLocalDevice} instance
     60      * @param avrAddress logical address of AVR device
     61      * @param targetStatus Whether to enable the system audio mode or not
     62      * @param callback callback interface to be notified when it's done
     63      * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
     64      */
     65     SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus,
     66             IHdmiControlCallback callback) {
     67         super(source);
     68         HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     69         mAvrLogicalAddress = avrAddress;
     70         mTargetAudioStatus = targetStatus;
     71         mCallback = callback;
     72     }
     73 
     74     // Seq #27
     75     protected void sendSystemAudioModeRequest() {
     76         List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class);
     77         if (!routingActions.isEmpty()) {
     78             mState = STATE_CHECK_ROUTING_IN_PRGRESS;
     79             // Should have only one Routing Control Action
     80             RoutingControlAction routingAction = routingActions.get(0);
     81             routingAction.addOnFinishedCallback(this, new Runnable() {
     82                 @Override
     83                 public void run() {
     84                     sendSystemAudioModeRequestInternal();
     85                 }
     86             });
     87             return;
     88         }
     89         sendSystemAudioModeRequestInternal();
     90     }
     91 
     92     private void sendSystemAudioModeRequestInternal() {
     93         HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
     94                 getSourceAddress(),
     95                 mAvrLogicalAddress, getSystemAudioModeRequestParam(), mTargetAudioStatus);
     96         sendCommand(command, new HdmiControlService.SendMessageCallback() {
     97             @Override
     98             public void onSendCompleted(int error) {
     99                 if (error != SendMessageResult.SUCCESS) {
    100                     HdmiLogger.debug("Failed to send <System Audio Mode Request>:" + error);
    101                     setSystemAudioMode(false);
    102                     finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
    103                 }
    104             }
    105         });
    106         mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE;
    107         addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS);
    108     }
    109 
    110     private int getSystemAudioModeRequestParam() {
    111         // <System Audio Mode Request> takes the physical address of the source device
    112         // as a parameter. Get it from following candidates, in the order listed below:
    113         // 1) physical address of the active source
    114         // 2) active routing path
    115         // 3) physical address of TV
    116         if (tv().getActiveSource().isValid()) {
    117             return tv().getActiveSource().physicalAddress;
    118         }
    119         int param = tv().getActivePath();
    120         return param != Constants.INVALID_PHYSICAL_ADDRESS
    121                 ? param : Constants.PATH_INTERNAL;
    122     }
    123 
    124     private void handleSendSystemAudioModeRequestTimeout() {
    125         if (!mTargetAudioStatus  // Don't retry for Off case.
    126                 || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) {
    127             HdmiLogger.debug("[T]:wait for <Set System Audio Mode>.");
    128             setSystemAudioMode(false);
    129             finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
    130             return;
    131         }
    132         sendSystemAudioModeRequest();
    133     }
    134 
    135     protected void setSystemAudioMode(boolean mode) {
    136         tv().setSystemAudioMode(mode);
    137     }
    138 
    139     @Override
    140     final boolean processCommand(HdmiCecMessage cmd) {
    141         if (cmd.getSource() != mAvrLogicalAddress) {
    142             return false;
    143         }
    144         switch (mState) {
    145             case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
    146                 if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT
    147                         && (cmd.getParams()[0] & 0xFF)
    148                                 == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) {
    149                     HdmiLogger.debug("Failed to start system audio mode request.");
    150                     setSystemAudioMode(false);
    151                     finishWithCallback(HdmiControlManager.RESULT_EXCEPTION);
    152                     return true;
    153                 }
    154                 if (cmd.getOpcode() != Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE
    155                         || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
    156                     return false;
    157                 }
    158                 boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
    159                 if (receivedStatus == mTargetAudioStatus) {
    160                     setSystemAudioMode(receivedStatus);
    161                     startAudioStatusAction();
    162                     return true;
    163                 } else {
    164                     HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
    165                     // Unexpected response, consider the request is newly initiated by AVR.
    166                     // To return 'false' will initiate new SystemAudioActionFromAvr by the control
    167                     // service.
    168                     finishWithCallback(HdmiControlManager.RESULT_EXCEPTION);
    169                     return false;
    170                 }
    171             default:
    172                 return false;
    173         }
    174     }
    175 
    176     protected void startAudioStatusAction() {
    177         addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallback));
    178         finish();
    179     }
    180 
    181     protected void removeSystemAudioActionInProgress() {
    182         removeActionExcept(SystemAudioActionFromTv.class, this);
    183         removeActionExcept(SystemAudioActionFromAvr.class, this);
    184     }
    185 
    186     @Override
    187     final void handleTimerEvent(int state) {
    188         if (mState != state) {
    189             return;
    190         }
    191         switch (mState) {
    192             case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
    193                 handleSendSystemAudioModeRequestTimeout();
    194                 return;
    195         }
    196     }
    197 
    198     // TODO: if IHdmiControlCallback is general to other FeatureAction,
    199     //       move it into FeatureAction.
    200     protected void finishWithCallback(int returnCode) {
    201         if (mCallback != null) {
    202             try {
    203                 mCallback.onComplete(returnCode);
    204             } catch (RemoteException e) {
    205                 Slog.e(TAG, "Failed to invoke callback.", e);
    206             }
    207         }
    208         finish();
    209     }
    210 }
    211