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

HdmiCecLocalDeviceTv

public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice
Represent a logical device of type TV residing in Android system.

Fields Summary
private static final String
TAG
private boolean
mArcEstablished
private boolean
mArcFeatureEnabled
private boolean
mSystemAudioActivated
private int
mPrevPortId
private int
mSystemAudioVolume
private boolean
mSystemAudioMute
private List
mSafeAllDeviceInfos
private List
mSafeExternalInputs
private final android.util.SparseArray
mDeviceInfos
private boolean
mAutoDeviceOff
private boolean
mAutoWakeup
private List
mLocalDeviceAddresses
private final HdmiCecStandbyModeHandler
mStandbyHandler
private boolean
mSkipRoutingControl
private final android.util.ArraySet
mCecSwitches
private final DelayedMessageBuffer
mDelayedMessageBuffer
private final android.media.tv.TvInputManager.TvInputCallback
mTvInputCallback
private final HashMap
mTvInputs
Constructors Summary
HdmiCecLocalDeviceTv(HdmiControlService service)

        super(service, HdmiDeviceInfo.DEVICE_TV);
        mPrevPortId = Constants.INVALID_PORT_ID;
        mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                true);
        mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
        mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
    
Methods Summary
final voidaddCecDevice(android.hardware.hdmi.HdmiDeviceInfo info)
Called when a device is newly added or a new device is detected or existing device is updated.

param
info device info of a new device.

        assertRunOnServiceThread();
        HdmiDeviceInfo old = addDeviceInfo(info);
        if (info.getLogicalAddress() == mAddress) {
            // The addition of TV device itself should not be notified.
            return;
        }
        if (old == null) {
            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
        } else if (!old.equals(info)) {
            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
        }
    
private android.hardware.hdmi.HdmiDeviceInfoaddDeviceInfo(android.hardware.hdmi.HdmiDeviceInfo deviceInfo)
Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same logical address as new device info's.

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

param
deviceInfo a new {@link HdmiDeviceInfo} to be added.
return
{@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} that has the same logical address as new one has.

        assertRunOnServiceThread();
        HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
        if (oldDeviceInfo != null) {
            removeDeviceInfo(deviceInfo.getId());
        }
        mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
        updateSafeDeviceInfoList();
        return oldDeviceInfo;
    
private voidaddTvInput(java.lang.String inputId, int deviceId)


    
          
        assertRunOnServiceThread();
        mTvInputs.put(inputId, deviceId);
    
voidannounceClearTimerRecordingResult(int recorderAddress, int result)

        mService.invokeClearTimerRecordingResult(recorderAddress, result);
    
voidannounceOneTouchRecordResult(int recorderAddress, int result)

        mService.invokeOneTouchRecordResult(recorderAddress, result);
    
voidannounceTimerRecordingResult(int recorderAddress, int result)

        mService.invokeTimerRecordingResult(recorderAddress, result);
    
booleanbroadcastMenuLanguage(java.lang.String language)

        assertRunOnServiceThread();
        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
                mAddress, language);
        if (command != null) {
            mService.sendCecCommand(command);
            return true;
        }
        return false;
    
private booleancanStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled)

        HdmiDeviceInfo avr = getAvrDeviceInfo();
        if (avr != null
                && (avrAddress == avr.getLogicalAddress())
                && isConnectedToArcPort(avr.getPhysicalAddress())
                && isDirectConnectAddress(avr.getPhysicalAddress())) {
            if (shouldCheckArcFeatureEnabled) {
                return isArcFeatureEnabled();
            } else {
                return true;
            }
        } else {
            return false;
        }
    
voidchangeArcFeatureEnabled(boolean enabled)

        assertRunOnServiceThread();

        if (mArcFeatureEnabled != enabled) {
            mArcFeatureEnabled = enabled;
            if (enabled) {
                if (!mArcEstablished) {
                    startArcAction(true);
                }
            } else {
                if (mArcEstablished) {
                    startArcAction(false);
                }
            }
        }
    
voidchangeMute(boolean mute)

        assertRunOnServiceThread();
        HdmiLogger.debug("[A]:Change mute:%b", mute);
        synchronized (mLock) {
            if (mSystemAudioMute == mute) {
                HdmiLogger.debug("No need to change mute.");
                return;
            }
        }
        if (!isSystemAudioActivated()) {
            HdmiLogger.debug("[A]:System audio is not activated.");
            return;
        }

        // Remove existing volume action.
        removeAction(VolumeControlAction.class);
        sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
                mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
                        HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
    
