FileDocCategorySizeDatePackage
HdmiCecController.javaAPI DocAndroid 5.1 API24264Thu Mar 12 22:22:42 GMT 2015com.android.server.hdmi

HdmiCecController

public final class HdmiCecController extends Object
Manages HDMI-CEC command and behaviors. It converts user's command into CEC command and pass it to CEC HAL so that it sends message to other device. For incoming message it translates the message and delegates it to proper module.

It should be careful to access member variables on IO thread because it can be accessed from system thread as well.

It can be created only by {@link HdmiCecController#create}

Declared as package-private, accessed by {@link HdmiControlService} only.

Fields Summary
private static final String
TAG
private static final byte[]
EMPTY_BODY
private static final int
NUM_LOGICAL_ADDRESS
private final com.android.internal.util.Predicate
mRemoteDeviceAddressPredicate
private final com.android.internal.util.Predicate
mSystemAudioAddressPredicate
private android.os.Handler
mIoHandler
private android.os.Handler
mControlHandler
private volatile long
mNativePtr
private final HdmiControlService
mService
private final android.util.SparseArray
mLocalDevices
Constructors Summary
private HdmiCecController(HdmiControlService service)


    // Private constructor.  Use HdmiCecController.create().
       
        mService = service;
    
Methods Summary
voidaddLocalDevice(int deviceType, HdmiCecLocalDevice device)

        assertRunOnServiceThread();
        mLocalDevices.put(deviceType, device);
    
intaddLogicalAddress(int newLogicalAddress)
Add a new logical address to the device. Device's HW should be notified when a new logical address is assigned to a device, so that it can accept a command having available destinations.

Declared as package-private. accessed by {@link HdmiControlService} only.

param
newLogicalAddress a logical address to be added
return
0 on success. Otherwise, returns negative value

        assertRunOnServiceThread();
        if (HdmiUtils.isValidAddress(newLogicalAddress)) {
            return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
        } else {
            return -1;
        }
    
voidallocateLogicalAddress(int deviceType, int preferredAddress, com.android.server.hdmi.HdmiCecController$AllocateAddressCallback callback)
Allocate a new logical address of the given device type. Allocated address will be reported through {@link AllocateAddressCallback}.

Declared as package-private, accessed by {@link HdmiControlService} only.

param
deviceType type of device to used to determine logical address
param
preferredAddress a logical address preferred to be allocated. If sets {@link Constants#ADDR_UNREGISTERED}, scans the smallest logical address matched with the given device type. Otherwise, scan address will start from {@code preferredAddress}
param
callback callback interface to report allocated logical address to caller

        assertRunOnServiceThread();

        runOnIoThread(new Runnable() {
            @Override
            public void run() {
                handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
            }
        });
    
private voidassertRunOnIoThread()

        if (Looper.myLooper() != mIoHandler.getLooper()) {
            throw new IllegalStateException("Should run on io thread.");
        }
    
private voidassertRunOnServiceThread()

        if (Looper.myLooper() != mControlHandler.getLooper()) {
            throw new IllegalStateException("Should run on service thread.");
        }
    
private static byte[]buildBody(int opcode, byte[] params)

        byte[] body = new byte[params.length + 1];
        body[0] = (byte) opcode;
        System.arraycopy(params, 0, body, 1, params.length);
        return body;
    
voidclearLocalDevices()

        assertRunOnServiceThread();
        mLocalDevices.clear();
    
voidclearLogicalAddress()
Clear all logical addresses registered in the device.

Declared as package-private. accessed by {@link HdmiControlService} only.

        assertRunOnServiceThread();
        for (int i = 0; i < mLocalDevices.size(); ++i) {
            mLocalDevices.valueAt(i).clearAddress();
        }
        nativeClearLogicalAddress(mNativePtr);
    
static com.android.server.hdmi.HdmiCecControllercreate(HdmiControlService service)
A factory method to get {@link HdmiCecController}. If it fails to initialize inner device or has no device it will return {@code null}.

Declared as package-private, accessed by {@link HdmiControlService} only.

param
service {@link HdmiControlService} instance used to create internal handler and to pass callback for incoming message or event.
return
{@link HdmiCecController} if device is initialized successfully. Otherwise, returns {@code null}.

        HdmiCecController controller = new HdmiCecController(service);
        long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
        if (nativePtr == 0L) {
            controller = null;
            return null;
        }

        controller.init(nativePtr);
        return controller;
    
voiddump(com.android.internal.util.IndentingPrintWriter pw)

        for (int i = 0; i < mLocalDevices.size(); ++i) {
            pw.println("HdmiCecLocalDevice #" + i + ":");
            pw.increaseIndent();
            mLocalDevices.valueAt(i).dump(pw);
            pw.decreaseIndent();
        }
    
voidflush(java.lang.Runnable runnable)

        assertRunOnServiceThread();
        runOnIoThread(new Runnable() {
            @Override
            public void run() {
                // This ensures the runnable for cleanup is performed after all the pending
                // commands are processed by IO thread.
                runOnServiceThread(runnable);
            }
        });
    
HdmiCecLocalDevicegetLocalDevice(int deviceType)
Return the locally hosted logical device of a given type.

param
deviceType logical device type
return
{@link HdmiCecLocalDevice} instance if the instance of the type is available; otherwise null.

        return mLocalDevices.get(deviceType);
    
java.util.ListgetLocalDeviceList()
Return a list of all {@link HdmiCecLocalDevice}s.

Declared as package-private. accessed by {@link HdmiControlService} only.

        assertRunOnServiceThread();
        return HdmiUtils.sparseArrayToList(mLocalDevices);
    
intgetPhysicalAddress()
Return the physical address of the device.

Declared as package-private. accessed by {@link HdmiControlService} only.

return
CEC physical address of the device. The range of success address is between 0x0000 and 0xFFFF. If failed it returns -1

        assertRunOnServiceThread();
        return nativeGetPhysicalAddress(mNativePtr);
    
android.hardware.hdmi.HdmiPortInfo[]getPortInfos()

        return nativeGetPortInfos(mNativePtr);
    
intgetVendorId()
Return vendor id of the device.

Declared as package-private. accessed by {@link HdmiControlService} only.

        assertRunOnServiceThread();
        return nativeGetVendorId(mNativePtr);
    
intgetVersion()
Return CEC version of the device.

Declared as package-private. accessed by {@link HdmiControlService} only.

        assertRunOnServiceThread();
        return nativeGetVersion(mNativePtr);
    
private voidhandleAllocateLogicalAddress(int deviceType, int preferredAddress, com.android.server.hdmi.HdmiCecController$AllocateAddressCallback callback)

        assertRunOnIoThread();
        int startAddress = preferredAddress;
        // If preferred address is "unregistered", start address will be the smallest
        // address matched with the given device type.
        if (preferredAddress == Constants.ADDR_UNREGISTERED) {
            for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
                if (deviceType == HdmiUtils.getTypeFromAddress(i)) {
                    startAddress = i;
                    break;
                }
            }
        }

        int logicalAddress = Constants.ADDR_UNREGISTERED;
        // Iterates all possible addresses which has the same device type.
        for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
            int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
            if (curAddress != Constants.ADDR_UNREGISTERED
                    && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
                int failedPollingCount = 0;
                for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
                    if (!sendPollMessage(curAddress, curAddress, 1)) {
                        failedPollingCount++;
                    }
                }

                // Pick logical address if failed ratio is more than a half of all retries.
                if (failedPollingCount * 2 >  HdmiConfig.ADDRESS_ALLOCATION_RETRY) {
                    logicalAddress = curAddress;
                    break;
                }
            }
        }

        final int assignedAddress = logicalAddress;
        HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]",
                        deviceType, preferredAddress, assignedAddress);
        if (callback != null) {
            runOnServiceThread(new Runnable() {
                @Override
                public void run() {
                    callback.onAllocated(deviceType, assignedAddress);
                }
            });
        }
    
