HotplugDetectionAction.java revision 26dc71e7feefb2417fd5f007af68614c1c197cf8
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