voidchangeSystemAudioMode(boolean enabled, android.hardware.hdmi.IHdmiControlCallback callback)

        assertRunOnServiceThread();
        if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
            setSystemAudioMode(false, true);
            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
            return;
        }
        HdmiDeviceInfo avr = getAvrDeviceInfo();
        if (avr == null) {
            setSystemAudioMode(false, true);
            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
            return;
        }

        addAndStartAction(
                new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
    
voidchangeVolume(int curVolume, int delta, int maxVolume)

        assertRunOnServiceThread();
        if (delta == 0 || !isSystemAudioActivated()) {
            return;
        }

        int targetVolume = curVolume + delta;
        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
        synchronized (mLock) {
            // If new volume is the same as current system audio volume, just ignore it.
            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
            if (cecVolume == mSystemAudioVolume) {
                // Update tv volume with system volume value.
                mService.setAudioStatus(false,
                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
                return;
            }
        }

        List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
        if (actions.isEmpty()) {
            addAndStartAction(new VolumeControlAction(this,
                    getAvrDeviceInfo().getLogicalAddress(), delta > 0));
        } else {
            actions.get(0).handleVolumeChange(delta > 0);
        }
    
private booleancheckRecordSource(byte[] recordSource)

        return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
    
private booleancheckRecorder(int recorderAddress)

        HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
        return (device != null)
                && (HdmiUtils.getTypeFromAddress(recorderAddress)
                        == HdmiDeviceInfo.DEVICE_RECORDER);
    
private booleancheckTimerRecordingSource(int sourceType, byte[] recordSource)

        return (recordSource != null)
                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
    
private voidclearDeviceInfoList()

        assertRunOnServiceThread();
        for (HdmiDeviceInfo info : mSafeExternalInputs) {
            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
        }
        mDeviceInfos.clear();
        updateSafeDeviceInfoList();
    
voidclearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)

        assertRunOnServiceThread();
        if (!mService.isControlEnabled()) {
            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
            announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
            return;
        }

        if (!checkRecorder(recorderAddress)) {
            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
            announceClearTimerRecordingResult(recorderAddress,
                    CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
            return;
        }

        if (!checkTimerRecordingSource(sourceType, recordSource)) {
            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
            announceClearTimerRecordingResult(recorderAddress,
                    CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
            return;
        }

        sendClearTimerMessage(recorderAddress, sourceType, recordSource);
    
voiddeviceSelect(int id, android.hardware.hdmi.IHdmiControlCallback callback)
Performs the action 'device select', or 'one touch play' initiated by TV.

param
id id of HDMI device to select
param
callback callback object to report the result with

        assertRunOnServiceThread();
        HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
        if (targetDevice == null) {
            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
            return;
        }
        int targetAddress = targetDevice.getLogicalAddress();
        ActiveSource active = getActiveSource();
        if (active.isValid() && targetAddress == active.logicalAddress) {
            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
            return;
        }
        if (targetAddress == Constants.ADDR_INTERNAL) {
            handleSelectInternalSource();
            // Switching to internal source is always successful even when CEC control is disabled.
            setActiveSource(targetAddress, mService.getPhysicalAddress());
            setActivePath(mService.getPhysicalAddress());
            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
            return;
        }
        if (!mService.isControlEnabled()) {
            setActiveSource(targetDevice);
            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
            return;
        }
        removeAction(DeviceSelectAction.class);
        addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
    
private voiddisableArcIfExist()

        assertRunOnServiceThread();
        HdmiDeviceInfo avr = getAvrDeviceInfo();
        if (avr == null) {
            return;
        }

        // Seq #44.
        removeAction(RequestArcInitiationAction.class);
        if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
            addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
        }
    
protected voiddisableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)

        super.disableDevice(initiatedByCec, callback);
        assertRunOnServiceThread();
        mService.unregisterTvInputCallback(mTvInputCallback);
        // Remove any repeated working actions.
        // HotplugDetectionAction will be reinstated during the wake up process.
        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
        //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
        removeAction(DeviceDiscoveryAction.class);
        removeAction(HotplugDetectionAction.class);
        removeAction(PowerStatusMonitorAction.class);
        // Remove recording actions.
        removeAction(OneTouchRecordAction.class);
        removeAction(TimerRecordingAction.class);

        disableSystemAudioIfExist();
        disableArcIfExist();
        clearDeviceInfoList();
        checkIfPendingActionsCleared();
    
private voiddisableSystemAudioIfExist()

        assertRunOnServiceThread();
        if (getAvrDeviceInfo() == null) {
            return;
        }

        // Seq #31.
        removeAction(SystemAudioActionFromAvr.class);
        removeAction(SystemAudioActionFromTv.class);
        removeAction(SystemAudioAutoInitiationAction.class);
        removeAction(SystemAudioStatusAction.class);
        removeAction(VolumeControlAction.class);

        // Turn off the mode but do not write it the settings, so that the next time TV powers on
        // the system audio mode setting can be restored automatically.
        setSystemAudioMode(false, false);
    
booleandispatchMessage(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) {
            return true;
        }
        return super.onMessage(message);
    
voiddisplayOsd(int messageId)

        assertRunOnServiceThread();
        mService.displayOsd(messageId);
    
voiddisplayOsd(int messageId, int extra)

        assertRunOnServiceThread();
        mService.displayOsd(messageId, extra);
    
voiddoManualPortSwitching(int portId, android.hardware.hdmi.IHdmiControlCallback callback)

        assertRunOnServiceThread();
        // Seq #20
        if (!mService.isValidPortId(portId)) {
            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
            return;
        }
        if (portId == getActivePortId()) {
            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
            return;
        }
        mActiveSource.invalidate();
        if (!mService.isControlEnabled()) {
            setActivePortId(portId);
            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
            return;
        }
        int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
                ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
        setActivePath(oldPath);
        if (mSkipRoutingControl) {
            mSkipRoutingControl = false;
            return;
        }
        int newPath = mService.portIdToPath(portId);
        startRoutingControl(oldPath, newPath, true, callback);
    
protected voiddump(com.android.internal.util.IndentingPrintWriter pw)

        super.dump(pw);
        pw.println("mArcEstablished: " + mArcEstablished);
        pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
        pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
        pw.println("mSystemAudioMute: " + mSystemAudioMute);
        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
        pw.println("mAutoWakeup: " + mAutoWakeup);
        pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
        pw.println("mPrevPortId: " + mPrevPortId);
        pw.println("CEC devices:");
        pw.increaseIndent();
        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
            pw.println(info);
        }
        pw.decreaseIndent();
    
