HotplugDetectionAction.java revision 377dcbd53af4529c352d453424539b069909fce4
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 17package com.android.server.hdmi; 18 19import android.hardware.hdmi.HdmiCecDeviceInfo; 20import android.util.Slog; 21 22import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 23 24import java.util.BitSet; 25import 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 36final class HotplugDetectionAction extends FeatureAction { 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 42 // State in which waits for next polling 43 private static final int STATE_WAIT_FOR_NEXT_POLLING = 1; 44 45 // All addresses except for broadcast (unregistered address). 46 private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE 47 - Constants.ADDR_TV + 1; 48 49 private int mTimeoutCount = 0; 50 51 /** 52 * Constructor 53 * 54 * @param source {@link HdmiCecLocalDevice} instance 55 */ 56 HotplugDetectionAction(HdmiCecLocalDevice source) { 57 super(source); 58 } 59 60 @Override 61 boolean start() { 62 Slog.v(TAG, "Hot-plug dection started."); 63 64 mState = STATE_WAIT_FOR_NEXT_POLLING; 65 mTimeoutCount = 0; 66 67 // Start timer without polling. 68 // The first check for all devices will be initiated 15 seconds later. 69 addTimer(mState, POLLING_INTERVAL_MS); 70 return true; 71 } 72 73 @Override 74 boolean processCommand(HdmiCecMessage cmd) { 75 // No-op 76 return false; 77 } 78 79 @Override 80 void handleTimerEvent(int state) { 81 if (mState != state) { 82 return; 83 } 84 85 if (mState == STATE_WAIT_FOR_NEXT_POLLING) { 86 mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT; 87 pollDevices(); 88 } 89 } 90 91 /** 92 * Start device polling immediately. 93 */ 94 void pollAllDevicesNow() { 95 // Clear existing timer to avoid overlapped execution 96 mActionTimer.clearTimerMessage(); 97 98 mTimeoutCount = 0; 99 mState = STATE_WAIT_FOR_NEXT_POLLING; 100 pollAllDevices(); 101 102 addTimer(mState, POLLING_INTERVAL_MS); 103 } 104 105 // This method is called every 5 seconds. 106 private void pollDevices() { 107 // All device check called every 15 seconds. 108 if (mTimeoutCount == 0) { 109 pollAllDevices(); 110 } else { 111 if (tv().isSystemAudioActivated()) { 112 pollAudioSystem(); 113 } 114 } 115 116 addTimer(mState, POLLING_INTERVAL_MS); 117 } 118 119 private void pollAllDevices() { 120 Slog.v(TAG, "Poll all devices."); 121 122 pollDevices(new DevicePollingCallback() { 123 @Override 124 public void onPollingFinished(List<Integer> ackedAddress) { 125 checkHotplug(ackedAddress, false); 126 } 127 }, Constants.POLL_ITERATION_IN_ORDER 128 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY); 129 } 130 131 private void pollAudioSystem() { 132 Slog.v(TAG, "Poll audio system."); 133 134 pollDevices(new DevicePollingCallback() { 135 @Override 136 public void onPollingFinished(List<Integer> ackedAddress) { 137 checkHotplug(ackedAddress, true); 138 } 139 }, Constants.POLL_ITERATION_IN_ORDER 140 | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY); 141 } 142 143 private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { 144 BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly); 145 BitSet polledResult = addressListToBitSet(ackedAddress); 146 147 // At first, check removed devices. 148 BitSet removed = complement(currentInfos, polledResult); 149 int index = -1; 150 while ((index = removed.nextSetBit(index + 1)) != -1) { 151 Slog.v(TAG, "Remove device by hot-plug detection:" + index); 152 removeDevice(index); 153 } 154 155 // Next, check added devices. 156 BitSet added = complement(polledResult, currentInfos); 157 index = -1; 158 while ((index = added.nextSetBit(index + 1)) != -1) { 159 Slog.v(TAG, "Add device by hot-plug detection:" + index); 160 addDevice(index); 161 } 162 } 163 164 private static BitSet infoListToBitSet(List<HdmiCecDeviceInfo> infoList, boolean audioOnly) { 165 BitSet set = new BitSet(NUM_OF_ADDRESS); 166 for (HdmiCecDeviceInfo info : infoList) { 167 if (audioOnly) { 168 if (info.getDeviceType() == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) { 169 set.set(info.getLogicalAddress()); 170 } 171 } else { 172 set.set(info.getLogicalAddress()); 173 } 174 } 175 return set; 176 } 177 178 private static BitSet addressListToBitSet(List<Integer> list) { 179 BitSet set = new BitSet(NUM_OF_ADDRESS); 180 for (Integer value : list) { 181 set.set(value); 182 } 183 return set; 184 } 185 186 // A - B = A & ~B 187 private static BitSet complement(BitSet first, BitSet second) { 188 // Need to clone it so that it doesn't touch original set. 189 BitSet clone = (BitSet) first.clone(); 190 clone.andNot(second); 191 return clone; 192 } 193 194 private void addDevice(int addedAddress) { 195 // Sending <Give Physical Address> will initiate new device action. 196 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), 197 addedAddress)); 198 } 199 200 private void removeDevice(int removedAddress) { 201 mayChangeRoutingPath(removedAddress); 202 mayCancelDeviceSelect(removedAddress); 203 mayCancelOneTouchRecord(removedAddress); 204 mayDisableSystemAudioAndARC(removedAddress); 205 206 tv().removeCecDevice(removedAddress); 207 } 208 209 private void mayChangeRoutingPath(int address) { 210 HdmiCecDeviceInfo info = tv().getDeviceInfo(address); 211 if (info != null) { 212 tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); 213 } 214 } 215 216 private void mayCancelDeviceSelect(int address) { 217 List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class); 218 if (actions.isEmpty()) { 219 return; 220 } 221 222 // Should have only one Device Select Action 223 DeviceSelectAction action = actions.get(0); 224 if (action.getTargetAddress() == address) { 225 removeAction(DeviceSelectAction.class); 226 } 227 } 228 229 private void mayCancelOneTouchRecord(int address) { 230 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 231 for (OneTouchRecordAction action : actions) { 232 if (action.getRecorderAddress() == address) { 233 removeAction(action); 234 } 235 } 236 } 237 238 private void mayDisableSystemAudioAndARC(int address) { 239 if (HdmiUtils.getTypeFromAddress(address) != HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) { 240 return; 241 } 242 243 // Turn off system audio mode and update settings. 244 tv().setSystemAudioMode(false, true); 245 if (tv().isArcEstabilished()) { 246 addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); 247 } 248 } 249} 250