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