private intfindKeyReceiverAddress()

        if (getActiveSource().isValid()) {
            return getActiveSource().logicalAddress;
        }
        HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
        if (info != null) {
            return info.getLogicalAddress();
        }
        return Constants.ADDR_INVALID;
    
booleangetAutoWakeup()

        assertRunOnServiceThread();
        return mAutoWakeup;
    
android.hardware.hdmi.HdmiDeviceInfogetAvrDeviceInfo()

        assertRunOnServiceThread();
        return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
    
android.hardware.hdmi.HdmiDeviceInfogetCecDeviceInfo(int logicalAddress)
Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.

param
logicalAddress logical address of the device to be retrieved
return
{@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. Returns null if no logical address matched

        assertRunOnServiceThread();
        return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
    
final android.hardware.hdmi.HdmiDeviceInfogetDeviceInfoByPath(int path)
Returns the {@link HdmiDeviceInfo} instance whose physical address matches the given routing path. CEC devices use routing path for its physical address to describe the hierarchy of the devices in the network.

param
path routing path or physical address
return
{@link HdmiDeviceInfo} if the matched info is found; otherwise null

        assertRunOnServiceThread();
        for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
            if (info.getPhysicalAddress() == path) {
                return info;
            }
        }
        return null;
    
java.util.ListgetDeviceInfoList(boolean includeLocalDevice)
Return a list of all {@link HdmiDeviceInfo}.

Declared as package-private. accessed by {@link HdmiControlService} only. This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which does not include local device.

        assertRunOnServiceThread();
        if (includeLocalDevice) {
            return HdmiUtils.sparseArrayToList(mDeviceInfos);
        } else {
            ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
            for (int i = 0; i < mDeviceInfos.size(); ++i) {
                HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
                if (!isLocalDeviceAddress(info.getLogicalAddress())) {
                    infoList.add(info);
                }
            }
            return infoList;
        }
    
private java.util.ListgetInputDevices()
Return a list of external cec input (source) devices.

Note that this effectively excludes non-source devices like system audio, secondary TV.

        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
        for (int i = 0; i < mDeviceInfos.size(); ++i) {
            HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
            if (isLocalDeviceAddress(info.getLogicalAddress())) {
                continue;
            }
            if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
                infoList.add(info);
            }
        }
        return infoList;
    
intgetPortId(int physicalAddress)

        return mService.pathToPortId(physicalAddress);
    
intgetPowerStatus()

        assertRunOnServiceThread();
        return mService.getPowerStatus();
    
protected intgetPreferredAddress()

        return Constants.ADDR_TV;
    
intgetPrevPortId()
Returns the previous port id kept to handle input switching on .

        synchronized (mLock) {
            return mPrevPortId;
        }
    
android.hardware.hdmi.HdmiDeviceInfogetSafeAvrDeviceInfo()

        return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
    
android.hardware.hdmi.HdmiDeviceInfogetSafeCecDeviceInfo(int logicalAddress)
Thread safe version of {@link #getCecDeviceInfo(int)}.

param
logicalAddress logical address to be retrieved
return
{@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. Returns null if no logical address matched

        synchronized (mLock) {
            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
                if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
                    return info;
                }
            }
            return null;
        }
    
java.util.ListgetSafeCecDevicesLocked()

        ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
        for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
            if (isLocalDeviceAddress(info.getLogicalAddress())) {
                continue;
            }
            infoList.add(info);
        }
        return infoList;
    
android.hardware.hdmi.HdmiDeviceInfogetSafeDeviceInfoByPath(int path)
Returns the {@link HdmiDeviceInfo} instance whose physical address matches the given routing path. This is the version accessible safely from threads other than service thread.

param
path routing path or physical address
return
{@link HdmiDeviceInfo} if the matched info is found; otherwise null

        synchronized (mLock) {
            for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
                if (info.getPhysicalAddress() == path) {
                    return info;
                }
            }
            return null;
        }
    
java.util.ListgetSafeExternalInputsLocked()
Return external input devices.

        return mSafeExternalInputs;
    
booleangetSystemAudioModeSetting()

        return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
    
protected booleanhandleActiveSource(HdmiCecMessage message)

        assertRunOnServiceThread();
        int logicalAddress = message.getSource();
        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
        if (info == null) {
            if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
                HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
                mDelayedMessageBuffer.add(message);
            }
        } else if (!isInputReady(info.getId())) {
            HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
            mDelayedMessageBuffer.add(message);
        } else {
            ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
            ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
        }
        return true;
    
