Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7fa3a664 authored by Jinsuk Kim's avatar Jinsuk Kim
Browse files

CEC: Buffer messages coming from unknown device

Introduced a buffer for messages to be kept for later processing
when they arrive before the service is ready to handle them.
<Active Source>/<Initiate ARC> are handled in this CL to address
the reported bug.

Bug: 18277839

Change-Id: I1e73dd83dc9dd4a822fbcf4fda4c6221f09631cb
parent 73546e53
Loading
Loading
Loading
Loading
+133 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.hdmi;

import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.Slog;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Buffer storage to keep incoming messages for later processing. Used to
 * handle messages that arrive when the device is not ready. Useful when
 * keeping the messages from a connected device which are not discovered yet.
 */
final class DelayedMessageBuffer {
    private final ArrayList<HdmiCecMessage> mBuffer = new ArrayList<>();
    private final HdmiCecLocalDevice mDevice;

    DelayedMessageBuffer(HdmiCecLocalDevice device) {
        mDevice = device;
    }

    /**
     * Add a new message to the buffer. The buffer keeps selected messages in
     * the order they are received.
     *
     * @param message {@link HdmiCecMessage} to add
     */
    void add(HdmiCecMessage message) {
        boolean buffered = true;

        // Note that all the messages are not handled in the same manner.
        // For &lt;Active Source&gt; we keep the latest one only.
        // TODO: This might not be the best way to choose the active source.
        //       Devise a better way to pick up the best one.
        switch (message.getOpcode()) {
            case Constants.MESSAGE_ACTIVE_SOURCE:
                removeActiveSource();
                mBuffer.add(message);
                break;
            case Constants.MESSAGE_INITIATE_ARC:
                mBuffer.add(message);
                break;
            default:
                buffered = false;
                break;
        }
        if (buffered) {
            HdmiLogger.debug("Buffering message:" + message);
        }
    }

