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                 // Check if the action was finished before the callback was called.
144                 // See {@link HdmiCecFeatureAction#finish}.
145                 if (mState == STATE_NONE) {
146                     Slog.v(TAG, "Action was already finished before the callback was called.");
147                     wrapUpAndFinish();
148                     return;
149                 }
150                 Slog.v(TAG, "Device detected: " + ackedAddress);
151                 allocateDevices(ackedAddress);
152                 if (mDelayPeriod > 0) {
153                     startToDelayAction();
154                 } else {
155                     startPhysicalAddressStage();
156                 }
157             }
158         }, Constants.POLL_ITERATION_REVERSE_ORDER
159             | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
160         return true;
161     }
162 
allocateDevices(List<Integer> addresses)163     private void allocateDevices(List<Integer> addresses) {
164         for (Integer i : addresses) {
165             DeviceInfo info = new DeviceInfo(i);
166             mDevices.add(info);
167         }
168     }
169 
startToDelayAction()170     private void startToDelayAction() {
171         Slog.v(TAG, "Waiting for connected devices to be ready");
172         mState = STATE_WAITING_FOR_DEVICES;
173 
174         checkAndProceedStage();
175     }
176 
startPhysicalAddressStage()177     private void startPhysicalAddressStage() {
178         Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
179         mProcessedDeviceCount = 0;
180         mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
181 
182         checkAndProceedStage();
183     }
184 
verifyValidLogicalAddress(int address)185     private boolean verifyValidLogicalAddress(int address) {
186         return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
187     }
188 
queryPhysicalAddress(int address)189     private void queryPhysicalAddress(int address) {
190         if (!verifyValidLogicalAddress(address)) {
191             checkAndProceedStage();
192             return;
193         }
194 
195         mActionTimer.clearTimerMessage();
196 
197         // Check cache first and send request if not exist.
198         if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
199             return;
200         }
201         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
202         addTimer(mState, HdmiConfig.TIMEOUT_MS);
203     }
204 
delayActionWithTimePeriod(int timeDelay)205     private void delayActionWithTimePeriod(int timeDelay) {
206         mActionTimer.clearTimerMessage();
207         addTimer(mState, timeDelay);
208     }
209 
startOsdNameStage()210     private void startOsdNameStage() {
211         Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
212         mProcessedDeviceCount = 0;
213         mState = STATE_WAITING_FOR_OSD_NAME;
214 
215         checkAndProceedStage();
216     }
217 
queryOsdName(int address)218     private void queryOsdName(int address) {
219         if (!verifyValidLogicalAddress(address)) {
220             checkAndProceedStage();
221             return;
222         }
223 
224         mActionTimer.clearTimerMessage();
225 
226         if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
227             return;
228         }
229         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
230         addTimer(mState, HdmiConfig.TIMEOUT_MS);
231     }
232 
startVendorIdStage()233     private void startVendorIdStage() {
234         Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
235 
236         mProcessedDeviceCount = 0;
237         mState = STATE_WAITING_FOR_VENDOR_ID;
238 
239         checkAndProceedStage();
240     }
241 
queryVendorId(int address)242     private void queryVendorId(int address) {
243         if (!verifyValidLogicalAddress(address)) {
244             checkAndProceedStage();
245             return;
246         }
247 
248         mActionTimer.clearTimerMessage();
249 
250         if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
251             return;
252         }
253         sendCommand(
254                 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
255         addTimer(mState, HdmiConfig.TIMEOUT_MS);
256     }
257 
startPowerStatusStage()258     private void startPowerStatusStage() {
259         Slog.v(TAG, "Start [Power Status Stage]:" + mDevices.size());
260         mProcessedDeviceCount = 0;
261         mState = STATE_WAITING_FOR_POWER;
262 
263         checkAndProceedStage();
264     }
265 
queryPowerStatus(int address)266     private void queryPowerStatus(int address) {
267         if (!verifyValidLogicalAddress(address)) {
268             checkAndProceedStage();
269             return;
270         }
271 
272         mActionTimer.clearTimerMessage();
273 
274         if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_POWER_STATUS)) {
275             return;
276         }
277         sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address));
278         addTimer(mState, HdmiConfig.TIMEOUT_MS);
279     }
280 
mayProcessMessageIfCached(int address, int opcode)281     private boolean mayProcessMessageIfCached(int address, int opcode) {
282         HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
283         if (message != null) {
284             processCommand(message);
285             return true;
286         }
287         return false;
288     }
289 
290     @Override
processCommand(HdmiCecMessage cmd)291     boolean processCommand(HdmiCecMessage cmd) {
292         switch (mState) {
293             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
294                 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
295                     handleReportPhysicalAddress(cmd);
296                     return true;
297                 }
298                 return false;
299             case STATE_WAITING_FOR_OSD_NAME:
300                 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
301                     handleSetOsdName(cmd);
302                     return true;
303                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
304                         ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_OSD_NAME)) {
305                     handleSetOsdName(cmd);
306                     return true;
307                 }
308                 return false;
309             case STATE_WAITING_FOR_VENDOR_ID:
310                 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
311                     handleVendorId(cmd);
312                     return true;
313                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
314                         ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID)) {
315                     handleVendorId(cmd);
316                     return true;
317                 }
318                 return false;
319             case STATE_WAITING_FOR_POWER:
320                 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
321                     handleReportPowerStatus(cmd);
322                     return true;
323                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)
324                         && ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_REPORT_POWER_STATUS)) {
325                     handleReportPowerStatus(cmd);
326                     return true;
327                 }
328                 return false;
329             case STATE_WAITING_FOR_DEVICE_POLLING:
330                 // Fall through.
331             default:
332                 return false;
333         }
334     }
335 
handleReportPhysicalAddress(HdmiCecMessage cmd)336     private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
337         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
338 
339         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
340         if (current.mLogicalAddress != cmd.getSource()) {
341             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
342                     cmd.getSource());
343             return;
344         }
345 
346         byte params[] = cmd.getParams();
347         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
348         current.mPortId = getPortId(current.mPhysicalAddress);
349         current.mDeviceType = params[2] & 0xFF;
350         // Keep display name empty. TIF fallbacks to the service label provided by the package mg.
351         current.mDisplayName = "";
352 
353         // This is to manager CEC device separately in case they don't have address.
354         if (mIsTvDevice) {
355             localDevice().mService.getHdmiCecNetwork().updateCecSwitchInfo(current.mLogicalAddress,
356                     current.mDeviceType,
357                     current.mPhysicalAddress);
358         }
359         increaseProcessedDeviceCount();
360         checkAndProceedStage();
361     }
362 
363     private int getPortId(int physicalAddress) {
364         return mIsTvDevice ? tv().getPortId(physicalAddress)
365             : source().getPortId(physicalAddress);
366     }
367 
368     private void handleSetOsdName(HdmiCecMessage cmd) {
369         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
370 
371         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
372         if (current.mLogicalAddress != cmd.getSource()) {
373             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
374                     cmd.getSource());
375             return;
376         }
377 
378         String displayName = "";
379         try {
380             if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
381                 displayName = new String(cmd.getParams(), "US-ASCII");
382             }
383         } catch (UnsupportedEncodingException e) {
384             Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
385         }
386         current.mDisplayName = displayName;
387         increaseProcessedDeviceCount();
388         checkAndProceedStage();
389     }
390 
391     private void handleVendorId(HdmiCecMessage cmd) {
392         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
393 
394         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
395         if (current.mLogicalAddress != cmd.getSource()) {
396             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
397                     cmd.getSource());
398             return;
399         }
400 
401         if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
402             byte[] params = cmd.getParams();
403             int vendorId = HdmiUtils.threeBytesToInt(params);
404             current.mVendorId = vendorId;
405         }
406 
407         increaseProcessedDeviceCount();
408         checkAndProceedStage();
409     }
410 
411     private void handleReportPowerStatus(HdmiCecMessage cmd) {
412         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
413 
414         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
415         if (current.mLogicalAddress != cmd.getSource()) {
416             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:"
417                     + cmd.getSource());
418             return;
419         }
420 
421         if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
422             byte[] params = cmd.getParams();
423             int powerStatus = params[0] & 0xFF;
424             current.mPowerStatus = powerStatus;
425         }
426 
427         increaseProcessedDeviceCount();
428         checkAndProceedStage();
429     }
430 
431     private void increaseProcessedDeviceCount() {
432         mProcessedDeviceCount++;
433         mTimeoutRetry = 0;
434     }
435 
436     private void removeDevice(int index) {
437         mDevices.remove(index);
438     }
439 
440     private void wrapUpAndFinish() {
441         Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
442         ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
443         for (DeviceInfo info : mDevices) {
444             HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
445             Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
446             result.add(cecDeviceInfo);
447         }
448         Slog.v(TAG, "--------------------------------------------");
449         mCallback.onDeviceDiscoveryDone(result);
450         finish();
451         // Process any commands buffered while device discovery action was in progress.
452         if (mIsTvDevice) {
453             tv().processAllDelayedMessages();
454         }
455     }
456 
457     private void checkAndProceedStage() {
458         if (mDevices.isEmpty()) {
459             wrapUpAndFinish();
460             return;
461         }
462         // If finished current stage, move on to next stage.
463         if (mProcessedDeviceCount == mDevices.size()) {
464             mProcessedDeviceCount = 0;
465             switch (mState) {
466                 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
467                     startOsdNameStage();
468                     return;
469                 case STATE_WAITING_FOR_OSD_NAME:
470                     startVendorIdStage();
471                     return;
472                 case STATE_WAITING_FOR_VENDOR_ID:
473                     startPowerStatusStage();
474                     return;
475                 case STATE_WAITING_FOR_POWER:
476                     wrapUpAndFinish();
477                     return;
478                 default:
479                     return;
480             }
481         } else {
482             sendQueryCommand();
483         }
484     }
485 
486     private void sendQueryCommand() {
487         int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
488         switch (mState) {
489             case STATE_WAITING_FOR_DEVICES:
490                 delayActionWithTimePeriod(mDelayPeriod);
491                 return;
492             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
493                 queryPhysicalAddress(address);
494                 return;
495             case STATE_WAITING_FOR_OSD_NAME:
496                 queryOsdName(address);
497                 return;
498             case STATE_WAITING_FOR_VENDOR_ID:
499                 queryVendorId(address);
500                 return;
501             case STATE_WAITING_FOR_POWER:
502                 queryPowerStatus(address);
503                 return;
504             default:
505                 return;
506         }
507     }
508 
509     @Override
510     void handleTimerEvent(int state) {
511         if (mState == STATE_NONE || mState != state) {
512             return;
513         }
514 
515         if (mState == STATE_WAITING_FOR_DEVICES) {
516             startPhysicalAddressStage();
517             return;
518         }
519         if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
520             sendQueryCommand();
521             return;
522         }
523         mTimeoutRetry = 0;
524         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
525         if (mState != STATE_WAITING_FOR_POWER && mState != STATE_WAITING_FOR_OSD_NAME) {
526             // We don't need to remove the device info if the power status is unknown.
527             // Some device does not have preferred OSD name and does not respond to Give OSD name.
528             // Like LG TV. We can give it default device name and not remove it.
529             removeDevice(mProcessedDeviceCount);
530         } else {
531             increaseProcessedDeviceCount();
532         }
533         checkAndProceedStage();
534     }
535 }
536