protected booleanhandleGetMenuLanguage(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (!broadcastMenuLanguage(mService.getLanguage())) {
            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
        }
        return true;
    
protected booleanhandleImageViewOn(HdmiCecMessage message)

        assertRunOnServiceThread();
        // Currently, it's the same as <Text View On>.
        return handleTextViewOn(message);
    
protected booleanhandleInactiveSource(HdmiCecMessage message)

        assertRunOnServiceThread();
        // Seq #10

        // Ignore <Inactive Source> from non-active source device.
        if (getActiveSource().logicalAddress != message.getSource()) {
            return true;
        }
        if (isProhibitMode()) {
            return true;
        }
        int portId = getPrevPortId();
        if (portId != Constants.INVALID_PORT_ID) {
            // TODO: Do this only if TV is not showing multiview like PIP/PAP.

            HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
            if (inactiveSource == null) {
                return true;
            }
            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
                return true;
            }
            // TODO: Switch the TV freeze mode off

            doManualPortSwitching(portId, null);
            setPrevPortId(Constants.INVALID_PORT_ID);
        } else {
            // No HDMI port to switch to was found. Notify the input change listers to
            // switch to the lastly shown internal input.
            mActiveSource.invalidate();
            setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
            mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
        }
        return true;
    
protected booleanhandleInitiateArc(HdmiCecMessage message)

        assertRunOnServiceThread();

        if (!canStartArcUpdateAction(message.getSource(), true)) {
            if (getAvrDeviceInfo() == null) {
                // AVR may not have been discovered yet. Delay the message processing.
                mDelayedMessageBuffer.add(message);
                return true;
            }
            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
            if (!isConnectedToArcPort(message.getSource())) {
                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
            }
            return true;
        }

        // In case where <Initiate Arc> is started by <Request ARC Initiation>
        // need to clean up RequestArcInitiationAction.
        removeAction(RequestArcInitiationAction.class);
        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
                message.getSource(), true);
        addAndStartAction(action);
        return true;
    
protected booleanhandleMenuStatus(HdmiCecMessage message)

        // Do nothing and just return true not to prevent from responding <Feature Abort>.
        return true;
    
private booleanhandleNewDeviceAtTheTailOfActivePath(int path)

        // Seq #22
        if (isTailOfActivePath(path, getActivePath())) {
            int newPath = mService.portIdToPath(getActivePortId());
            setActivePath(newPath);
            startRoutingControl(getActivePath(), newPath, false, null);
            return true;
        }
        return false;
    
protected booleanhandleRecordStatus(HdmiCecMessage message)

        // Do nothing.
        return true;
    
protected booleanhandleRecordTvScreen(HdmiCecMessage message)

        List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
        if (!actions.isEmpty()) {
            // Assumes only one OneTouchRecordAction.
            OneTouchRecordAction action = actions.get(0);
            if (action.getRecorderAddress() != message.getSource()) {
                announceOneTouchRecordResult(
                        message.getSource(),
                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
            }
            return super.handleRecordTvScreen(message);
        }

        int recorderAddress = message.getSource();
        byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
        int reason = startOneTouchRecord(recorderAddress, recordSource);
        if (reason != Constants.ABORT_NO_ERROR) {
            mService.maySendFeatureAbortCommand(message, reason);
        }
        return true;
    
voidhandleRemoveActiveRoutingPath(int path)

        assertRunOnServiceThread();
        // Seq #23
        if (isTailOfActivePath(path, getActivePath())) {
            int newPath = mService.portIdToPath(getActivePortId());
            startRoutingControl(getActivePath(), newPath, true, null);
        }
    
protected booleanhandleReportAudioStatus(HdmiCecMessage message)

        assertRunOnServiceThread();

        byte params[] = message.getParams();
        int mute = params[0] & 0x80;
        int volume = params[0] & 0x7F;
        setAudioStatus(mute == 0x80, volume);
        return true;
    
protected booleanhandleReportPhysicalAddress(HdmiCecMessage message)

        assertRunOnServiceThread();
        int path = HdmiUtils.twoBytesToInt(message.getParams());
        int address = message.getSource();
        int type = message.getParams()[2];

        if (updateCecSwitchInfo(address, type, path)) return true;

        // Ignore if [Device Discovery Action] is going on.
        if (hasAction(DeviceDiscoveryAction.class)) {
            Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
            return true;
        }

        if (!isInDeviceList(address, path)) {
            handleNewDeviceAtTheTailOfActivePath(path);
        }

        // Add the device ahead with default information to handle <Active Source>
        // promptly, rather than waiting till the new device action is finished.
        HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
        addCecDevice(deviceInfo);
        startNewDeviceAction(ActiveSource.of(address, path), type);
        return true;
    
protected booleanhandleReportPowerStatus(HdmiCecMessage command)

        int newStatus = command.getParams()[0] & 0xFF;
        updateDevicePowerStatus(command.getSource(), newStatus);
        return true;
    
protected booleanhandleRequestActiveSource(HdmiCecMessage message)

        assertRunOnServiceThread();
        // Seq #19
        if (mAddress == getActiveSource().logicalAddress) {
            mService.sendCecCommand(
                    HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
        }
        return true;
    
protected booleanhandleRoutingChange(HdmiCecMessage message)

        assertRunOnServiceThread();
        // Seq #21
        byte[] params = message.getParams();
        int currentPath = HdmiUtils.twoBytesToInt(params);
        if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
            mActiveSource.invalidate();
            removeAction(RoutingControlAction.class);
            int newPath = HdmiUtils.twoBytesToInt(params, 2);
            addAndStartAction(new RoutingControlAction(this, newPath, true, null));
        }
        return true;
    
private voidhandleSelectInternalSource()

        assertRunOnServiceThread();
        // Seq #18
        if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
            updateActiveSource(mAddress, mService.getPhysicalAddress());
            if (mSkipRoutingControl) {
                mSkipRoutingControl = false;
                return;
            }
            HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
                    mAddress, mService.getPhysicalAddress());
            mService.sendCecCommand(activeSource);
        }
    
