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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.hdmi.HdmiPortInfo;
22 import android.hardware.tv.cec.V1_0.HotplugEvent;
23 import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback;
24 import android.hardware.tv.cec.V1_0.OptionKey;
25 import android.hardware.tv.cec.V1_0.Result;
26 import android.hardware.tv.cec.V1_0.SendMessageResult;
27 import android.hardware.tv.hdmi.cec.CecMessage;
28 import android.hardware.tv.hdmi.cec.IHdmiCec;
29 import android.hardware.tv.hdmi.cec.IHdmiCecCallback;
30 import android.hardware.tv.hdmi.connection.IHdmiConnection;
31 import android.hardware.tv.hdmi.connection.IHdmiConnectionCallback;
32 import android.icu.util.IllformedLocaleException;
33 import android.icu.util.ULocale;
34 import android.os.Binder;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.IHwBinder;
38 import android.os.Looper;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.ServiceSpecificException;
42 import android.stats.hdmi.HdmiStatsEnums;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.FrameworkStatsLog;
47 import com.android.internal.util.IndentingPrintWriter;
48 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
49 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
50 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
51 
52 import libcore.util.EmptyArray;
53 
54 import java.text.SimpleDateFormat;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Date;
58 import java.util.List;
59 import java.util.NoSuchElementException;
60 import java.util.concurrent.ArrayBlockingQueue;
61 import java.util.function.Predicate;
62 
63 /**
64  * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
65  * and pass it to CEC HAL so that it sends message to other device. For incoming
66  * message it translates the message and delegates it to proper module.
67  *
68  * <p>It should be careful to access member variables on IO thread because
69  * it can be accessed from system thread as well.
70  *
71  * <p>It can be created only by {@link HdmiCecController#create}
72  *
73  * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
74  *
75  * <p>Also manages HDMI HAL methods that are shared between CEC and eARC. To make eARC
76  * fully independent of the presence of a CEC HAL, we should split this class into HdmiCecController
77  * and HdmiController TODO(b/255751565).
78  */
79 final class HdmiCecController {
80     private static final String TAG = "HdmiCecController";
81 
82     /**
83      * Interface to report allocated logical address.
84      */
85     interface AllocateAddressCallback {
86         /**
87          * Called when a new logical address is allocated.
88          *
89          * @param deviceType requested device type to allocate logical address
90          * @param logicalAddress allocated logical address. If it is
91          *                       {@link Constants#ADDR_UNREGISTERED}, it means that
92          *                       it failed to allocate logical address for the given device type
93          */
onAllocated(int deviceType, int logicalAddress)94         void onAllocated(int deviceType, int logicalAddress);
95     }
96 
97     private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
98 
99     private static final int NUM_LOGICAL_ADDRESS = 16;
100 
101     private static final int MAX_DEDICATED_ADDRESS = 11;
102 
103     private static final int INITIAL_HDMI_MESSAGE_HISTORY_SIZE = 250;
104 
105     private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
106 
107     /*
108      * The three flags below determine the action when a message is received. If CEC_DISABLED_IGNORE
109      * bit is set in ACTION_ON_RECEIVE_MSG, then the message is forwarded irrespective of whether
110      * CEC is enabled or disabled. The other flags/bits are also ignored.
111      */
112     private static final int CEC_DISABLED_IGNORE = 1 << 0;
113 
114     /* If CEC_DISABLED_LOG_WARNING bit is set, a warning message is printed if CEC is disabled. */
115     private static final int CEC_DISABLED_LOG_WARNING = 1 << 1;
116 
117     /* If CEC_DISABLED_DROP_MSG bit is set, the message is dropped if CEC is disabled. */
118     private static final int CEC_DISABLED_DROP_MSG = 1 << 2;
119 
120     private static final int ACTION_ON_RECEIVE_MSG = CEC_DISABLED_LOG_WARNING;
121 
122     /** Cookie for matching the right end point. */
123     protected static final int HDMI_CEC_HAL_DEATH_COOKIE = 353;
124 
125     // Predicate for whether the given logical address is remote device's one or not.
126     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
127         @Override
128         public boolean test(Integer address) {
129             return !mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address);
130         }
131     };
132 
133     // Predicate whether the given logical address is system audio's one or not
134     private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
135         @Override
136         public boolean test(Integer address) {
137             return HdmiUtils.isEligibleAddressForDevice(Constants.ADDR_AUDIO_SYSTEM, address);
138         }
139     };
140 
141     // Handler instance to process synchronous I/O (mainly send) message.
142     private Handler mIoHandler;
143 
144     // Handler instance to process various messages coming from other CEC
145     // device or issued by internal state change.
146     private Handler mControlHandler;
147 
148     private final HdmiControlService mService;
149 
150     // Stores recent CEC messages and HDMI Hotplug event history for debugging purpose.
151     private ArrayBlockingQueue<Dumpable> mMessageHistory =
152             new ArrayBlockingQueue<>(INITIAL_HDMI_MESSAGE_HISTORY_SIZE);
153 
154     private final Object mMessageHistoryLock = new Object();
155 
156     private final NativeWrapper mNativeWrapperImpl;
157 
158     private final HdmiCecAtomWriter mHdmiCecAtomWriter;
159 
160     // This variable is used for testing, in order to delay the logical address allocation.
161     private long mLogicalAddressAllocationDelay = 0;
162 
163     // Private constructor.  Use HdmiCecController.create().
HdmiCecController( HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter)164     private HdmiCecController(
165             HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
166         mService = service;
167         mNativeWrapperImpl = nativeWrapper;
168         mHdmiCecAtomWriter = atomWriter;
169     }
170 
171     /**
172      * A factory method to get {@link HdmiCecController}. If it fails to initialize
173      * inner device or has no device it will return {@code null}.
174      *
175      * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
176      * @param service    {@link HdmiControlService} instance used to create internal handler
177      *                   and to pass callback for incoming message or event.
178      * @param atomWriter {@link HdmiCecAtomWriter} instance for writing atoms for metrics.
179      * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
180      *         returns {@code null}.
181      */
create(HdmiControlService service, HdmiCecAtomWriter atomWriter)182     static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
183         HdmiCecController controller =
184                 createWithNativeWrapper(service, new NativeWrapperImplAidl(), atomWriter);
185         if (controller != null) {
186             return controller;
187         }
188         HdmiLogger.warning("Unable to use CEC and HDMI Connection AIDL HALs");
189 
190         controller = createWithNativeWrapper(service, new NativeWrapperImpl11(), atomWriter);
191         if (controller != null) {
192             return controller;
193         }
194         HdmiLogger.warning("Unable to use cec@1.1");
195         return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
196     }
197 
198     /**
199      * A factory method with injection of native methods for testing.
200      */
createWithNativeWrapper( HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter)201     static HdmiCecController createWithNativeWrapper(
202             HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
203         HdmiCecController controller = new HdmiCecController(service, nativeWrapper, atomWriter);
204         String nativePtr = nativeWrapper.nativeInit();
205         if (nativePtr == null) {
206             HdmiLogger.warning("Couldn't get tv.cec service.");
207             return null;
208         }
209         controller.init(nativeWrapper);
210         return controller;
211     }
212 
init(NativeWrapper nativeWrapper)213     private void init(NativeWrapper nativeWrapper) {
214         mIoHandler = new Handler(mService.getIoLooper());
215         mControlHandler = new Handler(mService.getServiceLooper());
216         nativeWrapper.setCallback(new HdmiCecCallback());
217     }
218 
219     /**
220      * Allocate a new logical address of the given device type. Allocated
221      * address will be reported through {@link AllocateAddressCallback}.
222      *
223      * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
224      *
225      * @param deviceType type of device to used to determine logical address
226      * @param preferredAddress a logical address preferred to be allocated.
227      *                         If sets {@link Constants#ADDR_UNREGISTERED}, scans
228      *                         the smallest logical address matched with the given device type.
229      *                         Otherwise, scan address will start from {@code preferredAddress}
230      * @param callback callback interface to report allocated logical address to caller
231      */
232     @ServiceThreadOnly
allocateLogicalAddress(final int deviceType, final int preferredAddress, final AllocateAddressCallback callback)233     void allocateLogicalAddress(final int deviceType, final int preferredAddress,
234             final AllocateAddressCallback callback) {
235         assertRunOnServiceThread();
236 
237         mIoHandler.postDelayed(new Runnable() {
238             @Override
239             public void run() {
240                 handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
241             }
242         }, mLogicalAddressAllocationDelay);
243     }
244 
245     /**
246      * Address allocation will check the following addresses (in order):
247      * <ul>
248      *     <li>Given preferred logical address (if the address is valid for the given device
249      *     type)</li>
250      *     <li>All dedicated logical addresses for the given device type</li>
251      *     <li>Backup addresses, if valid for the given device type</li>
252      * </ul>
253      */
254     @IoThreadOnly
handleAllocateLogicalAddress(final int deviceType, int preferredAddress, final AllocateAddressCallback callback)255     private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
256             final AllocateAddressCallback callback) {
257         assertRunOnIoThread();
258         List<Integer> logicalAddressesToPoll = new ArrayList<>();
259         if (HdmiUtils.isEligibleAddressForDevice(deviceType, preferredAddress)) {
260             logicalAddressesToPoll.add(preferredAddress);
261         }
262         for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
263             if (!logicalAddressesToPoll.contains(i) && HdmiUtils.isEligibleAddressForDevice(
264                     deviceType, i) && HdmiUtils.isEligibleAddressForCecVersion(
265                     mService.getCecVersion(), i)) {
266                 logicalAddressesToPoll.add(i);
267             }
268         }
269 
270         int logicalAddress = Constants.ADDR_UNREGISTERED;
271         for (Integer logicalAddressToPoll : logicalAddressesToPoll) {
272             boolean acked = false;
273             for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
274                 if (sendPollMessage(logicalAddressToPoll, logicalAddressToPoll, 1)) {
275                     acked = true;
276                     break;
277                 }
278             }
279             // If sending <Polling Message> failed, it becomes new logical address for the
280             // device because no device uses it as logical address of the device.
281             if (!acked) {
282                 logicalAddress = logicalAddressToPoll;
283                 break;
284             }
285         }
286 
287         final int assignedAddress = logicalAddress;
288         HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]",
289                         deviceType, preferredAddress, assignedAddress);
290         if (callback != null) {
291             runOnServiceThread(new Runnable() {
292                 @Override
293                 public void run() {
294                     callback.onAllocated(deviceType, assignedAddress);
295                 }
296             });
297         }
298     }
299 
buildBody(int opcode, byte[] params)300     private static byte[] buildBody(int opcode, byte[] params) {
301         byte[] body = new byte[params.length + 1];
302         body[0] = (byte) opcode;
303         System.arraycopy(params, 0, body, 1, params.length);
304         return body;
305     }
306 
307 
getPortInfos()308     HdmiPortInfo[] getPortInfos() {
309         return mNativeWrapperImpl.nativeGetPortInfos();
310     }
311 
312     /**
313      * Add a new logical address to the device. Device's HW should be notified
314      * when a new logical address is assigned to a device, so that it can accept
315      * a command having available destinations.
316      *
317      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
318      *
319      * @param newLogicalAddress a logical address to be added
320      * @return 0 on success. Otherwise, returns negative value
321      */
322     @ServiceThreadOnly
addLogicalAddress(int newLogicalAddress)323     int addLogicalAddress(int newLogicalAddress) {
324         assertRunOnServiceThread();
325         if (HdmiUtils.isValidAddress(newLogicalAddress)) {
326             return mNativeWrapperImpl.nativeAddLogicalAddress(newLogicalAddress);
327         } else {
328             return Result.FAILURE_INVALID_ARGS;
329         }
330     }
331 
332     /**
333      * Clear all logical addresses registered in the device.
334      *
335      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
336      */
337     @ServiceThreadOnly
clearLogicalAddress()338     void clearLogicalAddress() {
339         assertRunOnServiceThread();
340         mNativeWrapperImpl.nativeClearLogicalAddress();
341     }
342 
343     /**
344      * Return the physical address of the device.
345      *
346      * <p>Declared as package-private. accessed by {@link HdmiControlService} and
347      * {@link HdmiCecNetwork} only.
348      *
349      * @return CEC physical address of the device. The range of success address
350      *         is between 0x0000 and 0xFFFF. If failed it returns -1
351      */
352     @ServiceThreadOnly
getPhysicalAddress()353     int getPhysicalAddress() {
354         assertRunOnServiceThread();
355         return mNativeWrapperImpl.nativeGetPhysicalAddress();
356     }
357 
358     /**
359      * Return highest CEC version supported by this device.
360      *
361      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
362      */
363     @ServiceThreadOnly
getVersion()364     int getVersion() {
365         assertRunOnServiceThread();
366         return mNativeWrapperImpl.nativeGetVersion();
367     }
368 
369     /**
370      * Return vendor id of the device.
371      *
372      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
373      */
374     @ServiceThreadOnly
getVendorId()375     int getVendorId() {
376         assertRunOnServiceThread();
377         return mNativeWrapperImpl.nativeGetVendorId();
378     }
379 
380     /**
381      * Configures the TV panel device wakeup behaviour in standby mode when it receives an OTP
382      * (One Touch Play) from a source device.
383      *
384      * @param enabled If true, the TV device will wake up when OTP is received and if false, the TV
385      *     device will not wake up for an OTP.
386      */
387     @ServiceThreadOnly
enableWakeupByOtp(boolean enabled)388     void enableWakeupByOtp(boolean enabled) {
389         assertRunOnServiceThread();
390         HdmiLogger.debug("enableWakeupByOtp: %b", enabled);
391         mNativeWrapperImpl.enableWakeupByOtp(enabled);
392     }
393 
394     /**
395      * Switch to enable or disable CEC on the device.
396      *
397      * @param enabled If true, the device will have all CEC functionalities and if false, the device
398      *     will not perform any CEC functions.
399      */
400     @ServiceThreadOnly
enableCec(boolean enabled)401     void enableCec(boolean enabled) {
402         assertRunOnServiceThread();
403         HdmiLogger.debug("enableCec: %b", enabled);
404         mNativeWrapperImpl.enableCec(enabled);
405     }
406 
407     /**
408      * Configures the module that processes CEC messages - the Android framework or the HAL.
409      *
410      * @param enabled If true, the Android framework will actively process CEC messages.
411      *                If false, only the HAL will process the CEC messages.
412      */
413     @ServiceThreadOnly
enableSystemCecControl(boolean enabled)414     void enableSystemCecControl(boolean enabled) {
415         assertRunOnServiceThread();
416         HdmiLogger.debug("enableSystemCecControl: %b", enabled);
417         mNativeWrapperImpl.enableSystemCecControl(enabled);
418     }
419 
420     /**
421      * Configures the type of HDP signal that the driver and HAL use for actions other than eARC,
422      * such as signaling EDID updates.
423      */
424     @ServiceThreadOnly
setHpdSignalType(@onstants.HpdSignalType int signal, int portId)425     void setHpdSignalType(@Constants.HpdSignalType int signal, int portId) {
426         assertRunOnServiceThread();
427         HdmiLogger.debug("setHpdSignalType: portId %b, signal %b", portId, signal);
428         mNativeWrapperImpl.nativeSetHpdSignalType(signal, portId);
429     }
430 
431     /**
432      * Gets the type of the HDP signal that the driver and HAL use for actions other than eARC,
433      * such as signaling EDID updates.
434      */
435     @ServiceThreadOnly
436     @Constants.HpdSignalType
getHpdSignalType(int portId)437     int getHpdSignalType(int portId) {
438         assertRunOnServiceThread();
439         HdmiLogger.debug("getHpdSignalType: portId %b ", portId);
440         return mNativeWrapperImpl.nativeGetHpdSignalType(portId);
441     }
442 
443     /**
444      * Informs CEC HAL about the current system language.
445      *
446      * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters.
447      */
448     @ServiceThreadOnly
setLanguage(String language)449     void setLanguage(String language) {
450         assertRunOnServiceThread();
451         if (!isLanguage(language)) {
452             return;
453         }
454         mNativeWrapperImpl.nativeSetLanguage(language);
455     }
456 
457     /**
458      * This method is used for testing, in order to delay the logical address allocation.
459      */
460     @VisibleForTesting
setLogicalAddressAllocationDelay(long delay)461     void setLogicalAddressAllocationDelay(long delay) {
462         mLogicalAddressAllocationDelay = delay;
463     }
464 
465     /**
466      * Returns true if the language code is well-formed.
467      */
isLanguage(String language)468     @VisibleForTesting static boolean isLanguage(String language) {
469         // Handle null and empty string because because ULocale.Builder#setLanguage accepts them.
470         if (language == null || language.isEmpty()) {
471             return false;
472         }
473 
474         ULocale.Builder builder = new ULocale.Builder();
475         try {
476             builder.setLanguage(language);
477             return true;
478         } catch (IllformedLocaleException e) {
479             return false;
480         }
481     }
482 
483     /**
484      * Configure ARC circuit in the hardware logic to start or stop the feature.
485      *
486      * @param port ID of HDMI port to which AVR is connected
487      * @param enabled whether to enable/disable ARC
488      */
489     @ServiceThreadOnly
enableAudioReturnChannel(int port, boolean enabled)490     void enableAudioReturnChannel(int port, boolean enabled) {
491         assertRunOnServiceThread();
492         mNativeWrapperImpl.nativeEnableAudioReturnChannel(port, enabled);
493     }
494 
495     /**
496      * Return the connection status of the specified port
497      *
498      * @param port port number to check connection status
499      * @return true if connected; otherwise, return false
500      */
501     @ServiceThreadOnly
isConnected(int port)502     boolean isConnected(int port) {
503         assertRunOnServiceThread();
504         return mNativeWrapperImpl.nativeIsConnected(port);
505     }
506 
507     /**
508      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
509      * devices.
510      *
511      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
512      *
513      * @param callback an interface used to get a list of all remote devices' address
514      * @param sourceAddress a logical address of source device where sends polling message
515      * @param pickStrategy strategy how to pick polling candidates
516      * @param retryCount the number of retry used to send polling message to remote devices
517      */
518     @ServiceThreadOnly
pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)519     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
520             int retryCount) {
521         assertRunOnServiceThread();
522 
523         // Extract polling candidates. No need to poll against local devices.
524         List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
525         ArrayList<Integer> allocated = new ArrayList<>();
526         runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
527     }
528 
pickPollCandidates(int pickStrategy)529     private List<Integer> pickPollCandidates(int pickStrategy) {
530         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
531         Predicate<Integer> pickPredicate = null;
532         switch (strategy) {
533             case Constants.POLL_STRATEGY_SYSTEM_AUDIO:
534                 pickPredicate = mSystemAudioAddressPredicate;
535                 break;
536             case Constants.POLL_STRATEGY_REMOTES_DEVICES:
537             default:  // The default is POLL_STRATEGY_REMOTES_DEVICES.
538                 pickPredicate = mRemoteDeviceAddressPredicate;
539                 break;
540         }
541 
542         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
543         ArrayList<Integer> pollingCandidates = new ArrayList<>();
544         switch (iterationStrategy) {
545             case Constants.POLL_ITERATION_IN_ORDER:
546                 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
547                     if (pickPredicate.test(i)) {
548                         pollingCandidates.add(i);
549                     }
550                 }
551                 break;
552             case Constants.POLL_ITERATION_REVERSE_ORDER:
553             default:  // The default is reverse order.
554                 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) {
555                     if (pickPredicate.test(i)) {
556                         pollingCandidates.add(i);
557                     }
558                 }
559                 break;
560         }
561         return pollingCandidates;
562     }
563 
564     @ServiceThreadOnly
runDevicePolling(final int sourceAddress, final List<Integer> candidates, final int retryCount, final DevicePollingCallback callback, final List<Integer> allocated)565     private void runDevicePolling(final int sourceAddress,
566             final List<Integer> candidates, final int retryCount,
567             final DevicePollingCallback callback, final List<Integer> allocated) {
568         assertRunOnServiceThread();
569         if (candidates.isEmpty()) {
570             if (callback != null) {
571                 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString());
572                 callback.onPollingFinished(allocated);
573             }
574             return;
575         }
576 
577         final Integer candidate = candidates.remove(0);
578         // Proceed polling action for the next address once polling action for the
579         // previous address is done.
580         runOnIoThread(new Runnable() {
581             @Override
582             public void run() {
583                 if (sendPollMessage(sourceAddress, candidate, retryCount)) {
584                     allocated.add(candidate);
585                 }
586                 runOnServiceThread(new Runnable() {
587                     @Override
588                     public void run() {
589                         runDevicePolling(sourceAddress, candidates, retryCount, callback,
590                                 allocated);
591                     }
592                 });
593             }
594         });
595     }
596 
597     @IoThreadOnly
sendPollMessage(int sourceAddress, int destinationAddress, int retryCount)598     private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) {
599         assertRunOnIoThread();
600         for (int i = 0; i < retryCount; ++i) {
601             // <Polling Message> is a message which has empty body.
602             int ret =
603                     mNativeWrapperImpl.nativeSendCecCommand(
604                         sourceAddress, destinationAddress, EMPTY_BODY);
605             if (ret == SendMessageResult.SUCCESS) {
606                 return true;
607             } else if (ret != SendMessageResult.NACK) {
608                 // Unusual failure
609                 HdmiLogger.warning("Failed to send a polling message(%d->%d) with return code %d",
610                         sourceAddress, destinationAddress, ret);
611             }
612         }
613         return false;
614     }
615 
assertRunOnIoThread()616     private void assertRunOnIoThread() {
617         if (Looper.myLooper() != mIoHandler.getLooper()) {
618             throw new IllegalStateException("Should run on io thread.");
619         }
620     }
621 
assertRunOnServiceThread()622     private void assertRunOnServiceThread() {
623         if (Looper.myLooper() != mControlHandler.getLooper()) {
624             throw new IllegalStateException("Should run on service thread.");
625         }
626     }
627 
628     // Run a Runnable on IO thread.
629     // It should be careful to access member variables on IO thread because
630     // it can be accessed from system thread as well.
631     @VisibleForTesting
runOnIoThread(Runnable runnable)632     void runOnIoThread(Runnable runnable) {
633         mIoHandler.post(new WorkSourceUidPreservingRunnable(runnable));
634     }
635 
636     @VisibleForTesting
runOnServiceThread(Runnable runnable)637     void runOnServiceThread(Runnable runnable) {
638         mControlHandler.post(new WorkSourceUidPreservingRunnable(runnable));
639     }
640 
641     @ServiceThreadOnly
flush(final Runnable runnable)642     void flush(final Runnable runnable) {
643         assertRunOnServiceThread();
644         runOnIoThread(new Runnable() {
645             @Override
646             public void run() {
647                 // This ensures the runnable for cleanup is performed after all the pending
648                 // commands are processed by IO thread.
649                 runOnServiceThread(runnable);
650             }
651         });
652     }
653 
isAcceptableAddress(int address)654     private boolean isAcceptableAddress(int address) {
655         // Can access command targeting devices available in local device or broadcast command.
656         if (address == Constants.ADDR_BROADCAST) {
657             return true;
658         }
659         return mService.getHdmiCecNetwork().isAllocatedLocalDeviceAddress(address);
660     }
661 
662     @ServiceThreadOnly
663     @VisibleForTesting
onReceiveCommand(HdmiCecMessage message)664     void onReceiveCommand(HdmiCecMessage message) {
665         assertRunOnServiceThread();
666         if (((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_IGNORE) == 0)
667                 && !mService.isCecControlEnabled()
668                 && !HdmiCecMessage.isCecTransportMessage(message.getOpcode())) {
669             if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_LOG_WARNING) != 0) {
670                 HdmiLogger.warning("Message " + message + " received when cec disabled");
671             }
672             if ((ACTION_ON_RECEIVE_MSG & CEC_DISABLED_DROP_MSG) != 0) {
673                 return;
674             }
675         }
676         if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) {
677             return;
678         }
679         @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message);
680         if (messageState == Constants.NOT_HANDLED) {
681             // Message was not handled
682             maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
683         } else if (messageState != Constants.HANDLED) {
684             // Message handler wants to send a feature abort
685             maySendFeatureAbortCommand(message, messageState);
686         }
687     }
688 
689     @ServiceThreadOnly
maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason)690     void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) {
691         assertRunOnServiceThread();
692         // Swap the source and the destination.
693         int src = message.getDestination();
694         int dest = message.getSource();
695         if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) {
696             // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted
697             // messages. See CEC 12.2 Protocol General Rules for detail.
698             return;
699         }
700         int originalOpcode = message.getOpcode();
701         if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) {
702             return;
703         }
704         sendCommand(
705                 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason));
706     }
707 
708     @ServiceThreadOnly
sendCommand(HdmiCecMessage cecMessage)709     void sendCommand(HdmiCecMessage cecMessage) {
710         assertRunOnServiceThread();
711         sendCommand(cecMessage, null);
712     }
713 
714     /**
715      * Returns the calling UID of the original Binder call that triggered this code.
716      * If this code was not triggered by a Binder call, returns the UID of this process.
717      */
getCallingUid()718     private int getCallingUid() {
719         int workSourceUid = Binder.getCallingWorkSourceUid();
720         if (workSourceUid == -1) {
721             return Binder.getCallingUid();
722         }
723         return workSourceUid;
724     }
725 
726     @ServiceThreadOnly
sendCommand(final HdmiCecMessage cecMessage, final HdmiControlService.SendMessageCallback callback)727     void sendCommand(final HdmiCecMessage cecMessage,
728             final HdmiControlService.SendMessageCallback callback) {
729         assertRunOnServiceThread();
730         List<String> sendResults = new ArrayList<>();
731         runOnIoThread(new Runnable() {
732             @Override
733             public void run() {
734                 HdmiLogger.debug("[S]:" + cecMessage);
735                 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
736                 int retransmissionCount = 0;
737                 int errorCode = SendMessageResult.SUCCESS;
738                 do {
739                     errorCode = mNativeWrapperImpl.nativeSendCecCommand(
740                         cecMessage.getSource(), cecMessage.getDestination(), body);
741                     switch (errorCode) {
742                         case SendMessageResult.SUCCESS: sendResults.add("ACK"); break;
743                         case SendMessageResult.FAIL: sendResults.add("FAIL"); break;
744                         case SendMessageResult.NACK: sendResults.add("NACK"); break;
745                         case SendMessageResult.BUSY: sendResults.add("BUSY"); break;
746                     }
747                     if (errorCode == SendMessageResult.SUCCESS) {
748                         break;
749                     }
750                 } while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT);
751 
752                 final int finalError = errorCode;
753                 if (finalError != SendMessageResult.SUCCESS) {
754                     Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
755                 }
756                 runOnServiceThread(new Runnable() {
757                     @Override
758                     public void run() {
759                         mHdmiCecAtomWriter.messageReported(
760                                 cecMessage,
761                                 FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED__DIRECTION__OUTGOING,
762                                 getCallingUid(),
763                                 finalError
764                         );
765                         if (callback != null) {
766                             callback.onSendCompleted(finalError);
767                         }
768                     }
769                 });
770             }
771         });
772 
773         addCecMessageToHistory(false /* isReceived */, cecMessage, sendResults);
774     }
775 
776     /**
777      * Called when incoming CEC message arrived.
778      */
779     @ServiceThreadOnly
handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body)780     private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
781         assertRunOnServiceThread();
782 
783         if (body.length == 0) {
784             Slog.e(TAG, "Message with empty body received.");
785             return;
786         }
787 
788         HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0],
789                 Arrays.copyOfRange(body, 1, body.length));
790 
791         if (command.getValidationResult() != HdmiCecMessageValidator.OK) {
792             Slog.e(TAG, "Invalid message received: " + command);
793         }
794 
795         HdmiLogger.debug("[R]:" + command);
796         addCecMessageToHistory(true /* isReceived */, command, null);
797 
798         mHdmiCecAtomWriter.messageReported(command,
799                 incomingMessageDirection(srcAddress, dstAddress), getCallingUid());
800 
801         onReceiveCommand(command);
802     }
803 
804     /**
805      * Computes the direction of an incoming message, as implied by the source and
806      * destination addresses. This will usually return INCOMING; if not, it can indicate a bug.
807      */
incomingMessageDirection(int srcAddress, int dstAddress)808     private int incomingMessageDirection(int srcAddress, int dstAddress) {
809         boolean sourceIsLocal = false;
810         boolean destinationIsLocal = dstAddress == Constants.ADDR_BROADCAST;
811         for (HdmiCecLocalDevice localDevice : mService.getHdmiCecNetwork().getLocalDeviceList()) {
812             int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress();
813             if (logicalAddress == srcAddress) {
814                 sourceIsLocal = true;
815             }
816             if (logicalAddress == dstAddress) {
817                 destinationIsLocal = true;
818             }
819         }
820 
821         if (!sourceIsLocal && destinationIsLocal) {
822             return HdmiStatsEnums.INCOMING;
823         } else if (sourceIsLocal && destinationIsLocal) {
824             return HdmiStatsEnums.TO_SELF;
825         }
826         return HdmiStatsEnums.MESSAGE_DIRECTION_OTHER;
827     }
828 
829     /**
830      * Called when a hotplug event issues.
831      */
832     @ServiceThreadOnly
handleHotplug(int port, boolean connected)833     private void handleHotplug(int port, boolean connected) {
834         assertRunOnServiceThread();
835         HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
836         addHotplugEventToHistory(port, connected);
837         mService.onHotplug(port, connected);
838     }
839 
840     @ServiceThreadOnly
addHotplugEventToHistory(int port, boolean connected)841     private void addHotplugEventToHistory(int port, boolean connected) {
842         assertRunOnServiceThread();
843         addEventToHistory(new HotplugHistoryRecord(port, connected));
844     }
845 
846     @ServiceThreadOnly
addCecMessageToHistory(boolean isReceived, HdmiCecMessage message, List<String> sendResults)847     private void addCecMessageToHistory(boolean isReceived, HdmiCecMessage message,
848             List<String> sendResults) {
849         assertRunOnServiceThread();
850         addEventToHistory(new MessageHistoryRecord(isReceived, message, sendResults));
851     }
852 
addEventToHistory(Dumpable event)853     private void addEventToHistory(Dumpable event) {
854         synchronized (mMessageHistoryLock) {
855             if (!mMessageHistory.offer(event)) {
856                 mMessageHistory.poll();
857                 mMessageHistory.offer(event);
858             }
859         }
860     }
861 
getMessageHistorySize()862     int getMessageHistorySize() {
863         synchronized (mMessageHistoryLock) {
864             return mMessageHistory.size() + mMessageHistory.remainingCapacity();
865         }
866     }
867 
setMessageHistorySize(int newSize)868     boolean setMessageHistorySize(int newSize) {
869         if (newSize < INITIAL_HDMI_MESSAGE_HISTORY_SIZE) {
870             return false;
871         }
872         ArrayBlockingQueue<Dumpable> newMessageHistory = new ArrayBlockingQueue<>(newSize);
873 
874         synchronized (mMessageHistoryLock) {
875             if (newSize < mMessageHistory.size()) {
876                 for (int i = 0; i < mMessageHistory.size() - newSize; i++) {
877                     mMessageHistory.poll();
878                 }
879             }
880 
881             newMessageHistory.addAll(mMessageHistory);
882             mMessageHistory = newMessageHistory;
883         }
884         return true;
885     }
886 
dump(final IndentingPrintWriter pw)887     void dump(final IndentingPrintWriter pw) {
888         pw.println("CEC message history:");
889         pw.increaseIndent();
890         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
891         for (Dumpable record : mMessageHistory) {
892             record.dump(pw, sdf);
893         }
894         pw.decreaseIndent();
895     }
896 
897     protected interface NativeWrapper {
nativeInit()898         String nativeInit();
setCallback(HdmiCecCallback callback)899         void setCallback(HdmiCecCallback callback);
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)900         int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body);
nativeAddLogicalAddress(int logicalAddress)901         int nativeAddLogicalAddress(int logicalAddress);
nativeClearLogicalAddress()902         void nativeClearLogicalAddress();
nativeGetPhysicalAddress()903         int nativeGetPhysicalAddress();
nativeGetVersion()904         int nativeGetVersion();
nativeGetVendorId()905         int nativeGetVendorId();
nativeGetPortInfos()906         HdmiPortInfo[] nativeGetPortInfos();
907 
enableWakeupByOtp(boolean enabled)908         void enableWakeupByOtp(boolean enabled);
909 
enableCec(boolean enabled)910         void enableCec(boolean enabled);
911 
enableSystemCecControl(boolean enabled)912         void enableSystemCecControl(boolean enabled);
913 
nativeSetLanguage(String language)914         void nativeSetLanguage(String language);
nativeEnableAudioReturnChannel(int port, boolean flag)915         void nativeEnableAudioReturnChannel(int port, boolean flag);
nativeIsConnected(int port)916         boolean nativeIsConnected(int port);
nativeSetHpdSignalType(int signal, int portId)917         void nativeSetHpdSignalType(int signal, int portId);
nativeGetHpdSignalType(int portId)918         int nativeGetHpdSignalType(int portId);
919     }
920 
921     private static final class NativeWrapperImplAidl
922             implements NativeWrapper, IBinder.DeathRecipient {
923         private IHdmiCec mHdmiCec;
924         private IHdmiConnection mHdmiConnection;
925         @Nullable private HdmiCecCallback mCallback;
926 
927         private final Object mLock = new Object();
928 
929         @Override
nativeInit()930         public String nativeInit() {
931             return connectToHal() ? mHdmiCec.toString() + " " + mHdmiConnection.toString() : null;
932         }
933 
connectToHal()934         boolean connectToHal() {
935             mHdmiCec =
936                     IHdmiCec.Stub.asInterface(
937                             ServiceManager.getService(IHdmiCec.DESCRIPTOR + "/default"));
938             if (mHdmiCec == null) {
939                 HdmiLogger.error("Could not initialize HDMI CEC AIDL HAL");
940                 return false;
941             }
942             try {
943                 mHdmiCec.asBinder().linkToDeath(this, 0);
944             } catch (RemoteException e) {
945                 HdmiLogger.error("Couldn't link to death : ", e);
946             }
947 
948             mHdmiConnection =
949                     IHdmiConnection.Stub.asInterface(
950                             ServiceManager.getService(IHdmiConnection.DESCRIPTOR + "/default"));
951             if (mHdmiConnection == null) {
952                 HdmiLogger.error("Could not initialize HDMI Connection AIDL HAL");
953                 return false;
954             }
955             try {
956                 mHdmiConnection.asBinder().linkToDeath(this, 0);
957             } catch (RemoteException e) {
958                 HdmiLogger.error("Couldn't link to death : ", e);
959             }
960             return true;
961         }
962 
963         @Override
binderDied()964         public void binderDied() {
965             // One of the services died, try to reconnect to both.
966             mHdmiCec.asBinder().unlinkToDeath(this, 0);
967             mHdmiConnection.asBinder().unlinkToDeath(this, 0);
968             HdmiLogger.error("HDMI Connection or CEC service died, reconnecting");
969             connectToHal();
970             // Reconnect the callback
971             if (mCallback != null) {
972                 setCallback(mCallback);
973             }
974         }
975 
976         @Override
setCallback(HdmiCecCallback callback)977         public void setCallback(HdmiCecCallback callback) {
978             mCallback = callback;
979             try {
980                 // Create an AIDL callback that can callback onCecMessage
981                 mHdmiCec.setCallback(new HdmiCecCallbackAidl(callback));
982             } catch (RemoteException e) {
983                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
984             }
985             try {
986                 // Create an AIDL callback that can callback onHotplugEvent
987                 mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback));
988             } catch (RemoteException e) {
989                 HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e);
990             }
991         }
992 
993         @Override
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)994         public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
995             CecMessage message = new CecMessage();
996             message.initiator = (byte) (srcAddress & 0xF);
997             message.destination = (byte) (dstAddress & 0xF);
998             message.body = body;
999             try {
1000                 return mHdmiCec.sendMessage(message);
1001             } catch (RemoteException e) {
1002                 HdmiLogger.error("Failed to send CEC message : ", e);
1003                 return SendMessageResult.FAIL;
1004             }
1005         }
1006 
1007         @Override
nativeAddLogicalAddress(int logicalAddress)1008         public int nativeAddLogicalAddress(int logicalAddress) {
1009             try {
1010                 return mHdmiCec.addLogicalAddress((byte) logicalAddress);
1011             } catch (RemoteException e) {
1012                 HdmiLogger.error("Failed to add a logical address : ", e);
1013                 return Result.FAILURE_INVALID_ARGS;
1014             }
1015         }
1016 
1017         @Override
nativeClearLogicalAddress()1018         public void nativeClearLogicalAddress() {
1019             try {
1020                 mHdmiCec.clearLogicalAddress();
1021             } catch (RemoteException e) {
1022                 HdmiLogger.error("Failed to clear logical address : ", e);
1023             }
1024         }
1025 
1026         @Override
nativeGetPhysicalAddress()1027         public int nativeGetPhysicalAddress() {
1028             try {
1029                 return mHdmiCec.getPhysicalAddress();
1030             } catch (RemoteException e) {
1031                 HdmiLogger.error("Failed to get physical address : ", e);
1032                 return INVALID_PHYSICAL_ADDRESS;
1033             }
1034         }
1035 
1036         @Override
nativeGetVersion()1037         public int nativeGetVersion() {
1038             try {
1039                 return mHdmiCec.getCecVersion();
1040             } catch (RemoteException e) {
1041                 HdmiLogger.error("Failed to get cec version : ", e);
1042                 return Result.FAILURE_UNKNOWN;
1043             }
1044         }
1045 
1046         @Override
nativeGetVendorId()1047         public int nativeGetVendorId() {
1048             try {
1049                 return mHdmiCec.getVendorId();
1050             } catch (RemoteException e) {
1051                 HdmiLogger.error("Failed to get vendor id : ", e);
1052                 return Result.FAILURE_UNKNOWN;
1053             }
1054         }
1055 
1056         @Override
enableWakeupByOtp(boolean enabled)1057         public void enableWakeupByOtp(boolean enabled) {
1058             try {
1059                 mHdmiCec.enableWakeupByOtp(enabled);
1060             } catch (RemoteException e) {
1061                 HdmiLogger.error("Failed call to enableWakeupByOtp : ", e);
1062             }
1063         }
1064 
1065         @Override
enableCec(boolean enabled)1066         public void enableCec(boolean enabled) {
1067             try {
1068                 mHdmiCec.enableCec(enabled);
1069             } catch (RemoteException e) {
1070                 HdmiLogger.error("Failed call to enableCec : ", e);
1071             }
1072         }
1073 
1074         @Override
enableSystemCecControl(boolean enabled)1075         public void enableSystemCecControl(boolean enabled) {
1076             try {
1077                 mHdmiCec.enableSystemCecControl(enabled);
1078             } catch (RemoteException e) {
1079                 HdmiLogger.error("Failed call to enableSystemCecControl : ", e);
1080             }
1081         }
1082 
1083         @Override
nativeSetLanguage(String language)1084         public void nativeSetLanguage(String language) {
1085             try {
1086                 mHdmiCec.setLanguage(language);
1087             } catch (RemoteException e) {
1088                 HdmiLogger.error("Failed to set language : ", e);
1089             }
1090         }
1091 
1092         @Override
nativeEnableAudioReturnChannel(int port, boolean flag)1093         public void nativeEnableAudioReturnChannel(int port, boolean flag) {
1094             try {
1095                 mHdmiCec.enableAudioReturnChannel(port, flag);
1096             } catch (RemoteException e) {
1097                 HdmiLogger.error("Failed to enable/disable ARC : ", e);
1098             }
1099         }
1100 
1101         @Override
nativeGetPortInfos()1102         public HdmiPortInfo[] nativeGetPortInfos() {
1103             try {
1104                 android.hardware.tv.hdmi.connection.HdmiPortInfo[] hdmiPortInfos =
1105                         mHdmiConnection.getPortInfo();
1106                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length];
1107                 int i = 0;
1108                 for (android.hardware.tv.hdmi.connection.HdmiPortInfo portInfo : hdmiPortInfos) {
1109                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
1110                                     portInfo.portId,
1111                                     portInfo.type,
1112                                     portInfo.physicalAddress)
1113                                     .setCecSupported(portInfo.cecSupported)
1114                                     .setMhlSupported(false)
1115                                     .setArcSupported(portInfo.arcSupported)
1116                                     .setEarcSupported(portInfo.eArcSupported)
1117                                     .build();
1118                     i++;
1119                 }
1120                 return hdmiPortInfo;
1121             } catch (RemoteException e) {
1122                 HdmiLogger.error("Failed to get port information : ", e);
1123                 return null;
1124             }
1125         }
1126 
1127         @Override
nativeIsConnected(int port)1128         public boolean nativeIsConnected(int port) {
1129             try {
1130                 return mHdmiConnection.isConnected(port);
1131             } catch (RemoteException e) {
1132                 HdmiLogger.error("Failed to get connection info : ", e);
1133                 return false;
1134             }
1135         }
1136 
1137         @Override
nativeSetHpdSignalType(int signal, int portId)1138         public void nativeSetHpdSignalType(int signal, int portId) {
1139             try {
1140                 mHdmiConnection.setHpdSignal((byte) signal, portId);
1141             } catch (ServiceSpecificException sse) {
1142                 HdmiLogger.error(
1143                         "Could not set HPD signal type for portId " + portId + " to " + signal
1144                                 + ". Error: ", sse.errorCode);
1145             } catch (RemoteException e) {
1146                 HdmiLogger.error(
1147                         "Could not set HPD signal type for portId " + portId + " to " + signal
1148                                 + ". Exception: ", e);
1149             }
1150         }
1151 
1152         @Override
nativeGetHpdSignalType(int portId)1153         public int nativeGetHpdSignalType(int portId) {
1154             try {
1155                 return mHdmiConnection.getHpdSignal(portId);
1156             } catch (RemoteException e) {
1157                 HdmiLogger.error(
1158                         "Could not get HPD signal type for portId " + portId + ". Exception: ", e);
1159                 return Constants.HDMI_HPD_TYPE_PHYSICAL;
1160             }
1161         }
1162     }
1163 
1164     private static final class NativeWrapperImpl11 implements NativeWrapper,
1165             IHwBinder.DeathRecipient, getPhysicalAddressCallback {
1166         private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;
1167         @Nullable private HdmiCecCallback mCallback;
1168 
1169         private final Object mLock = new Object();
1170         private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
1171 
1172         @Override
nativeInit()1173         public String nativeInit() {
1174             return (connectToHal() ? mHdmiCec.toString() : null);
1175         }
1176 
connectToHal()1177         boolean connectToHal() {
1178             try {
1179                 mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true);
1180                 try {
1181                     mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
1182                 } catch (RemoteException e) {
1183                     HdmiLogger.error("Couldn't link to death : ", e);
1184                 }
1185             } catch (RemoteException | NoSuchElementException e) {
1186                 HdmiLogger.error("Couldn't connect to cec@1.1", e);
1187                 return false;
1188             }
1189             return true;
1190         }
1191 
1192         @Override
onValues(int result, short addr)1193         public void onValues(int result, short addr) {
1194             if (result == Result.SUCCESS) {
1195                 synchronized (mLock) {
1196                     mPhysicalAddress = new Short(addr).intValue();
1197                 }
1198             }
1199         }
1200 
1201         @Override
serviceDied(long cookie)1202         public void serviceDied(long cookie) {
1203             if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
1204                 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
1205                 connectToHal();
1206                 // Reconnect the callback
1207                 if (mCallback != null) {
1208                     setCallback(mCallback);
1209                 }
1210             }
1211         }
1212 
1213         @Override
setCallback(HdmiCecCallback callback)1214         public void setCallback(HdmiCecCallback callback) {
1215             mCallback = callback;
1216             try {
1217                 mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback));
1218             } catch (RemoteException e) {
1219                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
1220             }
1221         }
1222 
1223         @Override
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1224         public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
1225             android.hardware.tv.cec.V1_1.CecMessage message =
1226                     new android.hardware.tv.cec.V1_1.CecMessage();
1227             message.initiator = srcAddress;
1228             message.destination = dstAddress;
1229             message.body = new ArrayList<>(body.length);
1230             for (byte b : body) {
1231                 message.body.add(b);
1232             }
1233             try {
1234                 return mHdmiCec.sendMessage_1_1(message);
1235             } catch (RemoteException e) {
1236                 HdmiLogger.error("Failed to send CEC message : ", e);
1237                 return SendMessageResult.FAIL;
1238             }
1239         }
1240 
1241         @Override
nativeAddLogicalAddress(int logicalAddress)1242         public int nativeAddLogicalAddress(int logicalAddress) {
1243             try {
1244                 return mHdmiCec.addLogicalAddress_1_1(logicalAddress);
1245             } catch (RemoteException e) {
1246                 HdmiLogger.error("Failed to add a logical address : ", e);
1247                 return Result.FAILURE_INVALID_ARGS;
1248             }
1249         }
1250 
1251         @Override
nativeClearLogicalAddress()1252         public void nativeClearLogicalAddress() {
1253             try {
1254                 mHdmiCec.clearLogicalAddress();
1255             } catch (RemoteException e) {
1256                 HdmiLogger.error("Failed to clear logical address : ", e);
1257             }
1258         }
1259 
1260         @Override
nativeGetPhysicalAddress()1261         public int nativeGetPhysicalAddress() {
1262             try {
1263                 mHdmiCec.getPhysicalAddress(this);
1264                 return mPhysicalAddress;
1265             } catch (RemoteException e) {
1266                 HdmiLogger.error("Failed to get physical address : ", e);
1267                 return INVALID_PHYSICAL_ADDRESS;
1268             }
1269         }
1270 
1271         @Override
nativeGetVersion()1272         public int nativeGetVersion() {
1273             try {
1274                 return mHdmiCec.getCecVersion();
1275             } catch (RemoteException e) {
1276                 HdmiLogger.error("Failed to get cec version : ", e);
1277                 return Result.FAILURE_UNKNOWN;
1278             }
1279         }
1280 
1281         @Override
nativeGetVendorId()1282         public int nativeGetVendorId() {
1283             try {
1284                 return mHdmiCec.getVendorId();
1285             } catch (RemoteException e) {
1286                 HdmiLogger.error("Failed to get vendor id : ", e);
1287                 return Result.FAILURE_UNKNOWN;
1288             }
1289         }
1290 
1291         @Override
nativeGetPortInfos()1292         public HdmiPortInfo[] nativeGetPortInfos() {
1293             try {
1294                 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
1295                         mHdmiCec.getPortInfo();
1296                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
1297                 int i = 0;
1298                 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
1299                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
1300                             portInfo.portId,
1301                             portInfo.type,
1302                             portInfo.physicalAddress)
1303                             .setCecSupported(portInfo.cecSupported)
1304                             .setMhlSupported(false)
1305                             .setArcSupported(portInfo.arcSupported)
1306                             .setEarcSupported(false)
1307                             .build();
1308                     i++;
1309                 }
1310                 return hdmiPortInfo;
1311             } catch (RemoteException e) {
1312                 HdmiLogger.error("Failed to get port information : ", e);
1313                 return null;
1314             }
1315         }
1316 
nativeSetOption(int flag, boolean enabled)1317         private void nativeSetOption(int flag, boolean enabled) {
1318             try {
1319                 mHdmiCec.setOption(flag, enabled);
1320             } catch (RemoteException e) {
1321                 HdmiLogger.error("Failed to set option : ", e);
1322             }
1323         }
1324 
1325         @Override
enableWakeupByOtp(boolean enabled)1326         public void enableWakeupByOtp(boolean enabled) {
1327             nativeSetOption(OptionKey.WAKEUP, enabled);
1328         }
1329 
1330         @Override
enableCec(boolean enabled)1331         public void enableCec(boolean enabled) {
1332             nativeSetOption(OptionKey.ENABLE_CEC, enabled);
1333         }
1334 
1335         @Override
enableSystemCecControl(boolean enabled)1336         public void enableSystemCecControl(boolean enabled) {
1337             nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled);
1338         }
1339 
1340         @Override
nativeSetLanguage(String language)1341         public void nativeSetLanguage(String language) {
1342             try {
1343                 mHdmiCec.setLanguage(language);
1344             } catch (RemoteException e) {
1345                 HdmiLogger.error("Failed to set language : ", e);
1346             }
1347         }
1348 
1349         @Override
nativeEnableAudioReturnChannel(int port, boolean flag)1350         public void nativeEnableAudioReturnChannel(int port, boolean flag) {
1351             try {
1352                 mHdmiCec.enableAudioReturnChannel(port, flag);
1353             } catch (RemoteException e) {
1354                 HdmiLogger.error("Failed to enable/disable ARC : ", e);
1355             }
1356         }
1357 
1358         @Override
nativeIsConnected(int port)1359         public boolean nativeIsConnected(int port) {
1360             try {
1361                 return mHdmiCec.isConnected(port);
1362             } catch (RemoteException e) {
1363                 HdmiLogger.error("Failed to get connection info : ", e);
1364                 return false;
1365             }
1366         }
1367 
1368         @Override
nativeSetHpdSignalType(int signal, int portId)1369         public void nativeSetHpdSignalType(int signal, int portId) {
1370             HdmiLogger.error(
1371                     "Failed to set HPD signal type: not supported by HAL.");
1372         }
1373 
1374         @Override
nativeGetHpdSignalType(int portId)1375         public int nativeGetHpdSignalType(int portId) {
1376             HdmiLogger.error(
1377                     "Failed to get HPD signal type: not supported by HAL.");
1378             return Constants.HDMI_HPD_TYPE_PHYSICAL;
1379         }
1380     }
1381 
1382     private static final class NativeWrapperImpl implements NativeWrapper,
1383             IHwBinder.DeathRecipient, getPhysicalAddressCallback {
1384         private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec;
1385         @Nullable private HdmiCecCallback mCallback;
1386 
1387         private final Object mLock = new Object();
1388         private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
1389 
1390         @Override
nativeInit()1391         public String nativeInit() {
1392             return (connectToHal() ? mHdmiCec.toString() : null);
1393         }
1394 
connectToHal()1395         boolean connectToHal() {
1396             try {
1397                 mHdmiCec = android.hardware.tv.cec.V1_0.IHdmiCec.getService(true);
1398                 try {
1399                     mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
1400                 } catch (RemoteException e) {
1401                     HdmiLogger.error("Couldn't link to death : ", e);
1402                 }
1403             } catch (RemoteException | NoSuchElementException e) {
1404                 HdmiLogger.error("Couldn't connect to cec@1.0", e);
1405                 return false;
1406             }
1407             return true;
1408         }
1409 
1410         @Override
setCallback(@onNull HdmiCecCallback callback)1411         public void setCallback(@NonNull HdmiCecCallback callback) {
1412             mCallback = callback;
1413             try {
1414                 mHdmiCec.setCallback(new HdmiCecCallback10(callback));
1415             } catch (RemoteException e) {
1416                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
1417             }
1418         }
1419 
1420         @Override
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1421         public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
1422             android.hardware.tv.cec.V1_0.CecMessage message =
1423                     new android.hardware.tv.cec.V1_0.CecMessage();
1424             message.initiator = srcAddress;
1425             message.destination = dstAddress;
1426             message.body = new ArrayList<>(body.length);
1427             for (byte b : body) {
1428                 message.body.add(b);
1429             }
1430             try {
1431                 return mHdmiCec.sendMessage(message);
1432             } catch (RemoteException e) {
1433                 HdmiLogger.error("Failed to send CEC message : ", e);
1434                 return SendMessageResult.FAIL;
1435             }
1436         }
1437 
1438         @Override
nativeAddLogicalAddress(int logicalAddress)1439         public int nativeAddLogicalAddress(int logicalAddress) {
1440             try {
1441                 return mHdmiCec.addLogicalAddress(logicalAddress);
1442             } catch (RemoteException e) {
1443                 HdmiLogger.error("Failed to add a logical address : ", e);
1444                 return Result.FAILURE_INVALID_ARGS;
1445             }
1446         }
1447 
1448         @Override
nativeClearLogicalAddress()1449         public void nativeClearLogicalAddress() {
1450             try {
1451                 mHdmiCec.clearLogicalAddress();
1452             } catch (RemoteException e) {
1453                 HdmiLogger.error("Failed to clear logical address : ", e);
1454             }
1455         }
1456 
1457         @Override
nativeGetPhysicalAddress()1458         public int nativeGetPhysicalAddress() {
1459             try {
1460                 mHdmiCec.getPhysicalAddress(this);
1461                 return mPhysicalAddress;
1462             } catch (RemoteException e) {
1463                 HdmiLogger.error("Failed to get physical address : ", e);
1464                 return INVALID_PHYSICAL_ADDRESS;
1465             }
1466         }
1467 
1468         @Override
nativeGetVersion()1469         public int nativeGetVersion() {
1470             try {
1471                 return mHdmiCec.getCecVersion();
1472             } catch (RemoteException e) {
1473                 HdmiLogger.error("Failed to get cec version : ", e);
1474                 return Result.FAILURE_UNKNOWN;
1475             }
1476         }
1477 
1478         @Override
nativeGetVendorId()1479         public int nativeGetVendorId() {
1480             try {
1481                 return mHdmiCec.getVendorId();
1482             } catch (RemoteException e) {
1483                 HdmiLogger.error("Failed to get vendor id : ", e);
1484                 return Result.FAILURE_UNKNOWN;
1485             }
1486         }
1487 
1488         @Override
nativeGetPortInfos()1489         public HdmiPortInfo[] nativeGetPortInfos() {
1490             try {
1491                 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
1492                         mHdmiCec.getPortInfo();
1493                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
1494                 int i = 0;
1495                 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
1496                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
1497                             portInfo.portId,
1498                             portInfo.type,
1499                             portInfo.physicalAddress)
1500                             .setCecSupported(portInfo.cecSupported)
1501                             .setMhlSupported(false)
1502                             .setArcSupported(portInfo.arcSupported)
1503                             .setEarcSupported(false)
1504                             .build();
1505                     i++;
1506                 }
1507                 return hdmiPortInfo;
1508             } catch (RemoteException e) {
1509                 HdmiLogger.error("Failed to get port information : ", e);
1510                 return null;
1511             }
1512         }
1513 
nativeSetOption(int flag, boolean enabled)1514         private void nativeSetOption(int flag, boolean enabled) {
1515             try {
1516                 mHdmiCec.setOption(flag, enabled);
1517             } catch (RemoteException e) {
1518                 HdmiLogger.error("Failed to set option : ", e);
1519             }
1520         }
1521 
1522         @Override
enableWakeupByOtp(boolean enabled)1523         public void enableWakeupByOtp(boolean enabled) {
1524             nativeSetOption(OptionKey.WAKEUP, enabled);
1525         }
1526 
1527         @Override
enableCec(boolean enabled)1528         public void enableCec(boolean enabled) {
1529             nativeSetOption(OptionKey.ENABLE_CEC, enabled);
1530         }
1531 
1532         @Override
enableSystemCecControl(boolean enabled)1533         public void enableSystemCecControl(boolean enabled) {
1534             nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled);
1535         }
1536 
1537         @Override
nativeSetLanguage(String language)1538         public void nativeSetLanguage(String language) {
1539             try {
1540                 mHdmiCec.setLanguage(language);
1541             } catch (RemoteException e) {
1542                 HdmiLogger.error("Failed to set language : ", e);
1543             }
1544         }
1545 
1546         @Override
nativeEnableAudioReturnChannel(int port, boolean flag)1547         public void nativeEnableAudioReturnChannel(int port, boolean flag) {
1548             try {
1549                 mHdmiCec.enableAudioReturnChannel(port, flag);
1550             } catch (RemoteException e) {
1551                 HdmiLogger.error("Failed to enable/disable ARC : ", e);
1552             }
1553         }
1554 
1555         @Override
nativeIsConnected(int port)1556         public boolean nativeIsConnected(int port) {
1557             try {
1558                 return mHdmiCec.isConnected(port);
1559             } catch (RemoteException e) {
1560                 HdmiLogger.error("Failed to get connection info : ", e);
1561                 return false;
1562             }
1563         }
1564 
1565         @Override
nativeSetHpdSignalType(int signal, int portId)1566         public void nativeSetHpdSignalType(int signal, int portId) {
1567             HdmiLogger.error(
1568                     "Failed to set HPD signal type: not supported by HAL.");
1569         }
1570 
1571         @Override
nativeGetHpdSignalType(int portId)1572         public int nativeGetHpdSignalType(int portId) {
1573             HdmiLogger.error(
1574                     "Failed to get HPD signal type: not supported by HAL.");
1575             return Constants.HDMI_HPD_TYPE_PHYSICAL;
1576         }
1577 
1578         @Override
serviceDied(long cookie)1579         public void serviceDied(long cookie) {
1580             if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
1581                 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
1582                 connectToHal();
1583                 // Reconnect the callback
1584                 if (mCallback != null) {
1585                     setCallback(mCallback);
1586                 }
1587             }
1588         }
1589 
1590         @Override
onValues(int result, short addr)1591         public void onValues(int result, short addr) {
1592             if (result == Result.SUCCESS) {
1593                 synchronized (mLock) {
1594                     mPhysicalAddress = new Short(addr).intValue();
1595                 }
1596             }
1597         }
1598     }
1599 
1600     final class HdmiCecCallback {
1601         @VisibleForTesting
onCecMessage(int initiator, int destination, byte[] body)1602         public void onCecMessage(int initiator, int destination, byte[] body) {
1603             runOnServiceThread(
1604                     () -> handleIncomingCecCommand(initiator, destination, body));
1605         }
1606 
1607         @VisibleForTesting
onHotplugEvent(int portId, boolean connected)1608         public void onHotplugEvent(int portId, boolean connected) {
1609             runOnServiceThread(() -> handleHotplug(portId, connected));
1610         }
1611     }
1612 
1613     private static final class HdmiCecCallback10
1614             extends android.hardware.tv.cec.V1_0.IHdmiCecCallback.Stub {
1615         private final HdmiCecCallback mHdmiCecCallback;
1616 
HdmiCecCallback10(HdmiCecCallback hdmiCecCallback)1617         HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
1618             mHdmiCecCallback = hdmiCecCallback;
1619         }
1620 
1621         @Override
onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1622         public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)
1623                 throws RemoteException {
1624             byte[] body = new byte[message.body.size()];
1625             for (int i = 0; i < message.body.size(); i++) {
1626                 body[i] = message.body.get(i);
1627             }
1628             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
1629         }
1630 
1631         @Override
onHotplugEvent(HotplugEvent event)1632         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
1633             mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
1634         }
1635     }
1636 
1637     private static final class HdmiCecCallback11
1638             extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub {
1639         private final HdmiCecCallback mHdmiCecCallback;
1640 
HdmiCecCallback11(HdmiCecCallback hdmiCecCallback)1641         HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) {
1642             mHdmiCecCallback = hdmiCecCallback;
1643         }
1644 
1645         @Override
onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)1646         public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)
1647                 throws RemoteException {
1648             byte[] body = new byte[message.body.size()];
1649             for (int i = 0; i < message.body.size(); i++) {
1650                 body[i] = message.body.get(i);
1651             }
1652             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
1653         }
1654 
1655         @Override
onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1656         public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)
1657                 throws RemoteException {
1658             byte[] body = new byte[message.body.size()];
1659             for (int i = 0; i < message.body.size(); i++) {
1660                 body[i] = message.body.get(i);
1661             }
1662             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
1663         }
1664 
1665         @Override
onHotplugEvent(HotplugEvent event)1666         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
1667             mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
1668         }
1669     }
1670 
1671     private static final class HdmiCecCallbackAidl extends IHdmiCecCallback.Stub {
1672         private final HdmiCecCallback mHdmiCecCallback;
1673 
HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback)1674         HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback) {
1675             mHdmiCecCallback = hdmiCecCallback;
1676         }
1677 
1678         @Override
onCecMessage(CecMessage message)1679         public void onCecMessage(CecMessage message) throws RemoteException {
1680             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, message.body);
1681         }
1682 
1683         @Override
getInterfaceHash()1684         public synchronized String getInterfaceHash() throws android.os.RemoteException {
1685             return IHdmiCecCallback.Stub.HASH;
1686         }
1687 
1688         @Override
getInterfaceVersion()1689         public int getInterfaceVersion() throws android.os.RemoteException {
1690             return IHdmiCecCallback.Stub.VERSION;
1691         }
1692     }
1693 
1694     private static final class HdmiConnectionCallbackAidl extends IHdmiConnectionCallback.Stub {
1695         private final HdmiCecCallback mHdmiCecCallback;
1696 
HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback)1697         HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback) {
1698             mHdmiCecCallback = hdmiCecCallback;
1699         }
1700 
1701         @Override
onHotplugEvent(boolean connected, int portId)1702         public void onHotplugEvent(boolean connected, int portId) throws RemoteException {
1703             mHdmiCecCallback.onHotplugEvent(portId, connected);
1704         }
1705 
1706         @Override
getInterfaceHash()1707         public synchronized String getInterfaceHash() throws android.os.RemoteException {
1708             return IHdmiConnectionCallback.Stub.HASH;
1709         }
1710 
1711         @Override
getInterfaceVersion()1712         public int getInterfaceVersion() throws android.os.RemoteException {
1713             return IHdmiConnectionCallback.Stub.VERSION;
1714         }
1715     }
1716 
1717     public abstract static class Dumpable {
1718         protected final long mTime;
1719 
Dumpable()1720         Dumpable() {
1721             mTime = System.currentTimeMillis();
1722         }
1723 
dump(IndentingPrintWriter pw, SimpleDateFormat sdf)1724         abstract void dump(IndentingPrintWriter pw, SimpleDateFormat sdf);
1725     }
1726 
1727     private static final class MessageHistoryRecord extends Dumpable {
1728         private final boolean mIsReceived; // true if received message and false if sent message
1729         private final HdmiCecMessage mMessage;
1730         private final List<String> mSendResults;
1731 
MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults)1732         MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults) {
1733             super();
1734             mIsReceived = isReceived;
1735             mMessage = message;
1736             mSendResults = sendResults;
1737         }
1738 
1739         @Override
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1740         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
1741             pw.print(mIsReceived ? "[R]" : "[S]");
1742             pw.print(" time=");
1743             pw.print(sdf.format(new Date(mTime)));
1744             pw.print(" message=");
1745             pw.print(mMessage);
1746 
1747             StringBuilder results = new StringBuilder();
1748             if (!mIsReceived && mSendResults != null) {
1749                 results.append(" (");
1750                 results.append(String.join(", ", mSendResults));
1751                 results.append(")");
1752             }
1753 
1754             pw.println(results);
1755         }
1756     }
1757 
1758     private static final class HotplugHistoryRecord extends Dumpable {
1759         private final int mPort;
1760         private final boolean mConnected;
1761 
HotplugHistoryRecord(int port, boolean connected)1762         HotplugHistoryRecord(int port, boolean connected) {
1763             super();
1764             mPort = port;
1765             mConnected = connected;
1766         }
1767 
1768         @Override
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1769         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
1770             pw.print("[H]");
1771             pw.print(" time=");
1772             pw.print(sdf.format(new Date(mTime)));
1773             pw.print(" hotplug port=");
1774             pw.print(mPort);
1775             pw.print(" connected=");
1776             pw.println(mConnected);
1777         }
1778     }
1779 }
1780