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