blob: 6c8694ea74aded64971c3dc980b3bc44de1da6ac [file] [log] [blame]
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001/*
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
Jungshik Jang60cffce2014-06-12 18:03:04 +090019import android.annotation.Nullable;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090020import android.hardware.hdmi.HdmiDeviceInfo;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090021import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang60cffce2014-06-12 18:03:04 +090022import android.hardware.hdmi.IHdmiControlCallback;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090023import 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 */
Jungshik Jangb509c2e2014-08-07 13:45:01 +090041final class RoutingControlAction extends HdmiCecFeatureAction {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090042 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.
Jinsuk Kim04fd2802014-07-03 14:04:02 +090046 private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090047
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.
Jinsuk Kim04fd2802014-07-03 14:04:02 +090052 private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090053
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
Jinsuk Kim04fd2802014-07-03 14:04:02 +090060 // true if <Give Power Status> should be sent once the new active routing path is determined.
61 private final boolean mQueryDevicePowerStatus;
62
Jinsuk Kim72b7d732014-07-24 09:15:35 +090063 // 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
Jungshik Jang60cffce2014-06-12 18:03:04 +090069 @Nullable private final IHdmiControlCallback mCallback;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090070
71 // The latest routing path. Updated by each <Routing Information> from CEC switches.
72 private int mCurrentRoutingPath;
73
Jinsuk Kim04fd2802014-07-03 14:04:02 +090074 RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus,
75 IHdmiControlCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +090076 super(localDevice);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090077 mCallback = callback;
Jinsuk Kim401e3de2014-06-14 07:47:39 +090078 mCurrentRoutingPath = path;
Jinsuk Kim04fd2802014-07-03 14:04:02 +090079 mQueryDevicePowerStatus = queryDevicePowerStatus;
Jinsuk Kim72b7d732014-07-24 09:15:35 +090080 // 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);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090085 }
86
87 @Override
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
95 public boolean processCommand(HdmiCecMessage cmd) {
96 int opcode = cmd.getOpcode();
97 byte[] params = cmd.getParams();
98 if (mState == STATE_WAIT_FOR_ROUTING_INFORMATION
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090099 && opcode == Constants.MESSAGE_ROUTING_INFORMATION) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900100 // Keep updating the physicalAddress as we receive <Routing Information>.
101 // If the routing path doesn't belong to the currently active one, we should
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900102 // ignore it since it might have come from other routing change sequence.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900103 int routingPath = HdmiUtils.twoBytesToInt(params);
Jinsuk Kim7c3a9562014-08-01 11:07:42 +0900104 if (!HdmiUtils.isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900105 return true;
106 }
107 mCurrentRoutingPath = routingPath;
108 // Stop possible previous routing change sequence if in progress.
Jinsuk Kim7c3a9562014-08-01 11:07:42 +0900109 removeActionExcept(RoutingControlAction.class, this);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900110 addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS);
111 return true;
112 } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900113 && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900114 handleReportPowerStatus(cmd.getParams()[0]);
115 return true;
116 }
117 return false;
118 }
119
120 private void handleReportPowerStatus(int devicePowerStatus) {
Jinsuk Kimcae66272014-07-04 13:26:44 +0900121 if (isPowerOnOrTransient(getTvPowerStatus())) {
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900122 updateActiveInput();
Jinsuk Kimcae66272014-07-04 13:26:44 +0900123 if (isPowerOnOrTransient(devicePowerStatus)) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900124 sendSetStreamPath();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900125 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900126 }
Jinsuk Kimdd233f32014-07-14 07:39:32 +0900127 finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
128 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900129
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900130 private void updateActiveInput() {
131 HdmiCecLocalDeviceTv tv = tv();
132 tv.setPrevPortId(tv.getActivePortId());
133 tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
134 }
135
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900136 private int getTvPowerStatus() {
Jinsuk Kimcae66272014-07-04 13:26:44 +0900137 return tv().getPowerStatus();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900138 }
139
Jinsuk Kimcae66272014-07-04 13:26:44 +0900140 private static boolean isPowerOnOrTransient(int status) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900141 return status == HdmiControlManager.POWER_STATUS_ON
142 || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900143 }
144
145 private void sendSetStreamPath() {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900146 sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(),
147 mCurrentRoutingPath));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900148 }
149
Jinsuk Kimdd233f32014-07-14 07:39:32 +0900150 private void finishWithCallback(int result) {
151 invokeCallback(result);
152 finish();
153 }
154
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900155 @Override
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:
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900163 HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath);
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900164 if (device != null && mQueryDevicePowerStatus) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900165 int deviceLogicalAddress = device.getLogicalAddress();
166 queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() {
167 @Override
168 public void onSendCompleted(int error) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900169 handlDevicePowerStatusAckResult(
170 error == HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900171 }
172 });
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900173 } else {
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900174 updateActiveInput();
Jinsuk Kimdd233f32014-07-14 07:39:32 +0900175 finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900176 }
177 return;
178 case STATE_WAIT_FOR_REPORT_POWER_STATUS:
Jinsuk Kimcae66272014-07-04 13:26:44 +0900179 if (isPowerOnOrTransient(getTvPowerStatus())) {
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900180 updateActiveInput();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900181 sendSetStreamPath();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900182 }
Jinsuk Kimdd233f32014-07-14 07:39:32 +0900183 finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900184 return;
185 }
186 }
187
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900188 private void queryDevicePowerStatus(int address, SendMessageCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900189 sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address),
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900190 callback);
191 }
192
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 {
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900198 updateActiveInput();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900199 sendSetStreamPath();
Jinsuk Kimdd233f32014-07-14 07:39:32 +0900200 finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900201 }
202 }
203
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900204 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}