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

Commit 23181eb8 authored by Yan Han's avatar Yan Han
Browse files

Add CEC feature tracking to HdmiDeviceInfo

HdmiDeviceInfo now contains an object for tracking features
whose support is communicated in the [Device Features] operand.

Bug: 204854610
Test: atest DeviceFeaturesTest
Change-Id: Id2e19fb2e4480b5ed88529fd15012c7abad5d3a7
parent 8bd2ec2f
Loading
Loading
Loading
Loading
+358 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.hardware.hdmi;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;

/**
 * Immutable class that stores support status for features in the [Device Features] operand.
 * Each feature may be supported, be not supported, or have an unknown support status.
 *
 * @hide
 */
public class DeviceFeatures {

    @IntDef({
            FEATURE_NOT_SUPPORTED,
            FEATURE_SUPPORTED,
            FEATURE_SUPPORT_UNKNOWN
    })
    public @interface FeatureSupportStatus {};

    public static final int FEATURE_NOT_SUPPORTED = 0;
    public static final int FEATURE_SUPPORTED = 1;
    public static final int FEATURE_SUPPORT_UNKNOWN = 2;

    /**
     * Instance representing no knowledge of any feature's support.
     */
    @NonNull
    public static final DeviceFeatures ALL_FEATURES_SUPPORT_UNKNOWN =
            new Builder(FEATURE_SUPPORT_UNKNOWN).build();

    /**
     * Instance representing no support for any feature.
     */
    @NonNull
    public static final DeviceFeatures NO_FEATURES_SUPPORTED =
            new Builder(FEATURE_NOT_SUPPORTED).build();

    @FeatureSupportStatus private final int mRecordTvScreenSupport;
    @FeatureSupportStatus private final int mSetOsdStringSupport;
    @FeatureSupportStatus private final int mDeckControlSupport;
    @FeatureSupportStatus private final int mSetAudioRateSupport;
    @FeatureSupportStatus private final int mArcTxSupport;
    @FeatureSupportStatus private final int mArcRxSupport;
    @FeatureSupportStatus private final int mSetAudioVolumeLevelSupport;

    private DeviceFeatures(@NonNull Builder builder) {
        this.mRecordTvScreenSupport = builder.mRecordTvScreenSupport;
        this.mSetOsdStringSupport = builder.mOsdStringSupport;
        this.mDeckControlSupport = builder.mDeckControlSupport;
        this.mSetAudioRateSupport = builder.mSetAudioRateSupport;
        this.mArcTxSupport = builder.mArcTxSupport;
        this.mArcRxSupport = builder.mArcRxSupport;
        this.mSetAudioVolumeLevelSupport = builder.mSetAudioVolumeLevelSupport;
    }

    /**
     * Converts an instance to a builder.
     */
    public Builder toBuilder() {
        return new Builder(this);
    }

    /**
     * Constructs an instance from a [Device Features] operand.
     *
     * Bit 7 of [Device Features] is currently ignored. It indicates whether the operand spans more
     * than one byte, but only the first byte contains information as of CEC 2.0.
     *
     * @param deviceFeaturesOperand The [Device Features] operand to parse.
     * @return Instance representing the [Device Features] operand.
     */
    @NonNull
    public static DeviceFeatures fromOperand(@NonNull byte[] deviceFeaturesOperand) {
        Builder builder = new Builder(FEATURE_SUPPORT_UNKNOWN);

        // Read feature support status from the bits of [Device Features]
        if (deviceFeaturesOperand.length >= 1) {
            byte b = deviceFeaturesOperand[0];
            builder
                    .setRecordTvScreenSupport(bitToFeatureSupportStatus(((b >> 6) & 1) == 1))
                    .setSetOsdStringSupport(bitToFeatureSupportStatus(((b >> 5) & 1) == 1))
                    .setDeckControlSupport(bitToFeatureSupportStatus(((b >> 4) & 1) == 1))
                    .setSetAudioRateSupport(bitToFeatureSupportStatus(((b >> 3) & 1) == 1))
                    .setArcTxSupport(bitToFeatureSupportStatus(((b >> 2) & 1) == 1))
                    .setArcRxSupport(bitToFeatureSupportStatus(((b >> 1) & 1) == 1))
                    .setSetAudioVolumeLevelSupport(bitToFeatureSupportStatus((b & 1) == 1));
        }
        return builder.build();
    }

