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

HdmiControlService

public final class HdmiControlService extends com.android.server.SystemService
Provides a service for sending and processing HDMI control messages, HDMI-CEC and MHL control command, and providing the information on both standard.

Fields Summary
private static final String
TAG
private final Locale
HONG_KONG
private final Locale
MACAU
static final String
PERMISSION
static final int
INITIATED_BY_ENABLE_CEC
static final int
INITIATED_BY_BOOT_UP
static final int
INITIATED_BY_SCREEN_ON
static final int
INITIATED_BY_WAKE_UP_MESSAGE
static final int
INITIATED_BY_HOTPLUG
private final android.os.HandlerThread
mIoThread
private final Object
mLock
private final List
mLocalDevices
private final ArrayList
mHotplugEventListenerRecords
private final ArrayList
mDeviceEventListenerRecords
private final ArrayList
mVendorCommandListenerRecords
private InputChangeListenerRecord
mInputChangeListenerRecord
private HdmiRecordListenerRecord
mRecordListenerRecord
private boolean
mHdmiControlEnabled
private boolean
mProhibitMode
private final ArrayList
mSystemAudioModeChangeListenerRecords
private final android.os.Handler
mHandler
private final SettingsObserver
mSettingsObserver
private final HdmiControlBroadcastReceiver
mHdmiControlBroadcastReceiver
private HdmiCecController
mCecController
private List
mPortInfo
private UnmodifiableSparseIntArray
mPortIdMap
private UnmodifiableSparseArray
mPortInfoMap
private UnmodifiableSparseArray
mPortDeviceMap
private HdmiCecMessageValidator
mMessageValidator
private int
mPowerStatus
private String
mLanguage
private boolean
mStandbyMessageReceived
private boolean
mWakeUpMessageReceived
private int
mActivePortId
private boolean
mMhlInputChangeEnabled
private final ArrayList
mMhlVendorCommandListenerRecords
private List
mMhlDevices
private HdmiMhlControllerStub
mMhlController
private android.media.tv.TvInputManager
mTvInputManager
private android.os.PowerManager
mPowerManager
private int
mLastInputMhl
private boolean
mAddressAllocated
private CecMessageBuffer
mCecMessageBuffer
Constructors Summary
public HdmiControlService(android.content.Context context)


       
        super(context);
        mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
        mSettingsObserver = new SettingsObserver(mHandler);
    
Methods Summary
private voidaddDeviceEventListener(android.hardware.hdmi.IHdmiDeviceEventListener listener)

        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
        try {
            listener.asBinder().linkToDeath(record, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "Listener already died");
            return;
        }
        synchronized (mLock) {
            mDeviceEventListenerRecords.add(record);
        }
    
private voidaddHdmiMhlVendorCommandListener(android.hardware.hdmi.IHdmiMhlVendorCommandListener listener)

        HdmiMhlVendorCommandListenerRecord record =
                new HdmiMhlVendorCommandListenerRecord(listener);
        try {
            listener.asBinder().linkToDeath(record, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "Listener already died.");
            return;
        }

        synchronized (mLock) {
            mMhlVendorCommandListenerRecords.add(record);
        }
    
private voidaddHotplugEventListener(android.hardware.hdmi.IHdmiHotplugEventListener listener)

        final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
        try {
            listener.asBinder().linkToDeath(record, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "Listener already died");
            return;
        }
        synchronized (mLock) {
            mHotplugEventListenerRecords.add(record);
        }

        // Inform the listener of the initial state of each HDMI port by generating
        // hotplug events.
        runOnServiceThread(new Runnable() {
            @Override
            public void run() {
                synchronized (mLock) {
                    if (!mHotplugEventListenerRecords.contains(record)) return;
                }
                for (HdmiPortInfo port : mPortInfo) {
                    HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
                            mCecController.isConnected(port.getId()));
                    synchronized (mLock) {
                        invokeHotplugEventListenerLocked(listener, event);
                    }
                }
            }
        });
    
private voidaddSystemAudioModeChangeListner(android.hardware.hdmi.IHdmiSystemAudioModeChangeListener listener)

        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
                listener);
        try {
            listener.asBinder().linkToDeath(record, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "Listener already died");
            return;
        }
        synchronized (mLock) {
            mSystemAudioModeChangeListenerRecords.add(record);
        }
    
