| /* |
| * 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(); |
| } |
| } |