1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.hdmi; 18 19 import android.hardware.hdmi.HdmiControlManager; 20 import android.hardware.hdmi.HdmiDeviceInfo; 21 import android.hardware.hdmi.HdmiTvClient; 22 import android.hardware.hdmi.IHdmiControlCallback; 23 import android.hardware.tv.cec.V1_0.SendMessageResult; 24 import android.os.RemoteException; 25 import android.util.Slog; 26 import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 27 28 /** 29 * Handles an action that selects a logical device as a new active source. 30 * 31 * Triggered by {@link HdmiTvClient}, attempts to select the given target device 32 * for a new active source. It does its best to wake up the target in standby mode 33 * before issuing the command >Set Stream path<. 34 */ 35 final class DeviceSelectAction extends HdmiCecFeatureAction { 36 private static final String TAG = "DeviceSelect"; 37 38 // Time in milliseconds we wait for the device power status to switch to 'Standby' 39 private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000; 40 41 // Time in milliseconds we wait for the device power status to turn to 'On'. 42 private static final int TIMEOUT_POWER_ON_MS = 5 * 1000; 43 44 // The number of times we try to wake up the target device before we give up 45 // and just send <Set Stream Path>. 46 private static final int LOOP_COUNTER_MAX = 20; 47 48 // State in which we wait for <Report Power Status> to come in response to the command 49 // <Give Device Power Status> we have sent. 50 private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; 51 52 // State in which we wait for the device power status to switch to 'Standby'. 53 // We wait till the status becomes 'Standby' before we send <Set Stream Path> 54 // to wake up the device again. 55 private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2; 56 57 // State in which we wait for the device power status to switch to 'on'. We wait 58 // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>. 59 private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; 60 61 private final HdmiDeviceInfo mTarget; 62 private final IHdmiControlCallback mCallback; 63 private final HdmiCecMessage mGivePowerStatus; 64 65 private int mPowerStatusCounter = 0; 66 67 /** 68 * Constructor. 69 * 70 * @param source {@link HdmiCecLocalDevice} instance 71 * @param target target logical device that will be a new active source 72 * @param callback callback object 73 */ DeviceSelectAction(HdmiCecLocalDeviceTv source, HdmiDeviceInfo target, IHdmiControlCallback callback)74 public DeviceSelectAction(HdmiCecLocalDeviceTv source, 75 HdmiDeviceInfo target, IHdmiControlCallback callback) { 76 super(source); 77 mCallback = callback; 78 mTarget = target; 79 mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( 80 getSourceAddress(), getTargetAddress()); 81 } 82 getTargetAddress()83 int getTargetAddress() { 84 return mTarget.getLogicalAddress(); 85 } 86 87 @Override start()88 public boolean start() { 89 // Seq #9 90 queryDevicePowerStatus(); 91 return true; 92 } 93 queryDevicePowerStatus()94 private void queryDevicePowerStatus() { 95 sendCommand(mGivePowerStatus, new SendMessageCallback() { 96 @Override 97 public void onSendCompleted(int error) { 98 if (error != SendMessageResult.SUCCESS) { 99 invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); 100 finish(); 101 return; 102 } 103 } 104 }); 105 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 106 addTimer(mState, HdmiConfig.TIMEOUT_MS); 107 } 108 109 @Override processCommand(HdmiCecMessage cmd)110 public boolean processCommand(HdmiCecMessage cmd) { 111 if (cmd.getSource() != getTargetAddress()) { 112 return false; 113 } 114 int opcode = cmd.getOpcode(); 115 byte[] params = cmd.getParams(); 116 117 switch (mState) { 118 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 119 if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { 120 return handleReportPowerStatus(params[0]); 121 } 122 return false; 123 default: 124 break; 125 } 126 return false; 127 } 128 handleReportPowerStatus(int powerStatus)129 private boolean handleReportPowerStatus(int powerStatus) { 130 switch (powerStatus) { 131 case HdmiControlManager.POWER_STATUS_ON: 132 sendSetStreamPath(); 133 return true; 134 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: 135 if (mPowerStatusCounter < 4) { 136 mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY; 137 addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS); 138 } else { 139 sendSetStreamPath(); 140 } 141 return true; 142 case HdmiControlManager.POWER_STATUS_STANDBY: 143 if (mPowerStatusCounter == 0) { 144 turnOnDevice(); 145 } else { 146 sendSetStreamPath(); 147 } 148 return true; 149 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON: 150 if (mPowerStatusCounter < LOOP_COUNTER_MAX) { 151 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 152 addTimer(mState, TIMEOUT_POWER_ON_MS); 153 } else { 154 sendSetStreamPath(); 155 } 156 return true; 157 } 158 return false; 159 } 160 turnOnDevice()161 private void turnOnDevice() { 162 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), 163 HdmiCecKeycode.CEC_KEYCODE_POWER); 164 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), 165 HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); 166 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 167 addTimer(mState, TIMEOUT_POWER_ON_MS); 168 } 169 sendSetStreamPath()170 private void sendSetStreamPath() { 171 // Turn the active source invalidated, which remains so till <Active Source> comes from 172 // the selected device. 173 tv().getActiveSource().invalidate(); 174 tv().setActivePath(mTarget.getPhysicalAddress()); 175 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( 176 getSourceAddress(), mTarget.getPhysicalAddress())); 177 invokeCallback(HdmiControlManager.RESULT_SUCCESS); 178 finish(); 179 } 180 181 @Override handleTimerEvent(int timeoutState)182 public void handleTimerEvent(int timeoutState) { 183 if (mState != timeoutState) { 184 Slog.w(TAG, "Timer in a wrong state. Ignored."); 185 return; 186 } 187 switch (mState) { 188 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 189 if (tv().isPowerStandbyOrTransient()) { 190 invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE); 191 finish(); 192 return; 193 } 194 sendSetStreamPath(); 195 break; 196 case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY: 197 case STATE_WAIT_FOR_DEVICE_POWER_ON: 198 mPowerStatusCounter++; 199 queryDevicePowerStatus(); 200 break; 201 } 202 } 203 invokeCallback(int result)204 private void invokeCallback(int result) { 205 if (mCallback == null) { 206 return; 207 } 208 try { 209 mCallback.onComplete(result); 210 } catch (RemoteException e) { 211 Slog.e(TAG, "Callback failed:" + e); 212 } 213 } 214 } 215