private voidaddVendorCommandListener(android.hardware.hdmi.IHdmiVendorCommandListener listener, int deviceType)

        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
        try {
            listener.asBinder().linkToDeath(record, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "Listener already died");
            return;
        }
        synchronized (mLock) {
            mVendorCommandListenerRecords.add(record);
        }
    
private voidallocateLogicalAddress(java.util.ArrayList allocatingDevices, int initiatedBy)

        assertRunOnServiceThread();
        mCecController.clearLogicalAddress();
        final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
        final int[] finished = new int[1];
        mAddressAllocated = allocatingDevices.isEmpty();

        for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
            mCecController.allocateLogicalAddress(localDevice.getType(),
                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
                @Override
                public void onAllocated(int deviceType, int logicalAddress) {
                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
                    } else {
                        // Set POWER_STATUS_ON to all local devices because they share lifetime
                        // with system.
                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
                                HdmiControlManager.POWER_STATUS_ON);
                        localDevice.setDeviceInfo(deviceInfo);
                        mCecController.addLocalDevice(deviceType, localDevice);
                        mCecController.addLogicalAddress(logicalAddress);
                        allocatedDevices.add(localDevice);
                    }

                    // Address allocation completed for all devices. Notify each device.
                    if (allocatingDevices.size() == ++finished[0]) {
                        mAddressAllocated = true;
                        if (initiatedBy != INITIATED_BY_HOTPLUG) {
                            // In case of the hotplug we don't call onInitializeCecComplete()
                            // since we reallocate the logical address only.
                            onInitializeCecComplete(initiatedBy);
                        }
                        notifyAddressAllocated(allocatedDevices, initiatedBy);
                        mCecMessageBuffer.processMessages();
                    }
                }
            });
        }
    
private voidannounceHotplugEvent(int portId, boolean connected)

        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
        synchronized (mLock) {
            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
                invokeHotplugEventListenerLocked(record.mListener, event);
            }
        }
    
voidannounceSystemAudioModeChange(boolean enabled)

        synchronized (mLock) {
            for (SystemAudioModeChangeListenerRecord record :
                    mSystemAudioModeChangeListenerRecords) {
                invokeSystemAudioModeChangeLocked(record.mListener, enabled);
            }
        }
    
private voidassertRunOnServiceThread()

        if (Looper.myLooper() != mHandler.getLooper()) {
            throw new IllegalStateException("Should run on service thread.");
        }
    
private booleancanGoToStandby()

        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
            if (!device.canGoToStandby()) return false;
        }
        return true;
    
voidchangeInputForMhl(int portId, boolean contentOn)
Performs input change, routing control for MHL device.

param
portId MHL port, or the last port to go back to if {@code contentOn} is false
param
contentOn {@code true} if RAP data is content on; otherwise false

        assertRunOnServiceThread();
        if (tv() == null) return;
        final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
        if (portId != Constants.INVALID_PORT_ID) {
            tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
                @Override
                public void onComplete(int result) throws RemoteException {
                    // Keep the last input to switch back later when RAP[ContentOff] is received.
                    // This effectively sets the port to invalid one if the switching is for
                    // RAP[ContentOff].
                    setLastInputForMhl(lastInput);
                }
            });
        }
        // MHL device is always directly connected to the port. Update the active port ID to avoid
        // unnecessary post-routing control task.
        tv().setActivePortId(portId);

        // The port is either the MHL-enabled port where the mobile device is connected, or
        // the last port to go back to when turnoff command is received. Note that the last port
        // may not be the MHL-enabled one. In this case the device info to be passed to
        // input change listener should be the one describing the corresponding HDMI port.
        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
        HdmiDeviceInfo info = (device != null) ? device.getInfo()
                : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
        invokeInputChangeListener(info);
    
private intcheckPollStrategy(int pickStrategy)

        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
        if (strategy == 0) {
            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
        }
        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
        if (iterationStrategy == 0) {
            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
        }
        return strategy | iterationStrategy;
    
private voidclearLocalDevices()

        assertRunOnServiceThread();
        if (mCecController == null) {
            return;
        }
        mCecController.clearLogicalAddress();
        mCecController.clearLocalDevices();
    
