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