/* * 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.HdmiDeviceInfo; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.IHdmiControlCallback; 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 . 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 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 != Constants.SEND_RESULT_SUCCESS) { HdmiLogger.debug("Failed to send :" + 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() { // 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 ."); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); return; } sendSystemAudioModeRequest(); } protected void setSystemAudioMode(boolean mode) { tv().setSystemAudioMode(mode, true); } @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(); } }