private android.hardware.hdmi.HdmiDeviceInfocreateDeviceInfo(int logicalAddress, int deviceType, int powerStatus)

        // TODO: find better name instead of model name.
        String displayName = Build.MODEL;
        return new HdmiDeviceInfo(logicalAddress,
                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
                getVendorId(), displayName);
    
private voiddisableDevices(com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback callback)

        if (mCecController != null) {
            for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
                device.disableDevice(mStandbyMessageReceived, callback);
            }
        }

        mMhlController.clearAllLocalDevices();
    
private voiddisableHdmiControlService()

        disableDevices(new PendingActionClearedCallback() {
            @Override
            public void onCleared(HdmiCecLocalDevice device) {
                assertRunOnServiceThread();
                mCecController.flush(new Runnable() {
                    @Override
                    public void run() {
                        mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
                        mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
                        clearLocalDevices();
                    }
                });
            }
        });
    
private booleandispatchMessageToLocalDevice(HdmiCecMessage message)

        assertRunOnServiceThread();
        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
            if (device.dispatchMessage(message)
                    && message.getDestination() != Constants.ADDR_BROADCAST) {
                return true;
            }
        }

        if (message.getDestination() != Constants.ADDR_BROADCAST) {
            HdmiLogger.warning("Unhandled cec command:" + message);
        }
        return false;
    
voiddisplayOsd(int messageId)

        assertRunOnServiceThread();
        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
                HdmiControlService.PERMISSION);
    
voiddisplayOsd(int messageId, int extra)

        assertRunOnServiceThread();
        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
                HdmiControlService.PERMISSION);
    
private voidenableHdmiControlService()

        mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
        mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);

        initializeCec(INITIATED_BY_ENABLE_CEC);
    
private voidenforceAccessPermission()

        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
    
java.util.ListgetAllLocalDevices()

        assertRunOnServiceThread();
        return mCecController.getLocalDeviceList();
    
android.media.AudioManagergetAudioManager()

        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
    
intgetCecVersion()
Returns version of CEC.

        return mCecController.getVersion();
    
android.hardware.hdmi.HdmiDeviceInfogetDeviceInfo(int logicalAddress)

        assertRunOnServiceThread();
        return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
    
android.hardware.hdmi.HdmiDeviceInfogetDeviceInfoByPort(int port)

        assertRunOnServiceThread();
        HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
        if (info != null) {
            return info.getInfo();
        }
        return null;
    
private static java.util.ListgetIntList(java.lang.String string)

        ArrayList<Integer> list = new ArrayList<>();
        TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',");
        splitter.setString(string);
        for (String item : splitter) {
            try {
                list.add(Integer.parseInt(item));
            } catch (NumberFormatException e) {
                Slog.w(TAG, "Can't parseInt: " + item);
            }
        }
        return Collections.unmodifiableList(list);
    
android.os.LoopergetIoLooper()
Returns {@link Looper} for IO operation.

Declared as package-private.

        return mIoThread.getLooper();
    
java.lang.StringgetLanguage()

        assertRunOnServiceThread();
        return mLanguage;
    
intgetLastInputForMhl()

        assertRunOnServiceThread();
        return mLastInputMhl;
    
private java.util.ListgetMhlDevicesLocked()

        return mMhlDevices;
    
intgetPhysicalAddress()
Returns physical address of the device.

        return mCecController.getPhysicalAddress();
    
java.util.ListgetPortInfo()

        return mPortInfo;
    
android.hardware.hdmi.HdmiPortInfogetPortInfo(int portId)
Returns HDMI port information for the given port id.

param
portId HDMI port id
return
{@link HdmiPortInfo} for the given port

        return mPortInfoMap.get(portId, null);
    
android.os.PowerManagergetPowerManager()

        return mPowerManager;
    
intgetPowerStatus()

        assertRunOnServiceThread();
        return mPowerStatus;
    
java.lang.ObjectgetServiceLock()

        return mLock;
    
android.os.LoopergetServiceLooper()
Returns {@link Looper} of main thread. Use this {@link Looper} instance for tasks that are running on main service thread.

Declared as package-private.

        return mHandler.getLooper();
    
