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