| /* |
| * 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.hardware.hdmi.HdmiControlManager; |
| import android.hardware.hdmi.HdmiDeviceInfo; |
| import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; |
| import android.hardware.hdmi.IHdmiControlCallback; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| /** |
| * Feature action that performs one touch play against TV/Display device. This action is initiated |
| * via {@link android.hardware.hdmi.HdmiPlaybackClient#oneTouchPlay(OneTouchPlayCallback)} from the |
| * Android system working as playback device to turn on the TV, and switch the input. |
| * <p> |
| * Package-private, accessed by {@link HdmiControlService} only. |
| */ |
| final class OneTouchPlayAction extends HdmiCecFeatureAction { |
| private static final String TAG = "OneTouchPlayAction"; |
| |
| // State in which the action is waiting for <Report Power Status>. In normal situation |
| // source device can simply send <Text|Image View On> and <Active Source> in succession |
| // since the standard requires that the TV/Display should buffer the <Active Source> |
| // if the TV is brought of out standby state. |
| // |
| // But there are TV's that fail to buffer the <Active Source> while getting out of |
| // standby mode, and do not accept the command until their power status becomes 'ON'. |
| // For a workaround, we send <Give Device Power Status> commands periodically to make sure |
| // the device switches its status to 'ON'. Then we send additional <Active Source>. |
| @VisibleForTesting |
| static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1; |
| |
| // The maximum number of times we send <Give Device Power Status> before we give up. |
| // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds. |
| private static final int LOOP_COUNTER_MAX = 10; |
| |
| private final int mTargetAddress; |
| private final boolean mIsCec20; |
| |
| private int mPowerStatusCounter = 0; |
| |
| private HdmiCecLocalDeviceSource mSource; |
| |
| // Factory method. Ensures arguments are valid. |
| static OneTouchPlayAction create(HdmiCecLocalDeviceSource source, |
| int targetAddress, IHdmiControlCallback callback) { |
| if (source == null || callback == null) { |
| Slog.e(TAG, "Wrong arguments"); |
| return null; |
| } |
| return new OneTouchPlayAction(source, targetAddress, callback); |
| } |
| |
| private OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, |
| IHdmiControlCallback callback) { |
| this(localDevice, targetAddress, callback, |
| localDevice.getDeviceInfo().getCecVersion() |
| >= HdmiControlManager.HDMI_CEC_VERSION_2_0 |
| && getTargetCecVersion(localDevice, targetAddress) |
| >= HdmiControlManager.HDMI_CEC_VERSION_2_0); |
| } |
| |
| @VisibleForTesting |
| OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress, |
| IHdmiControlCallback callback, boolean isCec20) { |
| super(localDevice, callback); |
| mTargetAddress = targetAddress; |
| mIsCec20 = isCec20; |
| } |
| |
| @Override |
| boolean start() { |
| // Because only source device can create this action, it's safe to cast. |
| mSource = source(); |
| sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress)); |
| |
| boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress, |
| HdmiControlManager.POWER_STATUS_UNKNOWN) == HdmiControlManager.POWER_STATUS_ON; |
| // Make the device the active source. |
| setAndBroadcastActiveSource(); |
| // If the device is not an audio system itself, request the connected audio system to |
| // turn on. |
| if (shouldTurnOnConnectedAudioSystem()) { |
| sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(), |
| Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true)); |
| } |
| |
| if (!mIsCec20) { |
| queryDevicePowerStatus(); |
| } else { |
| int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress, |
| HdmiControlManager.POWER_STATUS_UNKNOWN); |
| if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) { |
| queryDevicePowerStatus(); |
| } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) { |
| if (!is20TargetOnBefore) { |
| // If the device is still the active source, send the <Active Source> message |
| // again. |
| // Suppress 2nd <Active Source> message if the target device was already on when |
| // the 1st one was sent. |
| maySendActiveSource(); |
| } |
| finishWithCallback(HdmiControlManager.RESULT_SUCCESS); |
| return true; |
| } |
| } |
| mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; |
| addTimer(mState, HdmiConfig.TIMEOUT_MS); |
| return true; |
| } |
| |
| private void setAndBroadcastActiveSource() { |
| // If the device wasn´t the active source yet, |
| // this makes it the active source and wakes it up. |
| mSource.mService.setAndBroadcastActiveSourceFromOneDeviceType( |
| mTargetAddress, getSourcePath(), "OneTouchPlayAction#broadcastActiveSource()"); |
| // When OneTouchPlay is called, client side should be responsible to send out the intent |
| // of which internal source, for example YouTube, it would like to switch to. |
| // Here we only update the active port and the active source records in the local |
| // device as well as claiming Active Source. |
| if (mSource.mService.audioSystem() != null) { |
| mSource = mSource.mService.audioSystem(); |
| } |
| mSource.setRoutingPort(Constants.CEC_SWITCH_HOME); |
| mSource.setLocalActivePort(Constants.CEC_SWITCH_HOME); |
| } |
| |
| private void maySendActiveSource() { |
| // Only send <Active Source> if the device is already the active source at this time. |
| mSource.maySendActiveSource(mTargetAddress); |
| } |
| |
| private void queryDevicePowerStatus() { |
| sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), |
| mTargetAddress)); |
| } |
| |
| @Override |
| boolean processCommand(HdmiCecMessage cmd) { |
| if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS |
| || mTargetAddress != cmd.getSource()) { |
| return false; |
| } |
| if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) { |
| int status = cmd.getParams()[0]; |
| if (status == HdmiControlManager.POWER_STATUS_ON) { |
| // If the device is still the active source, send the <Active Source> message |
| // again. |
| maySendActiveSource(); |
| finishWithCallback(HdmiControlManager.RESULT_SUCCESS); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| void handleTimerEvent(int state) { |
| if (mState != state) { |
| return; |
| } |
| if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) { |
| if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) { |
| queryDevicePowerStatus(); |
| addTimer(mState, HdmiConfig.TIMEOUT_MS); |
| } else { |
| // Couldn't wake up the TV for whatever reason. Report failure. |
| finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); |
| } |
| } |
| } |
| |
| private boolean shouldTurnOnConnectedAudioSystem() { |
| HdmiControlService service = mSource.mService; |
| if (service.isAudioSystemDevice()) { |
| return false; |
| } |
| @HdmiControlManager.PowerControlMode String powerControlMode = |
| service.getHdmiCecConfig().getStringValue( |
| HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); |
| return powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM) |
| || powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST); |
| } |
| |
| private static int getTargetCecVersion(HdmiCecLocalDevice localDevice, |
| int targetLogicalAddress) { |
| HdmiDeviceInfo targetDevice = localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( |
| targetLogicalAddress); |
| if (targetDevice != null) { |
| return targetDevice.getCecVersion(); |
| } |
| return HdmiControlManager.HDMI_CEC_VERSION_1_4_B; |
| } |
| |
| private static int getTargetDevicePowerStatus(HdmiCecLocalDevice localDevice, |
| int targetLogicalAddress, int defaultPowerStatus) { |
| HdmiDeviceInfo targetDevice = localDevice.mService.getHdmiCecNetwork().getCecDeviceInfo( |
| targetLogicalAddress); |
| if (targetDevice != null) { |
| return targetDevice.getDevicePowerStatus(); |
| } |
| return defaultPowerStatus; |
| } |
| } |