DeviceSelectAction.java revision b509c2ecd99619248b7a07fb0fa978bb27f25cc3
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 17package com.android.server.hdmi; 18 19import android.hardware.hdmi.HdmiDeviceInfo; 20import android.hardware.hdmi.HdmiControlManager; 21import android.hardware.hdmi.HdmiTvClient; 22import android.hardware.hdmi.IHdmiControlCallback; 23import android.os.RemoteException; 24import android.util.Slog; 25 26import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; 27import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 28 29/** 30 * Handles an action that selects a logical device as a new active source. 31 * 32 * Triggered by {@link HdmiTvClient}, attempts to select the given target device 33 * for a new active source. It does its best to wake up the target in standby mode 34 * before issuing the command >Set Stream path<. 35 */ 36final class DeviceSelectAction extends HdmiCecFeatureAction { 37 private static final String TAG = "DeviceSelect"; 38 39 // Time in milliseconds we wait for the device power status to switch to 'Standby' 40 private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000; 41 42 // Time in milliseconds we wait for the device power status to turn to 'On'. 43 private static final int TIMEOUT_POWER_ON_MS = 5 * 1000; 44 45 // Time in milliseconds we wait for <Active Source>. 46 private static final int TIMEOUT_ACTIVE_SOURCE_MS = 20 * 1000; 47 48 // The number of times we try to wake up the target device before we give up 49 // and just send <Set Stream Path>. 50 private static final int LOOP_COUNTER_MAX = 20; 51 52 // State in which we wait for <Report Power Status> to come in response to the command 53 // <Give Device Power Status> we have sent. 54 private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; 55 56 // State in which we wait for the device power status to switch to 'Standby'. 57 // We wait till the status becomes 'Standby' before we send <Set Stream Path> 58 // to wake up the device again. 59 private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2; 60 61 // State in which we wait for the device power status to switch to 'on'. We wait 62 // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>. 63 private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; 64 65 // State in which we wait for the <Active Source> in response to the command 66 // <Set Stream Path> we have sent. We wait as much as TIMEOUT_ACTIVE_SOURCE_MS 67 // before we give up and mark the action as failure. 68 private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 4; 69 70 private final HdmiDeviceInfo mTarget; 71 private final IHdmiControlCallback mCallback; 72 private final HdmiCecMessage mGivePowerStatus; 73 74 private int mPowerStatusCounter = 0; 75 76 /** 77 * Constructor. 78 * 79 * @param source {@link HdmiCecLocalDevice} instance 80 * @param target target logical device that will be a new active source 81 * @param callback callback object 82 */ 83 public DeviceSelectAction(HdmiCecLocalDeviceTv source, 84 HdmiDeviceInfo target, IHdmiControlCallback callback) { 85 super(source); 86 mCallback = callback; 87 mTarget = target; 88 mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus( 89 getSourceAddress(), getTargetAddress()); 90 } 91 92 int getTargetAddress() { 93 return mTarget.getLogicalAddress(); 94 } 95 96 @Override 97 public boolean start() { 98 // Seq #9 99 queryDevicePowerStatus(); 100 return true; 101 } 102 103 private void queryDevicePowerStatus() { 104 sendCommand(mGivePowerStatus, new SendMessageCallback() { 105 @Override 106 public void onSendCompleted(int error) { 107 if (error == Constants.SEND_RESULT_NAK) { 108 invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); 109 finish(); 110 return; 111 } 112 } 113 }); 114 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 115 addTimer(mState, HdmiConfig.TIMEOUT_MS); 116 } 117 118 @Override 119 public boolean processCommand(HdmiCecMessage cmd) { 120 if (cmd.getSource() != getTargetAddress()) { 121 return false; 122 } 123 int opcode = cmd.getOpcode(); 124 byte[] params = cmd.getParams(); 125 126 switch (mState) { 127 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 128 if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { 129 return handleReportPowerStatus(params[0]); 130 } 131 return false; 132 case STATE_WAIT_FOR_ACTIVE_SOURCE: 133 if (opcode == Constants.MESSAGE_ACTIVE_SOURCE) { 134 int physicalAddress = HdmiUtils.twoBytesToInt(params); 135 ActiveSourceHandler 136 .create((HdmiCecLocalDeviceTv) localDevice(), mCallback) 137 .process(ActiveSource.of(cmd.getSource(), physicalAddress)); 138 finish(); 139 return true; 140 } 141 return false; 142 default: 143 break; 144 } 145 return false; 146 } 147 148 private boolean handleReportPowerStatus(int powerStatus) { 149 switch (powerStatus) { 150 case HdmiControlManager.POWER_STATUS_ON: 151 sendSetStreamPath(); 152 return true; 153 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: 154 if (mPowerStatusCounter < 4) { 155 mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY; 156 addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS); 157 } else { 158 sendSetStreamPath(); 159 } 160 return true; 161 case HdmiControlManager.POWER_STATUS_STANDBY: 162 if (mPowerStatusCounter == 0) { 163 turnOnDevice(); 164 } else { 165 sendSetStreamPath(); 166 } 167 return true; 168 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON: 169 if (mPowerStatusCounter < LOOP_COUNTER_MAX) { 170 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 171 addTimer(mState, TIMEOUT_POWER_ON_MS); 172 } else { 173 sendSetStreamPath(); 174 } 175 return true; 176 } 177 return false; 178 } 179 180 private void turnOnDevice() { 181 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), 182 HdmiCecKeycode.CEC_KEYCODE_POWER); 183 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), 184 HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); 185 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 186 addTimer(mState, TIMEOUT_POWER_ON_MS); 187 } 188 189 private void sendSetStreamPath() { 190 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( 191 getSourceAddress(), mTarget.getPhysicalAddress())); 192 mState = STATE_WAIT_FOR_ACTIVE_SOURCE; 193 addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS); 194 } 195 196 @Override 197 public void handleTimerEvent(int timeoutState) { 198 if (mState != timeoutState) { 199 Slog.w(TAG, "Timer in a wrong state. Ignored."); 200 return; 201 } 202 switch (mState) { 203 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 204 if (tv().isPowerStandbyOrTransient()) { 205 invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE); 206 finish(); 207 return; 208 } 209 sendSetStreamPath(); 210 break; 211 case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY: 212 case STATE_WAIT_FOR_DEVICE_POWER_ON: 213 mPowerStatusCounter++; 214 queryDevicePowerStatus(); 215 break; 216 case STATE_WAIT_FOR_ACTIVE_SOURCE: 217 invokeCallback(HdmiControlManager.RESULT_TIMEOUT); 218 finish(); 219 break; 220 } 221 } 222 223 private void invokeCallback(int result) { 224 if (mCallback == null) { 225 return; 226 } 227 try { 228 mCallback.onComplete(result); 229 } catch (RemoteException e) { 230 Slog.e(TAG, "Callback failed:" + e); 231 } 232 } 233} 234