android.media.tv.TvInputManagergetTvInputManager()

        return mTvInputManager;
    
intgetVendorId()
Returns vendor id of CEC service.

        return mCecController.getVendorId();
    
booleanhandleCecCommand(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (!mAddressAllocated) {
            mCecMessageBuffer.bufferMessage(message);
            return true;
        }
        int errorCode = mMessageValidator.isValid(message);
        if (errorCode != HdmiCecMessageValidator.OK) {
            // We'll not response on the messages with the invalid source or destination
            // or with parameter length shorter than specified in the standard.
            if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
                maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
            }
            return true;
        }
        return dispatchMessageToLocalDevice(message);
    
voidhandleMhlBusModeChanged(int portId, int busmode)

        assertRunOnServiceThread();
        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
        if (device != null) {
            device.setBusMode(busmode);
        } else {
            Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
                    ", busmode:" + busmode + "]");
        }
    
voidhandleMhlBusOvercurrent(int portId, boolean on)

        assertRunOnServiceThread();
        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
        if (device != null) {
            device.onBusOvercurrentDetected(on);
        } else {
            Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
        }
    
voidhandleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId)

        assertRunOnServiceThread();
        HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);

        if (device != null) {
            device.setDeviceStatusChange(adopterId, deviceId);
        } else {
            Slog.w(TAG, "No mhl device exists for device status event[portId:"
                    + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
        }
    
voidhandleMhlHotplugEvent(int portId, boolean connected)

        assertRunOnServiceThread();
        // Hotplug event is used to add/remove MHL devices as TV input.
        if (connected) {
            HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
            HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
            if (oldDevice != null) {
                oldDevice.onDeviceRemoved();
                Slog.i(TAG, "Old device of port " + portId + " is removed");
            }
            invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
            updateSafeMhlInput();
        } else {
            HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
            if (device != null) {
                device.onDeviceRemoved();
                invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
                updateSafeMhlInput();
            } else {
                Slog.w(TAG, "No device to remove:[portId=" + portId);
            }
        }
        announceHotplugEvent(portId, connected);
    
private voidinitPortInfo()

        assertRunOnServiceThread();
        HdmiPortInfo[] cecPortInfo = null;

        // CEC HAL provides majority of the info while MHL does only MHL support flag for
        // each port. Return empty array if CEC HAL didn't provide the info.
        if (mCecController != null) {
            cecPortInfo = mCecController.getPortInfos();
        }
        if (cecPortInfo == null) {
            return;
        }

        SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
        SparseIntArray portIdMap = new SparseIntArray();
        SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
        for (HdmiPortInfo info : cecPortInfo) {
            portIdMap.put(info.getAddress(), info.getId());
            portInfoMap.put(info.getId(), info);
            portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
        }
        mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
        mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
        mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);

        HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
        ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
        for (HdmiPortInfo info : mhlPortInfo) {
            if (info.isMhlSupported()) {
                mhlSupportedPorts.add(info.getId());
            }
        }

        // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
        // cec port info if we do not have have port that supports MHL.
        if (mhlSupportedPorts.isEmpty()) {
            mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
            return;
        }
        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
        for (HdmiPortInfo info : cecPortInfo) {
            if (mhlSupportedPorts.contains(info.getId())) {
                result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
                        info.isCecSupported(), true, info.isArcSupported()));
            } else {
                result.add(info);
            }
        }
        mPortInfo = Collections.unmodifiableList(result);
    
private voidinitializeCec(int initiatedBy)

        mAddressAllocated = false;
        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
        mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(mLanguage));
        initializeLocalDevices(initiatedBy);
    
private voidinitializeLocalDevices(int initiatedBy)

        assertRunOnServiceThread();
        // A container for [Device type, Local device info].
        ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
        for (int type : mLocalDevices) {
            HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
            if (localDevice == null) {
                localDevice = HdmiCecLocalDevice.create(this, type);
            }
            localDevice.init();
            localDevices.add(localDevice);
        }
        // It's now safe to flush existing local devices from mCecController since they were
        // already moved to 'localDevices'.
        clearLocalDevices();
        allocateLogicalAddress(localDevices, initiatedBy);
    
