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