DeviceDiscoveryAction.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.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 FeatureAction {
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<HdmiCecDeviceInfo> 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 mVendorId = Constants.UNKNOWN_VENDOR_ID;
73        private String mDisplayName = "";
74        private int mDeviceType = HdmiCecDeviceInfo.DEVICE_INACTIVE;
75
76        private DeviceInfo(int logicalAddress) {
77            mLogicalAddress = logicalAddress;
78        }
79
80        private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
81            return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
82                    mDisplayName);
83        }
84    }
85
86    private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
87    private final DeviceDiscoveryCallback mCallback;
88    private int mProcessedDeviceCount = 0;
89
90    /**
91     * Constructor.
92     *
93     * @param source an instance of {@link HdmiCecLocalDevice}.
94     */
95    DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
96        super(source);
97        mCallback = Preconditions.checkNotNull(callback);
98    }
99
100    @Override
101    boolean start() {
102        mDevices.clear();
103        mState = STATE_WAITING_FOR_DEVICE_POLLING;
104
105        pollDevices(new DevicePollingCallback() {
106            @Override
107            public void onPollingFinished(List<Integer> ackedAddress) {
108                if (ackedAddress.isEmpty()) {
109                    Slog.v(TAG, "No device is detected.");
110                    wrapUpAndFinish();
111                    return;
112                }
113
114                Slog.v(TAG, "Device detected: " + ackedAddress);
115                allocateDevices(ackedAddress);
116                startPhysicalAddressStage();
117            }
118        }, Constants.POLL_ITERATION_REVERSE_ORDER
119            | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
120        return true;
121    }
122
123    private void allocateDevices(List<Integer> addresses) {
124        for (Integer i : addresses) {
125            DeviceInfo info = new DeviceInfo(i);
126            mDevices.add(info);
127        }
128    }
129
130    private void startPhysicalAddressStage() {
131        Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
132        mProcessedDeviceCount = 0;
133        mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
134
135        checkAndProceedStage();
136    }
137
138    private boolean verifyValidLogicalAddress(int address) {
139        return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
140    }
141
142    private void queryPhysicalAddress(int address) {
143        if (!verifyValidLogicalAddress(address)) {
144            checkAndProceedStage();
145            return;
146        }
147
148        mActionTimer.clearTimerMessage();
149
150        // Check cache first and send request if not exist.
151        if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
152            return;
153        }
154        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
155        addTimer(mState, HdmiConfig.TIMEOUT_MS);
156    }
157
158    private void startOsdNameStage() {
159        Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
160        mProcessedDeviceCount = 0;
161        mState = STATE_WAITING_FOR_OSD_NAME;
162
163        checkAndProceedStage();
164    }
165
166    private void queryOsdName(int address) {
167        if (!verifyValidLogicalAddress(address)) {
168            checkAndProceedStage();
169            return;
170        }
171
172        mActionTimer.clearTimerMessage();
173
174        if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
175            return;
176        }
177        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
178        addTimer(mState, HdmiConfig.TIMEOUT_MS);
179    }
180
181    private void startVendorIdStage() {
182        Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
183
184        mProcessedDeviceCount = 0;
185        mState = STATE_WAITING_FOR_VENDOR_ID;
186
187        checkAndProceedStage();
188    }
189
190    private void queryVendorId(int address) {
191        if (!verifyValidLogicalAddress(address)) {
192            checkAndProceedStage();
193            return;
194        }
195
196        mActionTimer.clearTimerMessage();
197
198        if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
199            return;
200        }
201        sendCommand(
202                HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
203        addTimer(mState, HdmiConfig.TIMEOUT_MS);
204    }
205
206    private boolean mayProcessMessageIfCached(int address, int opcode) {
207        HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
208        if (message != null) {
209            processCommand(message);
210            return true;
211        }
212        return false;
213    }
214
215    @Override
216    boolean processCommand(HdmiCecMessage cmd) {
217        switch (mState) {
218            case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
219                if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
220                    handleReportPhysicalAddress(cmd);
221                    return true;
222                }
223                return false;
224            case STATE_WAITING_FOR_OSD_NAME:
225                if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
226                    handleSetOsdName(cmd);
227                    return true;
228                }
229                return false;
230            case STATE_WAITING_FOR_VENDOR_ID:
231                if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
232                    handleVendorId(cmd);
233                    return true;
234                }
235                return false;
236            case STATE_WAITING_FOR_DEVICE_POLLING:
237                // Fall through.
238            default:
239                return false;
240        }
241    }
242
243    private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
244        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
245
246        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
247        if (current.mLogicalAddress != cmd.getSource()) {
248            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
249                    cmd.getSource());
250            return;
251        }
252
253        byte params[] = cmd.getParams();
254        current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
255        current.mDeviceType = params[2] & 0xFF;
256
257        increaseProcessedDeviceCount();
258        checkAndProceedStage();
259    }
260
261    private void handleSetOsdName(HdmiCecMessage cmd) {
262        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
263
264        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
265        if (current.mLogicalAddress != cmd.getSource()) {
266            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
267                    cmd.getSource());
268            return;
269        }
270
271        String displayName = null;
272        try {
273            displayName = new String(cmd.getParams(), "US-ASCII");
274        } catch (UnsupportedEncodingException e) {
275            Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
276            // If failed to get display name, use the default name of device.
277            displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
278        }
279        current.mDisplayName = displayName;
280        increaseProcessedDeviceCount();
281        checkAndProceedStage();
282    }
283
284    private void handleVendorId(HdmiCecMessage cmd) {
285        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
286
287        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
288        if (current.mLogicalAddress != cmd.getSource()) {
289            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
290                    cmd.getSource());
291            return;
292        }
293
294        byte[] params = cmd.getParams();
295        int vendorId = HdmiUtils.threeBytesToInt(params);
296        current.mVendorId = vendorId;
297        increaseProcessedDeviceCount();
298        checkAndProceedStage();
299    }
300
301    private void increaseProcessedDeviceCount() {
302        mProcessedDeviceCount++;
303    }
304
305    private void removeDevice(int index) {
306        mDevices.remove(index);
307    }
308
309    private void wrapUpAndFinish() {
310        Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
311        ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>();
312        for (DeviceInfo info : mDevices) {
313            HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo();
314            Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
315            result.add(cecDeviceInfo);
316        }
317        Slog.v(TAG, "--------------------------------------------");
318        mCallback.onDeviceDiscoveryDone(result);
319        finish();
320    }
321
322    private void checkAndProceedStage() {
323        if (mDevices.isEmpty()) {
324            wrapUpAndFinish();
325            return;
326        }
327
328        // If finished current stage, move on to next stage.
329        if (mProcessedDeviceCount == mDevices.size()) {
330            mProcessedDeviceCount = 0;
331            switch (mState) {
332                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
333                    startOsdNameStage();
334                    return;
335                case STATE_WAITING_FOR_OSD_NAME:
336                    startVendorIdStage();
337                    return;
338                case STATE_WAITING_FOR_VENDOR_ID:
339                    wrapUpAndFinish();
340                    return;
341                default:
342                    return;
343            }
344        } else {
345            int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
346            switch (mState) {
347                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
348                    queryPhysicalAddress(address);
349                    return;
350                case STATE_WAITING_FOR_OSD_NAME:
351                    queryOsdName(address);
352                    return;
353                case STATE_WAITING_FOR_VENDOR_ID:
354                    queryVendorId(address);
355                default:
356                    return;
357            }
358        }
359    }
360
361    @Override
362    void handleTimerEvent(int state) {
363        if (mState == STATE_NONE || mState != state) {
364            return;
365        }
366
367        Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
368        removeDevice(mProcessedDeviceCount);
369        checkAndProceedStage();
370    }
371}
372