HotplugDetectionAction.java revision c0c20d0522d7756d80f011e7a54bf3b51c78df41
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.HdmiCecDeviceInfo;
20import 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 */
35// Seq #3
36final class HotplugDetectionAction extends FeatureAction {
37    private static final String TAG = "HotPlugDetectionAction";
38
39    private static final int POLLING_INTERVAL_MS = 5000;
40    private static final int TIMEOUT_COUNT = 3;
41    private static final int POLL_RETRY_COUNT = 2;
42
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).
47    private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE
48            - Constants.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        }, Constants.POLL_ITERATION_IN_ORDER
129                | Constants.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        }, Constants.POLL_ITERATION_IN_ORDER
141                | Constants.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() == HdmiCecDeviceInfo.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        // Sending <Give Physical Address> will initiate new device action.
197        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
198                addedAddress));
199    }
200
201    private void removeDevice(int removedAddress) {
202        mayChangeRoutingPath(removedAddress);
203        mayCancelDeviceSelect(removedAddress);
204        mayCancelOneTouchRecord(removedAddress);
205        mayDisableSystemAudioAndARC(removedAddress);
206
207        tv().removeCecDevice(removedAddress);
208    }
209
210    private void mayChangeRoutingPath(int address) {
211        HdmiCecDeviceInfo info = tv().getDeviceInfo(address);
212        if (info != null) {
213            tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
214        }
215    }
216
217    private void mayCancelDeviceSelect(int address) {
218        List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
219        if (actions.isEmpty()) {
220            return;
221        }
222
223        // Should ave only one Device Select Action
224        DeviceSelectAction action = actions.get(0);
225        if (action.getTargetAddress() == address) {
226            removeAction(DeviceSelectAction.class);
227        }
228    }
229
230    private void mayCancelOneTouchRecord(int address) {
231        // TODO: implement this.
232    }
233
234    private void mayDisableSystemAudioAndARC(int address) {
235        if (HdmiUtils.getTypeFromAddress(address) != HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
236            return;
237        }
238
239        // Turn off system audio mode.
240        tv().setSystemAudioMode(false);
241        if (tv().isArcEstabilished()) {
242            addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
243        }
244    }
245}
246