private voidhandleHotplug(int port, boolean connected)
Called by native when a hotplug event issues.

        assertRunOnServiceThread();
        HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
        mService.onHotplug(port, connected);
    
private voidhandleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body)
Called by native when incoming CEC message arrived.

        assertRunOnServiceThread();
        HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
        HdmiLogger.debug("[R]:" + command);
        onReceiveCommand(command);
    
private voidinit(long nativePtr)

        mIoHandler = new Handler(mService.getIoLooper());
        mControlHandler = new Handler(mService.getServiceLooper());
        mNativePtr = nativePtr;
    
private booleanisAcceptableAddress(int address)

        // Can access command targeting devices available in local device or broadcast command.
        if (address == Constants.ADDR_BROADCAST) {
            return true;
        }
        return isAllocatedLocalDeviceAddress(address);
    
private booleanisAllocatedLocalDeviceAddress(int address)

        assertRunOnServiceThread();
        for (int i = 0; i < mLocalDevices.size(); ++i) {
            if (mLocalDevices.valueAt(i).isAddressOf(address)) {
                return true;
            }
        }
        return false;
    
booleanisConnected(int port)
Return the connection status of the specified port

param
port port number to check connection status
return
true if connected; otherwise, return false

        assertRunOnServiceThread();
        return nativeIsConnected(mNativePtr, port);
    
