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