protected booleanhandleSetOsdName(HdmiCecMessage message)

        int source = message.getSource();
        HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
        // If the device is not in device list, ignore it.
        if (deviceInfo == null) {
            Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
            return true;
        }
        String osdName = null;
        try {
            osdName = new String(message.getParams(), "US-ASCII");
        } catch (UnsupportedEncodingException e) {
            Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
            return true;
        }

        if (deviceInfo.getDisplayName().equals(osdName)) {
            Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
            return true;
        }

        addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
        return true;
    
protected booleanhandleSetSystemAudioMode(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (!isMessageForSystemAudio(message)) {
            if (getAvrDeviceInfo() == null) {
                // AVR may not have been discovered yet. Delay the message processing.
                mDelayedMessageBuffer.add(message);
                return true;
            }
            HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
            return true;
        }
        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
                message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
        addAndStartAction(action);
        return true;
    
protected booleanhandleSystemAudioModeStatus(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (!isMessageForSystemAudio(message)) {
            HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
            // Ignore this message.
            return true;
        }
        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
        return true;
    
protected booleanhandleTerminateArc(HdmiCecMessage message)

        assertRunOnServiceThread();
        // In cast of termination, do not check ARC configuration in that AVR device
        // might be removed already.

        // In case where <Terminate Arc> is started by <Request ARC Termination>
        // need to clean up RequestArcInitiationAction.
        removeAction(RequestArcTerminationAction.class);
        SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
                message.getSource(), false);
        addAndStartAction(action);
        return true;
    
protected booleanhandleTextViewOn(HdmiCecMessage message)

        assertRunOnServiceThread();
        if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
            mService.wakeUp();
        }
        return true;
    
protected booleanhandleTimerClearedStatus(HdmiCecMessage message)

        byte[] params = message.getParams();
        int timerClearedStatusData = params[0] & 0xFF;
        announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
        return true;
    
protected booleanhandleTimerStatus(HdmiCecMessage message)

        // Do nothing.
        return true;
    
booleanhasSystemAudioDevice()

        return getSafeAvrDeviceInfo() != null;
    
private booleanhideDevicesBehindLegacySwitch(android.hardware.hdmi.HdmiDeviceInfo info)

        return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
                && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
    
private java.util.ListinitLocalDeviceAddresses()

        assertRunOnServiceThread();
        List<Integer> addresses = new ArrayList<>();
        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
            addresses.add(device.getDeviceInfo().getLogicalAddress());
        }
        return Collections.unmodifiableList(addresses);
    
private static voidinvokeCallback(android.hardware.hdmi.IHdmiControlCallback callback, int result)

        if (callback == null) {
            return;
        }
        try {
            callback.onComplete(result);
        } catch (RemoteException e) {
            Slog.e(TAG, "Invoking callback failed:" + e);
        }
    
private voidinvokeDeviceEventListener(android.hardware.hdmi.HdmiDeviceInfo info, int status)

        if (!hideDevicesBehindLegacySwitch(info)) {
            mService.invokeDeviceEventListeners(info, status);
        }
    
booleanisArcEstabilished()
Returns whether ARC is enabled or not.

        assertRunOnServiceThread();
        return mArcFeatureEnabled && mArcEstablished;
    
booleanisArcFeatureEnabled()

        assertRunOnServiceThread();
        return mArcFeatureEnabled;
    
private static booleanisConnectedToCecSwitch(int path, java.util.Collection switches)

        for (int switchPath : switches) {
            if (isParentPath(switchPath, path)) {
                return true;
            }
        }
        return false;
    
private booleanisDirectConnectAddress(int physicalAddress)

        return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
    
booleanisInDeviceList(int logicalAddress, int physicalAddress)
Whether a device of the specified physical address and logical address exists in a device info list. However, both are minimal condition and it could be different device from the original one.

param
logicalAddress logical address of a device to be searched
param
physicalAddress physical address of a device to be searched
return
true if exist; otherwise false

        assertRunOnServiceThread();
        HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
        if (device == null) {
            return false;
        }
        return device.getPhysicalAddress() == physicalAddress;
    
protected booleanisInputReady(int deviceId)

        assertRunOnServiceThread();
        return mTvInputs.containsValue(deviceId);
    
private booleanisLocalDeviceAddress(int address)

        return mLocalDeviceAddresses.contains(address);
    
private booleanisMessageForSystemAudio(HdmiCecMessage message)

        return mService.isControlEnabled()
                && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
                && (message.getDestination() == Constants.ADDR_TV
                        || message.getDestination() == Constants.ADDR_BROADCAST)
                && getAvrDeviceInfo() != null;
    
private static booleanisParentPath(int parentPath, int childPath)

        // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
        // If child's last non-zero nibble is removed, the result equals to the parent.
        for (int i = 0; i <= 12; i += 4) {
            int nibble = (childPath >> i) & 0xF;
            if (nibble != 0) {
                int parentNibble = (parentPath >> i) & 0xF;
                return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
            }
        }
        return false;
    