private voidinvokeCallback(android.hardware.hdmi.IHdmiControlCallback callback, int result)

        try {
            callback.onComplete(result);
        } catch (RemoteException e) {
            Slog.e(TAG, "Invoking callback failed:" + e);
        }
    
voidinvokeClearTimerRecordingResult(int recorderAddress, int result)

        synchronized (mLock) {
            if (mRecordListenerRecord != null) {
                try {
                    mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
                            result);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
                }
            }
        }
    
voidinvokeDeviceEventListeners(android.hardware.hdmi.HdmiDeviceInfo device, int status)

        synchronized (mLock) {
            for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
                try {
                    record.mListener.onStatusChanged(device, status);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Failed to report device event:" + e);
                }
            }
        }
    
private voidinvokeHotplugEventListenerLocked(android.hardware.hdmi.IHdmiHotplugEventListener listener, android.hardware.hdmi.HdmiHotplugEvent event)

        try {
            listener.onReceived(event);
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
        }
    
voidinvokeInputChangeListener(android.hardware.hdmi.HdmiDeviceInfo info)

        synchronized (mLock) {
            if (mInputChangeListenerRecord != null) {
                try {
                    mInputChangeListenerRecord.mListener.onChanged(info);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
                }
            }
        }
    
voidinvokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data)

        synchronized (mLock) {
            for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
                try {
                    record.mListener.onReceived(portId, offest, length, data);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Failed to notify MHL vendor command", e);
                }
            }
        }
    
voidinvokeOneTouchRecordResult(int recorderAddress, int result)

        synchronized (mLock) {
            if (mRecordListenerRecord != null) {
                try {
                    mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
                }
            }
        }
    
byte[]invokeRecordRequestListener(int recorderAddress)

        synchronized (mLock) {
            if (mRecordListenerRecord != null) {
                try {
                    return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failed to start record.", e);
                }
            }
            return EmptyArray.BYTE;
        }
    
private voidinvokeSystemAudioModeChangeLocked(android.hardware.hdmi.IHdmiSystemAudioModeChangeListener listener, boolean enabled)

        try {
            listener.onStatusChanged(enabled);
        } catch (RemoteException e) {
            Slog.e(TAG, "Invoking callback failed:" + e);
        }
    
voidinvokeTimerRecordingResult(int recorderAddress, int result)

        synchronized (mLock) {
            if (mRecordListenerRecord != null) {
                try {
                    mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
                }
            }
        }
    
booleaninvokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason)

        synchronized (mLock) {
            if (mVendorCommandListenerRecords.isEmpty()) {
                return false;
            }
            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
                try {
                    record.mListener.onControlStateChanged(enabled, reason);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
                }
            }
            return true;
        }
    
booleaninvokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, byte[] params, boolean hasVendorId)

        synchronized (mLock) {
            if (mVendorCommandListenerRecords.isEmpty()) {
                return false;
            }
            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
                if (record.mDeviceType != deviceType) {
                    continue;
                }
                try {
                    record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Failed to notify vendor command reception", e);
                }
            }
            return true;
        }
    
booleanisConnectedToArcPort(int physicalAddress)
Whether a device of the specified physical address is connected to ARC enabled port.

        int portId = pathToPortId(physicalAddress);
        if (portId != Constants.INVALID_PORT_ID) {
            return mPortInfoMap.get(portId).isArcSupported();
        }
        return false;
    
booleanisControlEnabled()

        synchronized (mLock) {
            return mHdmiControlEnabled;
        }
    
booleanisMhlInputChangeEnabled()

        synchronized (mLock) {
            return mMhlInputChangeEnabled;
        }
    
booleanisPowerOnOrTransient()

        assertRunOnServiceThread();
        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
    
booleanisPowerStandby()

        assertRunOnServiceThread();
        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
    
booleanisPowerStandbyOrTransient()

        assertRunOnServiceThread();
        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
    
booleanisProhibitMode()

        synchronized (mLock) {
            return mProhibitMode;
        }
    
booleanisTvDevice()

        return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
    
booleanisTvDeviceEnabled()

        return isTvDevice() && tv() != null;
    
booleanisValidPortId(int portId)

        return getPortInfo(portId) != null;
    
