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 17package com.android.server.hdmi; 18 19import android.annotation.Nullable; 20import android.hardware.hdmi.HdmiControlManager; 21import android.hardware.hdmi.HdmiDeviceInfo; 22import android.hardware.hdmi.IHdmiControlCallback; 23import android.hardware.tv.cec.V1_0.SendMessageResult; 24import android.os.RemoteException; 25import android.util.Slog; 26import java.util.List; 27 28/** 29 * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr. 30 */ 31abstract 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