voidmaySendFeatureAbortCommand(HdmiCecMessage message, int reason)

        assertRunOnServiceThread();
        // Swap the source and the destination.
        int src = message.getDestination();
        int dest = message.getSource();
        if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) {
            // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted
            // messages. See CEC 12.2 Protocol General Rules for detail.
            return;
        }
        int originalOpcode = message.getOpcode();
        if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) {
            return;
        }
        sendCommand(
                HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason));
    
private static native intnativeAddLogicalAddress(long controllerPtr, int logicalAddress)

private static native voidnativeClearLogicalAddress(long controllerPtr)

private static native intnativeGetPhysicalAddress(long controllerPtr)

private static native android.hardware.hdmi.HdmiPortInfo[]nativeGetPortInfos(long controllerPtr)

private static native intnativeGetVendorId(long controllerPtr)

private static native intnativeGetVersion(long controllerPtr)

private static native longnativeInit(com.android.server.hdmi.HdmiCecController handler, android.os.MessageQueue messageQueue)

private static native booleannativeIsConnected(long controllerPtr, int port)

private static native intnativeSendCecCommand(long controllerPtr, int srcAddress, int dstAddress, byte[] body)

private static native voidnativeSetAudioReturnChannel(long controllerPtr, int port, boolean flag)

private static native voidnativeSetOption(long controllerPtr, int flag, int value)

private voidonReceiveCommand(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (isAcceptableAddress(message.getDestination()) && mService.handleCecCommand(message)) {
            return;
        }
        // Not handled message, so we will reply it with <Feature Abort>.
        maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
    
private java.util.ListpickPollCandidates(int pickStrategy)

        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
        Predicate<Integer> pickPredicate = null;
        switch (strategy) {
            case Constants.POLL_STRATEGY_SYSTEM_AUDIO:
                pickPredicate = mSystemAudioAddressPredicate;
                break;
            case Constants.POLL_STRATEGY_REMOTES_DEVICES:
            default:  // The default is POLL_STRATEGY_REMOTES_DEVICES.
                pickPredicate = mRemoteDeviceAddressPredicate;
                break;
        }

        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
        LinkedList<Integer> pollingCandidates = new LinkedList<>();
        switch (iterationStrategy) {
            case Constants.POLL_ITERATION_IN_ORDER:
                for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
                    if (pickPredicate.apply(i)) {
                        pollingCandidates.add(i);
                    }
                }
                break;
            case Constants.POLL_ITERATION_REVERSE_ORDER:
            default:  // The default is reverse order.
                for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) {
                    if (pickPredicate.apply(i)) {
                        pollingCandidates.add(i);
                    }
                }
                break;
        }
        return pollingCandidates;
    