booleanisPowerStandbyOrTransient()

        return mService.isPowerStandbyOrTransient();
    
booleanisProhibitMode()

        return mService.isProhibitMode();
    
booleanisSystemAudioActivated()

        if (!hasSystemAudioDevice()) {
            return false;
        }
        synchronized (mLock) {
            return mSystemAudioActivated;
        }
    
static booleanisTailOfActivePath(int path, int activePath)
Whether the given path is located in the tail of current active path.

param
path to be tested
param
activePath current active path
return
true if the given path is located in the tail of current active path; otherwise, false

        // If active routing path is internal source, return false.
        if (activePath == 0) {
            return false;
        }
        for (int i = 12; i >= 0; i -= 4) {
            int curActivePath = (activePath >> i) & 0xF;
            if (curActivePath == 0) {
                return true;
            } else {
                int curPath = (path >> i) & 0xF;
                if (curPath != curActivePath) {
                    return false;
                }
            }
        }
        return false;
    
private voidlaunchDeviceDiscovery()

        assertRunOnServiceThread();
        clearDeviceInfoList();
        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
                new DeviceDiscoveryCallback() {
                    @Override
                    public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
                        for (HdmiDeviceInfo info : deviceInfos) {
                            addCecDevice(info);
                        }

                        // Since we removed all devices when it's start and
                        // device discovery action does not poll local devices,
                        // we should put device info of local device manually here
                        for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
                            addCecDevice(device.getDeviceInfo());
                        }

                        addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
                        addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));

                        // If there is AVR, initiate System Audio Auto initiation action,
                        // which turns on and off system audio according to last system
                        // audio setting.
                        HdmiDeviceInfo avr = getAvrDeviceInfo();
                        if (avr != null) {
                            onNewAvrAdded(avr);
                        }
                    }
                });
        addAndStartAction(action);
    
voidlaunchRoutingControl(boolean routingForBootup)
Launch routing control process.

param
routingForBootup true if routing control is initiated due to One Touch Play or TV power on

        assertRunOnServiceThread();
        // Seq #24
        if (getActivePortId() != Constants.INVALID_PORT_ID) {
            if (!routingForBootup && !isProhibitMode()) {
                int newPath = mService.portIdToPath(getActivePortId());
                setActivePath(newPath);
                startRoutingControl(getActivePath(), newPath, routingForBootup, null);
            }
        } else {
            int activePath = mService.getPhysicalAddress();
            setActivePath(activePath);
            if (!routingForBootup
                    && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
                mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
                        activePath));
            }
        }
    
private voidnotifyArcStatusToAudioService(boolean enabled)

        // Note that we don't set any name to ARC.
        mService.getAudioManager().setWiredDeviceConnectionState(
                AudioSystem.DEVICE_OUT_HDMI_ARC,
                enabled ? 1 : 0, "");
    
protected voidonAddressAllocated(int logicalAddress, int reason)

        assertRunOnServiceThread();
        mService.registerTvInputCallback(mTvInputCallback);
        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                mAddress, mService.getPhysicalAddress(), mDeviceType));
        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
                mAddress, mService.getVendorId()));
        mCecSwitches.add(mService.getPhysicalAddress());  // TV is a CEC switch too.
        mTvInputs.clear();
        mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
        launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
                reason != HdmiControlService.INITIATED_BY_BOOT_UP);
        mLocalDeviceAddresses = initLocalDeviceAddresses();
        launchDeviceDiscovery();
        startQueuedActions();
    
voidonHotplug(int portId, boolean connected)

        assertRunOnServiceThread();

        if (!connected) {
            removeCecSwitches(portId);
        }
        // Tv device will have permanent HotplugDetectionAction.
        List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
        if (!hotplugActions.isEmpty()) {
            // Note that hotplug action is single action running on a machine.
            // "pollAllDevicesNow" cleans up timer and start poll action immediately.
            // It covers seq #40, #43.
            hotplugActions.get(0).pollAllDevicesNow();
        }
        updateArcFeatureStatus(portId, connected);
    
voidonNewAvrAdded(android.hardware.hdmi.HdmiDeviceInfo avr)

        assertRunOnServiceThread();
        if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
        }
        if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
            startArcAction(true);
        }
    
protected voidonStandby(boolean initiatedByCec)

        assertRunOnServiceThread();
        // Seq #11
        if (!mService.isControlEnabled()) {
            return;
        }
        if (!initiatedByCec && mAutoDeviceOff) {
            mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
                    mAddress, Constants.ADDR_BROADCAST));
        }
    
voidprocessAllDelayedMessages()

        assertRunOnServiceThread();
        mDelayedMessageBuffer.processAllMessages();
    
voidprocessDelayedActiveSource(int address)

        assertRunOnServiceThread();
        mDelayedMessageBuffer.processActiveSource(address);
    
voidprocessDelayedMessages(int address)

        assertRunOnServiceThread();
        mDelayedMessageBuffer.processMessagesForDevice(address);
    
final voidremoveCecDevice(int address)
Called when a device is removed or removal of device is detected.

param
address a logical address of a device to be removed

        assertRunOnServiceThread();
        HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));

        mCecMessageCache.flushMessagesFrom(address);
        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
    
private voidremoveCecSwitches(int portId)

        Iterator<Integer> it = mCecSwitches.iterator();
        while (!it.hasNext()) {
            int path = it.next();
            if (pathToPortId(path) == portId) {
                it.remove();
            }
        }
    
