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.HdmiControlManager;
20 import android.hardware.hdmi.HdmiDeviceInfo;
21 import android.util.Slog;
22 
23 import com.android.internal.util.Preconditions;
24 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
25 
26 import java.io.UnsupportedEncodingException;
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 /**
31  * Feature action that handles device discovery sequences.
32  * Device discovery is launched when device is woken from "Standby" state
33  * or enabled "Control for Hdmi" from disabled state.
34  *
35  * <p>Device discovery goes through the following steps.
36  * <ol>
37  *   <li>Poll all non-local devices by sending &lt;Polling Message&gt;
38  *   <li>Gather "Physical address" and "device type" of all acknowledged devices
39  *   <li>Gather "OSD (display) name" of all acknowledge devices
40  *   <li>Gather "Vendor id" of all acknowledge devices
41  * </ol>
42  * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails.
43  */
44 final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
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     // State in which the action is waiting for devices to be ready.
56     private static final int STATE_WAITING_FOR_DEVICES = 5;
57     // State in which the action is waiting for gathering power status of non-local devices.
58     private static final int STATE_WAITING_FOR_POWER = 6;
59 
60     /**
61      * Interface used to report result of device discovery.
62      */
63     interface DeviceDiscoveryCallback {
64         /**
65          * Called when device discovery is done.
66          *
67          * @param deviceInfos a list of all non-local devices. It can be empty list.
68          */
onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos)69         void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos);
70     }
71 
72     // An internal container used to keep track of device information during
73     // this action.
74     private static final class DeviceInfo {
75         private final int mLogicalAddress;
76 
77         private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
78         private int mPortId = Constants.INVALID_PORT_ID;
79         private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
80         private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
81         private String mDisplayName = "";
82         private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
83 
DeviceInfo(int logicalAddress)84         private DeviceInfo(int logicalAddress) {
85             mLogicalAddress = logicalAddress;
86         }
87 
toHdmiDeviceInfo()88         private HdmiDeviceInfo toHdmiDeviceInfo() {
89             return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
90                     mVendorId, mDisplayName, mPowerStatus);
91         }
92     }
93 
94     private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
95     private final DeviceDiscoveryCallback mCallback;
96     private int mProcessedDeviceCount = 0;
97     private int mTimeoutRetry = 0;
98     private boolean mIsTvDevice = localDevice().mService.isTvDevice();
99     private final int mDelayPeriod;
100 
101     /**
102      * Constructor.
103      *
104      * @param source an instance of {@link HdmiCecLocalDevice}.
105      * @param delay delay action for this period between query Physical Address and polling
106      */
DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay)107     DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback, int delay) {
108         super(source);
109         mCallback = Preconditions.checkNotNull(callback);
110         mDelayPeriod = delay;
111     }
112 
113     /**
114      * Constructor.
115      *
116      * @param source an instance of {@link HdmiCecLocalDevice}.
117      */
DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback)118     DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
119         this(source, callback, 0);
120     }
121 
122     @Override
start()123     boolean start() {
124         mDevices.clear();
125         mState = STATE_WAITING_FOR_DEVICE_POLLING;
126 
127         pollDevices(new DevicePollingCallback() {
128             @Override
129             public void onPollingFinished(List<Integer> ackedAddress) {
130                 if (ackedAddress.isEmpty()) {
131                     Slog.v(TAG, "No device is detected.");
132                     wrapUpAndFinish();
133                     return;
134                 }
135 
136                 Slog.v(TAG, "Device detected: " + ackedAddress);
137                 allocateDevices(ackedAddress);
138                 if (mDelayPeriod > 0) {
139                     startToDelayAction();
140                 } else {
141                     startPhysicalAddressStage();
142                 }
143             }
144         }, Constants.POLL_ITERATION_REVERSE_ORDER
145             | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
146         return true;
147     }
148 
allocateDevices(List<Integer> addresses)149     private void allocateDevices(List<Integer> addresses) {
150         for (Integer i : addresses) {
151             DeviceInfo info = new DeviceInfo(i);
152             mDevices.add(info);
153         }
154     }
155 
startToDelayAction()156     private void startToDelayAction() {
157         Slog.v(TAG, "Waiting for connected devices to be ready");
158         mState = STATE_WAITING_FOR_DEVICES;
159 
160         checkAndProceedStage();
161     }
162 
startPhysicalAddressStage()163     private void startPhysicalAddressStage() {
164         Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
165         mProcessedDeviceCount = 0;
166         mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
167 
168         checkAndProceedStage();
169     }
170 
verifyValidLogicalAddress(int address)171     private boolean verifyValidLogicalAddress(int address) {
172         return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
173     }
174 
queryPhysicalAddress(int address)175     private void queryPhysicalAddress(int address) {
176         if (!verifyValidLogicalAddress(address)) {
177             checkAndProceedStage();
178             return;
179         }
180 
181         mActionTimer.clearTimerMessage();
182 
183         // Check cache first and send request if not exist.
184         if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
185             return;
186         }
187         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
188         addTimer(mState, HdmiConfig.TIMEOUT_MS);
189     }
190 
delayActionWithTimePeriod(int timeDelay)191     private void delayActionWithTimePeriod(int timeDelay) {
192         mActionTimer.clearTimerMessage();
193         addTimer(mState, timeDelay);
194     }
195 
startOsdNameStage()196     private void startOsdNameStage() {
197         Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
198         mProcessedDeviceCount = 0;
199         mState = STATE_WAITING_FOR_OSD_NAME;
200 
201         checkAndProceedStage();
202     }
203 
queryOsdName(int address)204     private void queryOsdName(int address) {
205         if (!verifyValidLogicalAddress(address)) {
206             checkAndProceedStage();
207             return;
208         }
209 
210         mActionTimer.clearTimerMessage();
211 
212         if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
213             return;
214         }
215         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
216         addTimer(mState, HdmiConfig.TIMEOUT_MS);
217     }
218 
startVendorIdStage()219     private void startVendorIdStage() {
220         Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
221 
222         mProcessedDeviceCount = 0;
223         mState = STATE_WAITING_FOR_VENDOR_ID;
224 
225         checkAndProceedStage();
226     }
227 
queryVendorId(int address)228     private void queryVendorId(int address) {
229         if (!verifyValidLogicalAddress(address)) {
230             checkAndProceedStage();
231             return;
232         }
233 
234         mActionTimer.clearTimerMessage();
235 
236         if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
237             return;
238         }
239         sendCommand(
240                 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
241         addTimer(mState, HdmiConfig.TIMEOUT_MS);
242     }
243 
startPowerStatusStage()244     private void startPowerStatusStage() {
245         Slog.v(TAG, "Start [Power Status Stage]:" + mDevices.size());
246         mProcessedDeviceCount = 0;
247         mState = STATE_WAITING_FOR_POWER;
248 
249         checkAndProceedStage();
250     }
251 
queryPowerStatus(int address)252     private void queryPowerStatus(int address) {
253         if (!verifyValidLogicalAddress(address)) {
254             checkAndProceedStage();
255             return;
256         }
257 
258         mActionTimer.clearTimerMessage();
259 
260         if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_POWER_STATUS)) {
261             return;
262         }
263         sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address));
264         addTimer(mState, HdmiConfig.TIMEOUT_MS);
265     }
266 
mayProcessMessageIfCached(int address, int opcode)267     private boolean mayProcessMessageIfCached(int address, int opcode) {
268         HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
269         if (message != null) {
270             processCommand(message);
271             return true;
272         }
273         return false;
274     }
275 
276     @Override
processCommand(HdmiCecMessage cmd)277     boolean processCommand(HdmiCecMessage cmd) {
278         switch (mState) {
279             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
280                 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
281                     handleReportPhysicalAddress(cmd);
282                     return true;
283                 }
284                 return false;
285             case STATE_WAITING_FOR_OSD_NAME:
286                 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
287                     handleSetOsdName(cmd);
288                     return true;
289                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
290                         ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_OSD_NAME)) {
291                     handleSetOsdName(cmd);
292                     return true;
293                 }
294                 return false;
295             case STATE_WAITING_FOR_VENDOR_ID:
296                 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
297                     handleVendorId(cmd);
298                     return true;
299                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
300                         ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID)) {
301                     handleVendorId(cmd);
302                     return true;
303                 }
304                 return false;
305             case STATE_WAITING_FOR_POWER:
306                 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
307                     handleReportPowerStatus(cmd);
308                     return true;
309                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)
310                         && ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REPORT_POWER_STATUS)) {
311                     handleReportPowerStatus(cmd);
312                     return true;
313                 }
314                 return false;
315             case STATE_WAITING_FOR_DEVICE_POLLING:
316                 // Fall through.
317             default:
318                 return false;
319         }
320     }
321 
handleReportPhysicalAddress(HdmiCecMessage cmd)322     private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
323         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
324 
325         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
326         if (current.mLogicalAddress != cmd.getSource()) {
327             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
328                     cmd.getSource());
329             return;
330         }
331 
332         byte params[] = cmd.getParams();
333         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
334         current.mPortId = getPortId(current.mPhysicalAddress);
335         current.mDeviceType = params[2] & 0xFF;
336         current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType);
337 
338         // This is to manager CEC device separately in case they don't have address.
339         if (mIsTvDevice) {
340             tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
341                     current.mPhysicalAddress);
342         }
343         increaseProcessedDeviceCount();
344         checkAndProceedStage();
345     }
346 
347     private int getPortId(int physicalAddress) {
348         return mIsTvDevice ? tv().getPortId(physicalAddress)
349             : source().getPortId(physicalAddress);
350     }
351 
352     private void handleSetOsdName(HdmiCecMessage cmd) {
353         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
354 
355         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
356         if (current.mLogicalAddress != cmd.getSource()) {
357             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
358                     cmd.getSource());
359             return;
360         }
361 
362         String displayName = null;
363         try {
364             if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
365                 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
366             } else {
367                 displayName = new String(cmd.getParams(), "US-ASCII");
368             }
369         } catch (UnsupportedEncodingException e) {
370             Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
371             // If failed to get display name, use the default name of device.
372             displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
373         }
374         current.mDisplayName = displayName;
375         increaseProcessedDeviceCount();
376         checkAndProceedStage();
377     }
378 
379     private void handleVendorId(HdmiCecMessage cmd) {
380         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
381 
382         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
383         if (current.mLogicalAddress != cmd.getSource()) {
384             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
385                     cmd.getSource());
386             return;
387         }
388 
389         if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
390             byte[] params = cmd.getParams();
391             int vendorId = HdmiUtils.threeBytesToInt(params);
392             current.mVendorId = vendorId;
393         }
394 
395         increaseProcessedDeviceCount();
396         checkAndProceedStage();
397     }
398 
399     private void handleReportPowerStatus(HdmiCecMessage cmd) {
400         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
401 
402         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
403         if (current.mLogicalAddress != cmd.getSource()) {
404             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:"
405                     + cmd.getSource());
406             return;
407         }
408 
409         if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
410             byte[] params = cmd.getParams();
411             int powerStatus = params[0] & 0xFF;
412             current.mPowerStatus = powerStatus;
413         }
414 
415         increaseProcessedDeviceCount();
416         checkAndProceedStage();
417     }
418 
419     private void increaseProcessedDeviceCount() {
420         mProcessedDeviceCount++;
421         mTimeoutRetry = 0;
422     }
423 
424     private void removeDevice(int index) {
425         mDevices.remove(index);
426     }
427 
428     private void wrapUpAndFinish() {
429         Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
430         ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
431         for (DeviceInfo info : mDevices) {
432             HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
433             Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
434             result.add(cecDeviceInfo);
435         }
436         Slog.v(TAG, "--------------------------------------------");
437         mCallback.onDeviceDiscoveryDone(result);
438         finish();
439         // Process any commands buffered while device discovery action was in progress.
440         if (mIsTvDevice) {
441             tv().processAllDelayedMessages();
442         }
443     }
444 
445     private void checkAndProceedStage() {
446         if (mDevices.isEmpty()) {
447             wrapUpAndFinish();
448             return;
449         }
450 
451         // If finished current stage, move on to next stage.
452         if (mProcessedDeviceCount == mDevices.size()) {
453             mProcessedDeviceCount = 0;
454             switch (mState) {
455                 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
456                     startOsdNameStage();
457                     return;
458                 case STATE_WAITING_FOR_OSD_NAME:
459                     startVendorIdStage();
460                     return;
461                 case STATE_WAITING_FOR_VENDOR_ID:
462                     startPowerStatusStage();
463                     return;
464                 case STATE_WAITING_FOR_POWER:
465                     wrapUpAndFinish();
466                     return;
467                 default:
468                     return;
469             }
470         } else {
471             sendQueryCommand();
472         }
473     }
474 
475     private void sendQueryCommand() {
476         int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
477         switch (mState) {
478             case STATE_WAITING_FOR_DEVICES:
479                 delayActionWithTimePeriod(mDelayPeriod);
480                 return;
481             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
482                 queryPhysicalAddress(address);
483                 return;
484             case STATE_WAITING_FOR_OSD_NAME:
485                 queryOsdName(address);
486                 return;
487             case STATE_WAITING_FOR_VENDOR_ID:
488                 queryVendorId(address);
489                 return;
490             case STATE_WAITING_FOR_POWER:
491                 queryPowerStatus(address);
492                 return;
493             default:
494                 return;
495         }
496     }
497 
498     @Override
499     void handleTimerEvent(int state) {
500         if (mState == STATE_NONE || mState != state) {
501             return;
502         }
503 
504         if (mState == STATE_WAITING_FOR_DEVICES) {
505             startPhysicalAddressStage();
506             return;
507         }
508         if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
509             sendQueryCommand();
510             return;
511         }
512         mTimeoutRetry = 0;
513         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
514         if (mState != STATE_WAITING_FOR_POWER && mState != STATE_WAITING_FOR_OSD_NAME) {
515             // We don't need to remove the device info if the power status is unknown.
516             // Some device does not have preferred OSD name and does not respond to Give OSD name.
517             // Like LG TV. We can give it default device name and not remove it.
518             removeDevice(mProcessedDeviceCount);
519         } else {
520             increaseProcessedDeviceCount();
521         }
522         checkAndProceedStage();
523     }
524 }
525