Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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 | |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 19 | import android.annotation.Nullable; |
Jungshik Jang | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 20 | import android.hardware.hdmi.HdmiDeviceInfo; |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 21 | import android.hardware.hdmi.HdmiControlManager; |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 22 | import android.hardware.hdmi.IHdmiControlCallback; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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 | */ |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 41 | final class RoutingControlAction extends HdmiCecFeatureAction { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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. |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 46 | private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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. |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 52 | private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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 | |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 60 | // true if <Give Power Status> should be sent once the new active routing path is determined. |
| 61 | private final boolean mQueryDevicePowerStatus; |
| 62 | |
Jinsuk Kim | 72b7d73 | 2014-07-24 09:15:35 +0900 | [diff] [blame] | 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 | |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 69 | @Nullable private final IHdmiControlCallback mCallback; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 70 | |
| 71 | // The latest routing path. Updated by each <Routing Information> from CEC switches. |
| 72 | private int mCurrentRoutingPath; |
| 73 | |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 74 | RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus, |
| 75 | IHdmiControlCallback callback) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 76 | super(localDevice); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 77 | mCallback = callback; |
Jinsuk Kim | 401e3de | 2014-06-14 07:47:39 +0900 | [diff] [blame] | 78 | mCurrentRoutingPath = path; |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 79 | mQueryDevicePowerStatus = queryDevicePowerStatus; |
Jinsuk Kim | 72b7d73 | 2014-07-24 09:15:35 +0900 | [diff] [blame] | 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); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 85 | } |
| 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 Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 99 | && opcode == Constants.MESSAGE_ROUTING_INFORMATION) { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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 |
Jinsuk Kim | 401e3de | 2014-06-14 07:47:39 +0900 | [diff] [blame] | 102 | // ignore it since it might have come from other routing change sequence. |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 103 | int routingPath = HdmiUtils.twoBytesToInt(params); |
Jinsuk Kim | 7c3a956 | 2014-08-01 11:07:42 +0900 | [diff] [blame] | 104 | if (!HdmiUtils.isInActiveRoutingPath(mCurrentRoutingPath, routingPath)) { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 105 | return true; |
| 106 | } |
| 107 | mCurrentRoutingPath = routingPath; |
| 108 | // Stop possible previous routing change sequence if in progress. |
Jinsuk Kim | 7c3a956 | 2014-08-01 11:07:42 +0900 | [diff] [blame] | 109 | removeActionExcept(RoutingControlAction.class, this); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 110 | addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS); |
| 111 | return true; |
| 112 | } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 113 | && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 114 | handleReportPowerStatus(cmd.getParams()[0]); |
| 115 | return true; |
| 116 | } |
| 117 | return false; |
| 118 | } |
| 119 | |
| 120 | private void handleReportPowerStatus(int devicePowerStatus) { |
Jinsuk Kim | cae6627 | 2014-07-04 13:26:44 +0900 | [diff] [blame] | 121 | if (isPowerOnOrTransient(getTvPowerStatus())) { |
Jinsuk Kim | 04f813c | 2015-04-02 16:56:49 +0900 | [diff] [blame] | 122 | updateActiveInput(); |
Jinsuk Kim | cae6627 | 2014-07-04 13:26:44 +0900 | [diff] [blame] | 123 | if (isPowerOnOrTransient(devicePowerStatus)) { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 124 | sendSetStreamPath(); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 125 | } |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 126 | } |
Jinsuk Kim | dd233f3 | 2014-07-14 07:39:32 +0900 | [diff] [blame] | 127 | finishWithCallback(HdmiControlManager.RESULT_SUCCESS); |
| 128 | } |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 129 | |
Jinsuk Kim | 04f813c | 2015-04-02 16:56:49 +0900 | [diff] [blame] | 130 | private void updateActiveInput() { |
| 131 | HdmiCecLocalDeviceTv tv = tv(); |
| 132 | tv.setPrevPortId(tv.getActivePortId()); |
| 133 | tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); |
| 134 | } |
| 135 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 136 | private int getTvPowerStatus() { |
Jinsuk Kim | cae6627 | 2014-07-04 13:26:44 +0900 | [diff] [blame] | 137 | return tv().getPowerStatus(); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 138 | } |
| 139 | |
Jinsuk Kim | cae6627 | 2014-07-04 13:26:44 +0900 | [diff] [blame] | 140 | private static boolean isPowerOnOrTransient(int status) { |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 141 | return status == HdmiControlManager.POWER_STATUS_ON |
| 142 | || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 143 | } |
| 144 | |
| 145 | private void sendSetStreamPath() { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 146 | sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(), |
| 147 | mCurrentRoutingPath)); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 148 | } |
| 149 | |
Jinsuk Kim | dd233f3 | 2014-07-14 07:39:32 +0900 | [diff] [blame] | 150 | private void finishWithCallback(int result) { |
| 151 | invokeCallback(result); |
| 152 | finish(); |
| 153 | } |
| 154 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 155 | @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 Jang | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 163 | HdmiDeviceInfo device = tv().getDeviceInfoByPath(mCurrentRoutingPath); |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 164 | if (device != null && mQueryDevicePowerStatus) { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 165 | int deviceLogicalAddress = device.getLogicalAddress(); |
| 166 | queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() { |
| 167 | @Override |
| 168 | public void onSendCompleted(int error) { |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 169 | handlDevicePowerStatusAckResult( |
| 170 | error == HdmiControlManager.RESULT_SUCCESS); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 171 | } |
| 172 | }); |
Jinsuk Kim | 04fd280 | 2014-07-03 14:04:02 +0900 | [diff] [blame] | 173 | } else { |
Jinsuk Kim | 04f813c | 2015-04-02 16:56:49 +0900 | [diff] [blame] | 174 | updateActiveInput(); |
Jinsuk Kim | dd233f3 | 2014-07-14 07:39:32 +0900 | [diff] [blame] | 175 | finishWithCallback(HdmiControlManager.RESULT_SUCCESS); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 176 | } |
| 177 | return; |
| 178 | case STATE_WAIT_FOR_REPORT_POWER_STATUS: |
Jinsuk Kim | cae6627 | 2014-07-04 13:26:44 +0900 | [diff] [blame] | 179 | if (isPowerOnOrTransient(getTvPowerStatus())) { |
Jinsuk Kim | 04f813c | 2015-04-02 16:56:49 +0900 | [diff] [blame] | 180 | updateActiveInput(); |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 181 | sendSetStreamPath(); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 182 | } |
Jinsuk Kim | dd233f3 | 2014-07-14 07:39:32 +0900 | [diff] [blame] | 183 | finishWithCallback(HdmiControlManager.RESULT_SUCCESS); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 184 | return; |
| 185 | } |
| 186 | } |
| 187 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 188 | private void queryDevicePowerStatus(int address, SendMessageCallback callback) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 189 | sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address), |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 190 | 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 Kim | 04f813c | 2015-04-02 16:56:49 +0900 | [diff] [blame] | 198 | updateActiveInput(); |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 199 | sendSetStreamPath(); |
Jinsuk Kim | dd233f3 | 2014-07-14 07:39:32 +0900 | [diff] [blame] | 200 | finishWithCallback(HdmiControlManager.RESULT_SUCCESS); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 201 | } |
| 202 | } |
| 203 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 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 | } |