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.HdmiDeviceInfo; 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 */ 35 // Seq #3 36 final class HotplugDetectionAction extends HdmiCecFeatureAction { 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; 41 private static final int AVR_COUNT_MAX = 3; 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). 47 private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE 48 - Constants.ADDR_TV + 1; 49 50 private int mTimeoutCount = 0; 51 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 58 /** 59 * Constructor 60 * 61 * @param source {@link HdmiCecLocalDevice} instance 62 */ HotplugDetectionAction(HdmiCecLocalDevice source)63 HotplugDetectionAction(HdmiCecLocalDevice source) { 64 super(source); 65 } 66 67 @Override start()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 processCommand(HdmiCecMessage cmd)81 boolean processCommand(HdmiCecMessage cmd) { 82 // No-op 83 return false; 84 } 85 86 @Override handleTimerEvent(int state)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 98 /** 99 * Start device polling immediately. 100 */ pollAllDevicesNow()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 112 // This method is called every 5 seconds. pollDevices()113 private void pollDevices() { 114 // All device check called every 15 seconds. 115 if (mTimeoutCount == 0) { 116 pollAllDevices(); 117 } else { 118 if (tv().isSystemAudioActivated()) { 119 pollAudioSystem(); 120 } 121 } 122 123 addTimer(mState, POLLING_INTERVAL_MS); 124 } 125 pollAllDevices()126 private void pollAllDevices() { 127 Slog.v(TAG, "Poll all devices."); 128 129 pollDevices(new DevicePollingCallback() { 130 @Override 131 public void onPollingFinished(List<Integer> ackedAddress) { 132 checkHotplug(ackedAddress, false); 133 } 134 }, Constants.POLL_ITERATION_IN_ORDER 135 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY); 136 } 137 pollAudioSystem()138 private void pollAudioSystem() { 139 Slog.v(TAG, "Poll audio system."); 140 141 pollDevices(new DevicePollingCallback() { 142 @Override 143 public void onPollingFinished(List<Integer> ackedAddress) { 144 checkHotplug(ackedAddress, true); 145 } 146 }, Constants.POLL_ITERATION_IN_ORDER 147 | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY); 148 } 149 checkHotplug(List<Integer> ackedAddress, boolean audioOnly)150 private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { 151 BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly); 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) { 158 if (index == Constants.ADDR_AUDIO_SYSTEM) { 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 } 166 } 167 } 168 Slog.v(TAG, "Remove device by hot-plug detection:" + index); 169 removeDevice(index); 170 } 171 172 // Reset the counter if the ack is returned from AVR. 173 if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) { 174 mAvrStatusCount = 0; 175 } 176 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 infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly)186 private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) { 187 BitSet set = new BitSet(NUM_OF_ADDRESS); 188 for (HdmiDeviceInfo info : infoList) { 189 if (audioOnly) { 190 if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 191 set.set(info.getLogicalAddress()); 192 } 193 } else { 194 set.set(info.getLogicalAddress()); 195 } 196 } 197 return set; 198 } 199 addressListToBitSet(List<Integer> list)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 complement(BitSet first, BitSet second)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 addDevice(int addedAddress)216 private void addDevice(int addedAddress) { 217 // Sending <Give Physical Address> will initiate new device action. 218 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), 219 addedAddress)); 220 } 221 removeDevice(int removedAddress)222 private void removeDevice(int removedAddress) { 223 mayChangeRoutingPath(removedAddress); 224 mayCancelDeviceSelect(removedAddress); 225 mayCancelOneTouchRecord(removedAddress); 226 mayDisableSystemAudioAndARC(removedAddress); 227 228 tv().removeCecDevice(removedAddress); 229 } 230 mayChangeRoutingPath(int address)231 private void mayChangeRoutingPath(int address) { 232 HdmiDeviceInfo info = tv().getCecDeviceInfo(address); 233 if (info != null) { 234 tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); 235 } 236 } 237 mayCancelDeviceSelect(int address)238 private void mayCancelDeviceSelect(int address) { 239 List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class); 240 if (actions.isEmpty()) { 241 return; 242 } 243 244 // Should have only one Device Select Action 245 DeviceSelectAction action = actions.get(0); 246 if (action.getTargetAddress() == address) { 247 removeAction(DeviceSelectAction.class); 248 } 249 } 250 mayCancelOneTouchRecord(int address)251 private void mayCancelOneTouchRecord(int address) { 252 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 253 for (OneTouchRecordAction action : actions) { 254 if (action.getRecorderAddress() == address) { 255 removeAction(action); 256 } 257 } 258 } 259 mayDisableSystemAudioAndARC(int address)260 private void mayDisableSystemAudioAndARC(int address) { 261 if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 262 return; 263 } 264 265 tv().setSystemAudioMode(false); 266 if (tv().isArcEstablished()) { 267 tv().enableAudioReturnChannel(false); 268 addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); 269 } 270 } 271 } 272