voidmaySendFeatureAbortCommand(HdmiCecMessage command, int reason)
Send command on the given CEC message if possible. If the aborted message is invalid, then it wont send the message.

param
command original command to be aborted
param
reason reason of feature abort

        assertRunOnServiceThread();
        mCecController.maySendFeatureAbortCommand(command, reason);
    
private voidnotifyAddressAllocated(java.util.ArrayList devices, int initiatedBy)

        assertRunOnServiceThread();
        for (HdmiCecLocalDevice device : devices) {
            int address = device.getDeviceInfo().getLogicalAddress();
            device.handleAddressAllocated(address, initiatedBy);
        }
    
public voidonBootPhase(int phase)

        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            mTvInputManager = (TvInputManager) getContext().getSystemService(
                    Context.TV_INPUT_SERVICE);
            mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
        }
    
voidonHotplug(int portId, boolean connected)
Called when a new hotplug event is issued.

param
portId hdmi port number where hot plug event issued.
param
connected whether to be plugged in or not

        assertRunOnServiceThread();

        if (connected && !isTvDevice()) {
            ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
            for (int type : mLocalDevices) {
                HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
                if (localDevice == null) {
                    localDevice = HdmiCecLocalDevice.create(this, type);
                    localDevice.init();
                }
                localDevices.add(localDevice);
            }
            allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
        }

        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
            device.onHotplug(portId, connected);
        }
        announceHotplugEvent(portId, connected);
    
private voidonInitializeCecComplete(int initiatedBy)
Called when the initialization of local devices is complete.

        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
        }
        mWakeUpMessageReceived = false;

        if (isTvDeviceEnabled()) {
            mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
        }
        int reason = -1;
        switch (initiatedBy) {
            case INITIATED_BY_BOOT_UP:
                reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
                break;
            case INITIATED_BY_ENABLE_CEC:
                reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
                break;
            case INITIATED_BY_SCREEN_ON:
            case INITIATED_BY_WAKE_UP_MESSAGE:
                reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
                break;
        }
        if (reason != -1) {
            invokeVendorCommandListenersOnControlStateChanged(true, reason);
        }
    
private voidonLanguageChanged(java.lang.String language)

        assertRunOnServiceThread();
        mLanguage = language;

        if (isTvDeviceEnabled()) {
            tv().broadcastMenuLanguage(language);
            mCecController.setOption(OPTION_CEC_SET_LANGUAGE, HdmiUtils.languageToInt(language));
        }
    
private voidonStandby()

        assertRunOnServiceThread();
        if (!canGoToStandby()) return;
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
        invokeVendorCommandListenersOnControlStateChanged(false,
                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);

        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
        disableDevices(new PendingActionClearedCallback() {
            @Override
            public void onCleared(HdmiCecLocalDevice device) {
                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
                devices.remove(device);
                if (devices.isEmpty()) {
                    onStandbyCompleted();
                    // We will not clear local devices here, since some OEM/SOC will keep passing
                    // the received packets until the application processor enters to the sleep
                    // actually.
                }
            }
        });
    
private voidonStandbyCompleted()

        assertRunOnServiceThread();
        Slog.v(TAG, "onStandbyCompleted");

        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
            return;
        }
        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
            device.onStandby(mStandbyMessageReceived);
        }
        mStandbyMessageReceived = false;
        mAddressAllocated = false;
        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
        mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
    
public voidonStart()

        mIoThread.start();
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
        mProhibitMode = false;
        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
        mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);

        mCecController = HdmiCecController.create(this);
        if (mCecController != null) {
            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
            mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);

            // TODO: load value for mHdmiControlEnabled from preference.
            if (mHdmiControlEnabled) {
                initializeCec(INITIATED_BY_BOOT_UP);
            }
        } else {
            Slog.i(TAG, "Device does not support HDMI-CEC.");
            return;
        }

        mMhlController = HdmiMhlControllerStub.create(this);
        if (!mMhlController.isReady()) {
            Slog.i(TAG, "Device does not support MHL-control.");
        }
        mMhlDevices = Collections.emptyList();

        initPortInfo();
        mMessageValidator = new HdmiCecMessageValidator(this);
        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());

        if (mCecController != null) {
            // Register broadcast receiver for power state change.
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
            getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);

            // Register ContentObserver to monitor the settings change.
            registerContentObserver();
        }
        mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
    
