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.hardware.hdmi.HdmiControlManager; 20 import android.hardware.hdmi.IHdmiControlCallback; 21 import android.util.Slog; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 /** 26 * Feature action for routing control. Exchanges routing-related commands with other devices 27 * to determine the new active source. 28 * 29 * <p>This action is initiated by various cases: 30 * <ul> 31 * <li> Manual TV input switching 32 * <li> Routing change of a CEC switch other than TV 33 * <li> New CEC device at the tail of the active routing path 34 * <li> Removed CEC device from the active routing path 35 * <li> Routing at CEC enable time 36 * </ul> 37 */ 38 final class RoutingControlAction extends HdmiCecFeatureAction { 39 private static final String TAG = "RoutingControlAction"; 40 41 // State in which we wait for <Routing Information> to arrive. If timed out, we use the 42 // latest routing path to set the new active source. 43 @VisibleForTesting 44 static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; 45 46 // Time out in millseconds used for <Routing Information> 47 private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000; 48 49 // If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when 50 // the routing control/active source change happens. The listener should be called if 51 // the events are triggered by external events such as manual switch port change or incoming 52 // <Inactive Source> command. 53 private final boolean mNotifyInputChange; 54 55 // The latest routing path. Updated by each <Routing Information> from CEC switches. 56 private int mCurrentRoutingPath; 57 RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback)58 RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback) { 59 super(localDevice, callback); 60 mCurrentRoutingPath = path; 61 // Callback is non-null when routing control action is brought up by binder API. Use 62 // this as an indicator for the input change notification. These API calls will get 63 // the result through this callback, not through notification. Any other events that 64 // trigger the routing control is external, for which notifcation is used. 65 mNotifyInputChange = (callback == null); 66 } 67 68 @Override start()69 public boolean start() { 70 mState = STATE_WAIT_FOR_ROUTING_INFORMATION; 71 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 72 return true; 73 } 74 75 @Override processCommand(HdmiCecMessage cmd)76 public boolean processCommand(HdmiCecMessage cmd) { 77 int opcode = cmd.getOpcode(); 78 byte[] params = cmd.getParams(); 79 if (mState == STATE_WAIT_FOR_ROUTING_INFORMATION 80 && opcode == Constants.MESSAGE_ROUTING_INFORMATION) { 81 // Keep updating the physicalAddress as we receive <Routing Information>. 82 // If the routing path doesn't belong to the currently active one, we should 83 // ignore it since it might have come from other routing change sequence. 84 int routingPath = HdmiUtils.twoBytesToInt(params); 85 if (!HdmiUtils.isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) { 86 return true; 87 } 88 mCurrentRoutingPath = routingPath; 89 // Stop possible previous routing change sequence if in progress. 90 removeActionExcept(RoutingControlAction.class, this); 91 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); 92 return true; 93 } 94 return false; 95 } 96 updateActiveInput()97 private void updateActiveInput() { 98 HdmiCecLocalDeviceTv tv = tv(); 99 tv.setPrevPortId(tv.getActivePortId()); 100 tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); 101 } 102 sendSetStreamPath()103 private void sendSetStreamPath() { 104 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(), 105 mCurrentRoutingPath)); 106 } 107 108 @Override handleTimerEvent(int timeoutState)109 public void handleTimerEvent(int timeoutState) { 110 if (mState != timeoutState || mState == STATE_NONE) { 111 Slog.w("CEC", "Timer in a wrong state. Ignored."); 112 return; 113 } 114 switch (timeoutState) { 115 case STATE_WAIT_FOR_ROUTING_INFORMATION: 116 updateActiveInput(); 117 sendSetStreamPath(); 118 finishWithCallback(HdmiControlManager.RESULT_SUCCESS); 119 return; 120 default: 121 Slog.e("CEC", "Invalid timeoutState (" + timeoutState + ")."); 122 return; 123 } 124 } 125 } 126