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