HotplugDetectionAction.java revision 5fba96df30b6b50b3cb9fe1d783320b1cc3bd6ea
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
42    // State in which waits for next polling
43    private static final int STATE_WAIT_FOR_NEXT_POLLING = 1;
44
45    // All addresses except for broadcast (unregistered address).
46    private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE
47            - Constants.ADDR_TV + 1;
48
49    private int mTimeoutCount = 0;
50
51    /**
52     * Constructor
53     *
54     * @param source {@link HdmiCecLocalDevice} instance
55     */
56    HotplugDetectionAction(HdmiCecLocalDevice source) {
57        super(source);
58    }
59
60    @Override
61    boolean start() {
62        Slog.v(TAG, "Hot-plug dection started.");
63
64        mState = STATE_WAIT_FOR_NEXT_POLLING;
65        mTimeoutCount = 0;
66
67        // Start timer without polling.
68        // The first check for all devices will be initiated 15 seconds later.
69        addTimer(mState, POLLING_INTERVAL_MS);
70        return true;
71    }
72
73    @Override
74    boolean processCommand(HdmiCecMessage cmd) {
75        // No-op
76        return false;
77    }
78
79    @Override
80    void handleTimerEvent(int state) {
81        if (mState != state) {
82            return;
83        }
84
85        if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
86            mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
87            pollDevices();
88        }
89    }
90
91    /**
92     * Start device polling immediately.
93     */
94    void pollAllDevicesNow() {
95        // Clear existing timer to avoid overlapped execution
96        mActionTimer.clearTimerMessage();
97
98        mTimeoutCount = 0;
99        mState = STATE_WAIT_FOR_NEXT_POLLING;
100        pollAllDevices();
101
102        addTimer(mState, POLLING_INTERVAL_MS);
103    }
104
105    // This method is called every 5 seconds.
106    private void pollDevices() {
107        // All device check called every 15 seconds.
108        if (mTimeoutCount == 0) {
109            pollAllDevices();
110        } else {
111            if (tv().getSystemAudioMode()) {
112                pollAudioSystem();
113            }
114        }
115
116        addTimer(mState, POLLING_INTERVAL_MS);
117    }
118
119    private void pollAllDevices() {
120        Slog.v(TAG, "Poll all devices.");
121
122        pollDevices(new DevicePollingCallback() {
123            @Override
124            public void onPollingFinished(List<Integer> ackedAddress) {
125                checkHotplug(ackedAddress, false);
126            }
127        }, Constants.POLL_ITERATION_IN_ORDER
128                | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY);
129    }
130
131    private void pollAudioSystem() {
132        Slog.v(TAG, "Poll audio system.");
133
134        pollDevices(new DevicePollingCallback() {
135            @Override
136            public void onPollingFinished(List<Integer> ackedAddress) {
137                checkHotplug(ackedAddress, true);
138            }
139        }, Constants.POLL_ITERATION_IN_ORDER
140                | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY);
141    }
142
143    private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
144        BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly);
145        BitSet polledResult = addressListToBitSet(ackedAddress);
146
147        // At first, check removed devices.
148        BitSet removed = complement(currentInfos, polledResult);
149        int index = -1;
150        while ((index = removed.nextSetBit(index + 1)) != -1) {
151            Slog.v(TAG, "Remove device by hot-plug detection:" + index);
152            removeDevice(index);
153        }
154
155        // Next, check added devices.
156        BitSet added = complement(polledResult, currentInfos);
157        index = -1;
158        while ((index = added.nextSetBit(index + 1)) != -1) {
159            Slog.v(TAG, "Add device by hot-plug detection:" + index);
160            addDevice(index);
161        }
162    }
163
164    private static BitSet infoListToBitSet(List<HdmiCecDeviceInfo> infoList, boolean audioOnly) {
165        BitSet set = new BitSet(NUM_OF_ADDRESS);
166        for (HdmiCecDeviceInfo info : infoList) {
167            if (audioOnly) {
168                if (info.getDeviceType() == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
169                    set.set(info.getLogicalAddress());
170                }
171            } else {
172                set.set(info.getLogicalAddress());
173            }
174        }
175        return set;
176    }
177
178    private static BitSet addressListToBitSet(List<Integer> list) {
179        BitSet set = new BitSet(NUM_OF_ADDRESS);
180        for (Integer value : list) {
181            set.set(value);
182        }
183        return set;
184    }
185
186    // A - B = A & ~B
187    private static BitSet complement(BitSet first, BitSet second) {
188        // Need to clone it so that it doesn't touch original set.
189        BitSet clone = (BitSet) first.clone();
190        clone.andNot(second);
191        return clone;
192    }
193
194    private void addDevice(int addedAddress) {
195        // Sending <Give Physical Address> will initiate new device action.
196        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
197                addedAddress));
198    }
199
200    private void removeDevice(int removedAddress) {
201        mayChangeRoutingPath(removedAddress);
202        mayCancelDeviceSelect(removedAddress);
203        mayCancelOneTouchRecord(removedAddress);
204        mayDisableSystemAudioAndARC(removedAddress);
205
206        tv().removeCecDevice(removedAddress);
207    }
208
209    private void mayChangeRoutingPath(int address) {
210        HdmiCecDeviceInfo info = tv().getDeviceInfo(address);
211        if (info != null) {
212            tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
213        }
214    }
215
216    private void mayCancelDeviceSelect(int address) {
217        List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
218        if (actions.isEmpty()) {
219            return;
220        }
221
222        // Should ave only one Device Select Action
223        DeviceSelectAction action = actions.get(0);
224        if (action.getTargetAddress() == address) {
225            removeAction(DeviceSelectAction.class);
226        }
227    }
228
229    private void mayCancelOneTouchRecord(int address) {
230        List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
231        for (OneTouchRecordAction action : actions) {
232            if (action.getRecorderAddress() == address) {
233                removeAction(action);
234            }
235        }
236    }
237
238    private void mayDisableSystemAudioAndARC(int address) {
239        if (HdmiUtils.getTypeFromAddress(address) != HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
240            return;
241        }
242
243        // Turn off system audio mode.
244        tv().setSystemAudioMode(false);
245        if (tv().isArcEstabilished()) {
246            addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
247        }
248    }
249}
250