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>TV checks 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 * <p>Playback devices check all devices every 1 minute. 36 */ 37 // Seq #3 38 final class HotplugDetectionAction extends HdmiCecFeatureAction { 39 private static final String TAG = "HotPlugDetectionAction"; 40 41 public static final long POLLING_MESSAGE_INTERVAL_MS_FOR_TV = 0; 42 public static final long POLLING_MESSAGE_INTERVAL_MS_FOR_PLAYBACK = 500; 43 public static final int POLLING_BATCH_INTERVAL_MS_FOR_TV = 5000; 44 public static final int POLLING_BATCH_INTERVAL_MS_FOR_PLAYBACK = 60000; 45 public static final int TIMEOUT_COUNT = 3; 46 private static final int AVR_COUNT_MAX = 3; 47 48 // State in which waits for next polling 49 private static final int STATE_WAIT_FOR_NEXT_POLLING = 1; 50 51 // All addresses except for broadcast (unregistered address). 52 private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE 53 - Constants.ADDR_TV + 1; 54 55 private int mTimeoutCount = 0; 56 57 // Counter used to ensure the connection to AVR is stable. Occasional failure to get 58 // polling response from AVR despite its presence leads to unstable status flipping. 59 // This is a workaround to deal with it, by removing the device only if the removal 60 // is detected {@code AVR_COUNT_MAX} times in a row. 61 private int mAvrStatusCount = 0; 62 63 private final boolean mIsTvDevice = localDevice().mService.isTvDevice(); 64 65 /** 66 * Constructor 67 * 68 * @param source {@link HdmiCecLocalDevice} instance 69 */ HotplugDetectionAction(HdmiCecLocalDevice source)70 HotplugDetectionAction(HdmiCecLocalDevice source) { 71 super(source); 72 } 73 getPollingBatchInterval()74 private int getPollingBatchInterval() { 75 return mIsTvDevice ? POLLING_BATCH_INTERVAL_MS_FOR_TV 76 : POLLING_BATCH_INTERVAL_MS_FOR_PLAYBACK; 77 } 78 79 @Override start()80 boolean start() { 81 Slog.v(TAG, "Hot-plug detection started."); 82 83 mState = STATE_WAIT_FOR_NEXT_POLLING; 84 mTimeoutCount = 0; 85 86 // Start timer without polling. 87 // The first check for all devices will be initiated 15 seconds later for TV panels and 60 88 // seconds later for playback devices. 89 addTimer(mState, getPollingBatchInterval()); 90 return true; 91 } 92 93 @Override processCommand(HdmiCecMessage cmd)94 boolean processCommand(HdmiCecMessage cmd) { 95 // No-op 96 return false; 97 } 98 99 @Override handleTimerEvent(int state)100 void handleTimerEvent(int state) { 101 if (mState != state) { 102 return; 103 } 104 105 if (mState == STATE_WAIT_FOR_NEXT_POLLING) { 106 if (mIsTvDevice) { 107 mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT; 108 if (mTimeoutCount == 0) { 109 pollAllDevices(); 110 } else if (tv().isSystemAudioActivated()) { 111 pollAudioSystem(); 112 } 113 addTimer(mState, POLLING_BATCH_INTERVAL_MS_FOR_TV); 114 return; 115 } 116 pollAllDevices(); 117 addTimer(mState, POLLING_BATCH_INTERVAL_MS_FOR_PLAYBACK); 118 } 119 } 120 121 /** 122 * Start device polling immediately. This method is called only by 123 * {@link HdmiCecLocalDeviceTv#onHotplug}. 124 */ pollAllDevicesNow()125 void pollAllDevicesNow() { 126 // Clear existing timer to avoid overlapped execution 127 mActionTimer.clearTimerMessage(); 128 129 mTimeoutCount = 0; 130 mState = STATE_WAIT_FOR_NEXT_POLLING; 131 pollAllDevices(); 132 133 addTimer(mState, getPollingBatchInterval()); 134 } 135 pollAllDevices()136 private void pollAllDevices() { 137 Slog.v(TAG, "Poll all devices."); 138 139 pollDevices( 140 new DevicePollingCallback() { 141 @Override 142 public void onPollingFinished(List<Integer> ackedAddress) { 143 checkHotplug(ackedAddress, false); 144 Slog.v(TAG, "Finish poll all devices."); 145 } 146 }, 147 Constants.POLL_ITERATION_IN_ORDER | Constants.POLL_STRATEGY_REMOTES_DEVICES, 148 HdmiConfig.HOTPLUG_DETECTION_RETRY, 149 mIsTvDevice ? POLLING_MESSAGE_INTERVAL_MS_FOR_TV 150 : POLLING_MESSAGE_INTERVAL_MS_FOR_PLAYBACK); 151 } 152 pollAudioSystem()153 private void pollAudioSystem() { 154 Slog.v(TAG, "Poll audio system."); 155 156 pollDevices(new DevicePollingCallback() { 157 @Override 158 public void onPollingFinished(List<Integer> ackedAddress) { 159 checkHotplug(ackedAddress, true); 160 } 161 }, Constants.POLL_ITERATION_IN_ORDER 162 | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY); 163 } 164 checkHotplug(List<Integer> ackedAddress, boolean audioOnly)165 private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { 166 List<HdmiDeviceInfo> deviceInfoList = 167 localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false); 168 BitSet currentInfos = infoListToBitSet(deviceInfoList, audioOnly, false); 169 BitSet polledResult = addressListToBitSet(ackedAddress); 170 171 // At first, check removed devices. 172 BitSet removed = complement(currentInfos, polledResult); 173 int index = -1; 174 while ((index = removed.nextSetBit(index + 1)) != -1) { 175 if (mIsTvDevice && index == Constants.ADDR_AUDIO_SYSTEM) { 176 HdmiDeviceInfo avr = tv().getAvrDeviceInfo(); 177 if (avr != null && tv().isConnected(avr.getPortId())) { 178 ++mAvrStatusCount; 179 Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); 180 if (mAvrStatusCount < AVR_COUNT_MAX) { 181 continue; 182 } 183 } 184 } 185 Slog.v(TAG, "Remove device by hot-plug detection:" + index); 186 removeDevice(index); 187 } 188 189 // Reset the counter if the ack is returned from AVR. 190 if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) { 191 mAvrStatusCount = 0; 192 } 193 194 // Next, check added devices. 195 BitSet currentInfosWithPhysicalAddress = infoListToBitSet(deviceInfoList, audioOnly, true); 196 BitSet added = complement(polledResult, currentInfosWithPhysicalAddress); 197 index = -1; 198 while ((index = added.nextSetBit(index + 1)) != -1) { 199 Slog.v(TAG, "Add device by hot-plug detection:" + index); 200 addDevice(index); 201 } 202 } 203 infoListToBitSet( List<HdmiDeviceInfo> infoList, boolean audioOnly, boolean requirePhysicalAddress)204 private static BitSet infoListToBitSet( 205 List<HdmiDeviceInfo> infoList, boolean audioOnly, boolean requirePhysicalAddress) { 206 BitSet set = new BitSet(NUM_OF_ADDRESS); 207 for (HdmiDeviceInfo info : infoList) { 208 boolean audioOnlyConditionMet = !audioOnly 209 || (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 210 boolean requirePhysicalAddressConditionMet = !requirePhysicalAddress 211 || (info.getPhysicalAddress() != HdmiDeviceInfo.PATH_INVALID); 212 if (audioOnlyConditionMet && requirePhysicalAddressConditionMet) { 213 set.set(info.getLogicalAddress()); 214 } 215 } 216 return set; 217 } 218 addressListToBitSet(List<Integer> list)219 private static BitSet addressListToBitSet(List<Integer> list) { 220 BitSet set = new BitSet(NUM_OF_ADDRESS); 221 for (Integer value : list) { 222 set.set(value); 223 } 224 return set; 225 } 226 227 // A - B = A & ~B complement(BitSet first, BitSet second)228 private static BitSet complement(BitSet first, BitSet second) { 229 // Need to clone it so that it doesn't touch original set. 230 BitSet clone = (BitSet) first.clone(); 231 clone.andNot(second); 232 return clone; 233 } 234 addDevice(int addedAddress)235 private void addDevice(int addedAddress) { 236 // Sending <Give Physical Address> will initiate new device action. 237 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), 238 addedAddress)); 239 } 240 removeDevice(int removedAddress)241 private void removeDevice(int removedAddress) { 242 if (mIsTvDevice) { 243 mayChangeRoutingPath(removedAddress); 244 mayCancelOneTouchRecord(removedAddress); 245 mayDisableSystemAudioAndARC(removedAddress); 246 } 247 mayCancelDeviceSelect(removedAddress); 248 localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress); 249 } 250 mayChangeRoutingPath(int address)251 private void mayChangeRoutingPath(int address) { 252 HdmiDeviceInfo info = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(address); 253 if (info != null) { 254 tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); 255 } 256 } 257 mayCancelDeviceSelect(int address)258 private void mayCancelDeviceSelect(int address) { 259 List<DeviceSelectActionFromTv> actionsFromTv = getActions(DeviceSelectActionFromTv.class); 260 for (DeviceSelectActionFromTv action : actionsFromTv) { 261 if (action.getTargetAddress() == address) { 262 removeAction(DeviceSelectActionFromTv.class); 263 } 264 } 265 266 List<DeviceSelectActionFromPlayback> actionsFromPlayback = getActions( 267 DeviceSelectActionFromPlayback.class); 268 for (DeviceSelectActionFromPlayback action : actionsFromPlayback) { 269 if (action.getTargetAddress() == address) { 270 removeAction(DeviceSelectActionFromTv.class); 271 } 272 } 273 } 274 mayCancelOneTouchRecord(int address)275 private void mayCancelOneTouchRecord(int address) { 276 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 277 for (OneTouchRecordAction action : actions) { 278 if (action.getRecorderAddress() == address) { 279 removeAction(action); 280 } 281 } 282 } 283 mayDisableSystemAudioAndARC(int address)284 private void mayDisableSystemAudioAndARC(int address) { 285 if (!HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, address)) { 286 return; 287 } 288 289 tv().setSystemAudioMode(false); 290 if (tv().isArcEstablished()) { 291 tv().enableAudioReturnChannel(false); 292 addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); 293 } 294 } 295 } 296