RoutingControlAction.java revision 04fd28046acc2ac74339ed94cec76a0bfda846f7
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.annotation.Nullable; 20import android.hardware.hdmi.HdmiCec; 21import android.hardware.hdmi.HdmiCecDeviceInfo; 22import android.hardware.hdmi.HdmiCecMessage; 23import android.hardware.hdmi.IHdmiControlCallback; 24import android.os.RemoteException; 25import android.util.Slog; 26 27import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 28 29/** 30 * Feature action for routing control. Exchanges routing-related commands with other devices 31 * to determine the new active source. 32 * 33 * <p>This action is initiated by various cases: 34 * <ul> 35 * <li> Manual TV input switching 36 * <li> Routing change of a CEC switch other than TV 37 * <li> New CEC device at the tail of the active routing path 38 * <li> Removed CEC device from the active routing path 39 * <li> Routing at CEC enable time 40 * </ul> 41 */ 42final class RoutingControlAction extends FeatureAction { 43 private static final String TAG = "RoutingControlAction"; 44 45 // State in which we wait for <Routing Information> to arrive. If timed out, we use the 46 // latest routing path to set the new active source. 47 private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; 48 49 // State in which we wait for <Report Power Status> in response to <Give Device Power Status> 50 // we have sent. If the response tells us the device power is on, we send <Set Stream Path> 51 // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly 52 // just show the blank screen. 53 private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2; 54 55 // Time out in millseconds used for <Routing Information> 56 private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000; 57 58 // Time out in milliseconds used for <Report Power Status> 59 private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000; 60 61 // true if <Give Power Status> should be sent once the new active routing path is determined. 62 private final boolean mQueryDevicePowerStatus; 63 64 @Nullable private final IHdmiControlCallback mCallback; 65 66 // The latest routing path. Updated by each <Routing Information> from CEC switches. 67 private int mCurrentRoutingPath; 68 69 RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus, 70 IHdmiControlCallback callback) { 71 super(localDevice); 72 mCallback = callback; 73 mCurrentRoutingPath = path; 74 mQueryDevicePowerStatus = queryDevicePowerStatus; 75 } 76 77 @Override 78 public boolean start() { 79 mState = STATE_WAIT_FOR_ROUTING_INFORMATION; 80 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 81 return true; 82 } 83 84 @Override 85 public boolean processCommand(HdmiCecMessage cmd) { 86 int opcode = cmd.getOpcode(); 87 byte[] params = cmd.getParams(); 88 if (mState == STATE_WAIT_FOR_ROUTING_INFORMATION 89 && opcode == HdmiCec.MESSAGE_ROUTING_INFORMATION) { 90 // Keep updating the physicalAddress as we receive <Routing Information>. 91 // If the routing path doesn't belong to the currently active one, we should 92 // ignore it since it might have come from other routing change sequence. 93 int routingPath = HdmiUtils.twoBytesToInt(params); 94 if (HdmiUtils.isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) { 95 return true; 96 } 97 mCurrentRoutingPath = routingPath; 98 // Stop possible previous routing change sequence if in progress. 99 removeAction(RoutingControlAction.class); 100 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 101 return true; 102 } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS 103 && opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS) { 104 handleReportPowerStatus(cmd.getParams()[0]); 105 return true; 106 } 107 return false; 108 } 109 110 private void handleReportPowerStatus(int devicePowerStatus) { 111 int tvPowerStatus = getTvPowerStatus(); 112 if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) { 113 if (isPowerStatusOnOrTransientToOn(devicePowerStatus)) { 114 sendSetStreamPath(); 115 } else { 116 tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath)); 117 } 118 } 119 invokeCallback(HdmiCec.RESULT_SUCCESS); 120 finish(); 121 } 122 123 private int getTvPowerStatus() { 124 // TODO: Obtain TV power status. 125 return HdmiCec.POWER_STATUS_ON; 126 } 127 128 private static boolean isPowerStatusOnOrTransientToOn(int status) { 129 return status == HdmiCec.POWER_STATUS_ON || status == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 130 } 131 132 private void sendSetStreamPath() { 133 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(), 134 mCurrentRoutingPath)); 135 } 136 137 @Override 138 public void handleTimerEvent(int timeoutState) { 139 if (mState != timeoutState || mState == STATE_NONE) { 140 Slog.w("CEC", "Timer in a wrong state. Ignored."); 141 return; 142 } 143 switch (timeoutState) { 144 case STATE_WAIT_FOR_ROUTING_INFORMATION: 145 HdmiCecDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath); 146 if (device != null && mQueryDevicePowerStatus) { 147 int deviceLogicalAddress = device.getLogicalAddress(); 148 queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { 149 @Override 150 public void onSendCompleted(int error) { 151 handlDevicePowerStatusAckResult(error == HdmiCec.RESULT_SUCCESS); 152 } 153 }); 154 } else { 155 tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath)); 156 } 157 return; 158 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 159 int tvPowerStatus = getTvPowerStatus(); 160 if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) { 161 tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath)); 162 sendSetStreamPath(); 163 } 164 invokeCallback(HdmiCec.RESULT_SUCCESS); 165 finish(); 166 return; 167 } 168 } 169 170 private void queryDevicePowerStatus(int address, SendMessageCallback callback) { 171 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address), 172 callback); 173 } 174 175 private void handlDevicePowerStatusAckResult(boolean acked) { 176 if (acked) { 177 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 178 addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS); 179 } else { 180 tv().updateActivePortId(tv().pathToPortId(mCurrentRoutingPath)); 181 sendSetStreamPath(); 182 } 183 } 184 185 private void invokeCallback(int result) { 186 if (mCallback == null) { 187 return; 188 } 189 try { 190 mCallback.onComplete(result); 191 } catch (RemoteException e) { 192 // Do nothing. 193 } 194 } 195} 196