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