    /**
     * Returns the [Device Features] operand corresponding to this instance.
     * {@link #FEATURE_SUPPORT_UNKNOWN} maps to 0, indicating no support.
     *
     * As of CEC 2.0, the returned byte array will always be of length 1.
     */
    @NonNull
    public byte[] toOperand() {
        byte result = 0;

        if (mRecordTvScreenSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 6);
        if (mSetOsdStringSupport == FEATURE_SUPPORTED) result = (byte) (1 << 5);
        if (mDeckControlSupport == FEATURE_SUPPORTED) result = (byte) (1 << 4);
        if (mSetAudioRateSupport == FEATURE_SUPPORTED) result = (byte) (1 << 3);
        if (mArcTxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 2);
        if (mArcRxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 1);
        if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result = (byte) 1;

        return new byte[]{ result };
    }

    @FeatureSupportStatus
    private static int bitToFeatureSupportStatus(boolean bit) {
        return bit ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED;
    }

    /**
     * Returns whether the device is a TV that supports <Record TV Screen>.
     */
    @FeatureSupportStatus
    public int getRecordTvScreenSupport() {
        return mRecordTvScreenSupport;
    }

    /**
     * Returns whether the device is a TV that supports <Set OSD String>.
     */
    @FeatureSupportStatus
    public int getSetOsdStringSupport() {
        return mSetOsdStringSupport;
    }

    /**
     * Returns whether the device supports being controlled by Deck Control.
     */
    @FeatureSupportStatus
    public int getDeckControlSupport() {
        return mDeckControlSupport;
    }

    /**
     * Returns whether the device is a Source that supports <Set Audio Rate>.
     */
    @FeatureSupportStatus
    public int getSetAudioRateSupport() {
        return mSetAudioRateSupport;
    }

    /**
     * Returns whether the device is a Sink that supports ARC Tx.
     */
    @FeatureSupportStatus
    public int getArcTxSupport() {
        return mArcTxSupport;
    }

    /**
     * Returns whether the device is a Source that supports ARC Rx.
     */
    @FeatureSupportStatus
    public int getArcRxSupport() {
        return mArcRxSupport;
    }

    /**
     * Returns whether the device supports <Set Audio Volume Level>.
     */
    @FeatureSupportStatus
    public int getSetAudioVolumeLevelSupport() {
        return mSetAudioVolumeLevelSupport;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (!(obj instanceof DeviceFeatures)) {
            return false;
        }

        DeviceFeatures other = (DeviceFeatures) obj;
        return mRecordTvScreenSupport == other.mRecordTvScreenSupport
                && mSetOsdStringSupport == other.mSetOsdStringSupport
                && mDeckControlSupport == other.mDeckControlSupport
                && mSetAudioRateSupport == other.mSetAudioRateSupport
                && mArcTxSupport == other.mArcTxSupport
                && mArcRxSupport == other.mArcRxSupport
                && mSetAudioVolumeLevelSupport == other.mSetAudioVolumeLevelSupport;
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(
                mRecordTvScreenSupport,
                mSetOsdStringSupport,
                mDeckControlSupport,
                mSetAudioRateSupport,
                mArcTxSupport,
                mArcRxSupport,
                mSetAudioVolumeLevelSupport
        );
    }

