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 */ 63 HotplugDetectionAction(HdmiCecLocalDevice source) { 64 super(source); 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 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 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 { 118 if (tv().isSystemAudioActivated()) { 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 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 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 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 ++mAvrStatusCount; 160 Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); 161 if (mAvrStatusCount < AVR_COUNT_MAX) { 162 continue; 163 } 164 } 165 Slog.v(TAG, "Remove device by hot-plug detection:" + index); 166 removeDevice(index); 167 } 168 169 // Reset the counter if the ack is returned from AVR. 170 if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) { 171 mAvrStatusCount = 0; 172 } 173 174 // Next, check added devices. 175 BitSet added = complement(polledResult, currentInfos); 176 index = -1; 177 while ((index = added.nextSetBit(index + 1)) != -1) { 178 Slog.v(TAG, "Add device by hot-plug detection:" + index); 179 addDevice(index); 180 } 181 } 182 183 private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) { 184 BitSet set = new BitSet(NUM_OF_ADDRESS); 185 for (HdmiDeviceInfo info : infoList) { 186 if (audioOnly) { 187 if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 188 set.set(info.getLogicalAddress()); 189 } 190 } else { 191 set.set(info.getLogicalAddress()); 192 } 193 } 194 return set; 195 } 196 197 private static BitSet addressListToBitSet(List<Integer> list) { 198 BitSet set = new BitSet(NUM_OF_ADDRESS); 199 for (Integer value : list) { 200 set.set(value); 201 } 202 return set; 203 } 204 205 // A - B = A & ~B 206 private static BitSet complement(BitSet first, BitSet second) { 207 // Need to clone it so that it doesn't touch original set. 208 BitSet clone = (BitSet) first.clone(); 209 clone.andNot(second); 210 return clone; 211 } 212 213 private void addDevice(int addedAddress) { 214 // Sending <Give Physical Address> will initiate new device action. 215 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), 216 addedAddress)); 217 } 218 219 private void removeDevice(int removedAddress) { 220 mayChangeRoutingPath(removedAddress); 221 mayCancelDeviceSelect(removedAddress); 222 mayCancelOneTouchRecord(removedAddress); 223 mayDisableSystemAudioAndARC(removedAddress); 224 225 tv().removeCecDevice(removedAddress); 226 } 227 228 private void mayChangeRoutingPath(int address) { 229 HdmiDeviceInfo info = tv().getCecDeviceInfo(address); 230 if (info != null) { 231 tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); 232 } 233 } 234 235 private void mayCancelDeviceSelect(int address) { 236 List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class); 237 if (actions.isEmpty()) { 238 return; 239 } 240 241 // Should have only one Device Select Action 242 DeviceSelectAction action = actions.get(0); 243 if (action.getTargetAddress() == address) { 244 removeAction(DeviceSelectAction.class); 245 } 246 } 247 248 private void mayCancelOneTouchRecord(int address) { 249 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 250 for (OneTouchRecordAction action : actions) { 251 if (action.getRecorderAddress() == address) { 252 removeAction(action); 253 } 254 } 255 } 256 257 private void mayDisableSystemAudioAndARC(int address) { 258 if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 259 return; 260 } 261 262 // Turn off system audio mode and update settings. 263 tv().setSystemAudioMode(false, true); 264 if (tv().isArcEstabilished()) { 265 addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); 266 } 267 } 268 } 269