voidpollDevices(com.android.server.hdmi.HdmiControlService.DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)
Poll all remote devices. It sends <Polling Message> to all remote devices.

Declared as package-private. accessed by {@link HdmiControlService} only.

param
callback an interface used to get a list of all remote devices' address
param
sourceAddress a logical address of source device where sends polling message
param
pickStrategy strategy how to pick polling candidates
param
retryCount the number of retry used to send polling message to remote devices

        assertRunOnServiceThread();

        // Extract polling candidates. No need to poll against local devices.
        List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
        ArrayList<Integer> allocated = new ArrayList<>();
        runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
    
private voidrunDevicePolling(int sourceAddress, java.util.List candidates, int retryCount, com.android.server.hdmi.HdmiControlService.DevicePollingCallback callback, java.util.List allocated)

        assertRunOnServiceThread();
        if (candidates.isEmpty()) {
            if (callback != null) {
                HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString());
                callback.onPollingFinished(allocated);
            }
            return;
        }

        final Integer candidate = candidates.remove(0);
        // Proceed polling action for the next address once polling action for the
        // previous address is done.
        runOnIoThread(new Runnable() {
            @Override
            public void run() {
                if (sendPollMessage(sourceAddress, candidate, retryCount)) {
                    allocated.add(candidate);
                }
                runOnServiceThread(new Runnable() {
                    @Override
                    public void run() {
                        runDevicePolling(sourceAddress, candidates, retryCount, callback,
                                allocated);
                    }
                });
            }
        });
    
private voidrunOnIoThread(java.lang.Runnable runnable)

        mIoHandler.post(runnable);
    
private voidrunOnServiceThread(java.lang.Runnable runnable)

        mControlHandler.post(runnable);
    
voidsendCommand(HdmiCecMessage cecMessage)

        assertRunOnServiceThread();
        sendCommand(cecMessage, null);
    
voidsendCommand(HdmiCecMessage cecMessage, HdmiControlService.SendMessageCallback callback)

        assertRunOnServiceThread();
        runOnIoThread(new Runnable() {
            @Override
            public void run() {
                HdmiLogger.debug("[S]:" + cecMessage);
                byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
                int i = 0;
                int errorCode = Constants.SEND_RESULT_SUCCESS;
                do {
                    errorCode = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
                            cecMessage.getDestination(), body);
                    if (errorCode == Constants.SEND_RESULT_SUCCESS) {
                        break;
                    }
                } while (i++ < HdmiConfig.RETRANSMISSION_COUNT);

                final int finalError = errorCode;
                if (finalError != Constants.SEND_RESULT_SUCCESS) {
                    Slog.w(TAG, "Failed to send " + cecMessage);
                }
                if (callback != null) {
                    runOnServiceThread(new Runnable() {
                        @Override
                        public void run() {
                            callback.onSendCompleted(finalError);
                        }
                    });
                }
            }
        });
    
private booleansendPollMessage(int sourceAddress, int destinationAddress, int retryCount)

        assertRunOnIoThread();
        for (int i = 0; i < retryCount; ++i) {
            // <Polling Message> is a message which has empty body.
            // If sending <Polling Message> failed (NAK), it becomes
            // new logical address for the device because no device uses
            // it as logical address of the device.
            if (nativeSendCecCommand(mNativePtr, sourceAddress, destinationAddress, EMPTY_BODY)
                    == Constants.SEND_RESULT_SUCCESS) {
                return true;
            }
        }
        return false;
    
voidsetAudioReturnChannel(int port, boolean enabled)
Configure ARC circuit in the hardware logic to start or stop the feature.

param
port ID of HDMI port to which AVR is connected
param
enabled whether to enable/disable ARC

        assertRunOnServiceThread();
        nativeSetAudioReturnChannel(mNativePtr, port, enabled);
    
voidsetOption(int flag, int value)
Set an option to CEC HAL.

param
flag key of option
param
value value of option

        assertRunOnServiceThread();
        HdmiLogger.debug("setOption: [flag:%d, value:%d]", flag, value);
        nativeSetOption(mNativePtr, flag, value);