DeviceSelectAction.java revision 60cffce420db4c3395f86d3b9bb36003adf26f5d
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.IHdmiControlCallback; 20import android.hardware.hdmi.HdmiCecDeviceInfo; 21import android.hardware.hdmi.HdmiCec; 22import android.hardware.hdmi.HdmiCecMessage; 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 private final int mSourcePath; 70 71 private int mPowerStatusCounter = 0; 72 73 /** 74 * Constructor. 75 * 76 * @param service {@link HdmiControlService} instance 77 * @param sourceAddress logical address of TV initiating this action 78 * @param sourcePath physical address of TV 79 * @param target target logical device that will be a new active source 80 * @param callback callback object 81 */ 82 public DeviceSelectAction(HdmiControlService service, int sourceAddress, int sourcePath, 83 HdmiCecDeviceInfo target, IHdmiControlCallback callback) { 84 super(service, sourceAddress); 85 mCallback = callback; 86 mSourcePath = sourcePath; 87 mTarget = target; 88 } 89 90 @Override 91 public boolean start() { 92 // TODO: Call the logic that display a banner saying the select action got started. 93 queryDevicePowerStatus(); 94 return true; 95 } 96 97 private void queryDevicePowerStatus() { 98 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus( 99 mSourceAddress, mTarget.getLogicalAddress())); 100 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 101 addTimer(mState, TIMEOUT_MS); 102 } 103 104 @Override 105 public boolean processCommand(HdmiCecMessage cmd) { 106 if (cmd.getSource() != mTarget.getLogicalAddress()) { 107 return false; 108 } 109 int opcode = cmd.getOpcode(); 110 byte[] params = cmd.getParams(); 111 112 switch (mState) { 113 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 114 if (opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS && params.length == 1) { 115 return handleReportPowerStatus(params[0]); 116 } 117 return false; 118 case STATE_WAIT_FOR_ACTIVE_SOURCE: 119 if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE && params.length == 2) { 120 int activePath = HdmiUtils.twoBytesToInt(params); 121 ActiveSourceHandler.create(mService, mSourceAddress, mSourcePath, mCallback) 122 .process(cmd.getSource(), activePath); 123 finish(); 124 return true; 125 } 126 return false; 127 default: 128 break; 129 } 130 return false; 131 } 132 133 private boolean handleReportPowerStatus(int powerStatus) { 134 // TODO: Check TV's own status which might have been updated during the action. 135 // If in 'Standby' or 'Transit to standby', remove the banner 136 // and stop this action. Otherwise, send <Set Stream Path> 137 switch (powerStatus) { 138 case HdmiCec.POWER_STATUS_ON: 139 sendSetStreamPath(); 140 return true; 141 case HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY: 142 if (mPowerStatusCounter < 4) { 143 mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY; 144 addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS); 145 } else { 146 sendSetStreamPath(); 147 } 148 return true; 149 case HdmiCec.POWER_STATUS_STANDBY: 150 if (mPowerStatusCounter == 0) { 151 turnOnDevice(); 152 } else { 153 sendSetStreamPath(); 154 } 155 return true; 156 case HdmiCec.POWER_STATUS_TRANSIENT_TO_ON: 157 if (mPowerStatusCounter < LOOP_COUNTER_MAX) { 158 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 159 addTimer(mState, TIMEOUT_POWER_ON_MS); 160 } else { 161 sendSetStreamPath(); 162 } 163 return true; 164 } 165 return false; 166 } 167 168 private void turnOnDevice() { 169 sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER); 170 sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION); 171 mState = STATE_WAIT_FOR_DEVICE_POWER_ON; 172 addTimer(mState, TIMEOUT_POWER_ON_MS); 173 } 174 175 private void sendSetStreamPath() { 176 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( 177 mSourceAddress, mTarget.getPhysicalAddress())); 178 mState = STATE_WAIT_FOR_ACTIVE_SOURCE; 179 addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS); 180 } 181 182 private void sendRemoteKeyCommand(int keyCode) { 183 sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(mSourceAddress, 184 mTarget.getLogicalAddress(), keyCode)); 185 sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(mSourceAddress, 186 mTarget.getLogicalAddress())); 187 } 188 189 @Override 190 public void handleTimerEvent(int timeoutState) { 191 if (mState != timeoutState) { 192 Slog.w(TAG, "Timer in a wrong state. Ignored."); 193 return; 194 } 195 switch (mState) { 196 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 197 sendSetStreamPath(); 198 break; 199 case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY: 200 case STATE_WAIT_FOR_DEVICE_POWER_ON: 201 mPowerStatusCounter++; 202 queryDevicePowerStatus(); 203 break; 204 case STATE_WAIT_FOR_ACTIVE_SOURCE: 205 // TODO: Remove the banner 206 // Display banner "Communication failed. Please check your cable or connection" 207 invokeCallback(HdmiCec.RESULT_TIMEOUT); 208 finish(); 209 break; 210 } 211 } 212 213 private void invokeCallback(int result) { 214 if (mCallback == null) { 215 return; 216 } 217 try { 218 mCallback.onComplete(result); 219 } catch (RemoteException e) { 220 Slog.e(TAG, "Callback failed:" + e); 221 } 222 } 223 224 int getTargetAddress() { 225 return mTarget.getLogicalAddress(); 226 } 227} 228