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.HdmiPortInfo;
20 import android.hardware.tv.cec.V1_0.CecMessage;
21 import android.hardware.tv.cec.V1_0.HotplugEvent;
22 import android.hardware.tv.cec.V1_0.IHdmiCec;
23 import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback;
24 import android.hardware.tv.cec.V1_0.IHdmiCecCallback;
25 import android.hardware.tv.cec.V1_0.Result;
26 import android.hardware.tv.cec.V1_0.SendMessageResult;
27 import android.os.Handler;
28 import android.os.IHwBinder;
29 import android.os.Looper;
30 import android.os.RemoteException;
31 import android.util.Slog;
32 import android.util.SparseArray;
33 
34 import com.android.internal.util.IndentingPrintWriter;
35 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
36 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
37 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
38 
39 import libcore.util.EmptyArray;
40 
41 import java.text.SimpleDateFormat;
42 import java.util.ArrayList;
43 import java.util.Date;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.concurrent.ArrayBlockingQueue;
47 import java.util.function.Predicate;
48 
49 import sun.util.locale.LanguageTag;
50 
51 /**
52  * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
53  * and pass it to CEC HAL so that it sends message to other device. For incoming
54  * message it translates the message and delegates it to proper module.
55  *
56  * <p>It should be careful to access member variables on IO thread because
57  * it can be accessed from system thread as well.
58  *
59  * <p>It can be created only by {@link HdmiCecController#create}
60  *
61  * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
62  */
63 final class HdmiCecController {
64     private static final String TAG = "HdmiCecController";
65 
66     /**
67      * Interface to report allocated logical address.
68      */
69     interface AllocateAddressCallback {
70         /**
71          * Called when a new logical address is allocated.
72          *
73          * @param deviceType requested device type to allocate logical address
74          * @param logicalAddress allocated logical address. If it is
75          *                       {@link Constants#ADDR_UNREGISTERED}, it means that
76          *                       it failed to allocate logical address for the given device type
77          */
onAllocated(int deviceType, int logicalAddress)78         void onAllocated(int deviceType, int logicalAddress);
79     }
80 
81     private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
82 
83     private static final int NUM_LOGICAL_ADDRESS = 16;
84 
85     private static final int MAX_CEC_MESSAGE_HISTORY = 200;
86 
87     private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
88 
89     /** Cookie for matching the right end point. */
90     protected static final int HDMI_CEC_HAL_DEATH_COOKIE = 353;
91 
92     // Predicate for whether the given logical address is remote device's one or not.
93     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
94         @Override
95         public boolean test(Integer address) {
96             return !isAllocatedLocalDeviceAddress(address);
97         }
98     };
99 
100     // Predicate whether the given logical address is system audio's one or not
101     private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
102         @Override
103         public boolean test(Integer address) {
104             return HdmiUtils.getTypeFromAddress(address) == Constants.ADDR_AUDIO_SYSTEM;
105         }
106     };
107 
108     // Handler instance to process synchronous I/O (mainly send) message.
109     private Handler mIoHandler;
110 
111     // Handler instance to process various messages coming from other CEC
112     // device or issued by internal state change.
113     private Handler mControlHandler;
114 
115     private final HdmiControlService mService;
116 
117     // Stores the local CEC devices in the system. Device type is used for key.
118     private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
119 
120     // Stores recent CEC messages history for debugging purpose.
121     private final ArrayBlockingQueue<MessageHistoryRecord> mMessageHistory =
122             new ArrayBlockingQueue<>(MAX_CEC_MESSAGE_HISTORY);
123 
124     private final NativeWrapper mNativeWrapperImpl;
125 
126     // Private constructor.  Use HdmiCecController.create().
HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper)127     private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
128         mService = service;
129         mNativeWrapperImpl = nativeWrapper;
130     }
131 
132     /**
133      * A factory method to get {@link HdmiCecController}. If it fails to initialize
134      * inner device or has no device it will return {@code null}.
135      *
136      * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
137      * @param service {@link HdmiControlService} instance used to create internal handler
138      *                and to pass callback for incoming message or event.
139      * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
140      *         returns {@code null}.
141      */
create(HdmiControlService service)142     static HdmiCecController create(HdmiControlService service) {
143         return createWithNativeWrapper(service, new NativeWrapperImpl());
144     }
145 
146     /**
147      * A factory method with injection of native methods for testing.
148      */
createWithNativeWrapper( HdmiControlService service, NativeWrapper nativeWrapper)149     static HdmiCecController createWithNativeWrapper(
150             HdmiControlService service, NativeWrapper nativeWrapper) {
151         HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
152         String nativePtr = nativeWrapper.nativeInit();
153         if (nativePtr == null) {
154             HdmiLogger.warning("Couldn't get tv.cec service.");
155             return null;
156         }
157         controller.init(nativeWrapper);
158         return controller;
159     }
160 
init(NativeWrapper nativeWrapper)161     private void init(NativeWrapper nativeWrapper) {
162         mIoHandler = new Handler(mService.getIoLooper());
163         mControlHandler = new Handler(mService.getServiceLooper());
164         nativeWrapper.setCallback(new HdmiCecCallback());
165     }
166 
167     @ServiceThreadOnly
addLocalDevice(int deviceType, HdmiCecLocalDevice device)168     void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
169         assertRunOnServiceThread();
170         mLocalDevices.put(deviceType, device);
171     }
172 
173     /**
174      * Allocate a new logical address of the given device type. Allocated
175      * address will be reported through {@link AllocateAddressCallback}.
176      *
177      * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
178      *
179      * @param deviceType type of device to used to determine logical address
180      * @param preferredAddress a logical address preferred to be allocated.
181      *                         If sets {@link Constants#ADDR_UNREGISTERED}, scans
182      *                         the smallest logical address matched with the given device type.
183      *                         Otherwise, scan address will start from {@code preferredAddress}
184      * @param callback callback interface to report allocated logical address to caller
185      */
186     @ServiceThreadOnly
allocateLogicalAddress(final int deviceType, final int preferredAddress, final AllocateAddressCallback callback)187     void allocateLogicalAddress(final int deviceType, final int preferredAddress,
188             final AllocateAddressCallback callback) {
189         assertRunOnServiceThread();
190 
191         runOnIoThread(new Runnable() {
192             @Override
193             public void run() {
194                 handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
195             }
196         });
197     }
198 
199     @IoThreadOnly
handleAllocateLogicalAddress(final int deviceType, int preferredAddress, final AllocateAddressCallback callback)200     private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
201             final AllocateAddressCallback callback) {
202         assertRunOnIoThread();
203         int startAddress = preferredAddress;
204         // If preferred address is "unregistered", start address will be the smallest
205         // address matched with the given device type.
206         if (preferredAddress == Constants.ADDR_UNREGISTERED) {
207             for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
208                 if (deviceType == HdmiUtils.getTypeFromAddress(i)) {
209                     startAddress = i;
210                     break;
211                 }
212             }
213         }
214 
215         int logicalAddress = Constants.ADDR_UNREGISTERED;
216         // Iterates all possible addresses which has the same device type.
217         for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
218             int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
219             if (curAddress != Constants.ADDR_UNREGISTERED
220                     && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
221                 boolean acked = false;
222                 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
223                     if (sendPollMessage(curAddress, curAddress, 1)) {
224                         acked = true;
225                         break;
226                     }
227                 }
228                 // If sending <Polling Message> failed, it becomes new logical address for the
229                 // device because no device uses it as logical address of the device.
230                 if (!acked) {
231                     logicalAddress = curAddress;
232                     break;
233                 }
234             }
235         }
236 
237         final int assignedAddress = logicalAddress;
238         HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]",
239                         deviceType, preferredAddress, assignedAddress);
240         if (callback != null) {
241             runOnServiceThread(new Runnable() {
242                 @Override
243                 public void run() {
244                     callback.onAllocated(deviceType, assignedAddress);
245                 }
246             });
247         }
248     }
249 
buildBody(int opcode, byte[] params)250     private static byte[] buildBody(int opcode, byte[] params) {
251         byte[] body = new byte[params.length + 1];
252         body[0] = (byte) opcode;
253         System.arraycopy(params, 0, body, 1, params.length);
254         return body;
255     }
256 
257 
getPortInfos()258     HdmiPortInfo[] getPortInfos() {
259         return mNativeWrapperImpl.nativeGetPortInfos();
260     }
261 
262     /**
263      * Return the locally hosted logical device of a given type.
264      *
265      * @param deviceType logical device type
266      * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
267      *          otherwise null.
268      */
getLocalDevice(int deviceType)269     HdmiCecLocalDevice getLocalDevice(int deviceType) {
270         return mLocalDevices.get(deviceType);
271     }
272 
273     /**
274      * Add a new logical address to the device. Device's HW should be notified
275      * when a new logical address is assigned to a device, so that it can accept
276      * a command having available destinations.
277      *
278      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
279      *
280      * @param newLogicalAddress a logical address to be added
281      * @return 0 on success. Otherwise, returns negative value
282      */
283     @ServiceThreadOnly
addLogicalAddress(int newLogicalAddress)284     int addLogicalAddress(int newLogicalAddress) {
285         assertRunOnServiceThread();
286         if (HdmiUtils.isValidAddress(newLogicalAddress)) {
287             return mNativeWrapperImpl.nativeAddLogicalAddress(newLogicalAddress);
288         } else {
289             return Result.FAILURE_INVALID_ARGS;
290         }
291     }
292 
293     /**
294      * Clear all logical addresses registered in the device.
295      *
296      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
297      */
298     @ServiceThreadOnly
clearLogicalAddress()299     void clearLogicalAddress() {
300         assertRunOnServiceThread();
301         for (int i = 0; i < mLocalDevices.size(); ++i) {
302             mLocalDevices.valueAt(i).clearAddress();
303         }
304         mNativeWrapperImpl.nativeClearLogicalAddress();
305     }
306 
307     @ServiceThreadOnly
clearLocalDevices()308     void clearLocalDevices() {
309         assertRunOnServiceThread();
310         mLocalDevices.clear();
311     }
312 
313     /**
314      * Return the physical address of the device.
315      *
316      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
317      *
318      * @return CEC physical address of the device. The range of success address
319      *         is between 0x0000 and 0xFFFF. If failed it returns -1
320      */
321     @ServiceThreadOnly
getPhysicalAddress()322     int getPhysicalAddress() {
323         assertRunOnServiceThread();
324         return mNativeWrapperImpl.nativeGetPhysicalAddress();
325     }
326 
327     /**
328      * Return CEC version of the device.
329      *
330      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
331      */
332     @ServiceThreadOnly
getVersion()333     int getVersion() {
334         assertRunOnServiceThread();
335         return mNativeWrapperImpl.nativeGetVersion();
336     }
337 
338     /**
339      * Return vendor id of the device.
340      *
341      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
342      */
343     @ServiceThreadOnly
getVendorId()344     int getVendorId() {
345         assertRunOnServiceThread();
346         return mNativeWrapperImpl.nativeGetVendorId();
347     }
348 
349     /**
350      * Set an option to CEC HAL.
351      *
352      * @param flag key of option
353      * @param enabled whether to enable/disable the given option.
354      */
355     @ServiceThreadOnly
setOption(int flag, boolean enabled)356     void setOption(int flag, boolean enabled) {
357         assertRunOnServiceThread();
358         HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled);
359         mNativeWrapperImpl.nativeSetOption(flag, enabled);
360     }
361 
362     /**
363      * Informs CEC HAL about the current system language.
364      *
365      * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters.
366      */
367     @ServiceThreadOnly
setLanguage(String language)368     void setLanguage(String language) {
369         assertRunOnServiceThread();
370         if (!LanguageTag.isLanguage(language)) {
371             return;
372         }
373         mNativeWrapperImpl.nativeSetLanguage(language);
374     }
375 
376     /**
377      * Configure ARC circuit in the hardware logic to start or stop the feature.
378      *
379      * @param port ID of HDMI port to which AVR is connected
380      * @param enabled whether to enable/disable ARC
381      */
382     @ServiceThreadOnly
enableAudioReturnChannel(int port, boolean enabled)383     void enableAudioReturnChannel(int port, boolean enabled) {
384         assertRunOnServiceThread();
385         mNativeWrapperImpl.nativeEnableAudioReturnChannel(port, enabled);
386     }
387 
388     /**
389      * Return the connection status of the specified port
390      *
391      * @param port port number to check connection status
392      * @return true if connected; otherwise, return false
393      */
394     @ServiceThreadOnly
isConnected(int port)395     boolean isConnected(int port) {
396         assertRunOnServiceThread();
397         return mNativeWrapperImpl.nativeIsConnected(port);
398     }
399 
400     /**
401      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
402      * devices.
403      *
404      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
405      *
406      * @param callback an interface used to get a list of all remote devices' address
407      * @param sourceAddress a logical address of source device where sends polling message
408      * @param pickStrategy strategy how to pick polling candidates
409      * @param retryCount the number of retry used to send polling message to remote devices
410      */
411     @ServiceThreadOnly
pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)412     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
413             int retryCount) {
414         assertRunOnServiceThread();
415 
416         // Extract polling candidates. No need to poll against local devices.
417         List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
418         ArrayList<Integer> allocated = new ArrayList<>();
419         runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
420     }
421 
422     /**
423      * Return a list of all {@link HdmiCecLocalDevice}s.
424      *
425      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
426      */
427     @ServiceThreadOnly
getLocalDeviceList()428     List<HdmiCecLocalDevice> getLocalDeviceList() {
429         assertRunOnServiceThread();
430         return HdmiUtils.sparseArrayToList(mLocalDevices);
431     }
432 
pickPollCandidates(int pickStrategy)433     private List<Integer> pickPollCandidates(int pickStrategy) {
434         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
435         Predicate<Integer> pickPredicate = null;
436         switch (strategy) {
437             case Constants.POLL_STRATEGY_SYSTEM_AUDIO:
438                 pickPredicate = mSystemAudioAddressPredicate;
439                 break;
440             case Constants.POLL_STRATEGY_REMOTES_DEVICES:
441             default:  // The default is POLL_STRATEGY_REMOTES_DEVICES.
442                 pickPredicate = mRemoteDeviceAddressPredicate;
443                 break;
444         }
445 
446         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
447         LinkedList<Integer> pollingCandidates = new LinkedList<>();
448         switch (iterationStrategy) {
449             case Constants.POLL_ITERATION_IN_ORDER:
450                 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
451                     if (pickPredicate.test(i)) {
452                         pollingCandidates.add(i);
453                     }
454                 }
455                 break;
456             case Constants.POLL_ITERATION_REVERSE_ORDER:
457             default:  // The default is reverse order.
458                 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) {
459                     if (pickPredicate.test(i)) {
460                         pollingCandidates.add(i);
461                     }
462                 }
463                 break;
464         }
465         return pollingCandidates;
466     }
467 
468     @ServiceThreadOnly
isAllocatedLocalDeviceAddress(int address)469     private boolean isAllocatedLocalDeviceAddress(int address) {
470         assertRunOnServiceThread();
471         for (int i = 0; i < mLocalDevices.size(); ++i) {
472             if (mLocalDevices.valueAt(i).isAddressOf(address)) {
473                 return true;
474             }
475         }
476         return false;
477     }
478 
479     @ServiceThreadOnly
runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated)480     private void runDevicePolling(final int sourceAddress,
481             final List<Integer> candidates, final int retryCount,
482             final DevicePollingCallback callback, final List<Integer> allocated) {
483         assertRunOnServiceThread();
484         if (candidates.isEmpty()) {
485             if (callback != null) {
486                 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString());
487                 callback.onPollingFinished(allocated);
488             }
489             return;
490         }
491 
492         final Integer candidate = candidates.remove(0);
493         // Proceed polling action for the next address once polling action for the
494         // previous address is done.
495         runOnIoThread(new Runnable() {
496             @Override
497             public void run() {
498                 if (sendPollMessage(sourceAddress, candidate, retryCount)) {
499                     allocated.add(candidate);
500                 }
501                 runOnServiceThread(new Runnable() {
502                     @Override
503                     public void run() {
504                         runDevicePolling(sourceAddress, candidates, retryCount, callback,
505                                 allocated);
506                     }
507                 });
508             }
509         });
510     }
511 
512     @IoThreadOnly
sendPollMessage(int sourceAddress, int destinationAddress, int retryCount)513     private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) {
514         assertRunOnIoThread();
515         for (int i = 0; i < retryCount; ++i) {
516             // <Polling Message> is a message which has empty body.
517             int ret =
518                     mNativeWrapperImpl.nativeSendCecCommand(
519                         sourceAddress, destinationAddress, EMPTY_BODY);
520             if (ret == SendMessageResult.SUCCESS) {
521                 return true;
522             } else if (ret != SendMessageResult.NACK) {
523                 // Unusual failure
524                 HdmiLogger.warning("Failed to send a polling message(%d->%d) with return code %d",
525                         sourceAddress, destinationAddress, ret);
526             }
527         }
528         return false;
529     }
530 
assertRunOnIoThread()531     private void assertRunOnIoThread() {
532         if (Looper.myLooper() != mIoHandler.getLooper()) {
533             throw new IllegalStateException("Should run on io thread.");
534         }
535     }
536 
assertRunOnServiceThread()537     private void assertRunOnServiceThread() {
538         if (Looper.myLooper() != mControlHandler.getLooper()) {
539             throw new IllegalStateException("Should run on service thread.");
540         }
541     }
542 
543     // Run a Runnable on IO thread.
544     // It should be careful to access member variables on IO thread because
545     // it can be accessed from system thread as well.
runOnIoThread(Runnable runnable)546     private void runOnIoThread(Runnable runnable) {
547         mIoHandler.post(runnable);
548     }
549 
runOnServiceThread(Runnable runnable)550     private void runOnServiceThread(Runnable runnable) {
551         mControlHandler.post(runnable);
552     }
553 
554     @ServiceThreadOnly
flush(final Runnable runnable)555     void flush(final Runnable runnable) {
556         assertRunOnServiceThread();
557         runOnIoThread(new Runnable() {
558             @Override
559             public void run() {
560                 // This ensures the runnable for cleanup is performed after all the pending
561                 // commands are processed by IO thread.
562                 runOnServiceThread(runnable);
563             }
564         });
565     }
566 
isAcceptableAddress(int address)567     private boolean isAcceptableAddress(int address) {
568         // Can access command targeting devices available in local device or broadcast command.
569         if (address == Constants.ADDR_BROADCAST) {
570             return true;
571         }
572         return isAllocatedLocalDeviceAddress(address);
573     }
574 
575     @ServiceThreadOnly
onReceiveCommand(HdmiCecMessage message)576     private void onReceiveCommand(HdmiCecMessage message) {
577         assertRunOnServiceThread();
578         if (isAcceptableAddress(message.getDestination()) && mService.handleCecCommand(message)) {
579             return;
580         }
581         // Not handled message, so we will reply it with <Feature Abort>.
582         maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
583     }
584 
585     @ServiceThreadOnly
maySendFeatureAbortCommand(HdmiCecMessage message, int reason)586     void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) {
587         assertRunOnServiceThread();
588         // Swap the source and the destination.
589         int src = message.getDestination();
590         int dest = message.getSource();
591         if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) {
592             // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted
593             // messages. See CEC 12.2 Protocol General Rules for detail.
594             return;
595         }
596         int originalOpcode = message.getOpcode();
597         if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) {
598             return;
599         }
600         sendCommand(
601                 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason));
602     }
603 
604     @ServiceThreadOnly
sendCommand(HdmiCecMessage cecMessage)605     void sendCommand(HdmiCecMessage cecMessage) {
606         assertRunOnServiceThread();
607         sendCommand(cecMessage, null);
608     }
609 
610     @ServiceThreadOnly
sendCommand(final HdmiCecMessage cecMessage, final HdmiControlService.SendMessageCallback callback)611     void sendCommand(final HdmiCecMessage cecMessage,
612             final HdmiControlService.SendMessageCallback callback) {
613         assertRunOnServiceThread();
614         addMessageToHistory(false /* isReceived */, cecMessage);
615         runOnIoThread(new Runnable() {
616             @Override
617             public void run() {
618                 HdmiLogger.debug("[S]:" + cecMessage);
619                 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
620                 int i = 0;
621                 int errorCode = SendMessageResult.SUCCESS;
622                 do {
623                     errorCode = mNativeWrapperImpl.nativeSendCecCommand(
624                         cecMessage.getSource(), cecMessage.getDestination(), body);
625                     if (errorCode == SendMessageResult.SUCCESS) {
626                         break;
627                     }
628                 } while (i++ < HdmiConfig.RETRANSMISSION_COUNT);
629 
630                 final int finalError = errorCode;
631                 if (finalError != SendMessageResult.SUCCESS) {
632                     Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
633                 }
634                 if (callback != null) {
635                     runOnServiceThread(new Runnable() {
636                         @Override
637                         public void run() {
638                             callback.onSendCompleted(finalError);
639                         }
640                     });
641                 }
642             }
643         });
644     }
645 
646     /**
647      * Called when incoming CEC message arrived.
648      */
649     @ServiceThreadOnly
handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body)650     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
651         assertRunOnServiceThread();
652         HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
653         HdmiLogger.debug("[R]:" + command);
654         addMessageToHistory(true /* isReceived */, command);
655         onReceiveCommand(command);
656     }
657 
658     /**
659      * Called when a hotplug event issues.
660      */
661     @ServiceThreadOnly
handleHotplug(int port, boolean connected)662     private void handleHotplug(int port, boolean connected) {
663         assertRunOnServiceThread();
664         HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
665         mService.onHotplug(port, connected);
666     }
667 
668     @ServiceThreadOnly
addMessageToHistory(boolean isReceived, HdmiCecMessage message)669     private void addMessageToHistory(boolean isReceived, HdmiCecMessage message) {
670         assertRunOnServiceThread();
671         MessageHistoryRecord record = new MessageHistoryRecord(isReceived, message);
672         if (!mMessageHistory.offer(record)) {
673             mMessageHistory.poll();
674             mMessageHistory.offer(record);
675         }
676     }
677 
dump(final IndentingPrintWriter pw)678     void dump(final IndentingPrintWriter pw) {
679         for (int i = 0; i < mLocalDevices.size(); ++i) {
680             pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":");
681             pw.increaseIndent();
682             mLocalDevices.valueAt(i).dump(pw);
683             pw.decreaseIndent();
684         }
685         pw.println("CEC message history:");
686         pw.increaseIndent();
687         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
688         for (MessageHistoryRecord record : mMessageHistory) {
689             record.dump(pw, sdf);
690         }
691         pw.decreaseIndent();
692     }
693 
694     protected interface NativeWrapper {
nativeInit()695         String nativeInit();
setCallback(HdmiCecCallback callback)696         void setCallback(HdmiCecCallback callback);
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)697         int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body);
nativeAddLogicalAddress(int logicalAddress)698         int nativeAddLogicalAddress(int logicalAddress);
nativeClearLogicalAddress()699         void nativeClearLogicalAddress();
nativeGetPhysicalAddress()700         int nativeGetPhysicalAddress();
nativeGetVersion()701         int nativeGetVersion();
nativeGetVendorId()702         int nativeGetVendorId();
nativeGetPortInfos()703         HdmiPortInfo[] nativeGetPortInfos();
nativeSetOption(int flag, boolean enabled)704         void nativeSetOption(int flag, boolean enabled);
nativeSetLanguage(String language)705         void nativeSetLanguage(String language);
nativeEnableAudioReturnChannel(int port, boolean flag)706         void nativeEnableAudioReturnChannel(int port, boolean flag);
nativeIsConnected(int port)707         boolean nativeIsConnected(int port);
708     }
709 
710     private static final class NativeWrapperImpl implements NativeWrapper,
711             IHwBinder.DeathRecipient, getPhysicalAddressCallback {
712         private IHdmiCec mHdmiCec;
713         private final Object mLock = new Object();
714         private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
715 
716         @Override
nativeInit()717         public String nativeInit() {
718             return (connectToHal() ? mHdmiCec.toString() : null);
719         }
720 
connectToHal()721         boolean connectToHal() {
722             try {
723                 mHdmiCec = IHdmiCec.getService();
724                 try {
725                     mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
726                 } catch (RemoteException e) {
727                     HdmiLogger.error("Couldn't link to death : ", e);
728                 }
729             } catch (RemoteException e) {
730                 HdmiLogger.error("Couldn't get tv.cec service : ", e);
731                 return false;
732             }
733             return true;
734         }
735 
736         @Override
setCallback(HdmiCecCallback callback)737         public void setCallback(HdmiCecCallback callback) {
738             try {
739                 mHdmiCec.setCallback(callback);
740             } catch (RemoteException e) {
741                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
742             }
743         }
744 
745         @Override
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)746         public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
747             CecMessage message = new CecMessage();
748             message.initiator = srcAddress;
749             message.destination = dstAddress;
750             message.body = new ArrayList<>(body.length);
751             for (byte b : body) {
752                 message.body.add(b);
753             }
754             try {
755                 return mHdmiCec.sendMessage(message);
756             } catch (RemoteException e) {
757                 HdmiLogger.error("Failed to send CEC message : ", e);
758                 return SendMessageResult.FAIL;
759             }
760         }
761 
762         @Override
nativeAddLogicalAddress(int logicalAddress)763         public int nativeAddLogicalAddress(int logicalAddress) {
764             try {
765                 return mHdmiCec.addLogicalAddress(logicalAddress);
766             } catch (RemoteException e) {
767                 HdmiLogger.error("Failed to add a logical address : ", e);
768                 return Result.FAILURE_INVALID_ARGS;
769             }
770         }
771 
772         @Override
nativeClearLogicalAddress()773         public void nativeClearLogicalAddress() {
774             try {
775                 mHdmiCec.clearLogicalAddress();
776             } catch (RemoteException e) {
777                 HdmiLogger.error("Failed to clear logical address : ", e);
778             }
779         }
780 
781         @Override
nativeGetPhysicalAddress()782         public int nativeGetPhysicalAddress() {
783             try {
784                 mHdmiCec.getPhysicalAddress(this);
785                 return mPhysicalAddress;
786             } catch (RemoteException e) {
787                 HdmiLogger.error("Failed to get physical address : ", e);
788                 return INVALID_PHYSICAL_ADDRESS;
789             }
790         }
791 
792         @Override
nativeGetVersion()793         public int nativeGetVersion() {
794             try {
795                 return mHdmiCec.getCecVersion();
796             } catch (RemoteException e) {
797                 HdmiLogger.error("Failed to get cec version : ", e);
798                 return Result.FAILURE_UNKNOWN;
799             }
800         }
801 
802         @Override
nativeGetVendorId()803         public int nativeGetVendorId() {
804             try {
805                 return mHdmiCec.getVendorId();
806             } catch (RemoteException e) {
807                 HdmiLogger.error("Failed to get vendor id : ", e);
808                 return Result.FAILURE_UNKNOWN;
809             }
810         }
811 
812         @Override
nativeGetPortInfos()813         public HdmiPortInfo[] nativeGetPortInfos() {
814             try {
815                 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
816                         mHdmiCec.getPortInfo();
817                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
818                 int i = 0;
819                 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
820                     hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
821                             portInfo.type,
822                             portInfo.physicalAddress,
823                             portInfo.cecSupported,
824                             false,
825                             portInfo.arcSupported);
826                     i++;
827                 }
828                 return hdmiPortInfo;
829             } catch (RemoteException e) {
830                 HdmiLogger.error("Failed to get port information : ", e);
831                 return null;
832             }
833         }
834 
835         @Override
nativeSetOption(int flag, boolean enabled)836         public void nativeSetOption(int flag, boolean enabled) {
837             try {
838                 mHdmiCec.setOption(flag, enabled);
839             } catch (RemoteException e) {
840                 HdmiLogger.error("Failed to set option : ", e);
841             }
842         }
843 
844         @Override
nativeSetLanguage(String language)845         public void nativeSetLanguage(String language) {
846             try {
847                 mHdmiCec.setLanguage(language);
848             } catch (RemoteException e) {
849                 HdmiLogger.error("Failed to set language : ", e);
850             }
851         }
852 
853         @Override
nativeEnableAudioReturnChannel(int port, boolean flag)854         public void nativeEnableAudioReturnChannel(int port, boolean flag) {
855             try {
856                 mHdmiCec.enableAudioReturnChannel(port, flag);
857             } catch (RemoteException e) {
858                 HdmiLogger.error("Failed to enable/disable ARC : ", e);
859             }
860         }
861 
862         @Override
nativeIsConnected(int port)863         public boolean nativeIsConnected(int port) {
864             try {
865                 return mHdmiCec.isConnected(port);
866             } catch (RemoteException e) {
867                 HdmiLogger.error("Failed to get connection info : ", e);
868                 return false;
869             }
870         }
871 
872         @Override
serviceDied(long cookie)873         public void serviceDied(long cookie) {
874             if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
875                 HdmiLogger.error(TAG, "Service died cokkie : " + cookie + "; reconnecting");
876                 connectToHal();
877             }
878         }
879 
880         @Override
onValues(int result, short addr)881         public void onValues(int result, short addr) {
882             if (result == Result.SUCCESS) {
883                 synchronized (mLock) {
884                     mPhysicalAddress = new Short(addr).intValue();
885                 }
886             }
887         }
888     }
889 
890     final class HdmiCecCallback extends IHdmiCecCallback.Stub {
891         @Override
onCecMessage(CecMessage message)892         public void onCecMessage(CecMessage message) throws RemoteException {
893             byte[] body = new byte[message.body.size()];
894             for (int i = 0; i < message.body.size(); i++) {
895                 body[i] = message.body.get(i);
896             }
897             runOnServiceThread(
898                     () -> handleIncomingCecCommand(message.initiator, message.destination, body));
899         }
900 
901         @Override
onHotplugEvent(HotplugEvent event)902         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
903             runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
904         }
905     }
906 
907     private final class MessageHistoryRecord {
908         private final long mTime;
909         private final boolean mIsReceived; // true if received message and false if sent message
910         private final HdmiCecMessage mMessage;
911 
MessageHistoryRecord(boolean isReceived, HdmiCecMessage message)912         public MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) {
913             mTime = System.currentTimeMillis();
914             mIsReceived = isReceived;
915             mMessage = message;
916         }
917 
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)918         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
919             pw.print(mIsReceived ? "[R]" : "[S]");
920             pw.print(" time=");
921             pw.print(sdf.format(new Date(mTime)));
922             pw.print(" message=");
923             pw.println(mMessage);
924         }
925     }
926 }
927