| /* | |
| * 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 android.annotation.Nullable; | |
| import android.hardware.hdmi.HdmiControlManager; | |
| import android.hardware.hdmi.HdmiDeviceInfo; | |
| import android.hardware.hdmi.IHdmiControlCallback; | |
| import android.hardware.tv.cec.V1_0.SendMessageResult; | |
| import android.os.RemoteException; | |
| import android.util.Slog; | |
| import java.util.List; | |
| /** | |
| * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr. | |
| */ | |
| abstract class SystemAudioAction extends HdmiCecFeatureAction { | |
| private static final String TAG = "SystemAudioAction"; | |
| // Transient state to differentiate with STATE_NONE where the on-finished callback | |
| // will not be called. | |
| private static final int STATE_CHECK_ROUTING_IN_PRGRESS = 1; | |
| // State in which waits for <SetSystemAudioMode>. | |
| private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 2; | |
| private static final int MAX_SEND_RETRY_COUNT = 2; | |
| private static final int ON_TIMEOUT_MS = 5000; | |
| private static final int OFF_TIMEOUT_MS = HdmiConfig.TIMEOUT_MS; | |
| // Logical address of AV Receiver. | |
| protected final int mAvrLogicalAddress; | |
| // The target audio status of the action, whether to enable the system audio mode or not. | |
| protected boolean mTargetAudioStatus; | |
| @Nullable private final IHdmiControlCallback mCallback; | |
| private int mSendRetryCount = 0; | |
| /** | |
| * Constructor | |
| * | |
| * @param source {@link HdmiCecLocalDevice} instance | |
| * @param avrAddress logical address of AVR device | |
| * @param targetStatus Whether to enable the system audio mode or not | |
| * @param callback callback interface to be notified when it's done | |
| * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid | |
| */ | |
| SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, | |
| IHdmiControlCallback callback) { | |
| super(source); | |
| HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); | |
| mAvrLogicalAddress = avrAddress; | |
| mTargetAudioStatus = targetStatus; | |
| mCallback = callback; | |
| } | |
| // Seq #27 | |
| protected void sendSystemAudioModeRequest() { | |
| List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class); | |
| if (!routingActions.isEmpty()) { | |
| mState = STATE_CHECK_ROUTING_IN_PRGRESS; | |
| // Should have only one Routing Control Action | |
| RoutingControlAction routingAction = routingActions.get(0); | |
| routingAction.addOnFinishedCallback(this, new Runnable() { | |
| @Override | |
| public void run() { | |
| sendSystemAudioModeRequestInternal(); | |
| } | |
| }); | |
| return; | |
| } | |
| sendSystemAudioModeRequestInternal(); | |
| } | |
| private void sendSystemAudioModeRequestInternal() { | |
| HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest( | |
| getSourceAddress(), | |
| mAvrLogicalAddress, getSystemAudioModeRequestParam(), mTargetAudioStatus); | |
| sendCommand(command, new HdmiControlService.SendMessageCallback() { | |
| @Override | |
| public void onSendCompleted(int error) { | |
| if (error != SendMessageResult.SUCCESS) { | |
| HdmiLogger.debug("Failed to send <System Audio Mode Request>:" + error); | |
| setSystemAudioMode(false); | |
| finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); | |
| } | |
| } | |
| }); | |
| mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE; | |
| addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS); | |
| } | |
| private int getSystemAudioModeRequestParam() { | |
| // <System Audio Mode Request> takes the physical address of the source device | |
| // as a parameter. Get it from following candidates, in the order listed below: | |
| // 1) physical address of the active source | |
| // 2) active routing path | |
| // 3) physical address of TV | |
| if (tv().getActiveSource().isValid()) { | |
| return tv().getActiveSource().physicalAddress; | |
| } | |
| int param = tv().getActivePath(); | |
| return param != Constants.INVALID_PHYSICAL_ADDRESS | |
| ? param : Constants.PATH_INTERNAL; | |
| } | |
| private void handleSendSystemAudioModeRequestTimeout() { | |
| if (!mTargetAudioStatus // Don't retry for Off case. | |
| || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) { | |
| HdmiLogger.debug("[T]:wait for <Set System Audio Mode>."); | |
| setSystemAudioMode(false); | |
| finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); | |
| return; | |
| } | |
| sendSystemAudioModeRequest(); | |
| } | |
| protected void setSystemAudioMode(boolean mode) { | |
| tv().setSystemAudioMode(mode); | |
| } | |
| @Override | |
| final boolean processCommand(HdmiCecMessage cmd) { | |
| if (cmd.getSource() != mAvrLogicalAddress) { | |
| return false; | |
| } | |
| switch (mState) { | |
| case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: | |
| if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT | |
| && (cmd.getParams()[0] & 0xFF) | |
| == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) { | |
| HdmiLogger.debug("Failed to start system audio mode request."); | |
| setSystemAudioMode(false); | |
| finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); | |
| return true; | |
| } | |
| if (cmd.getOpcode() != Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE | |
| || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) { | |
| return false; | |
| } | |
| boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd); | |
| if (receivedStatus == mTargetAudioStatus) { | |
| setSystemAudioMode(receivedStatus); | |
| startAudioStatusAction(); | |
| return true; | |
| } else { | |
| HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus); | |
| // Unexpected response, consider the request is newly initiated by AVR. | |
| // To return 'false' will initiate new SystemAudioActionFromAvr by the control | |
| // service. | |
| finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); | |
| return false; | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| protected void startAudioStatusAction() { | |
| addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallback)); | |
| finish(); | |
| } | |
| protected void removeSystemAudioActionInProgress() { | |
| removeActionExcept(SystemAudioActionFromTv.class, this); | |
| removeActionExcept(SystemAudioActionFromAvr.class, this); | |
| } | |
| @Override | |
| final void handleTimerEvent(int state) { | |
| if (mState != state) { | |
| return; | |
| } | |
| switch (mState) { | |
| case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: | |
| handleSendSystemAudioModeRequestTimeout(); | |
| return; | |
| } | |
| } | |
| // TODO: if IHdmiControlCallback is general to other FeatureAction, | |
| // move it into FeatureAction. | |
| protected void finishWithCallback(int returnCode) { | |
| if (mCallback != null) { | |
| try { | |
| mCallback.onComplete(returnCode); | |
| } catch (RemoteException e) { | |
| Slog.e(TAG, "Failed to invoke callback.", e); | |
| } | |
| } | |
| finish(); | |
| } | |
| } |