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