DeviceSelectAction.java revision c0c20d0522d7756d80f011e7a54bf3b51c78df41
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 26/** 27 * Handles an action that selects a logical device as a new active source. 28 * 29 * Triggered by {@link HdmiTvClient}, attempts to select the given target device 30 * for a new active source. It does its best to wake up the target in standby mode 31 * before issuing the command >Set Stream path<. 32 */ 33final class DeviceSelectAction extends FeatureAction { 34 private static final String TAG = "DeviceSelect"; 35 36 // Time in milliseconds we wait for the device power status to switch to 'Standby' 37 private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000; 38 39 // Time in milliseconds we wait for the device power status to turn to 'On'. 40 private static final int TIMEOUT_POWER_ON_MS = 5 * 1000; 41 42 // Time in milliseconds we wait for <Active Source>. 43 private static final int TIMEOUT_ACTIVE_SOURCE_MS = 20 * 1000; 44 45 // The number of times we try to wake up the target device before we give up 46 // and just send <Set Stream Path>. 47 private static final int LOOP_COUNTER_MAX = 20; 48 49 // State in which we wait for <Report Power Status> to come in response to the command 50 // <Give Device Power Status> we have sent. 51 private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1; 52 53 // State in which we wait for the device power status to switch to 'Standby'. 54 // We wait till the status becomes 'Standby' before we send <Set Stream Path> 55 // to wake up the device again. 56 private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2; 57 58 // State in which we wait for the device power status to switch to 'on'. We wait 59 // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>. 60 private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3; 61 62 // State in which we wait for the <Active Source> in response to the command 63 // <Set Stream Path> we have sent. We wait as much as TIMEOUT_ACTIVE_SOURCE_MS 64 // before we give up and mark the action as failure. 65 private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 4; 66 67 private final HdmiCecDeviceInfo mTarget; 68 private final IHdmiControlCallback mCallback; 69 70 private int mPowerStatusCounter = 0; 71 72 /** 73 * Constructor. 74 * 75 * @param source {@link HdmiCecLocalDevice} instance 76 * @param target target logical device that will be a new active source 77 * @param callback callback object 78 */ 79 public DeviceSelectAction(HdmiCecLocalDeviceTv source, 80 HdmiCecDeviceInfo target, IHdmiControlCallback callback) { 81 super(source); 82 mCallback = callback; 83 mTarget = target; 84 } 85 86 @Override 87 public boolean start() { 88 // TODO: Call the logic that display a banner saying the select action got started. 89 queryDevicePowerStatus(); 90 return true; 91 } 92 93 private void queryDevicePowerStatus() { 94 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus( 95 getSourceAddress(), mTarget.getLogicalAddress())); 96 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 97 addTimer(mState, TIMEOUT_MS); 98 } 99 100 @Override 101 public boolean processCommand(HdmiCecMessage cmd) { 102 if (cmd.getSource() != mTarget.getLogicalAddress()) { 103 return false; 104 } 105 int opcode = cmd.getOpcode(); 106 byte[] params = cmd.getParams(); 107 108 switch (mState) { 109 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 110 if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS && params.length == 1) { 111 return handleReportPowerStatus(params[0]); 112 } 113 return false; 114 case STATE_WAIT_FOR_ACTIVE_SOURCE: 115 if (opcode == Constants.MESSAGE_ACTIVE_SOURCE && params.length == 2) { 116 int activePath = HdmiUtils.twoBytesToInt(params); 117 ActiveSourceHandler 118 .create((HdmiCecLocalDeviceTv) localDevice(), mCallback) 119 .process(cmd.getSource(), activePath); 120 finish(); 121 return true; 122 } 123 return false; 124 default: 125 break; 126 } 127 return false; 128 } 129 130 private boolean handleReportPowerStatus(int powerStatus) { 131 // TODO: Check TV's own status which might have been updated during the action. 132 // If in 'Standby' or 'Transit to standby', remove the banner 133 // and stop this action. Otherwise, send <Set Stream Path> 134 switch (powerStatus) { 135 case HdmiControlManager.POWER_STATUS_ON: 136 sendSetStreamPath(); 137 return true; 138 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY: 139 if (mPowerStatusCounter < 4) { 140 mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY; 141 addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS); 142 } else { 143 sendSetStreamPath(); 144 } 145 return true; 146 case HdmiControlManager.POWER_STATUS_STANDBY: 147 if (mPowerStatusCounter == 0) { 148 turnOnDevice(); 149 } else { 150 sendSetStreamPath(); 151 } 152 return true; 153 case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON: 154 if (mPowerStatusCounter < LOOP_COUNTER_MAX) { 155 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 156 addTimer(mState, TIMEOUT_POWER_ON_MS); 157 } else { 158 sendSetStreamPath(); 159 } 160 return true; 161 } 162 return false; 163 } 164 165 private void turnOnDevice() { 166 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), 167 HdmiCecKeycode.CEC_KEYCODE_POWER); 168 sendUserControlPressedAndReleased(mTarget.getLogicalAddress(), 169 HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); 170 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 171 addTimer(mState, TIMEOUT_POWER_ON_MS); 172 } 173 174 private void sendSetStreamPath() { 175 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( 176 getSourceAddress(), mTarget.getPhysicalAddress())); 177 mState = STATE_WAIT_FOR_ACTIVE_SOURCE; 178 addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS); 179 } 180 181 @Override 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 sendSetStreamPath(); 190 break; 191 case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY: 192 case STATE_WAIT_FOR_DEVICE_POWER_ON: 193 mPowerStatusCounter++; 194 queryDevicePowerStatus(); 195 break; 196 case STATE_WAIT_FOR_ACTIVE_SOURCE: 197 // TODO: Remove the banner 198 // Display banner "Communication failed. Please check your cable or connection" 199 invokeCallback(HdmiControlManager.RESULT_TIMEOUT); 200 finish(); 201 break; 202 } 203 } 204 205 private void invokeCallback(int result) { 206 if (mCallback == null) { 207 return; 208 } 209 try { 210 mCallback.onComplete(result); 211 } catch (RemoteException e) { 212 Slog.e(TAG, "Callback failed:" + e); 213 } 214 } 215 216 int getTargetAddress() { 217 return mTarget.getLogicalAddress(); 218 } 219} 220