DeviceDiscoveryAction.java revision 7fa3a66470d2133796defd14a0600578758882ac
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.HdmiDeviceInfo;
20import android.util.Slog;
21
22import com.android.internal.util.Preconditions;
23import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
24
25import java.io.UnsupportedEncodingException;
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Feature action that handles device discovery sequences.
31 * Device discovery is launched when TV device is woken from "Standby" state
32 * or enabled "Control for Hdmi" from disabled state.
33 *
34 * <p>Device discovery goes through the following steps.
35 * <ol>
36 *   <li>Poll all non-local devices by sending &lt;Polling Message&gt;
37 *   <li>Gather "Physical address" and "device type" of all acknowledged devices
38 *   <li>Gather "OSD (display) name" of all acknowledge devices
39 *   <li>Gather "Vendor id" of all acknowledge devices
40 * </ol>
41 */
42final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
43    private static final String TAG = "DeviceDiscoveryAction";
44
45    // State in which the action is waiting for device polling.
46    private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
47    // State in which the action is waiting for gathering physical address of non-local devices.
48    private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
49    // State in which the action is waiting for gathering osd name of non-local devices.
50    private static final int STATE_WAITING_FOR_OSD_NAME = 3;
51    // State in which the action is waiting for gathering vendor id of non-local devices.
52    private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
53
54    /**
55     * Interface used to report result of device discovery.
56     */
57    interface DeviceDiscoveryCallback {
58        /**
59         * Called when device discovery is done.
60         *
61         * @param deviceInfos a list of all non-local devices. It can be empty list.
62         */
63        void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos);
64    }
65
66    // An internal container used to keep track of device information during
67    // this action.
68    private static final class DeviceInfo {
69        private final int mLogicalAddress;
70
71        private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
72        private int mPortId = Constants.INVALID_PORT_ID;
73        private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
74        private String mDisplayName = "";
75        private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
76
77        private DeviceInfo(int logicalAddress) {
78            mLogicalAddress = logicalAddress;
79        }
80
81        private HdmiDeviceInfo toHdmiDeviceInfo() {
82            return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
83                    mVendorId, mDisplayName);
84        }
85    }
86
87    private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
88    private final DeviceDiscoveryCallback mCallback;
89    private int mProcessedDeviceCount = 0;
90
91    /**
92     * Constructor.
93     *
94     * @param source an instance of {@link HdmiCecLocalDevice}.
95     */
96    DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
97        super(source);
98        mCallback = Preconditions.checkNotNull(callback);
99    }
100
101    @Override
102    boolean start() {
103        mDevices.clear();
104        mState = STATE_WAITING_FOR_DEVICE_POLLING;
105
106        pollDevices(new DevicePollingCallback() {
107            @Override
108            public void onPollingFinished(List<Integer> ackedAddress) {
109                if (ackedAddress.isEmpty()) {
110                    Slog.v(TAG, "No device is detected.");
111                    wrapUpAndFinish();
112                    return;
113                }
114
115                Slog.v(TAG, "Device detected: " + ackedAddress);
116                allocateDevices(ackedAddress);
117                startPhysicalAddressStage();
118            }
119        }, Constants.POLL_ITERATION_REVERSE_ORDER
120            | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
121        return true;
122    }
123
124    private void allocateDevices(List<Integer> addresses) {
125        for (Integer i : addresses) {
126            DeviceInfo info = new DeviceInfo(i);
127            mDevices.add(info);
128        }
129    }
130
131    private void startPhysicalAddressStage() {
132        Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
133        mProcessedDeviceCount = 0;
134        mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
135
136        checkAndProceedStage();
137    }
138
139    private boolean verifyValidLogicalAddress(int address) {
140        return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
141    }
142
143    private void queryPhysicalAddress(int address) {
144        if (!verifyValidLogicalAddress(address)) {
145            checkAndProceedStage();
146            return;
147        }
148
149        mActionTimer.clearTimerMessage();
150
151        // Check cache first and send request if not exist.
152        if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
153            return;
154        }
155        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
156        addTimer(mState, HdmiConfig.TIMEOUT_MS);
157    }
158
159    private void startOsdNameStage() {
160        Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
161        mProcessedDeviceCount = 0;
162        mState = STATE_WAITING_FOR_OSD_NAME;
163
164        checkAndProceedStage();
165    }
166
167    private void queryOsdName(int address) {
168        if (!verifyValidLogicalAddress(address)) {
169            checkAndProceedStage();
170            return;
171        }
172
173        mActionTimer.clearTimerMessage();
174
175        if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
176            return;
177        }
178        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
179        addTimer(mState, HdmiConfig.TIMEOUT_MS);
180    }
181
182    private void startVendorIdStage() {
183        Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
184
185        mProcessedDeviceCount = 0;
186        mState = STATE_WAITING_FOR_VENDOR_ID;
187
188        checkAndProceedStage();
189    }
190
191    private void queryVendorId(int address) {
192        if (!verifyValidLogicalAddress(address)) {
193            checkAndProceedStage();
194            return;
195        }
196
197        mActionTimer.clearTimerMessage();
198
199        if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
200            return;
201        }
202        sendCommand(
203                HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
204        addTimer(mState, HdmiConfig.TIMEOUT_MS);
205    }
206
207    private boolean mayProcessMessageIfCached(int address, int opcode) {
208        HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
209        if (message != null) {
210            processCommand(message);
211            return true;
212        }
213        return false;
214    }
215
216    @Override
217    boolean processCommand(HdmiCecMessage cmd) {
218        switch (mState) {
219            case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
220                if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
221                    handleReportPhysicalAddress(cmd);
222                    return true;
223                }
224                return false;
225            case STATE_WAITING_FOR_OSD_NAME:
226                if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
227                    handleSetOsdName(cmd);
228                    return true;
229                }
230                return false;
231            case STATE_WAITING_FOR_VENDOR_ID:
232                if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
233                    handleVendorId(cmd);
234                    return true;
235                }
236                return false;
237            case STATE_WAITING_FOR_DEVICE_POLLING:
238                // Fall through.
239            default:
240                return false;
241        }
242    }
243
244    private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
245        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
246
247        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
248        if (current.mLogicalAddress != cmd.getSource()) {
249            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
250                    cmd.getSource());
251            return;
252        }
253
254        byte params[] = cmd.getParams();
255        current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
256        current.mPortId = getPortId(current.mPhysicalAddress);
257        current.mDeviceType = params[2] & 0xFF;
258
259        tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
260                    current.mPhysicalAddress);
261
262        increaseProcessedDeviceCount();
263        checkAndProceedStage();
264    }
265
266    private int getPortId(int physicalAddress) {
267        return tv().getPortId(physicalAddress);
268    }
269
270    private void handleSetOsdName(HdmiCecMessage cmd) {
271        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
272
273        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
274        if (current.mLogicalAddress != cmd.getSource()) {
275            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
276                    cmd.getSource());
277            return;
278        }
279
280        String displayName = null;
281        try {
282            displayName = new String(cmd.getParams(), "US-ASCII");
283        } catch (UnsupportedEncodingException e) {
284            Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
285            // If failed to get display name, use the default name of device.
286            displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
287        }
288        current.mDisplayName = displayName;
289        increaseProcessedDeviceCount();
290        checkAndProceedStage();
291    }
292
293    private void handleVendorId(HdmiCecMessage cmd) {
294        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
295
296        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
297        if (current.mLogicalAddress != cmd.getSource()) {
298            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
299                    cmd.getSource());
300            return;
301        }
302
303        byte[] params = cmd.getParams();
304        int vendorId = HdmiUtils.threeBytesToInt(params);
305        current.mVendorId = vendorId;
306        increaseProcessedDeviceCount();
307        checkAndProceedStage();
308    }
309
310    private void increaseProcessedDeviceCount() {
311        mProcessedDeviceCount++;
312    }
313
314    private void removeDevice(int index) {
315        mDevices.remove(index);
316    }
317
318    private void wrapUpAndFinish() {
319        Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
320        ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
321        for (DeviceInfo info : mDevices) {
322            HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
323            Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
324            result.add(cecDeviceInfo);
325        }
326        Slog.v(TAG, "--------------------------------------------");
327        mCallback.onDeviceDiscoveryDone(result);
328        finish();
329        // Process any commands buffered while device discovery action was in progress.
330        tv().processAllDelayedMessages();
331    }
332
333    private void checkAndProceedStage() {
334        if (mDevices.isEmpty()) {
335            wrapUpAndFinish();
336            return;
337        }
338
339        // If finished current stage, move on to next stage.
340        if (mProcessedDeviceCount == mDevices.size()) {
341            mProcessedDeviceCount = 0;
342            switch (mState) {
343                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
344                    startOsdNameStage();
345                    return;
346                case STATE_WAITING_FOR_OSD_NAME:
347                    startVendorIdStage();
348                    return;
349                case STATE_WAITING_FOR_VENDOR_ID:
350                    wrapUpAndFinish();
351                    return;
352                default:
353                    return;
354            }
355        } else {
356            int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
357            switch (mState) {
358                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
359                    queryPhysicalAddress(address);
360                    return;
361                case STATE_WAITING_FOR_OSD_NAME:
362                    queryOsdName(address);
363                    return;
364                case STATE_WAITING_FOR_VENDOR_ID:
365                    queryVendorId(address);
366                default:
367                    return;
368            }
369        }
370    }
371
372    @Override
373    void handleTimerEvent(int state) {
374        if (mState == STATE_NONE || mState != state) {
375            return;
376        }
377
378        Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
379        removeDevice(mProcessedDeviceCount);
380        checkAndProceedStage();
381    }
382}
383