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
17package com.android.server.hdmi;
18
19import android.hardware.hdmi.HdmiPortInfo;
20import android.hardware.tv.cec.V1_0.Result;
21import android.hardware.tv.cec.V1_0.SendMessageResult;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.MessageQueue;
25import android.util.Slog;
26import android.util.SparseArray;
27import com.android.internal.util.IndentingPrintWriter;
28import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
29import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
30import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
31import java.text.SimpleDateFormat;
32import java.util.ArrayList;
33import java.util.Date;
34import java.util.LinkedList;
35import java.util.List;
36import java.util.function.Predicate;
37import java.util.concurrent.ArrayBlockingQueue;
38import libcore.util.EmptyArray;
39import 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 */
53final 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         */
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().
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     */
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
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
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
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
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
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
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     */
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
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
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
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
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
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
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
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
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
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
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
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
407    List<HdmiCecLocalDevice> getLocalDeviceList() {
408        assertRunOnServiceThread();
409        return HdmiUtils.sparseArrayToList(mLocalDevices);
410    }
411
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
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
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
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
509    private void assertRunOnIoThread() {
510        if (Looper.myLooper() != mIoHandler.getLooper()) {
511            throw new IllegalStateException("Should run on io thread.");
512        }
513    }
514
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.
524    private void runOnIoThread(Runnable runnable) {
525        mIoHandler.post(runnable);
526    }
527
528    private void runOnServiceThread(Runnable runnable) {
529        mControlHandler.post(runnable);
530    }
531
532    @ServiceThreadOnly
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
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
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
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
583    void sendCommand(HdmiCecMessage cecMessage) {
584        assertRunOnServiceThread();
585        sendCommand(cecMessage, null);
586    }
587
588    @ServiceThreadOnly
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
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
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
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
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
672    private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
673    private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
674            int dstAddress, byte[] body);
675    private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
676    private static native void nativeClearLogicalAddress(long controllerPtr);
677    private static native int nativeGetPhysicalAddress(long controllerPtr);
678    private static native int nativeGetVersion(long controllerPtr);
679    private static native int nativeGetVendorId(long controllerPtr);
680    private static native HdmiPortInfo[] nativeGetPortInfos(long controllerPtr);
681    private static native void nativeSetOption(long controllerPtr, int flag, boolean enabled);
682    private static native void nativeSetLanguage(long controllerPtr, String language);
683    private static native void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag);
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
691        public MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) {
692            mTime = System.currentTimeMillis();
693            mIsReceived = isReceived;
694            mMessage = message;
695        }
696
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