HotplugDetectionAction.java revision 3ecdd832c77483c909fbf90d17d0e6d97ca365ee
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 */ 37final class HotplugDetectionAction extends FeatureAction { 38 private static final String TAG = "HotPlugDetectionAction"; 39 40 private static final int POLLING_INTERVAL_MS = 5000; 41 private static final int TIMEOUT_COUNT = 3; 42 private static final int POLL_RETRY_COUNT = 2; 43 44 // State in which waits for next polling 45 private static final int STATE_WAIT_FOR_NEXT_POLLING = 1; 46 47 // All addresses except for broadcast (unregistered address). 48 private static final int NUM_OF_ADDRESS = HdmiCec.ADDR_SPECIFIC_USE - HdmiCec.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 }, HdmiConstants.POLL_ITERATION_IN_ORDER 129 | HdmiConstants.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 }, HdmiConstants.POLL_ITERATION_IN_ORDER 141 | HdmiConstants.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() == HdmiCec.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 // Send <Give Physical Address>. 197 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), 198 addedAddress)); 199 } 200 201 private void removeDevice(int removedAddress) { 202 // TODO: move the following logic to local device once move many logic to 203 // local device. 204 mayChangeRoutingPath(removedAddress); 205 mayCancelDeviceSelect(removedAddress); 206 mayCancelOneTouchRecord(removedAddress); 207 mayDisableSystemAudioAndARC(removedAddress); 208 209 tv().removeCecDevice(removedAddress); 210 } 211 212 private void mayChangeRoutingPath(int address) { 213 // TODO: if removed address is current active source, it should change active source 214 // path new one. we can keep previous selection or can choose default input, 215 // such as internal tuner. Consider send intent to app so that app 216 // can handle it. 217 } 218 219 private void mayCancelDeviceSelect(int address) { 220 List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class); 221 if (actions.isEmpty()) { 222 return; 223 } 224 225 // Should ave only one Device Select Action 226 DeviceSelectAction action = actions.get(0); 227 if (action.getTargetAddress() == address) { 228 removeAction(DeviceSelectAction.class); 229 } 230 } 231 232 private void mayCancelOneTouchRecord(int address) { 233 // TODO: implement this. 234 } 235 236 private void mayDisableSystemAudioAndARC(int address) { 237 if (HdmiCec.getTypeFromAddress(address) != HdmiCec.DEVICE_AUDIO_SYSTEM) { 238 return; 239 } 240 241 // Turn off system audio mode. 242 tv().setSystemAudioMode(false); 243 if (tv().getArcStatus()) { 244 addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); 245 } 246 } 247} 248