    private void removeActiveSource() {
        // Uses iterator to remove elements while looping through the list.
        for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) {
            HdmiCecMessage message = iter.next();
            if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
                iter.remove();
            }
        }
    }

    void processAllMessages() {
        for (HdmiCecMessage message : mBuffer) {
            mDevice.onMessage(message);
            HdmiLogger.debug("Processing message:" + message);
        }
        mBuffer.clear();
    }

    /**
     * Process messages from a given logical device. Called by
     * {@link NewDeviceAction} actions when they finish adding the device
     * information.
     * <p>&lt;Active Source&gt; is not processed in this method but processed
     * separately via {@link #processActiveSource()}.
     *
     * @param address logical address of CEC device which the messages to process
     *        are associated with
     */
    void processMessagesForDevice(int address) {
        HdmiLogger.debug("Processing message for address:" + address);
        for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) {
            HdmiCecMessage message = iter.next();
            if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
                continue;
            }
            if (message.getSource() == address) {
                mDevice.onMessage(message);
                HdmiLogger.debug("Processing message:" + message);
                iter.remove();
            }
        }
    }

    /**
     * Process &lt;Active Source&gt;.
     *
     * <p>The message has a dependency on TV input framework. Should be invoked
     * after we get the callback
     * {@link android.media.tv.TvInputManager.TvInputCallback#onInputAdded(String)}
     * to ensure the processing of the message takes effect when transformed
     * to input change callback.
     *
     * @param address logical address of the device to be the active source
     */
    void processActiveSource(int address) {
        for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) {
            HdmiCecMessage message = iter.next();
            if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE
                    && message.getSource() == address) {
                mDevice.onMessage(message);
                HdmiLogger.debug("Processing message:" + message);
                iter.remove();
            }
        }
    }
}
+2 −0
Original line number Original line Diff line number Diff line
@@ -326,6 +326,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
        Slog.v(TAG, "--------------------------------------------");
        Slog.v(TAG, "--------------------------------------------");
        mCallback.onDeviceDiscoveryDone(result);
        mCallback.onDeviceDiscoveryDone(result);
        finish();
        finish();
        // Process any commands buffered while device discovery action was in progress.
        tv().processAllDelayedMessages();
    }
    }


    private void checkAndProceedStage() {
    private void checkAndProceedStage() {
+55 −4
Original line number Original line Diff line number Diff line
@@ -30,6 +30,8 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANAL
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;


import android.annotation.Nullable;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiRecordSources;
import android.hardware.hdmi.HdmiRecordSources;
@@ -37,6 +39,9 @@ import android.hardware.hdmi.HdmiTimerRecordSources;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioManager;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.AudioSystem;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.SystemProperties;
import android.provider.Settings.Global;
import android.provider.Settings.Global;
@@ -49,6 +54,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
import com.android.server.SystemService;


import java.io.UnsupportedEncodingException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.ArrayList;
@@ -123,6 +129,26 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    // other CEC devices since they might not have logical address.
    // other CEC devices since they might not have logical address.
    private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
    private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();


    // Message buffer used to buffer selected messages to process later. <Active Source>
    // from a source device, for instance, needs to be buffered if the device is not
    // discovered yet. The buffered commands are taken out and when they are ready to
    // handle.
    private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);

    // Defines the callback invoked when TV input framework is updated with input status.
    // We are interested in the notification for HDMI input addition event, in order to
    // process any CEC commands that arrived before the input is added.
    private final TvInputCallback mTvInputCallback = new TvInputCallback() {
        @Override
        public void onInputAdded(String inputId) {
            TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
            HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
            if (info != null && info.isCecDevice()) {
                mDelayedMessageBuffer.processActiveSource(info.getLogicalAddress());
            }
        }
    };

    HdmiCecLocalDeviceTv(HdmiControlService service) {
    HdmiCecLocalDeviceTv(HdmiControlService service) {
        super(service, HdmiDeviceInfo.DEVICE_TV);
        super(service, HdmiDeviceInfo.DEVICE_TV);
        mPrevPortId = Constants.INVALID_PORT_ID;
        mPrevPortId = Constants.INVALID_PORT_ID;
@@ -136,6 +162,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    @ServiceThreadOnly
    @ServiceThreadOnly
    protected void onAddressAllocated(int logicalAddress, int reason) {
    protected void onAddressAllocated(int logicalAddress, int reason) {
        assertRunOnServiceThread();
        assertRunOnServiceThread();
        mService.registerTvInputCallback(mTvInputCallback);
        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
                mAddress, mService.getPhysicalAddress(), mDeviceType));
                mAddress, mService.getPhysicalAddress(), mDeviceType));
        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
@@ -407,7 +434,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
        if (info == null) {
        if (info == null) {
            handleNewDeviceAtTheTailOfActivePath(physicalAddress);
            if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
                mDelayedMessageBuffer.add(message);
            }
        } else {
        } else {
            ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
            ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
            ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
            ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
@@ -555,7 +584,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
                activeSource.physicalAddress, deviceType));
                activeSource.physicalAddress, deviceType));
    }
    }


    private void handleNewDeviceAtTheTailOfActivePath(int path) {
    private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
        // Seq #22
        // Seq #22
        if (isTailOfActivePath(path, getActivePath())) {
        if (isTailOfActivePath(path, getActivePath())) {
            removeAction(RoutingControlAction.class);
            removeAction(RoutingControlAction.class);
@@ -564,7 +593,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
            mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
            mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
                    mAddress, getActivePath(), newPath));
                    mAddress, getActivePath(), newPath));
            addAndStartAction(new RoutingControlAction(this, newPath, false, null));
            addAndStartAction(new RoutingControlAction(this, newPath, false, null));
            return true;
        }
        }
        return false;
    }
    }


    /**
    /**
@@ -706,8 +737,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    @ServiceThreadOnly
    @ServiceThreadOnly
    void onNewAvrAdded(HdmiDeviceInfo avr) {
    void onNewAvrAdded(HdmiDeviceInfo avr) {
        assertRunOnServiceThread();
        assertRunOnServiceThread();
        if (getSystemAudioModeSetting()) {
            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
            addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
        if (isArcFeatureEnabled()) {
        }
        if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
            startArcAction(true);
            startArcAction(true);
        }
        }
    }
    }
@@ -941,6 +974,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        assertRunOnServiceThread();
        assertRunOnServiceThread();


        if (!canStartArcUpdateAction(message.getSource(), true)) {
        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);
            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
            if (!isConnectedToArcPort(message.getSource())) {
            if (!isConnectedToArcPort(message.getSource())) {
                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
                displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
@@ -1436,6 +1474,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
        super.disableDevice(initiatedByCec, callback);
        super.disableDevice(initiatedByCec, callback);
        assertRunOnServiceThread();
        assertRunOnServiceThread();
        mService.unregisterTvInputCallback(mTvInputCallback);
        // Remove any repeated working actions.
        // Remove any repeated working actions.
        // HotplugDetectionAction will be reinstated during the wake up process.
        // HotplugDetectionAction will be reinstated during the wake up process.
        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
        // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
@@ -1714,6 +1753,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
        mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
    }
    }


    @ServiceThreadOnly
    void processAllDelayedMessages() {
        assertRunOnServiceThread();
        mDelayedMessageBuffer.processAllMessages();
    }

    @ServiceThreadOnly
    void processDelayedMessages(int address) {
        assertRunOnServiceThread();
        mDelayedMessageBuffer.processMessagesForDevice(address);
    }

    @Override
    @Override
    protected void dump(final IndentingPrintWriter pw) {
    protected void dump(final IndentingPrintWriter pw) {
        super.dump(pw);
        super.dump(pw);
+27 −0
Original line number Original line Diff line number Diff line
@@ -48,6 +48,8 @@ import android.hardware.hdmi.IHdmiRecordListener;
import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.media.AudioManager;
import android.media.AudioManager;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.net.Uri;
import android.net.Uri;
import android.os.Build;
import android.os.Build;
import android.os.Handler;
import android.os.Handler;
@@ -272,6 +274,9 @@ public final class HdmiControlService extends SystemService {
    @Nullable
    @Nullable
    private HdmiMhlControllerStub mMhlController;
    private HdmiMhlControllerStub mMhlController;


    @Nullable
    private TvInputManager mTvInputManager;

    // Last input port before switching to the MHL port. Should switch back to this port
    // Last input port before switching to the MHL port. Should switch back to this port
    // when the mobile device sends the request one touch play with off.
    // when the mobile device sends the request one touch play with off.
    // Gets invalidated if we go to other port/input.
    // Gets invalidated if we go to other port/input.
@@ -343,6 +348,28 @@ public final class HdmiControlService extends SystemService {
        }
        }
    }
    }


    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            mTvInputManager = (TvInputManager) getContext().getSystemService(
                    Context.TV_INPUT_SERVICE);
        }
    }

    TvInputManager getTvInputManager() {
        return mTvInputManager;
    }

    void registerTvInputCallback(TvInputCallback callback) {
        if (mTvInputManager == null) return;
        mTvInputManager.registerCallback(callback, mHandler);
    }

    void unregisterTvInputCallback(TvInputCallback callback) {
        if (mTvInputManager == null) return;
        mTvInputManager.unregisterCallback(callback);
    }

    /**
    /**
     * Called when the initialization of local devices is complete.
     * Called when the initialization of local devices is complete.
     */
     */
+3 −0
Original line number Original line Diff line number Diff line
@@ -161,6 +161,9 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
                mDeviceType, mVendorId, mDisplayName);
                mDeviceType, mVendorId, mDisplayName);
        tv().addCecDevice(deviceInfo);
        tv().addCecDevice(deviceInfo);


        // Consume CEC messages we already got for this newly found device.
        tv().processDelayedMessages(mDeviceLogicalAddress);

        if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
        if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
                == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
                == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
            tv().onNewAvrAdded(deviceInfo);
            tv().onNewAvrAdded(deviceInfo);