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