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