blob: 5f2d6517218098fc0f7ace7784a3f24e2f677c02 [file] [log] [blame]
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001/*
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
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090019import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090020import 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 */
Jungshik Jang210d73d2014-07-04 11:11:29 +090035// Seq #3
Jungshik Jangb509c2e2014-08-07 13:45:01 +090036final class HotplugDetectionAction extends HdmiCecFeatureAction {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090037 private static final String TAG = "HotPlugDetectionAction";
38
39 private static final int POLLING_INTERVAL_MS = 5000;
40 private static final int TIMEOUT_COUNT = 3;
Jinsuk Kim07600112015-01-29 07:34:34 +090041 private static final int AVR_COUNT_MAX = 3;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090042
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).
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090047 private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE
48 - Constants.ADDR_TV + 1;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090049
50 private int mTimeoutCount = 0;
51
Jinsuk Kim07600112015-01-29 07:34:34 +090052 // Counter used to ensure the connection to AVR is stable. Occasional failure to get
53 // polling response from AVR despite its presence leads to unstable status flipping.
54 // This is a workaround to deal with it, by removing the device only if the removal
55 // is detected {@code AVR_COUNT_MAX} times in a row.
56 private int mAvrStatusCount = 0;
57
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090058 /**
59 * Constructor
60 *
Jungshik Jang79c58a42014-06-16 16:45:36 +090061 * @param source {@link HdmiCecLocalDevice} instance
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090062 */
Jungshik Jang79c58a42014-06-16 16:45:36 +090063 HotplugDetectionAction(HdmiCecLocalDevice source) {
64 super(source);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090065 }
66
67 @Override
68 boolean start() {
69 Slog.v(TAG, "Hot-plug dection started.");
70
71 mState = STATE_WAIT_FOR_NEXT_POLLING;
72 mTimeoutCount = 0;
73
74 // Start timer without polling.
75 // The first check for all devices will be initiated 15 seconds later.
76 addTimer(mState, POLLING_INTERVAL_MS);
77 return true;
78 }
79
80 @Override
81 boolean processCommand(HdmiCecMessage cmd) {
82 // No-op
83 return false;
84 }
85
86 @Override
87 void handleTimerEvent(int state) {
88 if (mState != state) {
89 return;
90 }
91
92 if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
93 mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
94 pollDevices();
95 }
96 }
97
Jungshik Jang60cffce2014-06-12 18:03:04 +090098 /**
99 * Start device polling immediately.
100 */
101 void pollAllDevicesNow() {
102 // Clear existing timer to avoid overlapped execution
103 mActionTimer.clearTimerMessage();
104
105 mTimeoutCount = 0;
106 mState = STATE_WAIT_FOR_NEXT_POLLING;
107 pollAllDevices();
108
109 addTimer(mState, POLLING_INTERVAL_MS);
110 }
111
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900112 // This method is called every 5 seconds.
113 private void pollDevices() {
114 // All device check called every 15 seconds.
115 if (mTimeoutCount == 0) {
116 pollAllDevices();
117 } else {
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900118 if (tv().isSystemAudioActivated()) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900119 pollAudioSystem();
120 }
121 }
122
123 addTimer(mState, POLLING_INTERVAL_MS);
124 }
125
126 private void pollAllDevices() {
127 Slog.v(TAG, "Poll all devices.");
128
Jungshik Jang79c58a42014-06-16 16:45:36 +0900129 pollDevices(new DevicePollingCallback() {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900130 @Override
131 public void onPollingFinished(List<Integer> ackedAddress) {
132 checkHotplug(ackedAddress, false);
133 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900134 }, Constants.POLL_ITERATION_IN_ORDER
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900135 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900136 }
137
138 private void pollAudioSystem() {
139 Slog.v(TAG, "Poll audio system.");
140
Jungshik Jang79c58a42014-06-16 16:45:36 +0900141 pollDevices(new DevicePollingCallback() {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900142 @Override
143 public void onPollingFinished(List<Integer> ackedAddress) {
Jungshik Janga4669292014-06-10 14:48:30 +0900144 checkHotplug(ackedAddress, true);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900145 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900146 }, Constants.POLL_ITERATION_IN_ORDER
Jinsuk Kim5fba96d2014-07-11 11:51:34 +0900147 | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900148 }
149
150 private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900151 BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900152 BitSet polledResult = addressListToBitSet(ackedAddress);
153
154 // At first, check removed devices.
155 BitSet removed = complement(currentInfos, polledResult);
156 int index = -1;
157 while ((index = removed.nextSetBit(index + 1)) != -1) {
Jinsuk Kim07600112015-01-29 07:34:34 +0900158 if (index == Constants.ADDR_AUDIO_SYSTEM) {
Jinsuk Kim7b0cf642015-04-14 09:43:45 +0900159 HdmiDeviceInfo avr = tv().getAvrDeviceInfo();
160 if (avr != null && tv().isConnected(avr.getPortId())) {
161 ++mAvrStatusCount;
162 Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
163 if (mAvrStatusCount < AVR_COUNT_MAX) {
164 continue;
165 }
Jinsuk Kim07600112015-01-29 07:34:34 +0900166 }
167 }
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900168 Slog.v(TAG, "Remove device by hot-plug detection:" + index);
169 removeDevice(index);
170 }
171
Jinsuk Kim07600112015-01-29 07:34:34 +0900172 // Reset the counter if the ack is returned from AVR.
173 if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) {
174 mAvrStatusCount = 0;
175 }
176
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900177 // Next, check added devices.
178 BitSet added = complement(polledResult, currentInfos);
179 index = -1;
180 while ((index = added.nextSetBit(index + 1)) != -1) {
181 Slog.v(TAG, "Add device by hot-plug detection:" + index);
182 addDevice(index);
183 }
184 }
185
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900186 private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900187 BitSet set = new BitSet(NUM_OF_ADDRESS);
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900188 for (HdmiDeviceInfo info : infoList) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900189 if (audioOnly) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900190 if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900191 set.set(info.getLogicalAddress());
192 }
193 } else {
194 set.set(info.getLogicalAddress());
195 }
196 }
197 return set;
198 }
199
200 private static BitSet addressListToBitSet(List<Integer> list) {
201 BitSet set = new BitSet(NUM_OF_ADDRESS);
202 for (Integer value : list) {
203 set.set(value);
204 }
205 return set;
206 }
207
208 // A - B = A & ~B
209 private static BitSet complement(BitSet first, BitSet second) {
210 // Need to clone it so that it doesn't touch original set.
211 BitSet clone = (BitSet) first.clone();
212 clone.andNot(second);
213 return clone;
214 }
215
216 private void addDevice(int addedAddress) {
Jungshik Jang26dc71e2014-07-04 10:53:27 +0900217 // Sending <Give Physical Address> will initiate new device action.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900218 sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
219 addedAddress));
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900220 }
221
222 private void removeDevice(int removedAddress) {
Jungshik Jang60cffce2014-06-12 18:03:04 +0900223 mayChangeRoutingPath(removedAddress);
224 mayCancelDeviceSelect(removedAddress);
225 mayCancelOneTouchRecord(removedAddress);
226 mayDisableSystemAudioAndARC(removedAddress);
227
Jungshik Jang79c58a42014-06-16 16:45:36 +0900228 tv().removeCecDevice(removedAddress);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900229 }
230
231 private void mayChangeRoutingPath(int address) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900232 HdmiDeviceInfo info = tv().getCecDeviceInfo(address);
Jungshik Jang26dc71e2014-07-04 10:53:27 +0900233 if (info != null) {
234 tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
235 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900236 }
237
238 private void mayCancelDeviceSelect(int address) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900239 List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900240 if (actions.isEmpty()) {
241 return;
242 }
243
Yuncheol Heoc516d652014-07-11 18:23:24 +0900244 // Should have only one Device Select Action
Jungshik Jang60cffce2014-06-12 18:03:04 +0900245 DeviceSelectAction action = actions.get(0);
246 if (action.getTargetAddress() == address) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900247 removeAction(DeviceSelectAction.class);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900248 }
249 }
250
251 private void mayCancelOneTouchRecord(int address) {
Jungshik Jang3dcdd712014-07-07 13:44:07 +0900252 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
253 for (OneTouchRecordAction action : actions) {
254 if (action.getRecorderAddress() == address) {
255 removeAction(action);
256 }
257 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900258 }
259
260 private void mayDisableSystemAudioAndARC(int address) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900261 if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
Jungshik Jang60cffce2014-06-12 18:03:04 +0900262 return;
263 }
264
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900265 // Turn off system audio mode and update settings.
266 tv().setSystemAudioMode(false, true);
Jinsuk Kim5bcf5bf2015-04-02 07:31:21 +0900267 if (tv().isArcEstablished()) {
Jinsuk Kim757c0972015-02-23 10:15:42 +0900268 tv().setAudioReturnChannel(false);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900269 addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
Jungshik Jang60cffce2014-06-12 18:03:04 +0900270 }
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900271 }
272}