private android.hardware.hdmi.HdmiDeviceInforemoveDeviceInfo(int id)
Remove a device info corresponding to the given {@code logicalAddress}. It returns removed {@link HdmiDeviceInfo} if exists.

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

param
id id of device to be removed
return
removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}

        assertRunOnServiceThread();
        HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
        if (deviceInfo != null) {
            mDeviceInfos.remove(id);
        }
        updateSafeDeviceInfoList();
        return deviceInfo;
    
private voidremoveTvInput(java.lang.String inputId)

        assertRunOnServiceThread();
        mTvInputs.remove(inputId);
    
private voidsendClearTimerMessage(int recorderAddress, int sourceType, byte[] recordSource)

        HdmiCecMessage message = null;
        switch (sourceType) {
            case TIMER_RECORDING_TYPE_DIGITAL:
                message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
                        recordSource);
                break;
            case TIMER_RECORDING_TYPE_ANALOGUE:
                message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
                        recordSource);
                break;
            case TIMER_RECORDING_TYPE_EXTERNAL:
                message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
                        recordSource);
                break;
            default:
                Slog.w(TAG, "Invalid source type:" + recorderAddress);
                announceClearTimerRecordingResult(recorderAddress,
                        CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
                return;

        }
        mService.sendCecCommand(message, new SendMessageCallback() {
            @Override
            public void onSendCompleted(int error) {
                if (error != Constants.SEND_RESULT_SUCCESS) {
                    announceClearTimerRecordingResult(recorderAddress,
                            CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
                }
            }
        });
    
protected voidsendKeyEvent(int keyCode, boolean isPressed)
Sends key to a target CEC device.

param
keyCode key code to send. Defined in {@link android.view.KeyEvent}.
param
isPressed true if this is key press event

        assertRunOnServiceThread();
        if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
            Slog.w(TAG, "Unsupported key: " + keyCode);
            return;
        }
        List<SendKeyAction> action = getActions(SendKeyAction.class);
        int logicalAddress = findKeyReceiverAddress();
        if (logicalAddress == mAddress) {
            Slog.w(TAG, "Discard key event to itself :" + keyCode + " pressed:" + isPressed);
            return;
        }
        if (!action.isEmpty()) {
            action.get(0).processKeyEvent(keyCode, isPressed);
        } else {
            if (isPressed) {
                if (logicalAddress != Constants.ADDR_INVALID) {
                    addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
                    return;
                }
            }
            Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
        }
    
protected voidsendStandby(int deviceId)

        HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
        if (targetDevice == null) {
            return;
        }
        int targetAddress = targetDevice.getLogicalAddress();
        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
    
booleansetArcStatus(boolean enabled)
Change ARC status into the given {@code enabled} status.

return
{@code true} if ARC was in "Enabled" status

        assertRunOnServiceThread();

        HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
        boolean oldStatus = mArcEstablished;
        // 1. Enable/disable ARC circuit.
        mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled);
        // 2. Notify arc status to audio service.
        notifyArcStatusToAudioService(enabled);
        // 3. Update arc status;
        mArcEstablished = enabled;
        return oldStatus;
    
voidsetAudioStatus(boolean mute, int volume)

        synchronized (mLock) {
            mSystemAudioMute = mute;
            mSystemAudioVolume = volume;
            int maxVolume = mService.getAudioManager().getStreamMaxVolume(
                    AudioManager.STREAM_MUSIC);
            mService.setAudioStatus(mute,
                    VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
            displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
                    mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
        }
    
voidsetAutoDeviceOff(boolean enabled)

        assertRunOnServiceThread();
        mAutoDeviceOff = enabled;
    
voidsetAutoWakeup(boolean enabled)

        assertRunOnServiceThread();
        mAutoWakeup = enabled;
    
protected voidsetPreferredAddress(int addr)

        Slog.w(TAG, "Preferred addres will not be stored for TV");
    
voidsetPrevPortId(int portId)
Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be taken for .

        synchronized (mLock) {
            mPrevPortId = portId;
        }
    
voidsetSystemAudioMode(boolean on, boolean updateSetting)

        HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);

        if (updateSetting) {
            mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
        }
        updateAudioManagerForSystemAudio(on);
        synchronized (mLock) {
            if (mSystemAudioActivated != on) {
                mSystemAudioActivated = on;
                mService.announceSystemAudioModeChange(on);
            }
        }
    
voidstartArcAction(boolean enabled)

        assertRunOnServiceThread();
        HdmiDeviceInfo info = getAvrDeviceInfo();
        if (info == null) {
            Slog.w(TAG, "Failed to start arc action; No AVR device.");
            return;
        }
        if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
            Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
            if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
            }
            return;
        }

        // Terminate opposite action and start action if not exist.
        if (enabled) {
            removeAction(RequestArcTerminationAction.class);
            if (!hasAction(RequestArcInitiationAction.class)) {
                addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
            }
        } else {
            removeAction(RequestArcInitiationAction.class);
            if (!hasAction(RequestArcTerminationAction.class)) {
                addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
            }
        }
    