private voidonWakeUp()

        assertRunOnServiceThread();
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
        if (mCecController != null) {
            if (mHdmiControlEnabled) {
                int startReason = INITIATED_BY_SCREEN_ON;
                if (mWakeUpMessageReceived) {
                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
                }
                initializeCec(startReason);
            }
        } else {
            Slog.i(TAG, "Device does not support HDMI-CEC.");
        }
        // TODO: Initialize MHL local devices.
    
private voidoneTouchPlay(android.hardware.hdmi.IHdmiControlCallback callback)

        assertRunOnServiceThread();
        HdmiCecLocalDevicePlayback source = playback();
        if (source == null) {
            Slog.w(TAG, "Local playback device not available");
            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
            return;
        }
        source.oneTouchPlay(callback);
    
intpathToPortId(int path)
Returns the id of HDMI port located at the top of the hierarchy of the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, the port id to be returned is the ID associated with the port address 0x1000 (1.0.0.0) which is the topmost path of the given routing path.

        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
        return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
    
private HdmiCecLocalDevicePlaybackplayback()

        return (HdmiCecLocalDevicePlayback)
                mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
    
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.

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
throw
IllegalArgumentException if {@code pickStrategy} is invalid value

        assertRunOnServiceThread();
        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
                retryCount);
    
intportIdToPath(int portId)
Returns the routing path (physical address) of the HDMI port for the given port id.

        HdmiPortInfo portInfo = getPortInfo(portId);
        if (portInfo == null) {
            Slog.e(TAG, "Cannot find the port info: " + portId);
            return Constants.INVALID_PHYSICAL_ADDRESS;
        }
        return portInfo.getAddress();
    
private voidqueryDisplayStatus(android.hardware.hdmi.IHdmiControlCallback callback)

        assertRunOnServiceThread();
        HdmiCecLocalDevicePlayback source = playback();
        if (source == null) {
            Slog.w(TAG, "Local playback device not available");
            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
            return;
        }
        source.queryDisplayStatus(callback);
    
booleanreadBooleanSetting(java.lang.String key, boolean defVal)

        ContentResolver cr = getContext().getContentResolver();
        return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
    
private voidregisterContentObserver()

        ContentResolver resolver = getContext().getContentResolver();
        String[] settings = new String[] {
                Global.HDMI_CONTROL_ENABLED,
                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                Global.MHL_INPUT_SWITCHING_ENABLED,
                Global.MHL_POWER_CHARGE_ENABLED
        };
        for (String s : settings) {
            resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
                    UserHandle.USER_ALL);
        }
    
voidregisterTvInputCallback(android.media.tv.TvInputManager.TvInputCallback callback)

        if (mTvInputManager == null) return;
        mTvInputManager.registerCallback(callback, mHandler);
    
private voidremoveHotplugEventListener(android.hardware.hdmi.IHdmiHotplugEventListener listener)

        synchronized (mLock) {
            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
                if (record.mListener.asBinder() == listener.asBinder()) {
                    listener.asBinder().unlinkToDeath(record, 0);
                    mHotplugEventListenerRecords.remove(record);
                    break;
                }
            }
        }
    
private voidremoveSystemAudioModeChangeListener(android.hardware.hdmi.IHdmiSystemAudioModeChangeListener listener)

        synchronized (mLock) {
            for (SystemAudioModeChangeListenerRecord record :
                    mSystemAudioModeChangeListenerRecords) {
                if (record.mListener.asBinder() == listener) {
                    listener.asBinder().unlinkToDeath(record, 0);
                    mSystemAudioModeChangeListenerRecords.remove(record);
                    break;
                }
            }
        }
    
voidrunOnServiceThread(java.lang.Runnable runnable)

        mHandler.post(runnable);
    
voidrunOnServiceThreadAtFrontOfQueue(java.lang.Runnable runnable)

        mHandler.postAtFrontOfQueue(runnable);
    
voidsendCecCommand(HdmiCecMessage command, com.android.server.hdmi.HdmiControlService$SendMessageCallback callback)
Transmit a CEC command to CEC bus.

