Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +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 | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 19 | import android.hardware.hdmi.HdmiDeviceInfo; |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 20 | import android.util.Slog; |
| 21 | |
| 22 | import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; |
| 23 | |
| 24 | import java.util.BitSet; |
| 25 | import java.util.List; |
| 26 | |
| 27 | /** |
| 28 | * Feature action that handles hot-plug detection mechanism. |
| 29 | * Hot-plug event is initiated by timer after device discovery action. |
| 30 | * |
| 31 | * <p>Check all devices every 15 secs except for system audio. |
| 32 | * If system audio is on, check hot-plug for audio system every 5 secs. |
| 33 | * For other devices, keep 15 secs period. |
| 34 | */ |
Jungshik Jang | 210d73d | 2014-07-04 11:11:29 +0900 | [diff] [blame] | 35 | // Seq #3 |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 36 | final class HotplugDetectionAction extends HdmiCecFeatureAction { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 37 | private static final String TAG = "HotPlugDetectionAction"; |
| 38 | |
| 39 | private static final int POLLING_INTERVAL_MS = 5000; |
| 40 | private static final int TIMEOUT_COUNT = 3; |
Jinsuk Kim | 0760011 | 2015-01-29 07:34:34 +0900 | [diff] [blame] | 41 | private static final int AVR_COUNT_MAX = 3; |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 42 | |
| 43 | // State in which waits for next polling |
| 44 | private static final int STATE_WAIT_FOR_NEXT_POLLING = 1; |
| 45 | |
| 46 | // All addresses except for broadcast (unregistered address). |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 47 | private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE |
| 48 | - Constants.ADDR_TV + 1; |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 49 | |
| 50 | private int mTimeoutCount = 0; |
| 51 | |
Jinsuk Kim | 0760011 | 2015-01-29 07:34:34 +0900 | [diff] [blame] | 52 | // Counter used to ensure the connection to AVR is stable. Occasional failure to get |
| 53 | // polling response from AVR despite its presence leads to unstable status flipping. |
| 54 | // This is a workaround to deal with it, by removing the device only if the removal |
| 55 | // is detected {@code AVR_COUNT_MAX} times in a row. |
| 56 | private int mAvrStatusCount = 0; |
| 57 | |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 58 | /** |
| 59 | * Constructor |
| 60 | * |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 61 | * @param source {@link HdmiCecLocalDevice} instance |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 62 | */ |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 63 | HotplugDetectionAction(HdmiCecLocalDevice source) { |
| 64 | super(source); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | @Override |
| 68 | boolean start() { |
| 69 | Slog.v(TAG, "Hot-plug dection started."); |
| 70 | |
| 71 | mState = STATE_WAIT_FOR_NEXT_POLLING; |
| 72 | mTimeoutCount = 0; |
| 73 | |
| 74 | // Start timer without polling. |
| 75 | // The first check for all devices will be initiated 15 seconds later. |
| 76 | addTimer(mState, POLLING_INTERVAL_MS); |
| 77 | return true; |
| 78 | } |
| 79 | |
| 80 | @Override |
| 81 | boolean processCommand(HdmiCecMessage cmd) { |
| 82 | // No-op |
| 83 | return false; |
| 84 | } |
| 85 | |
| 86 | @Override |
| 87 | void handleTimerEvent(int state) { |
| 88 | if (mState != state) { |
| 89 | return; |
| 90 | } |
| 91 | |
| 92 | if (mState == STATE_WAIT_FOR_NEXT_POLLING) { |
| 93 | mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT; |
| 94 | pollDevices(); |
| 95 | } |
| 96 | } |
| 97 | |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 98 | /** |
| 99 | * Start device polling immediately. |
| 100 | */ |
| 101 | void pollAllDevicesNow() { |
| 102 | // Clear existing timer to avoid overlapped execution |
| 103 | mActionTimer.clearTimerMessage(); |
| 104 | |
| 105 | mTimeoutCount = 0; |
| 106 | mState = STATE_WAIT_FOR_NEXT_POLLING; |
| 107 | pollAllDevices(); |
| 108 | |
| 109 | addTimer(mState, POLLING_INTERVAL_MS); |
| 110 | } |
| 111 | |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 112 | // This method is called every 5 seconds. |
| 113 | private void pollDevices() { |
| 114 | // All device check called every 15 seconds. |
| 115 | if (mTimeoutCount == 0) { |
| 116 | pollAllDevices(); |
| 117 | } else { |
Jungshik Jang | 377dcbd | 2014-07-15 15:49:02 +0900 | [diff] [blame] | 118 | if (tv().isSystemAudioActivated()) { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 119 | pollAudioSystem(); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | addTimer(mState, POLLING_INTERVAL_MS); |
| 124 | } |
| 125 | |
| 126 | private void pollAllDevices() { |
| 127 | Slog.v(TAG, "Poll all devices."); |
| 128 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 129 | pollDevices(new DevicePollingCallback() { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 130 | @Override |
| 131 | public void onPollingFinished(List<Integer> ackedAddress) { |
| 132 | checkHotplug(ackedAddress, false); |
| 133 | } |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 134 | }, Constants.POLL_ITERATION_IN_ORDER |
Jinsuk Kim | 5fba96d | 2014-07-11 11:51:34 +0900 | [diff] [blame] | 135 | | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | private void pollAudioSystem() { |
| 139 | Slog.v(TAG, "Poll audio system."); |
| 140 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 141 | pollDevices(new DevicePollingCallback() { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 142 | @Override |
| 143 | public void onPollingFinished(List<Integer> ackedAddress) { |
Jungshik Jang | a466929 | 2014-06-10 14:48:30 +0900 | [diff] [blame] | 144 | checkHotplug(ackedAddress, true); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 145 | } |
Jinsuk Kim | c0c20d0 | 2014-07-04 14:34:31 +0900 | [diff] [blame] | 146 | }, Constants.POLL_ITERATION_IN_ORDER |
Jinsuk Kim | 5fba96d | 2014-07-11 11:51:34 +0900 | [diff] [blame] | 147 | | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 148 | } |
| 149 | |
| 150 | private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 151 | BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 152 | BitSet polledResult = addressListToBitSet(ackedAddress); |
| 153 | |
| 154 | // At first, check removed devices. |
| 155 | BitSet removed = complement(currentInfos, polledResult); |
| 156 | int index = -1; |
| 157 | while ((index = removed.nextSetBit(index + 1)) != -1) { |
Jinsuk Kim | 0760011 | 2015-01-29 07:34:34 +0900 | [diff] [blame] | 158 | if (index == Constants.ADDR_AUDIO_SYSTEM) { |
Jinsuk Kim | 7b0cf64 | 2015-04-14 09:43:45 +0900 | [diff] [blame] | 159 | HdmiDeviceInfo avr = tv().getAvrDeviceInfo(); |
| 160 | if (avr != null && tv().isConnected(avr.getPortId())) { |
| 161 | ++mAvrStatusCount; |
| 162 | Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); |
| 163 | if (mAvrStatusCount < AVR_COUNT_MAX) { |
| 164 | continue; |
| 165 | } |
Jinsuk Kim | 0760011 | 2015-01-29 07:34:34 +0900 | [diff] [blame] | 166 | } |
| 167 | } |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 168 | Slog.v(TAG, "Remove device by hot-plug detection:" + index); |
| 169 | removeDevice(index); |
| 170 | } |
| 171 | |
Jinsuk Kim | 0760011 | 2015-01-29 07:34:34 +0900 | [diff] [blame] | 172 | // Reset the counter if the ack is returned from AVR. |
| 173 | if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) { |
| 174 | mAvrStatusCount = 0; |
| 175 | } |
| 176 | |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 177 | // Next, check added devices. |
| 178 | BitSet added = complement(polledResult, currentInfos); |
| 179 | index = -1; |
| 180 | while ((index = added.nextSetBit(index + 1)) != -1) { |
| 181 | Slog.v(TAG, "Add device by hot-plug detection:" + index); |
| 182 | addDevice(index); |
| 183 | } |
| 184 | } |
| 185 | |
Jungshik Jang | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 186 | private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 187 | BitSet set = new BitSet(NUM_OF_ADDRESS); |
Jungshik Jang | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 188 | for (HdmiDeviceInfo info : infoList) { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 189 | if (audioOnly) { |
Jungshik Jang | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 190 | if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 191 | set.set(info.getLogicalAddress()); |
| 192 | } |
| 193 | } else { |
| 194 | set.set(info.getLogicalAddress()); |
| 195 | } |
| 196 | } |
| 197 | return set; |
| 198 | } |
| 199 | |
| 200 | private static BitSet addressListToBitSet(List<Integer> list) { |
| 201 | BitSet set = new BitSet(NUM_OF_ADDRESS); |
| 202 | for (Integer value : list) { |
| 203 | set.set(value); |
| 204 | } |
| 205 | return set; |
| 206 | } |
| 207 | |
| 208 | // A - B = A & ~B |
| 209 | private static BitSet complement(BitSet first, BitSet second) { |
| 210 | // Need to clone it so that it doesn't touch original set. |
| 211 | BitSet clone = (BitSet) first.clone(); |
| 212 | clone.andNot(second); |
| 213 | return clone; |
| 214 | } |
| 215 | |
| 216 | private void addDevice(int addedAddress) { |
Jungshik Jang | 26dc71e | 2014-07-04 10:53:27 +0900 | [diff] [blame] | 217 | // Sending <Give Physical Address> will initiate new device action. |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 218 | sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), |
| 219 | addedAddress)); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 220 | } |
| 221 | |
| 222 | private void removeDevice(int removedAddress) { |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 223 | mayChangeRoutingPath(removedAddress); |
| 224 | mayCancelDeviceSelect(removedAddress); |
| 225 | mayCancelOneTouchRecord(removedAddress); |
| 226 | mayDisableSystemAudioAndARC(removedAddress); |
| 227 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 228 | tv().removeCecDevice(removedAddress); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 229 | } |
| 230 | |
| 231 | private void mayChangeRoutingPath(int address) { |
Jinsuk Kim | 8960d1b | 2014-08-13 10:48:30 +0900 | [diff] [blame] | 232 | HdmiDeviceInfo info = tv().getCecDeviceInfo(address); |
Jungshik Jang | 26dc71e | 2014-07-04 10:53:27 +0900 | [diff] [blame] | 233 | if (info != null) { |
| 234 | tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); |
| 235 | } |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 236 | } |
| 237 | |
| 238 | private void mayCancelDeviceSelect(int address) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 239 | List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 240 | if (actions.isEmpty()) { |
| 241 | return; |
| 242 | } |
| 243 | |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 244 | // Should have only one Device Select Action |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 245 | DeviceSelectAction action = actions.get(0); |
| 246 | if (action.getTargetAddress() == address) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 247 | removeAction(DeviceSelectAction.class); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 248 | } |
| 249 | } |
| 250 | |
| 251 | private void mayCancelOneTouchRecord(int address) { |
Jungshik Jang | 3dcdd71 | 2014-07-07 13:44:07 +0900 | [diff] [blame] | 252 | List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); |
| 253 | for (OneTouchRecordAction action : actions) { |
| 254 | if (action.getRecorderAddress() == address) { |
| 255 | removeAction(action); |
| 256 | } |
| 257 | } |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 258 | } |
| 259 | |
| 260 | private void mayDisableSystemAudioAndARC(int address) { |
Jungshik Jang | 61f4fbd | 2014-08-06 19:21:12 +0900 | [diff] [blame] | 261 | if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 262 | return; |
| 263 | } |
| 264 | |
Jinsuk Kim | 7ecfbae | 2014-07-11 14:16:29 +0900 | [diff] [blame] | 265 | // Turn off system audio mode and update settings. |
| 266 | tv().setSystemAudioMode(false, true); |
Jinsuk Kim | 5bcf5bf | 2015-04-02 07:31:21 +0900 | [diff] [blame] | 267 | if (tv().isArcEstablished()) { |
Jinsuk Kim | 757c097 | 2015-02-23 10:15:42 +0900 | [diff] [blame] | 268 | tv().setAudioReturnChannel(false); |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 269 | addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 270 | } |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 271 | } |
| 272 | } |