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