DeviceDiscoveryAction.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.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    private static final int DEVICE_POLLING_RETRY = 1;
55
56    /**
57     * Interface used to report result of device discovery.
58     */
59    interface DeviceDiscoveryCallback {
60        /**
61         * Called when device discovery is done.
62         *
63         * @param deviceInfos a list of all non-local devices. It can be empty list.
64         */
65        void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos);
66    }
67
68    // An internal container used to keep track of device information during
69    // this action.
70    private static final class DeviceInfo {
71        private final int mLogicalAddress;
72
73        private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
74        private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
75        private String mDisplayName = "";
76        private int mDeviceType = HdmiCecDeviceInfo.DEVICE_INACTIVE;
77
78        private DeviceInfo(int logicalAddress) {
79            mLogicalAddress = logicalAddress;
80        }
81
82        private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
83            return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
84                    mDisplayName);
85        }
86    }
87
88    private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
89    private final DeviceDiscoveryCallback mCallback;
90    private int mProcessedDeviceCount = 0;
91
92    /**
93     * Constructor.
94     *
95     * @param source an instance of {@link HdmiCecLocalDevice}.
96     */
97    DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
98        super(source);
99        mCallback = Preconditions.checkNotNull(callback);
100    }
101
102    @Override
103    boolean start() {
104        mDevices.clear();
105        mState = STATE_WAITING_FOR_DEVICE_POLLING;
106
107        pollDevices(new DevicePollingCallback() {
108            @Override
109            public void onPollingFinished(List<Integer> ackedAddress) {
110                if (ackedAddress.isEmpty()) {
111                    Slog.v(TAG, "No device is detected.");
112                    wrapUpAndFinish();
113                    return;
114                }
115
116                Slog.v(TAG, "Device detected: " + ackedAddress);
117                allocateDevices(ackedAddress);
118                startPhysicalAddressStage();
119            }
120        }, Constants.POLL_ITERATION_REVERSE_ORDER
121            | Constants.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY);
122        return true;
123    }
124
125    private void allocateDevices(List<Integer> addresses) {
126        for (Integer i : addresses) {
127            DeviceInfo info = new DeviceInfo(i);
128            mDevices.add(info);
129        }
130    }
131
132    private void startPhysicalAddressStage() {
133        Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
134        mProcessedDeviceCount = 0;
135        mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
136
137        checkAndProceedStage();
138    }
139
140    private boolean verifyValidLogicalAddress(int address) {
141        return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
142    }
143
144    private void queryPhysicalAddress(int address) {
145        if (!verifyValidLogicalAddress(address)) {
146            checkAndProceedStage();
147            return;
148        }
149
150        mActionTimer.clearTimerMessage();
151
152        // Check cache first and send request if not exist.
153        if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
154            return;
155        }
156        sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
157        addTimer(mState, TIMEOUT_MS);
158    }
159
160    private void startOsdNameStage() {
161        Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
162        mProcessedDeviceCount = 0;
163        mState = STATE_WAITING_FOR_OSD_NAME;
164
165        checkAndProceedStage();
166    }
167
168    private void queryOsdName(int address) {
169        if (!verifyValidLogicalAddress(address)) {
170            checkAndProceedStage();
171            return;
172        }
173
174        mActionTimer.clearTimerMessage();
175
176        if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
177            return;
178        }
179        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
180        addTimer(mState, TIMEOUT_MS);
181    }
182
183    private void startVendorIdStage() {
184        Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
185
186        mProcessedDeviceCount = 0;
187        mState = STATE_WAITING_FOR_VENDOR_ID;
188
189        checkAndProceedStage();
190    }
191
192    private void queryVendorId(int address) {
193        if (!verifyValidLogicalAddress(address)) {
194            checkAndProceedStage();
195            return;
196        }
197
198        mActionTimer.clearTimerMessage();
199
200        if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
201            return;
202        }
203        sendCommand(
204                HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
205        addTimer(mState, TIMEOUT_MS);
206    }
207
208    private boolean mayProcessMessageIfCached(int address, int opcode) {
209        HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
210        if (message != null) {
211            processCommand(message);
212            return true;
213        }
214        return false;
215    }
216
217    @Override
218    boolean processCommand(HdmiCecMessage cmd) {
219        switch (mState) {
220            case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
221                if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
222                    handleReportPhysicalAddress(cmd);
223                    return true;
224                }
225                return false;
226            case STATE_WAITING_FOR_OSD_NAME:
227                if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
228                    handleSetOsdName(cmd);
229                    return true;
230                }
231                return false;
232            case STATE_WAITING_FOR_VENDOR_ID:
233                if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
234                    handleVendorId(cmd);
235                    return true;
236                }
237                return false;
238            case STATE_WAITING_FOR_DEVICE_POLLING:
239                // Fall through.
240            default:
241                return false;
242        }
243    }
244
245    private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
246        Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
247
248        DeviceInfo current = mDevices.get(mProcessedDeviceCount);
249        if (current.mLogicalAddress != cmd.getSource()) {
250            Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
251                    cmd.getSource());
252            return;
253        }
254
255        byte params[] = cmd.getParams();
256        if (params.length == 3) {
257            current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
258            current.mDeviceType = params[2] & 0xFF;
259
260            increaseProcessedDeviceCount();
261            checkAndProceedStage();
262        } else {
263            // Physical address is a critical element in device info.
264            // If failed, remove device from device list and proceed to the next device.
265            removeDevice(mProcessedDeviceCount);
266            checkAndProceedStage();
267        }
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        if (params.length == 3) {
305            int vendorId = HdmiUtils.threeBytesToInt(params);
306            current.mVendorId = vendorId;
307        } else {
308            Slog.w(TAG, "Invalid vendor id: " + cmd.toString());
309        }
310        increaseProcessedDeviceCount();
311        checkAndProceedStage();
312    }
313
314    private void increaseProcessedDeviceCount() {
315        mProcessedDeviceCount++;
316    }
317
318    private void removeDevice(int index) {
319        mDevices.remove(index);
320    }
321
322    private void wrapUpAndFinish() {
323        Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
324        ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>();
325        for (DeviceInfo info : mDevices) {
326            HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo();
327            Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
328            result.add(cecDeviceInfo);
329        }
330        Slog.v(TAG, "--------------------------------------------");
331        mCallback.onDeviceDiscoveryDone(result);
332        finish();
333    }
334
335    private void checkAndProceedStage() {
336        if (mDevices.isEmpty()) {
337            wrapUpAndFinish();
338            return;
339        }
340
341        // If finished current stage, move on to next stage.
342        if (mProcessedDeviceCount == mDevices.size()) {
343            mProcessedDeviceCount = 0;
344            switch (mState) {
345                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
346                    startOsdNameStage();
347                    return;
348                case STATE_WAITING_FOR_OSD_NAME:
349                    startVendorIdStage();
350                    return;
351                case STATE_WAITING_FOR_VENDOR_ID:
352                    wrapUpAndFinish();
353                    return;
354                default:
355                    return;
356            }
357        } else {
358            int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
359            switch (mState) {
360                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
361                    queryPhysicalAddress(address);
362                    return;
363                case STATE_WAITING_FOR_OSD_NAME:
364                    queryOsdName(address);
365                    return;
366                case STATE_WAITING_FOR_VENDOR_ID:
367                    queryVendorId(address);
368                default:
369                    return;
370            }
371        }
372    }
373
374    @Override
375    void handleTimerEvent(int state) {
376        if (mState == STATE_NONE || mState != state) {
377            return;
378        }
379
380        Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
381        removeDevice(mProcessedDeviceCount);
382        checkAndProceedStage();
383    }
384}
385