RoutingControlAction.java revision 401e3de791c0e2a4348361fbd560da9530156e22
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 java.util.concurrent.TimeUnit; 20 21import android.hardware.hdmi.IHdmiControlCallback; 22import android.hardware.hdmi.HdmiCec; 23import android.hardware.hdmi.HdmiCecDeviceInfo; 24import android.hardware.hdmi.HdmiCecMessage; 25import android.os.RemoteException; 26import android.util.Slog; 27 28import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 29 30/** 31 * Feature action for routing control. Exchanges routing-related commands with other devices 32 * to determine the new active source. 33 * 34 * <p>This action is initiated by various cases: 35 * <ul> 36 * <li> Manual TV input switching 37 * <li> Routing change of a CEC switch other than TV 38 * <li> New CEC device at the tail of the active routing path 39 * <li> Removed CEC device from the active routing path 40 * <li> Routing at CEC enable time 41 * </ul> 42 */ 43public class RoutingControlAction extends FeatureAction { 44 private static final String TAG = "RoutingControlAction"; 45 46 // State in which we wait for <Routing Information> to arrive. If timed out, we use the 47 // latest routing path to set the new active source. 48 private final static int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; 49 50 // State in which we wait for <Report Power Status> in response to <Give Device Power Status> 51 // we have sent. If the response tells us the device power is on, we send <Set Stream Path> 52 // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly 53 // just show the blank screen. 54 private final static int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2; 55 56 // Time out in millseconds used for <Routing Information> 57 private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000; 58 59 // Time out in milliseconds used for <Report Power Status> 60 private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000; 61 62 private final IHdmiControlCallback mCallback; 63 64 // The latest routing path. Updated by each <Routing Information> from CEC switches. 65 private int mCurrentRoutingPath; 66 67 RoutingControlAction(HdmiControlService service, int sourceAddress, int path, 68 IHdmiControlCallback callback) { 69 super(service, sourceAddress); 70 mCallback = callback; 71 mCurrentRoutingPath = path; 72 } 73 74 @Override 75 public boolean start() { 76 mState = STATE_WAIT_FOR_ROUTING_INFORMATION; 77 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 78 return true; 79 } 80 81 @Override 82 public boolean processCommand(HdmiCecMessage cmd) { 83 int opcode = cmd.getOpcode(); 84 byte[] params = cmd.getParams(); 85 if (mState == STATE_WAIT_FOR_ROUTING_INFORMATION 86 && opcode == HdmiCec.MESSAGE_ROUTING_INFORMATION) { 87 // Keep updating the physicalAddress as we receive <Routing Information>. 88 // If the routing path doesn't belong to the currently active one, we should 89 // ignore it since it might have come from other routing change sequence. 90 int routingPath = HdmiUtils.twoBytesToInt(params); 91 if (isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) { 92 return true; 93 } 94 mCurrentRoutingPath = routingPath; 95 // Stop possible previous routing change sequence if in progress. 96 mService.removeAction(RoutingControlAction.class); 97 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 98 return true; 99 } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS 100 && opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS) { 101 handleReportPowerStatus(cmd.getParams()[0]); 102 return true; 103 } 104 return false; 105 } 106 107 private void handleReportPowerStatus(int devicePowerStatus) { 108 int tvPowerStatus = getTvPowerStatus(); 109 if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) { 110 if (isPowerStatusOnOrTransientToOn(devicePowerStatus)) { 111 sendSetStreamPath(); 112 } else { 113 // The whole action should be stopped here if the device is in standby mode. 114 // We don't attempt to wake it up by sending <Set Stream Path>. 115 } 116 invokeCallback(HdmiCec.RESULT_SUCCESS); 117 finish(); 118 } else { 119 // TV is going into standby mode. 120 // TODO: Figure out what to do. 121 } 122 } 123 124 private int getTvPowerStatus() { 125 // TODO: Obtain TV power status. 126 return HdmiCec.POWER_STATUS_ON; 127 } 128 129 private static boolean isPowerStatusOnOrTransientToOn(int status) { 130 return status == HdmiCec.POWER_STATUS_ON || status == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 131 } 132 133 private void sendSetStreamPath() { 134 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(mSourceAddress, mCurrentRoutingPath)); 135 } 136 137 private static boolean isInActiveRoutingPath(int activePath, int newPath) { 138 // Check each nibble of the currently active path and the new path till the position 139 // where the active nibble is not zero. For (activePath, newPath), 140 // (1.1.0.0, 1.0.0.0) -> true, new path is a parent 141 // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant 142 // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling 143 // (1.0.0.0, 2.0.0.0) -> false, in a completely different path 144 for (int i = 12; i >= 0; i -= 4) { 145 int nibbleActive = (activePath >> i) & 0xF; 146 if (nibbleActive == 0) { 147 break; 148 } 149 int nibbleNew = (newPath >> i) & 0xF; 150 if (nibbleNew == 0) { 151 break; 152 } 153 if (nibbleActive != nibbleNew) { 154 return false; 155 } 156 } 157 return true; 158 } 159 160 @Override 161 public void handleTimerEvent(int timeoutState) { 162 if (mState != timeoutState || mState == STATE_NONE) { 163 Slog.w("CEC", "Timer in a wrong state. Ignored."); 164 return; 165 } 166 switch (timeoutState) { 167 case STATE_WAIT_FOR_ROUTING_INFORMATION: 168 HdmiCecDeviceInfo device = mService.getDeviceInfoByPath(mCurrentRoutingPath); 169 if (device == null) { 170 maybeChangeActiveInput(mCurrentRoutingPath); 171 } else { 172 // TODO: Also check followings and then proceed: 173 // if routing change was neither triggered by TV at CEC enable time, nor 174 // at the detection of new device at the end of the active routing path, nor 175 // by TV power on with HDMI input as the active signal source. 176 int deviceLogicalAddress = device.getLogicalAddress(); 177 queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { 178 @Override 179 public void onSendCompleted(int error) { 180 handlDevicePowerStatusAckResult(error == HdmiCec.RESULT_SUCCESS); 181 } 182 }); 183 } 184 return; 185 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 186 int tvPowerStatus = getTvPowerStatus(); 187 if (isPowerStatusOnOrTransientToOn(tvPowerStatus)) { 188 if (!maybeChangeActiveInput(mCurrentRoutingPath)) { 189 sendSetStreamPath(); 190 } 191 } 192 invokeCallback(HdmiCec.RESULT_SUCCESS); 193 finish(); 194 return; 195 } 196 } 197 198 // Called whenever an HDMI input of the TV shall become the active input. 199 private boolean maybeChangeActiveInput(int path) { 200 if (mService.getActiveInput() == mService.pathToPortId(path)) { 201 return false; 202 } 203 // TODO: Remember the currently active input 204 // if PAP/PIP is active, move the focus to the right window, otherwise switch 205 // the port. 206 // Show the OSD input change banner. 207 return true; 208 } 209 210 private void queryDevicePowerStatus(int address, SendMessageCallback callback) { 211 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mSourceAddress, address), 212 callback); 213 } 214 215 private void handlDevicePowerStatusAckResult(boolean acked) { 216 if (acked) { 217 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 218 addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS); 219 } else { 220 maybeChangeActiveInput(mCurrentRoutingPath); 221 } 222 } 223 224 // Given the HDMI port id, return the port address. 225 private int portToPath(int portId) { 226 return mService.getPortInfo(portId).getAddress(); 227 } 228 229 private void invokeCallback(int result) { 230 if (mCallback == null) { 231 return; 232 } 233 try { 234 mCallback.onComplete(result); 235 } catch (RemoteException e) { 236 // Do nothing. 237 } 238 } 239} 240