voidstartNewDeviceAction(ActiveSource activeSource, int deviceType)

        for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
            // If there is new device action which has the same logical address and path
            // ignore new request.
            // NewDeviceAction is created whenever it receives <Report Physical Address>.
            // And there is a chance starting NewDeviceAction for the same source.
            // Usually, new device sends <Report Physical Address> when it's plugged
            // in. However, TV can detect a new device from HotPlugDetectionAction,
            // which sends <Give Physical Address> to the source for newly detected
            // device.
            if (action.isActionOf(activeSource)) {
                return;
            }
        }

        addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
                activeSource.physicalAddress, deviceType));
    
intstartOneTouchRecord(int recorderAddress, byte[] recordSource)

        assertRunOnServiceThread();
        if (!mService.isControlEnabled()) {
            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
            return Constants.ABORT_NOT_IN_CORRECT_MODE;
        }

        if (!checkRecorder(recorderAddress)) {
            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
            announceOneTouchRecordResult(recorderAddress,
                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
            return Constants.ABORT_NOT_IN_CORRECT_MODE;
        }

        if (!checkRecordSource(recordSource)) {
            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
            announceOneTouchRecordResult(recorderAddress,
                    ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
            return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
        }

        addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
        Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
                + Arrays.toString(recordSource));
        return Constants.ABORT_NO_ERROR;
    
voidstartRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus, android.hardware.hdmi.IHdmiControlCallback callback)

        assertRunOnServiceThread();
        if (oldPath == newPath) {
            return;
        }
        HdmiCecMessage routingChange =
                HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
        mService.sendCecCommand(routingChange);
        removeAction(RoutingControlAction.class);
        addAndStartAction(
                new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
    
voidstartTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)

        assertRunOnServiceThread();
        if (!mService.isControlEnabled()) {
            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
            announceTimerRecordingResult(recorderAddress,
                    TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
            return;
        }

        if (!checkRecorder(recorderAddress)) {
            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
            announceTimerRecordingResult(recorderAddress,
                    TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
            return;
        }

        if (!checkTimerRecordingSource(sourceType, recordSource)) {
            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
            announceTimerRecordingResult(
                    recorderAddress,
                    TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
            return;
        }

        addAndStartAction(
                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
    
voidstopOneTouchRecord(int recorderAddress)

        assertRunOnServiceThread();
        if (!mService.isControlEnabled()) {
            Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
            announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
            return;
        }

        if (!checkRecorder(recorderAddress)) {
            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
            announceOneTouchRecordResult(recorderAddress,
                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
            return;
        }

        // Remove one touch record action so that other one touch record can be started.
        removeAction(OneTouchRecordAction.class);
        mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
        Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
    
voidupdateActiveInput(int path, boolean notifyInputChange)

        assertRunOnServiceThread();
        // Seq #15
        setPrevPortId(getActivePortId());
        setActivePath(path);
        // TODO: Handle PAP/PIP case.
        // Show OSD port change banner
        if (notifyInputChange) {
            ActiveSource activeSource = getActiveSource();
            HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
            if (info == null) {
                info = mService.getDeviceInfoByPort(getActivePortId());
                if (info == null) {
                    // No CEC/MHL device is present at the port. Attempt to switch to
                    // the hardware port itself for non-CEC devices that may be connected.
                    info = new HdmiDeviceInfo(path, getActivePortId());
                }
            }
            mService.invokeInputChangeListener(info);
        }
    
voidupdateActiveSource(int logicalAddress, int physicalAddress)

        assertRunOnServiceThread();
        updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
    
voidupdateActiveSource(ActiveSource newActive)

        assertRunOnServiceThread();
        // Seq #14
        if (mActiveSource.equals(newActive)) {
            return;
        }
        setActiveSource(newActive);
        int logicalAddress = newActive.logicalAddress;
        if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
            if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
                setPrevPortId(getActivePortId());
            }
            // TODO: Show the OSD banner related to the new active source device.
        } else {
            // TODO: If displayed, remove the OSD banner related to the previous
            //       active source device.
        }
    
private voidupdateArcFeatureStatus(int portId, boolean isConnected)

        assertRunOnServiceThread();
        // HEAC 2.4, HEACT 5-15
        // Should not activate ARC if +5V status is false.
        HdmiPortInfo portInfo = mService.getPortInfo(portId);
        if (portInfo.isArcSupported()) {
            changeArcFeatureEnabled(isConnected);
        }
    
private voidupdateAudioManagerForSystemAudio(boolean on)

        int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
        HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
    
booleanupdateCecSwitchInfo(int address, int type, int path)

        if (address == Constants.ADDR_UNREGISTERED
                && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
            mCecSwitches.add(path);
            updateSafeDeviceInfoList();
            return true;  // Pure switch does not need further processing. Return here.
        }
        if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
            mCecSwitches.add(path);
        }
        return false;
    
voidupdateDevicePowerStatus(int logicalAddress, int newPowerStatus)

        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
        if (info == null) {
            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
            return;
        }

        if (info.getDevicePowerStatus() == newPowerStatus) {
            return;
        }

        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
        // addDeviceInfo replaces old device info with new one if exists.
        addDeviceInfo(newInfo);

        invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
    
private voidupdateSafeDeviceInfoList()

        assertRunOnServiceThread();
        List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
        List<HdmiDeviceInfo> externalInputs = getInputDevices();
        synchronized (mLock) {
            mSafeAllDeviceInfos = copiedDevices;
            mSafeExternalInputs = externalInputs;
        }