param
command CEC command to send out
param
callback interface used to the result of send command

        assertRunOnServiceThread();
        if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
            mCecController.sendCommand(command, callback);
        } else {
            HdmiLogger.error("Invalid message type:" + command);
            if (callback != null) {
                callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
            }
        }
    
voidsendCecCommand(HdmiCecMessage command)

        assertRunOnServiceThread();
        sendCecCommand(command, null);
    
voidsetActivePortId(int portId)

        assertRunOnServiceThread();
        mActivePortId = portId;

        // Resets last input for MHL, which stays valid only after the MHL device was selected,
        // and no further switching is done.
        setLastInputForMhl(Constants.INVALID_PORT_ID);
    
voidsetAudioReturnChannel(int portId, boolean enabled)

        mCecController.setAudioReturnChannel(portId, enabled);
    
voidsetAudioStatus(boolean mute, int volume)

        AudioManager audioManager = getAudioManager();
        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
        if (mute) {
            if (!muted) {
                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
            }
        } else {
            if (muted) {
                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
            }
            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
            // volume change notification back to hdmi control service.
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
                    AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
        }
    
voidsetCecOption(int key, int value)

        assertRunOnServiceThread();
        mCecController.setOption(key, value);
    
voidsetControlEnabled(boolean enabled)

        assertRunOnServiceThread();

        synchronized (mLock) {
            mHdmiControlEnabled = enabled;
        }

        if (enabled) {
            enableHdmiControlService();
            return;
        }
        // Call the vendor handler before the service is disabled.
        invokeVendorCommandListenersOnControlStateChanged(false,
                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
        // Post the remained tasks in the service thread again to give the vendor-issued-tasks
        // a chance to run.
        runOnServiceThread(new Runnable() {
            @Override
            public void run() {
                disableHdmiControlService();
            }
        });
        return;
    
private voidsetHdmiRecordListener(android.hardware.hdmi.IHdmiRecordListener listener)

        synchronized (mLock) {
            mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
            try {
                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
            } catch (RemoteException e) {
                Slog.w(TAG, "Listener already died.", e);
            }
        }
    
private voidsetInputChangeListener(android.hardware.hdmi.IHdmiInputChangeListener listener)

        synchronized (mLock) {
            mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
            try {
                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
            } catch (RemoteException e) {
                Slog.w(TAG, "Listener already died");
                return;
            }
        }
    
voidsetLastInputForMhl(int portId)

        assertRunOnServiceThread();
        mLastInputMhl = portId;
    
voidsetMhlInputChangeEnabled(boolean enabled)

       mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));

        synchronized (mLock) {
            mMhlInputChangeEnabled = enabled;
        }
    
voidsetProhibitMode(boolean enabled)

        synchronized (mLock) {
            mProhibitMode = enabled;
        }
    
voidstandby()

        assertRunOnServiceThread();
        mStandbyMessageReceived = true;
        mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
        // the intent, the sequence will continue at onStandby().
    
private static inttoInt(boolean enabled)

        return enabled ? ENABLED : DISABLED;
    
private HdmiCecLocalDeviceTvtv()

        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
    
voidunregisterTvInputCallback(android.media.tv.TvInputManager.TvInputCallback callback)

        if (mTvInputManager == null) return;
        mTvInputManager.unregisterCallback(callback);
    
private voidupdateSafeMhlInput()

        assertRunOnServiceThread();
        List<HdmiDeviceInfo> inputs = Collections.emptyList();
        SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
        for (int i = 0; i < devices.size(); ++i) {
            HdmiMhlLocalDeviceStub device = devices.valueAt(i);
            HdmiDeviceInfo info = device.getInfo();
            if (info != null) {
                if (inputs.isEmpty()) {
                    inputs = new ArrayList<>();
                }
                inputs.add(device.getInfo());
            }
        }
        synchronized (mLock) {
            mMhlDevices = inputs;
        }
    
voidwakeUp()

        assertRunOnServiceThread();
        mWakeUpMessageReceived = true;
        mPowerManager.wakeUp(SystemClock.uptimeMillis());
        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
        // the intent, the sequence will continue at onWakeUp().
    
voidwriteBooleanSetting(java.lang.String key, boolean value)

        ContentResolver cr = getContext().getContentResolver();
        Global.putInt(cr, key, toInt(value));