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 17 package com.android.server.hdmi; 18 19 import android.annotation.Nullable; 20 import android.hardware.hdmi.HdmiDeviceInfo; 21 import android.hardware.hdmi.HdmiControlManager; 22 import android.hardware.hdmi.IHdmiControlCallback; 23 import android.os.RemoteException; 24 import android.util.Slog; 25 26 import 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 */ 41 final class RoutingControlAction extends HdmiCecFeatureAction { 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 // If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when 64 // the routing control/active source change happens. The listener should be called if 65 // the events are triggered by external events such as manual switch port change or incoming 66 // <Inactive Source> command. 67 private final boolean mNotifyInputChange; 68 69 @Nullable private final IHdmiControlCallback mCallback; 70 71 // The latest routing path. Updated by each <Routing Information> from CEC switches. 72 private int mCurrentRoutingPath; 73 RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus, IHdmiControlCallback callback)74 RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus, 75 IHdmiControlCallback callback) { 76 super(localDevice); 77 mCallback = callback; 78 mCurrentRoutingPath = path; 79 mQueryDevicePowerStatus = queryDevicePowerStatus; 80 // Callback is non-null when routing control action is brought up by binder API. Use 81 // this as an indicator for the input change notification. These API calls will get 82 // the result through this callback, not through notification. Any other events that 83 // trigger the routing control is external, for which notifcation is used. 84 mNotifyInputChange = (callback == null); 85 } 86 87 @Override start()88 public boolean start() { 89 mState = STATE_WAIT_FOR_ROUTING_INFORMATION; 90 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 91 return true; 92 } 93 94 @Override processCommand(HdmiCecMessage cmd)95 public boolean processCommand(HdmiCecMessage cmd) { 96 int opcode = cmd.getOpcode(); 97 byte[] params = cmd.getParams(); 98 if (mState == STATE_WAIT_FOR_ROUTING_INFORMATION 99 && opcode == Constants.MESSAGE_ROUTING_INFORMATION) { 100 // Keep updating the physicalAddress as we receive <Routing Information>. 101 // If the routing path doesn't belong to the currently active one, we should 102 // ignore it since it might have come from other routing change sequence. 103 int routingPath = HdmiUtils.twoBytesToInt(params); 104 if (!HdmiUtils.isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) { 105 return true; 106 } 107 mCurrentRoutingPath = routingPath; 108 // Stop possible previous routing change sequence if in progress. 109 removeActionExcept(RoutingControlAction.class, this); 110 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 111 return true; 112 } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS 113 && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { 114 handleReportPowerStatus(cmd.getParams()[0]); 115 return true; 116 } 117 return false; 118 } 119 handleReportPowerStatus(int devicePowerStatus)120 private void handleReportPowerStatus(int devicePowerStatus) { 121 if (isPowerOnOrTransient(getTvPowerStatus())) { 122 updateActiveInput(); 123 if (isPowerOnOrTransient(devicePowerStatus)) { 124 sendSetStreamPath(); 125 } 126 } 127 finishWithCallback(HdmiControlManager.RESULT_SUCCESS); 128 } 129 updateActiveInput()130 private void updateActiveInput() { 131 HdmiCecLocalDeviceTv tv = tv(); 132 tv.setPrevPortId(tv.getActivePortId()); 133 tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); 134 } 135 getTvPowerStatus()136 private int getTvPowerStatus() { 137 return tv().getPowerStatus(); 138 } 139 isPowerOnOrTransient(int status)140 private static boolean isPowerOnOrTransient(int status) { 141 return status == HdmiControlManager.POWER_STATUS_ON 142 || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 143 } 144 sendSetStreamPath()145 private void sendSetStreamPath() { 146 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(), 147 mCurrentRoutingPath)); 148 } 149 finishWithCallback(int result)150 private void finishWithCallback(int result) { 151 invokeCallback(result); 152 finish(); 153 } 154 155 @Override handleTimerEvent(int timeoutState)156 public void handleTimerEvent(int timeoutState) { 157 if (mState != timeoutState || mState == STATE_NONE) { 158 Slog.w("CEC", "Timer in a wrong state. Ignored."); 159 return; 160 } 161 switch (timeoutState) { 162 case STATE_WAIT_FOR_ROUTING_INFORMATION: 163 HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath); 164 if (device != null && mQueryDevicePowerStatus) { 165 int deviceLogicalAddress = device.getLogicalAddress(); 166 queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { 167 @Override 168 public void onSendCompleted(int error) { 169 handlDevicePowerStatusAckResult( 170 error == HdmiControlManager.RESULT_SUCCESS); 171 } 172 }); 173 } else { 174 updateActiveInput(); 175 finishWithCallback(HdmiControlManager.RESULT_SUCCESS); 176 } 177 return; 178 case STATE_WAIT_FOR_REPORT_POWER_STATUS: 179 if (isPowerOnOrTransient(getTvPowerStatus())) { 180 updateActiveInput(); 181 sendSetStreamPath(); 182 } 183 finishWithCallback(HdmiControlManager.RESULT_SUCCESS); 184 return; 185 } 186 } 187 queryDevicePowerStatus(int address, SendMessageCallback callback)188 private void queryDevicePowerStatus(int address, SendMessageCallback callback) { 189 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address), 190 callback); 191 } 192 handlDevicePowerStatusAckResult(boolean acked)193 private void handlDevicePowerStatusAckResult(boolean acked) { 194 if (acked) { 195 mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; 196 addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS); 197 } else { 198 updateActiveInput(); 199 sendSetStreamPath(); 200 finishWithCallback(HdmiControlManager.RESULT_SUCCESS); 201 } 202 } 203 invokeCallback(int result)204 private void invokeCallback(int result) { 205 if (mCallback == null) { 206 return; 207 } 208 try { 209 mCallback.onComplete(result); 210 } catch (RemoteException e) { 211 // Do nothing. 212 } 213 } 214 } 215