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 
17 package com.android.server.hdmi;
18 
19 import android.hardware.hdmi.HdmiDeviceInfo;
20 import android.util.Slog;
21 
22 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
23 
24 import java.util.BitSet;
25 import 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>TV checks 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  * <p>Playback devices check all devices every 1 minute.
36  */
37 // Seq #3
38 final class HotplugDetectionAction extends HdmiCecFeatureAction {
39     private static final String TAG = "HotPlugDetectionAction";
40 
41     public static final long POLLING_MESSAGE_INTERVAL_MS_FOR_TV = 0;
42     public static final long POLLING_MESSAGE_INTERVAL_MS_FOR_PLAYBACK = 500;
43     public static final int POLLING_BATCH_INTERVAL_MS_FOR_TV = 5000;
44     public static final int POLLING_BATCH_INTERVAL_MS_FOR_PLAYBACK = 60000;
45     public static final int TIMEOUT_COUNT = 3;
46     private static final int AVR_COUNT_MAX = 3;
47 
48     // State in which waits for next polling
49     private static final int STATE_WAIT_FOR_NEXT_POLLING = 1;
50 
51     // All addresses except for broadcast (unregistered address).
52     private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE
53             - Constants.ADDR_TV + 1;
54 
55     private int mTimeoutCount = 0;
56 
57     // Counter used to ensure the connection to AVR is stable. Occasional failure to get
58     // polling response from AVR despite its presence leads to unstable status flipping.
59     // This is a workaround to deal with it, by removing the device only if the removal
60     // is detected {@code AVR_COUNT_MAX} times in a row.
61     private int mAvrStatusCount = 0;
62 
63     private final boolean mIsTvDevice = localDevice().mService.isTvDevice();
64 
65     /**
66      * Constructor
67      *
68      * @param source {@link HdmiCecLocalDevice} instance
69      */
HotplugDetectionAction(HdmiCecLocalDevice source)70     HotplugDetectionAction(HdmiCecLocalDevice source) {
71         super(source);
72     }
73 
getPollingBatchInterval()74     private int getPollingBatchInterval() {
75         return mIsTvDevice ? POLLING_BATCH_INTERVAL_MS_FOR_TV
76                            : POLLING_BATCH_INTERVAL_MS_FOR_PLAYBACK;
77     }
78 
79     @Override
start()80     boolean start() {
81         Slog.v(TAG, "Hot-plug detection started.");
82 
83         mState = STATE_WAIT_FOR_NEXT_POLLING;
84         mTimeoutCount = 0;
85 
86         // Start timer without polling.
87         // The first check for all devices will be initiated 15 seconds later for TV panels and 60
88         // seconds later for playback devices.
89         addTimer(mState, getPollingBatchInterval());
90         return true;
91     }
92 
93     @Override
processCommand(HdmiCecMessage cmd)94     boolean processCommand(HdmiCecMessage cmd) {
95         // No-op
96         return false;
97     }
98 
99     @Override
handleTimerEvent(int state)100     void handleTimerEvent(int state) {
101         if (mState != state) {
102             return;
103         }
104 
105         if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
106             if (mIsTvDevice) {
107                 mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
108                 if (mTimeoutCount == 0) {
109                     pollAllDevices();
110                 } else if (tv().isSystemAudioActivated()) {
111                     pollAudioSystem();
112                 }
113                 addTimer(mState, POLLING_BATCH_INTERVAL_MS_FOR_TV);
114                 return;
115             }
116             pollAllDevices();
117             addTimer(mState, POLLING_BATCH_INTERVAL_MS_FOR_PLAYBACK);
118         }
119     }
120 
121     /**
122      * Start device polling immediately. This method is called only by
123      * {@link HdmiCecLocalDeviceTv#onHotplug}.
124      */
pollAllDevicesNow()125     void pollAllDevicesNow() {
126         // Clear existing timer to avoid overlapped execution
127         mActionTimer.clearTimerMessage();
128 
129         mTimeoutCount = 0;
130         mState = STATE_WAIT_FOR_NEXT_POLLING;
131         pollAllDevices();
132 
133         addTimer(mState, getPollingBatchInterval());
134     }
135 
pollAllDevices()136     private void pollAllDevices() {
137         Slog.v(TAG, "Poll all devices.");
138 
139         pollDevices(
140                 new DevicePollingCallback() {
141                     @Override
142                     public void onPollingFinished(List<Integer> ackedAddress) {
143                         checkHotplug(ackedAddress, false);
144                         Slog.v(TAG, "Finish poll all devices.");
145                     }
146                 },
147                 Constants.POLL_ITERATION_IN_ORDER | Constants.POLL_STRATEGY_REMOTES_DEVICES,
148                 HdmiConfig.HOTPLUG_DETECTION_RETRY,
149                 mIsTvDevice ? POLLING_MESSAGE_INTERVAL_MS_FOR_TV
150                             : POLLING_MESSAGE_INTERVAL_MS_FOR_PLAYBACK);
151     }
152 
pollAudioSystem()153     private void pollAudioSystem() {
154         Slog.v(TAG, "Poll audio system.");
155 
156         pollDevices(new DevicePollingCallback() {
157             @Override
158             public void onPollingFinished(List<Integer> ackedAddress) {
159                 checkHotplug(ackedAddress, true);
160             }
161         }, Constants.POLL_ITERATION_IN_ORDER
162                 | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY);
163     }
164 
checkHotplug(List<Integer> ackedAddress, boolean audioOnly)165     private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
166         List<HdmiDeviceInfo> deviceInfoList =
167                 localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
168         BitSet currentInfos = infoListToBitSet(deviceInfoList, audioOnly, false);
169         BitSet polledResult = addressListToBitSet(ackedAddress);
170 
171         // At first, check removed devices.
172         BitSet removed = complement(currentInfos, polledResult);
173         int index = -1;
174         while ((index = removed.nextSetBit(index + 1)) != -1) {
175             if (mIsTvDevice && index == Constants.ADDR_AUDIO_SYSTEM) {
176                 HdmiDeviceInfo avr = tv().getAvrDeviceInfo();
177                 if (avr != null && tv().isConnected(avr.getPortId())) {
178                     ++mAvrStatusCount;
179                     Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
180                     if (mAvrStatusCount < AVR_COUNT_MAX) {
181                         continue;
182                     }
183                 }
184             }
185             Slog.v(TAG, "Remove device by hot-plug detection:" + index);
186             removeDevice(index);
187         }
188 
189         // Reset the counter if the ack is returned from AVR.
190         if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) {
191             mAvrStatusCount = 0;
192         }
193 
194         // Next, check added devices.
195         BitSet currentInfosWithPhysicalAddress = infoListToBitSet(deviceInfoList, audioOnly, true);
196         BitSet added = complement(polledResult, currentInfosWithPhysicalAddress);
197         index = -1;
198         while ((index = added.nextSetBit(index + 1)) != -1) {
199             Slog.v(TAG, "Add device by hot-plug detection:" + index);
200             addDevice(index);
201         }
202     }
203 
infoListToBitSet( List<HdmiDeviceInfo> infoList, boolean audioOnly, boolean requirePhysicalAddress)204     private static BitSet infoListToBitSet(
205             List<HdmiDeviceInfo> infoList, boolean audioOnly, boolean requirePhysicalAddress) {
206         BitSet set = new BitSet(NUM_OF_ADDRESS);
207         for (HdmiDeviceInfo info : infoList) {
208             boolean audioOnlyConditionMet = !audioOnly
209                     || (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
210             boolean requirePhysicalAddressConditionMet = !requirePhysicalAddress
211                     || (info.getPhysicalAddress() != HdmiDeviceInfo.PATH_INVALID);
212             if (audioOnlyConditionMet && requirePhysicalAddressConditionMet) {
213                 set.set(info.getLogicalAddress());
214             }
215         }
216         return set;
217     }
218 
addressListToBitSet(List<Integer> list)219     private static BitSet addressListToBitSet(List<Integer> list) {
220         BitSet set = new BitSet(NUM_OF_ADDRESS);
221         for (Integer value : list) {
222             set.set(value);
223         }
224         return set;
225     }
226 
227     // A - B = A & ~B
complement(BitSet first, BitSet second)228     private static BitSet complement(BitSet first, BitSet second) {
229         // Need to clone it so that it doesn't touch original set.
230         BitSet clone = (BitSet) first.clone();
231         clone.andNot(second);
232         return clone;
233     }
234 
addDevice(int addedAddress)235     private void addDevice(int addedAddress) {
236         // Sending <Give Physical Address> will initiate new device action.
237         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
238                 addedAddress));
239     }
240 
removeDevice(int removedAddress)241     private void removeDevice(int removedAddress) {
242         if (mIsTvDevice) {
243             mayChangeRoutingPath(removedAddress);
244             mayCancelOneTouchRecord(removedAddress);
245             mayDisableSystemAudioAndARC(removedAddress);
246         }
247         mayCancelDeviceSelect(removedAddress);
248         localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress);
249     }
250 
mayChangeRoutingPath(int address)251     private void mayChangeRoutingPath(int address) {
252         HdmiDeviceInfo info = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(address);
253         if (info != null) {
254             tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
255         }
256     }
257 
mayCancelDeviceSelect(int address)258     private void mayCancelDeviceSelect(int address) {
259         List<DeviceSelectActionFromTv> actionsFromTv = getActions(DeviceSelectActionFromTv.class);
260         for (DeviceSelectActionFromTv action : actionsFromTv) {
261             if (action.getTargetAddress() == address) {
262                 removeAction(DeviceSelectActionFromTv.class);
263             }
264         }
265 
266         List<DeviceSelectActionFromPlayback> actionsFromPlayback = getActions(
267                 DeviceSelectActionFromPlayback.class);
268         for (DeviceSelectActionFromPlayback action : actionsFromPlayback) {
269             if (action.getTargetAddress() == address) {
270                 removeAction(DeviceSelectActionFromTv.class);
271             }
272         }
273     }
274 
mayCancelOneTouchRecord(int address)275     private void mayCancelOneTouchRecord(int address) {
276         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
277         for (OneTouchRecordAction action : actions) {
278             if (action.getRecorderAddress() == address) {
279                 removeAction(action);
280             }
281         }
282     }
283 
mayDisableSystemAudioAndARC(int address)284     private void mayDisableSystemAudioAndARC(int address) {
285         if (!HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, address)) {
286             return;
287         }
288 
289         tv().setSystemAudioMode(false);
290         if (tv().isArcEstablished()) {
291             tv().enableAudioReturnChannel(false);
292             addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
293         }
294     }
295 }
296