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