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 %b, signal %b", 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 %b ", 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 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 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 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 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 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             if (result == Result.SUCCESS) {
1211                 synchronized (mLock) {
1212                     mPhysicalAddress = new Short(addr).intValue();
1213                 }
1214             }
1215         }
1216 
1217         @Override
serviceDied(long cookie)1218         public void serviceDied(long cookie) {
1219             if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
1220                 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
1221                 connectToHal();
1222                 // Reconnect the callback
1223                 if (mCallback != null) {
1224                     setCallback(mCallback);
1225                 }
1226             }
1227         }
1228 
1229         @Override
setCallback(HdmiCecCallback callback)1230         public void setCallback(HdmiCecCallback callback) {
1231             mCallback = callback;
1232             try {
1233                 mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback));
1234             } catch (RemoteException e) {
1235                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
1236             }
1237         }
1238 
1239         @Override
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1240         public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
1241             android.hardware.tv.cec.V1_1.CecMessage message =
1242                     new android.hardware.tv.cec.V1_1.CecMessage();
1243             message.initiator = srcAddress;
1244             message.destination = dstAddress;
1245             message.body = new ArrayList<>(body.length);
1246             for (byte b : body) {
1247                 message.body.add(b);
1248             }
1249             try {
1250                 return mHdmiCec.sendMessage_1_1(message);
1251             } catch (RemoteException e) {
1252                 HdmiLogger.error("Failed to send CEC message : ", e);
1253                 return SendMessageResult.FAIL;
1254             }
1255         }
1256 
1257         @Override
nativeAddLogicalAddress(int logicalAddress)1258         public int nativeAddLogicalAddress(int logicalAddress) {
1259             try {
1260                 return mHdmiCec.addLogicalAddress_1_1(logicalAddress);
1261             } catch (RemoteException e) {
1262                 HdmiLogger.error("Failed to add a logical address : ", e);
1263                 return Result.FAILURE_INVALID_ARGS;
1264             }
1265         }
1266 
1267         @Override
nativeClearLogicalAddress()1268         public void nativeClearLogicalAddress() {
1269             try {
1270                 mHdmiCec.clearLogicalAddress();
1271             } catch (RemoteException e) {
1272                 HdmiLogger.error("Failed to clear logical address : ", e);
1273             }
1274         }
1275 
1276         @Override
nativeGetPhysicalAddress()1277         public int nativeGetPhysicalAddress() {
1278             try {
1279                 mHdmiCec.getPhysicalAddress(this);
1280                 return mPhysicalAddress;
1281             } catch (RemoteException e) {
1282                 HdmiLogger.error("Failed to get physical address : ", e);
1283                 return INVALID_PHYSICAL_ADDRESS;
1284             }
1285         }
1286 
1287         @Override
nativeGetVersion()1288         public int nativeGetVersion() {
1289             try {
1290                 return mHdmiCec.getCecVersion();
1291             } catch (RemoteException e) {
1292                 HdmiLogger.error("Failed to get cec version : ", e);
1293                 return Result.FAILURE_UNKNOWN;
1294             }
1295         }
1296 
1297         @Override
nativeGetVendorId()1298         public int nativeGetVendorId() {
1299             try {
1300                 return mHdmiCec.getVendorId();
1301             } catch (RemoteException e) {
1302                 HdmiLogger.error("Failed to get vendor id : ", e);
1303                 return Result.FAILURE_UNKNOWN;
1304             }
1305         }
1306 
1307         @Override
nativeGetPortInfos()1308         public HdmiPortInfo[] nativeGetPortInfos() {
1309             try {
1310                 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
1311                         mHdmiCec.getPortInfo();
1312                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
1313                 int i = 0;
1314                 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
1315                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
1316                             portInfo.portId,
1317                             portInfo.type,
1318                             Short.toUnsignedInt(portInfo.physicalAddress))
1319                             .setCecSupported(portInfo.cecSupported)
1320                             .setMhlSupported(false)
1321                             .setArcSupported(portInfo.arcSupported)
1322                             .setEarcSupported(false)
1323                             .build();
1324                     i++;
1325                 }
1326                 return hdmiPortInfo;
1327             } catch (RemoteException e) {
1328                 HdmiLogger.error("Failed to get port information : ", e);
1329                 return null;
1330             }
1331         }
1332 
nativeSetOption(int flag, boolean enabled)1333         private void nativeSetOption(int flag, boolean enabled) {
1334             try {
1335                 mHdmiCec.setOption(flag, enabled);
1336             } catch (RemoteException e) {
1337                 HdmiLogger.error("Failed to set option : ", e);
1338             }
1339         }
1340 
1341         @Override
enableWakeupByOtp(boolean enabled)1342         public void enableWakeupByOtp(boolean enabled) {
1343             nativeSetOption(OptionKey.WAKEUP, enabled);
1344         }
1345 
1346         @Override
enableCec(boolean enabled)1347         public void enableCec(boolean enabled) {
1348             nativeSetOption(OptionKey.ENABLE_CEC, enabled);
1349         }
1350 
1351         @Override
enableSystemCecControl(boolean enabled)1352         public void enableSystemCecControl(boolean enabled) {
1353             nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled);
1354         }
1355 
1356         @Override
nativeSetLanguage(String language)1357         public void nativeSetLanguage(String language) {
1358             try {
1359                 mHdmiCec.setLanguage(language);
1360             } catch (RemoteException e) {
1361                 HdmiLogger.error("Failed to set language : ", e);
1362             }
1363         }
1364 
1365         @Override
nativeEnableAudioReturnChannel(int port, boolean flag)1366         public void nativeEnableAudioReturnChannel(int port, boolean flag) {
1367             try {
1368                 mHdmiCec.enableAudioReturnChannel(port, flag);
1369             } catch (RemoteException e) {
1370                 HdmiLogger.error("Failed to enable/disable ARC : ", e);
1371             }
1372         }
1373 
1374         @Override
nativeIsConnected(int port)1375         public boolean nativeIsConnected(int port) {
1376             try {
1377                 return mHdmiCec.isConnected(port);
1378             } catch (RemoteException e) {
1379                 HdmiLogger.error("Failed to get connection info : ", e);
1380                 return false;
1381             }
1382         }
1383 
1384         @Override
nativeSetHpdSignalType(int signal, int portId)1385         public void nativeSetHpdSignalType(int signal, int portId) {
1386             HdmiLogger.error(
1387                     "Failed to set HPD signal type: not supported by HAL.");
1388         }
1389 
1390         @Override
nativeGetHpdSignalType(int portId)1391         public int nativeGetHpdSignalType(int portId) {
1392             HdmiLogger.error(
1393                     "Failed to get HPD signal type: not supported by HAL.");
1394             return Constants.HDMI_HPD_TYPE_PHYSICAL;
1395         }
1396     }
1397 
1398     private static final class NativeWrapperImpl implements NativeWrapper,
1399             IHwBinder.DeathRecipient, getPhysicalAddressCallback {
1400         private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec;
1401         @Nullable private HdmiCecCallback mCallback;
1402 
1403         private final Object mLock = new Object();
1404         private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
1405 
1406         @Override
nativeInit()1407         public String nativeInit() {
1408             return (connectToHal() ? mHdmiCec.toString() : null);
1409         }
1410 
connectToHal()1411         boolean connectToHal() {
1412             try {
1413                 mHdmiCec = android.hardware.tv.cec.V1_0.IHdmiCec.getService(true);
1414                 try {
1415                     mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
1416                 } catch (RemoteException e) {
1417                     HdmiLogger.error("Couldn't link to death : ", e);
1418                 }
1419             } catch (RemoteException | NoSuchElementException e) {
1420                 HdmiLogger.error("Couldn't connect to cec@1.0", e);
1421                 return false;
1422             }
1423             return true;
1424         }
1425 
1426         @Override
setCallback(@onNull HdmiCecCallback callback)1427         public void setCallback(@NonNull HdmiCecCallback callback) {
1428             mCallback = callback;
1429             try {
1430                 mHdmiCec.setCallback(new HdmiCecCallback10(callback));
1431             } catch (RemoteException e) {
1432                 HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
1433             }
1434         }
1435 
1436         @Override
nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body)1437         public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
1438             android.hardware.tv.cec.V1_0.CecMessage message =
1439                     new android.hardware.tv.cec.V1_0.CecMessage();
1440             message.initiator = srcAddress;
1441             message.destination = dstAddress;
1442             message.body = new ArrayList<>(body.length);
1443             for (byte b : body) {
1444                 message.body.add(b);
1445             }
1446             try {
1447                 return mHdmiCec.sendMessage(message);
1448             } catch (RemoteException e) {
1449                 HdmiLogger.error("Failed to send CEC message : ", e);
1450                 return SendMessageResult.FAIL;
1451             }
1452         }
1453 
1454         @Override
nativeAddLogicalAddress(int logicalAddress)1455         public int nativeAddLogicalAddress(int logicalAddress) {
1456             try {
1457                 return mHdmiCec.addLogicalAddress(logicalAddress);
1458             } catch (RemoteException e) {
1459                 HdmiLogger.error("Failed to add a logical address : ", e);
1460                 return Result.FAILURE_INVALID_ARGS;
1461             }
1462         }
1463 
1464         @Override
nativeClearLogicalAddress()1465         public void nativeClearLogicalAddress() {
1466             try {
1467                 mHdmiCec.clearLogicalAddress();
1468             } catch (RemoteException e) {
1469                 HdmiLogger.error("Failed to clear logical address : ", e);
1470             }
1471         }
1472 
1473         @Override
nativeGetPhysicalAddress()1474         public int nativeGetPhysicalAddress() {
1475             try {
1476                 mHdmiCec.getPhysicalAddress(this);
1477                 return mPhysicalAddress;
1478             } catch (RemoteException e) {
1479                 HdmiLogger.error("Failed to get physical address : ", e);
1480                 return INVALID_PHYSICAL_ADDRESS;
1481             }
1482         }
1483 
1484         @Override
nativeGetVersion()1485         public int nativeGetVersion() {
1486             try {
1487                 return mHdmiCec.getCecVersion();
1488             } catch (RemoteException e) {
1489                 HdmiLogger.error("Failed to get cec version : ", e);
1490                 return Result.FAILURE_UNKNOWN;
1491             }
1492         }
1493 
1494         @Override
nativeGetVendorId()1495         public int nativeGetVendorId() {
1496             try {
1497                 return mHdmiCec.getVendorId();
1498             } catch (RemoteException e) {
1499                 HdmiLogger.error("Failed to get vendor id : ", e);
1500                 return Result.FAILURE_UNKNOWN;
1501             }
1502         }
1503 
1504         @Override
nativeGetPortInfos()1505         public HdmiPortInfo[] nativeGetPortInfos() {
1506             try {
1507                 ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
1508                         mHdmiCec.getPortInfo();
1509                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
1510                 int i = 0;
1511                 for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
1512                     hdmiPortInfo[i] = new HdmiPortInfo.Builder(
1513                             portInfo.portId,
1514                             portInfo.type,
1515                             Short.toUnsignedInt(portInfo.physicalAddress))
1516                             .setCecSupported(portInfo.cecSupported)
1517                             .setMhlSupported(false)
1518                             .setArcSupported(portInfo.arcSupported)
1519                             .setEarcSupported(false)
1520                             .build();
1521                     i++;
1522                 }
1523                 return hdmiPortInfo;
1524             } catch (RemoteException e) {
1525                 HdmiLogger.error("Failed to get port information : ", e);
1526                 return null;
1527             }
1528         }
1529 
nativeSetOption(int flag, boolean enabled)1530         private void nativeSetOption(int flag, boolean enabled) {
1531             try {
1532                 mHdmiCec.setOption(flag, enabled);
1533             } catch (RemoteException e) {
1534                 HdmiLogger.error("Failed to set option : ", e);
1535             }
1536         }
1537 
1538         @Override
enableWakeupByOtp(boolean enabled)1539         public void enableWakeupByOtp(boolean enabled) {
1540             nativeSetOption(OptionKey.WAKEUP, enabled);
1541         }
1542 
1543         @Override
enableCec(boolean enabled)1544         public void enableCec(boolean enabled) {
1545             nativeSetOption(OptionKey.ENABLE_CEC, enabled);
1546         }
1547 
1548         @Override
enableSystemCecControl(boolean enabled)1549         public void enableSystemCecControl(boolean enabled) {
1550             nativeSetOption(OptionKey.SYSTEM_CEC_CONTROL, enabled);
1551         }
1552 
1553         @Override
nativeSetLanguage(String language)1554         public void nativeSetLanguage(String language) {
1555             try {
1556                 mHdmiCec.setLanguage(language);
1557             } catch (RemoteException e) {
1558                 HdmiLogger.error("Failed to set language : ", e);
1559             }
1560         }
1561 
1562         @Override
nativeEnableAudioReturnChannel(int port, boolean flag)1563         public void nativeEnableAudioReturnChannel(int port, boolean flag) {
1564             try {
1565                 mHdmiCec.enableAudioReturnChannel(port, flag);
1566             } catch (RemoteException e) {
1567                 HdmiLogger.error("Failed to enable/disable ARC : ", e);
1568             }
1569         }
1570 
1571         @Override
nativeIsConnected(int port)1572         public boolean nativeIsConnected(int port) {
1573             try {
1574                 return mHdmiCec.isConnected(port);
1575             } catch (RemoteException e) {
1576                 HdmiLogger.error("Failed to get connection info : ", e);
1577                 return false;
1578             }
1579         }
1580 
1581         @Override
nativeSetHpdSignalType(int signal, int portId)1582         public void nativeSetHpdSignalType(int signal, int portId) {
1583             HdmiLogger.error(
1584                     "Failed to set HPD signal type: not supported by HAL.");
1585         }
1586 
1587         @Override
nativeGetHpdSignalType(int portId)1588         public int nativeGetHpdSignalType(int portId) {
1589             HdmiLogger.error(
1590                     "Failed to get HPD signal type: not supported by HAL.");
1591             return Constants.HDMI_HPD_TYPE_PHYSICAL;
1592         }
1593 
1594         @Override
serviceDied(long cookie)1595         public void serviceDied(long cookie) {
1596             if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
1597                 HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
1598                 connectToHal();
1599                 // Reconnect the callback
1600                 if (mCallback != null) {
1601                     setCallback(mCallback);
1602                 }
1603             }
1604         }
1605 
1606         @Override
onValues(int result, short addr)1607         public void onValues(int result, short addr) {
1608             if (result == Result.SUCCESS) {
1609                 synchronized (mLock) {
1610                     mPhysicalAddress = new Short(addr).intValue();
1611                 }
1612             }
1613         }
1614     }
1615 
1616     final class HdmiCecCallback {
1617         @VisibleForTesting
onCecMessage(int initiator, int destination, byte[] body)1618         public void onCecMessage(int initiator, int destination, byte[] body) {
1619             runOnServiceThread(
1620                     () -> handleIncomingCecCommand(initiator, destination, body));
1621         }
1622 
1623         @VisibleForTesting
onHotplugEvent(int portId, boolean connected)1624         public void onHotplugEvent(int portId, boolean connected) {
1625             runOnServiceThread(() -> handleHotplug(portId, connected));
1626         }
1627     }
1628 
1629     private static final class HdmiCecCallback10
1630             extends android.hardware.tv.cec.V1_0.IHdmiCecCallback.Stub {
1631         private final HdmiCecCallback mHdmiCecCallback;
1632 
HdmiCecCallback10(HdmiCecCallback hdmiCecCallback)1633         HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
1634             mHdmiCecCallback = hdmiCecCallback;
1635         }
1636 
1637         @Override
onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1638         public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)
1639                 throws RemoteException {
1640             byte[] body = new byte[message.body.size()];
1641             for (int i = 0; i < message.body.size(); i++) {
1642                 body[i] = message.body.get(i);
1643             }
1644             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
1645         }
1646 
1647         @Override
onHotplugEvent(HotplugEvent event)1648         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
1649             mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
1650         }
1651     }
1652 
1653     private static final class HdmiCecCallback11
1654             extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub {
1655         private final HdmiCecCallback mHdmiCecCallback;
1656 
HdmiCecCallback11(HdmiCecCallback hdmiCecCallback)1657         HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) {
1658             mHdmiCecCallback = hdmiCecCallback;
1659         }
1660 
1661         @Override
onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)1662         public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)
1663                 throws RemoteException {
1664             byte[] body = new byte[message.body.size()];
1665             for (int i = 0; i < message.body.size(); i++) {
1666                 body[i] = message.body.get(i);
1667             }
1668             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
1669         }
1670 
1671         @Override
onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)1672         public void onCecMessage(android.hardware.tv.cec.V1_0.CecMessage message)
1673                 throws RemoteException {
1674             byte[] body = new byte[message.body.size()];
1675             for (int i = 0; i < message.body.size(); i++) {
1676                 body[i] = message.body.get(i);
1677             }
1678             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
1679         }
1680 
1681         @Override
onHotplugEvent(HotplugEvent event)1682         public void onHotplugEvent(HotplugEvent event) throws RemoteException {
1683             mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
1684         }
1685     }
1686 
1687     private static final class HdmiCecCallbackAidl extends IHdmiCecCallback.Stub {
1688         private final HdmiCecCallback mHdmiCecCallback;
1689 
HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback)1690         HdmiCecCallbackAidl(HdmiCecCallback hdmiCecCallback) {
1691             mHdmiCecCallback = hdmiCecCallback;
1692         }
1693 
1694         @Override
onCecMessage(CecMessage message)1695         public void onCecMessage(CecMessage message) throws RemoteException {
1696             mHdmiCecCallback.onCecMessage(message.initiator, message.destination, message.body);
1697         }
1698 
1699         @Override
getInterfaceHash()1700         public synchronized String getInterfaceHash() throws android.os.RemoteException {
1701             return IHdmiCecCallback.Stub.HASH;
1702         }
1703 
1704         @Override
getInterfaceVersion()1705         public int getInterfaceVersion() throws android.os.RemoteException {
1706             return IHdmiCecCallback.Stub.VERSION;
1707         }
1708     }
1709 
1710     private static final class HdmiConnectionCallbackAidl extends IHdmiConnectionCallback.Stub {
1711         private final HdmiCecCallback mHdmiCecCallback;
1712 
HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback)1713         HdmiConnectionCallbackAidl(HdmiCecCallback hdmiCecCallback) {
1714             mHdmiCecCallback = hdmiCecCallback;
1715         }
1716 
1717         @Override
onHotplugEvent(boolean connected, int portId)1718         public void onHotplugEvent(boolean connected, int portId) throws RemoteException {
1719             mHdmiCecCallback.onHotplugEvent(portId, connected);
1720         }
1721 
1722         @Override
getInterfaceHash()1723         public synchronized String getInterfaceHash() throws android.os.RemoteException {
1724             return IHdmiConnectionCallback.Stub.HASH;
1725         }
1726 
1727         @Override
getInterfaceVersion()1728         public int getInterfaceVersion() throws android.os.RemoteException {
1729             return IHdmiConnectionCallback.Stub.VERSION;
1730         }
1731     }
1732 
1733     public abstract static class Dumpable {
1734         protected final long mTime;
1735 
Dumpable()1736         Dumpable() {
1737             mTime = System.currentTimeMillis();
1738         }
1739 
dump(IndentingPrintWriter pw, SimpleDateFormat sdf)1740         abstract void dump(IndentingPrintWriter pw, SimpleDateFormat sdf);
1741     }
1742 
1743     private static final class MessageHistoryRecord extends Dumpable {
1744         private final boolean mIsReceived; // true if received message and false if sent message
1745         private final HdmiCecMessage mMessage;
1746         private final List<String> mSendResults;
1747 
MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults)1748         MessageHistoryRecord(boolean isReceived, HdmiCecMessage message, List<String> sendResults) {
1749             super();
1750             mIsReceived = isReceived;
1751             mMessage = message;
1752             mSendResults = sendResults;
1753         }
1754 
1755         @Override
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1756         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
1757             pw.print(mIsReceived ? "[R]" : "[S]");
1758             pw.print(" time=");
1759             pw.print(sdf.format(new Date(mTime)));
1760             pw.print(" message=");
1761             pw.print(mMessage);
1762 
1763             StringBuilder results = new StringBuilder();
1764             if (!mIsReceived && mSendResults != null) {
1765                 results.append(" (");
1766                 results.append(String.join(", ", mSendResults));
1767                 results.append(")");
1768             }
1769 
1770             pw.println(results);
1771         }
1772     }
1773 
1774     private static final class HotplugHistoryRecord extends Dumpable {
1775         private final int mPort;
1776         private final boolean mConnected;
1777 
HotplugHistoryRecord(int port, boolean connected)1778         HotplugHistoryRecord(int port, boolean connected) {
1779             super();
1780             mPort = port;
1781             mConnected = connected;
1782         }
1783 
1784         @Override
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1785         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
1786             pw.print("[H]");
1787             pw.print(" time=");
1788             pw.print(sdf.format(new Date(mTime)));
1789             pw.print(" hotplug port=");
1790             pw.print(mPort);
1791             pw.print(" connected=");
1792             pw.println(mConnected);
1793         }
1794     }
1795 }
1796