    @NonNull
    @Override
    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append("Device features: ");
        s.append("record_tv_screen: ")
                .append(featureSupportStatusToString(mRecordTvScreenSupport)).append(" ");
        s.append("set_osd_string: ")
                .append(featureSupportStatusToString(mSetOsdStringSupport)).append(" ");
        s.append("deck_control: ")
                .append(featureSupportStatusToString(mDeckControlSupport)).append(" ");
        s.append("set_audio_rate: ")
                .append(featureSupportStatusToString(mSetAudioRateSupport)).append(" ");
        s.append("arc_tx: ")
                .append(featureSupportStatusToString(mArcTxSupport)).append(" ");
        s.append("arc_rx: ")
                .append(featureSupportStatusToString(mArcRxSupport)).append(" ");
        s.append("set_audio_volume_level: ")
                .append(featureSupportStatusToString(mSetAudioVolumeLevelSupport)).append(" ");
        return s.toString();
    }

    @NonNull
    private static String featureSupportStatusToString(@FeatureSupportStatus int status) {
        switch (status) {
            case FEATURE_SUPPORTED:
                return "Y";
            case FEATURE_NOT_SUPPORTED:
                return "N";
            case FEATURE_SUPPORT_UNKNOWN:
            default:
                return "?";
        }
    }

    /**
     * Builder for {@link DeviceFeatures} instances.
     */
    public static final class Builder {
        @FeatureSupportStatus private int mRecordTvScreenSupport;
        @FeatureSupportStatus private int mOsdStringSupport;
        @FeatureSupportStatus private int mDeckControlSupport;
        @FeatureSupportStatus private int mSetAudioRateSupport;
        @FeatureSupportStatus private int mArcTxSupport;
        @FeatureSupportStatus private int mArcRxSupport;
        @FeatureSupportStatus private int mSetAudioVolumeLevelSupport;

        private Builder(@FeatureSupportStatus int defaultFeatureSupportStatus) {
            mRecordTvScreenSupport = defaultFeatureSupportStatus;
            mOsdStringSupport = defaultFeatureSupportStatus;
            mDeckControlSupport = defaultFeatureSupportStatus;
            mSetAudioRateSupport = defaultFeatureSupportStatus;
            mArcTxSupport = defaultFeatureSupportStatus;
            mArcRxSupport = defaultFeatureSupportStatus;
            mSetAudioVolumeLevelSupport = defaultFeatureSupportStatus;
        }

        private Builder(DeviceFeatures info) {
            mRecordTvScreenSupport = info.getRecordTvScreenSupport();
            mOsdStringSupport = info.getSetOsdStringSupport();
            mDeckControlSupport = info.getDeckControlSupport();
            mSetAudioRateSupport = info.getSetAudioRateSupport();
            mArcTxSupport = info.getArcTxSupport();
            mArcRxSupport = info.getArcRxSupport();
            mSetAudioVolumeLevelSupport = info.getSetAudioVolumeLevelSupport();
        }

        /**
         * Creates a new {@link DeviceFeatures} object.
         */
        public DeviceFeatures build() {
            return new DeviceFeatures(this);
        }

        /**
         * Sets the value for {@link #getRecordTvScreenSupport()}.
         */
        @NonNull
        public Builder setRecordTvScreenSupport(@FeatureSupportStatus int recordTvScreenSupport) {
            mRecordTvScreenSupport = recordTvScreenSupport;
            return this;
        }

        /**
         * Sets the value for {@link #getSetOsdStringSupport()}.
         */
        @NonNull
        public Builder setSetOsdStringSupport(@FeatureSupportStatus int setOsdStringSupport) {
            mOsdStringSupport = setOsdStringSupport;
            return this;
        }

        /**
         * Sets the value for {@link #getDeckControlSupport()}.
         */
        @NonNull
        public Builder setDeckControlSupport(@FeatureSupportStatus int deckControlSupport) {
            mDeckControlSupport = deckControlSupport;
            return this;
        }

        /**
         * Sets the value for {@link #getSetAudioRateSupport()}.
         */
        @NonNull
        public Builder setSetAudioRateSupport(@FeatureSupportStatus int setAudioRateSupport) {
            mSetAudioRateSupport = setAudioRateSupport;
            return this;
        }

        /**
         * Sets the value for {@link #getArcTxSupport()}.
         */
        @NonNull
        public Builder setArcTxSupport(@FeatureSupportStatus int arcTxSupport) {
            mArcTxSupport = arcTxSupport;
            return this;
        }

        /**
         * Sets the value for {@link #getArcRxSupport()}.
         */
        @NonNull
        public Builder setArcRxSupport(@FeatureSupportStatus int arcRxSupport) {
            mArcRxSupport = arcRxSupport;
            return this;
        }

        /**
         * Sets the value for {@link #getSetAudioRateSupport()}.
         */
        @NonNull
        public Builder setSetAudioVolumeLevelSupport(
                @FeatureSupportStatus int setAudioVolumeLevelSupport) {
            mSetAudioVolumeLevelSupport = setAudioVolumeLevelSupport;
            return this;
        }
    }
}
+33 −0
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ public class HdmiDeviceInfo implements Parcelable {
    private final int mVendorId;
    private final String mDisplayName;
    private final int mDevicePowerStatus;
    private final DeviceFeatures mDeviceFeatures;

    // MHL only parameters.
    private final int mDeviceId;
@@ -198,6 +199,7 @@ public class HdmiDeviceInfo implements Parcelable {
        mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
        mDisplayName = "Inactive";
        mVendorId = 0;
        mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN;

        mDeviceId = -1;
        mAdopterId = -1;
@@ -222,6 +224,7 @@ public class HdmiDeviceInfo implements Parcelable {
        this.mVendorId = builder.mVendorId;
        this.mDisplayName = builder.mDisplayName;
        this.mDevicePowerStatus = builder.mDevicePowerStatus;
        this.mDeviceFeatures = builder.mDeviceFeatures;
        this.mDeviceId = builder.mDeviceId;
        this.mAdopterId = builder.mAdopterId;

@@ -294,6 +297,15 @@ public class HdmiDeviceInfo implements Parcelable {
        return mId;
    }

    /**
     * Returns the CEC features that this device supports.
     *
     * @hide
     */
    public DeviceFeatures getDeviceFeatures() {
        return mDeviceFeatures;
    }

    /**
     * Returns the id to be used for CEC device.
     *
@@ -521,6 +533,11 @@ public class HdmiDeviceInfo implements Parcelable {
        s.append("physical_address: ").append(String.format("0x%04X", mPhysicalAddress));
        s.append(" ");
        s.append("port_id: ").append(mPortId);

        if (mHdmiDeviceType == HDMI_DEVICE_TYPE_CEC) {
            s.append("\n  ").append(mDeviceFeatures.toString());
        }

        return s.toString();
    }

@@ -581,6 +598,7 @@ public class HdmiDeviceInfo implements Parcelable {
        private int mVendorId = VENDOR_ID_UNKNOWN;
        private String mDisplayName = "";
        private int mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
        private DeviceFeatures mDeviceFeatures;

        // MHL parameters
        private int mDeviceId = -1;
@@ -588,6 +606,11 @@ public class HdmiDeviceInfo implements Parcelable {

        private Builder(int hdmiDeviceType) {
            mHdmiDeviceType = hdmiDeviceType;
            if (hdmiDeviceType == HDMI_DEVICE_TYPE_CEC) {
                mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN;
            } else {
                mDeviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED;
            }
        }

        private Builder(@NonNull HdmiDeviceInfo hdmiDeviceInfo) {
@@ -602,6 +625,7 @@ public class HdmiDeviceInfo implements Parcelable {
            mDevicePowerStatus = hdmiDeviceInfo.mDevicePowerStatus;
            mDeviceId = hdmiDeviceInfo.mDeviceId;
            mAdopterId = hdmiDeviceInfo.mAdopterId;
            mDeviceFeatures = hdmiDeviceInfo.mDeviceFeatures;
        }

        /**
@@ -684,6 +708,15 @@ public class HdmiDeviceInfo implements Parcelable {
            return this;
        }

        /**
         * Sets the value for {@link #getDeviceFeatures()}.
         */
        @NonNull
        public Builder setDeviceFeatures(DeviceFeatures deviceFeatures) {
            this.mDeviceFeatures = deviceFeatures;
            return this;
        }

        /**
         * Sets the value for {@link #getDeviceId()}.
         */
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.hardware.hdmi;

import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;

import static com.google.common.truth.Truth.assertThat;

import androidx.test.filters.SmallTest;

import com.google.common.testing.EqualsTester;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for {@link DeviceFeatures} */
@RunWith(JUnit4.class)
@SmallTest
public class DeviceFeaturesTest {

    @Test
    public void testEquals() {
        new EqualsTester()
                .addEqualityGroup(DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN)
                .addEqualityGroup(DeviceFeatures.NO_FEATURES_SUPPORTED)
                .addEqualityGroup(
                        DeviceFeatures.fromOperand(
                                new byte[]{(byte) 0b0111_0000}),
                        DeviceFeatures.fromOperand(
                                new byte[]{(byte) 0b1111_0000}),
                        DeviceFeatures.fromOperand(
                                new byte[]{(byte) 0b1111_0000, (byte) 0b0101_0101}),
                        DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder()
                                .setRecordTvScreenSupport(FEATURE_SUPPORTED)
                                .setSetOsdStringSupport(FEATURE_SUPPORTED)
                                .setDeckControlSupport(FEATURE_SUPPORTED)
                                .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED)
                                .setArcTxSupport(FEATURE_NOT_SUPPORTED)
                                .setArcRxSupport(FEATURE_NOT_SUPPORTED)
                                .setSetAudioVolumeLevelSupport(FEATURE_NOT_SUPPORTED)
                                .build()
                )
                .testEquals();
    }

    @Test
    public void testDeviceFeaturesOperandConversion() {
        DeviceFeatures info = DeviceFeatures.fromOperand(
                new byte[]{(byte) 0b0111_0000});

        assertThat(info.getRecordTvScreenSupport()).isEqualTo(FEATURE_SUPPORTED);
        assertThat(info.getSetOsdStringSupport()).isEqualTo(FEATURE_SUPPORTED);
        assertThat(info.getDeckControlSupport()).isEqualTo(FEATURE_SUPPORTED);
        assertThat(info.getSetAudioRateSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
        assertThat(info.getArcTxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
        assertThat(info.getArcRxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);
        assertThat(info.getSetAudioVolumeLevelSupport()).isEqualTo(FEATURE_NOT_SUPPORTED);

        assertThat(info.toOperand()).isEqualTo(new byte[]{(byte) 0b0111_0000});
    }
}