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