| /* |
| * 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.HdmiTvClient; |
| import android.hardware.hdmi.IHdmiControlCallback; |
| import android.hardware.tv.cec.V1_0.SendMessageResult; |
| import android.os.RemoteException; |
| import android.util.Slog; |
| import com.android.server.hdmi.HdmiControlService.SendMessageCallback; |
| |
| /** |
| * Handles an action that selects a logical device as a new active source. |
| * |
| * Triggered by {@link HdmiTvClient}, attempts to select the given target device |
| * for a new active source. It does its best to wake up the target in standby mode |
| * before issuing the command >Set Stream path<. |
| */ |
| final class DeviceSelectAction extends HdmiCecFeatureAction { |
| private static final String TAG = "DeviceSelect"; |
| |
| // Time in milliseconds we wait for the device power status to switch to 'Standby' |
| private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000; |
| |
| // Time in milliseconds we wait for the device power status to turn to 'On'. |
| private static final int TIMEOUT_POWER_ON_MS = 5 * 1000; |
| |
| // The number of times we try to wake up the target device before we give up |
| // and just send <Set Stream Path>. |
| private static final int LOOP_COUNTER_MAX = 20; |
| |
| // State in which we wait for <Report Power Status> to come in response to the command |
| // <Give Device Power Status> we have sent. |
| private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; |
| |
| // State in which we wait for the device power status to switch to 'Standby'. |
| // We wait till the status becomes 'Standby' before we send <Set Stream Path> |
| // to wake up the device again. |
| private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2; |
| |
| // State in which we wait for the device power status to switch to 'on'. We wait |
| // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>. |
| private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; |
| |
| private final HdmiDeviceInfo mTarget; |
| private final IHdmiControlCallback mCallback; |
| private final HdmiCecMessage mGivePowerStatus; |
| |
| private int mPowerStatusCounter = 0; |
| |
| /** |
| * Constructor. |
| * |
| * @param source {@link HdmiCecLocalDevice} instance |
| * @param target target logical device that will be a new active source |
| * @param callback callback object |
| */ |
| public DeviceSelectAction(HdmiCecLocalDeviceTv source, |
| HdmiDeviceInfo target, IHdmiControlCallback callback) { |
| super(source); |
| mCallback = callback; |
| mTarget = target; |
| mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( |
| getSourceAddress(), getTargetAddress()); |
| } |
| |
| int getTargetAddress() { |
| return mTarget.getLogicalAddress(); |
| } |
| |
| @Override |
| public boolean start() { |
| // Seq #9 |
| queryDevicePowerStatus(); |
| return true; |
| } |
| |
| private void queryDevicePowerStatus() { |
| sendCommand(mGivePowerStatus, new SendMessageCallback() { |
| @Override |
| public void onSendCompleted(int error) { |
| if (error != SendMessageResult.SUCCESS) { |
| invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); |
| finish(); |
| return; |
| } |
| } |
| }); |
| mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; |
| addTimer(mState, HdmiConfig.TIMEOUT_MS); |
| } |
| |
| @Override |
| public boolean processCommand(HdmiCecMessage cmd) { |
| if (cmd.getSource() != getTargetAddress()) { |
| return false; |
| } |
| int opcode = cmd.getOpcode(); |
| byte[] params = cmd.getParams(); |
| |
| switch (mState) { |
| case STATE_WAIT_FOR_REPORT_POWER_STATUS: |
| if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { |
| return handleReportPowerStatus(params[0]); |
| } |
| return false; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| private boolean handleReportPowerStatus(int powerStatus) { |
| switch (powerStatus) { |
| case HdmiControlManager.POWER_STATUS_ON: |
| sendSetStreamPath(); |
| return true; |
| case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: |
| if (mPowerStatusCounter < 4) { |
| mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY; |
| addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS); |
| } else { |
| sendSetStreamPath(); |
| } |
| return true; |
| case HdmiControlManager.POWER_STATUS_STANDBY: |
| if (mPowerStatusCounter == 0) { |
| turnOnDevice(); |
| } else { |
| sendSetStreamPath(); |
| } |
| return true; |
| case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON: |
| if (mPowerStatusCounter < LOOP_COUNTER_MAX) { |
| mState = STATE_WAIT_FOR_DEVICE_POWER_ON; |
| addTimer(mState, TIMEOUT_POWER_ON_MS); |
| } else { |
| sendSetStreamPath(); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| private void turnOnDevice() { |
| sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), |
| HdmiCecKeycode.CEC_KEYCODE_POWER); |
| sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), |
| HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); |
| mState = STATE_WAIT_FOR_DEVICE_POWER_ON; |
| addTimer(mState, TIMEOUT_POWER_ON_MS); |
| } |
| |
| private void sendSetStreamPath() { |
| // Turn the active source invalidated, which remains so till <Active Source> comes from |
| // the selected device. |
| tv().getActiveSource().invalidate(); |
| tv().setActivePath(mTarget.getPhysicalAddress()); |
| sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( |
| getSourceAddress(), mTarget.getPhysicalAddress())); |
| invokeCallback(HdmiControlManager.RESULT_SUCCESS); |
| finish(); |
| } |
| |
| @Override |
| public void handleTimerEvent(int timeoutState) { |
| if (mState != timeoutState) { |
| Slog.w(TAG, "Timer in a wrong state. Ignored."); |
| return; |
| } |
| switch (mState) { |
| case STATE_WAIT_FOR_REPORT_POWER_STATUS: |
| if (tv().isPowerStandbyOrTransient()) { |
| invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE); |
| finish(); |
| return; |
| } |
| sendSetStreamPath(); |
| break; |
| case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY: |
| case STATE_WAIT_FOR_DEVICE_POWER_ON: |
| mPowerStatusCounter++; |
| queryDevicePowerStatus(); |
| break; |
| } |
| } |
| |
| private void invokeCallback(int result) { |
| if (mCallback == null) { |
| return; |
| } |
| try { |
| mCallback.onComplete(result); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Callback failed:" + e); |
| } |
| } |
| } |