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