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

Commit 595a183f authored by Shubang's avatar Shubang Committed by Nick Chalko
Browse files

Add SystemAudioInitiationActionFromAvr

To handle initiating the System Audio Control feature from an Amplifier
See CEC 13.15 for details.

Bug: 80297602
Test: make; local tests
Change-Id: I4772b6878bc1da816eea6c8e8b423c330315b1a8
parent 0fd41e32
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -256,6 +256,10 @@ abstract class HdmiCecFeatureAction {
        return (HdmiCecLocalDeviceTv) mSource;
    }

    protected final HdmiCecLocalDeviceAudioSystem audioSystem() {
        return (HdmiCecLocalDeviceAudioSystem) mSource;
    }

    protected final int getSourceAddress() {
        return mSource.getDeviceInfo().getLogicalAddress();
    }
+6 −1
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.hdmi;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioManager;
import android.os.SystemProperties;
import android.provider.Settings.Global;
import com.android.internal.annotations.GuardedBy;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;

@@ -252,6 +251,12 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
        }
    }

    protected boolean isSystemAudioActivated() {
        synchronized (mLock) {
            return mSystemAudioActivated;
        }
    }

    /** Reports if System Audio Mode is supported by the connected TV */
    interface TvSystemAudioModeSupportedCallback {

+10 −0
Original line number Diff line number Diff line
@@ -276,6 +276,16 @@ public class HdmiCecMessageBuilder {
        return buildCommand(src, dest, Constants.MESSAGE_TEXT_VIEW_ON);
    }

    /**
     * Build <Request Active Source> command.
     *
     * @param src source address of command
     * @return newly created {@link HdmiCecMessage}
     */
    static HdmiCecMessage buildRequestActiveSource(int src) {
        return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE);
    }

    /**
     * Build <Active Source> command.
     *
+99 −7
Original line number Diff line number Diff line
@@ -13,13 +13,23 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.hdmi;

import android.util.Log;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import com.android.internal.annotations.VisibleForTesting;

/**
 * Feature action that handles System Audio Mode initiated by AVR devices.
 */
public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction {
    private static final String TAG = "SystemAudioInitFromAvr";

    // State that waits for <Active Source> once send <Request Active Source>.
    private static final int STATE_WAITING_FOR_ACTIVE_SOURCE = 1;
    @VisibleForTesting
    static final int MAX_RETRY_COUNT = 5;

    private int mSendRequestActiveSourceRetryCount = 0;
    private int mSendSetSystemAudioModeRetryCount = 0;

    SystemAudioInitiationActionFromAvr(HdmiCecLocalDevice source) {
        super(source);
@@ -27,18 +37,100 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction {

    @Override
    boolean start() {
        Log.i(TAG, "start");
        return false;
        if (audioSystem().mActiveSource.physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) {
            mState = STATE_WAITING_FOR_ACTIVE_SOURCE;
            addTimer(mState, HdmiConfig.TIMEOUT_MS);
            sendRequestActiveSource();
        } else {
            queryTvSystemAudioModeSupport();
        }
        return true;
    }

    @Override
    boolean processCommand(HdmiCecMessage cmd) {
        Log.i(TAG, "processCommand. cmd = " + cmd);
        switch (cmd.getOpcode()) {
            case Constants.MESSAGE_ACTIVE_SOURCE:
                // received <Active Source>
                if (mState != STATE_WAITING_FOR_ACTIVE_SOURCE) {
                    return false;
                }
                mActionTimer.clearTimerMessage();
                int physicalAddress = HdmiUtils.twoBytesToInt(cmd.getParams());
                if (physicalAddress != getSourcePath()) {
                    audioSystem().setActiveSource(cmd.getSource(), physicalAddress);
                }
                queryTvSystemAudioModeSupport();
                return true;
        }
        return false;
    }

    @Override
    void handleTimerEvent(int state) {
        Log.i(TAG, "handleTimerEvent. state = " + state);
        if (mState != state) {
            return;
        }

        switch (mState) {
            case STATE_WAITING_FOR_ACTIVE_SOURCE:
                handleActiveSourceTimeout();
                break;
        }
    }

    protected void sendRequestActiveSource() {
        sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()),
                result -> {
                    if (result != SendMessageResult.SUCCESS) {
                        if (mSendRequestActiveSourceRetryCount < MAX_RETRY_COUNT) {
                            mSendRequestActiveSourceRetryCount++;
                            sendRequestActiveSource();
                        } else {
                            audioSystem().setSystemAudioMode(false);
                            finish();
                        }
                    }
                });
    }

    protected void sendSetSystemAudioMode(boolean on, int dest) {
        sendCommand(HdmiCecMessageBuilder.buildSetSystemAudioMode(getSourceAddress(),
                dest, on), result -> {
                    if (result != SendMessageResult.SUCCESS) {
                        if (mSendSetSystemAudioModeRetryCount < MAX_RETRY_COUNT) {
                            mSendSetSystemAudioModeRetryCount++;
                            sendSetSystemAudioMode(on, dest);
                        } else {
                            audioSystem().setSystemAudioMode(false);
                            finish();
                        }
                    }
                });
    }

    private void handleActiveSourceTimeout() {
        HdmiLogger.debug("Cannot get active source.");
        audioSystem().setSystemAudioMode(false);
        finish();
    }

    private void queryTvSystemAudioModeSupport() {
        audioSystem().queryTvSystemAudioModeSupport(
                supported -> {
                    if (supported) {
                        if (audioSystem().setSystemAudioMode(true)) {
                            sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST);
                        }
                        finish();
                    } else {
                        audioSystem().setSystemAudioMode(false);
                        finish();
                    }
                });
    }

    private void switchToRelevantInputForDeviceAt(int physicalAddress) {
        // TODO(shubang): implement this method
    }
}
+231 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.annotation.Nullable;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.support.test.filters.SmallTest;

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

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

    private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
    private TestLooper mTestLooper = new TestLooper();

    private boolean mShouldDispatchActiveSource;
    private boolean mTvSystemAudioModeSupport;
    private int mTryCountBeforeSucceed;
    private HdmiDeviceInfo mDeviceInfoForTests;

    private int mMsgRequestActiveSourceCount;
    private int mMsgSetSystemAudioModeCount;
    private int mQueryTvSystemAudioModeSupportCount;

    @Before
    public void SetUp() {
        mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234);
        HdmiControlService hdmiControlService = new HdmiControlService(null) {

            @Override
            void sendCecCommand(HdmiCecMessage command,
                    @Nullable SendMessageCallback callback) {
                switch (command.getOpcode()) {
                    case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
                        mMsgRequestActiveSourceCount++;
                        if (mTryCountBeforeSucceed >= mMsgRequestActiveSourceCount
                                && callback != null) {
                            callback.onSendCompleted(SendMessageResult.NACK);
                            break;
                        }
                        if (mShouldDispatchActiveSource) {
                            mHdmiCecLocalDeviceAudioSystem.dispatchMessage(
                                    HdmiCecMessageBuilder.buildActiveSource(
                                            Constants.ADDR_TV, 1002));
                        }
                        break;
                    case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
                        mMsgSetSystemAudioModeCount++;
                        if (mTryCountBeforeSucceed >= mMsgSetSystemAudioModeCount
                                && callback != null) {
                            callback.onSendCompleted(SendMessageResult.NACK);
                        }
                        break;
                    default:
                        throw new IllegalArgumentException("Unexpected message");
                }
            }

            @Override
            AudioManager getAudioManager() {
                return new AudioManager() {

                    @Override
                    public int setHdmiSystemAudioSupported(boolean on) {
                        return 0;
                    }
                };
            }

            @Override
            boolean isPowerStandby() {
                return false;
            }

            @Override
            boolean isAddressAllocated() {
                return true;
            }
        };
        mHdmiCecLocalDeviceAudioSystem =
                new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
                    @Override
                    void queryTvSystemAudioModeSupport(
                            TvSystemAudioModeSupportedCallback callback) {
                        mQueryTvSystemAudioModeSupportCount++;
                        if (callback != null) {
                            callback.onResult(mTvSystemAudioModeSupport);
                        }
                    }

                    @Override
                    HdmiDeviceInfo getDeviceInfo() {
                        return mDeviceInfoForTests;
                    }
                };
        mHdmiCecLocalDeviceAudioSystem.init();
        Looper looper = mTestLooper.getLooper();
        hdmiControlService.setIoLooper(looper);
    }

    @Test
    public void testNoActiveSourceMessageReceived() {
        resetTestVariables();
        mShouldDispatchActiveSource = false;

        assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress)
                .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS);

        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
                new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
        mTestLooper.dispatchAll();

        assertThat(mMsgRequestActiveSourceCount).isEqualTo(1);
        assertThat(mMsgSetSystemAudioModeCount).isEqualTo(0);
        assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(0);
        assertFalse(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());

        assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress)
                .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS);
    }

    @Test
    public void testTvNotSupport() {
        resetTestVariables();
        mShouldDispatchActiveSource = true;
        mTvSystemAudioModeSupport = false;

        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
                new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
        mTestLooper.dispatchAll();

        assertThat(mMsgRequestActiveSourceCount).isEqualTo(1);
        assertThat(mMsgSetSystemAudioModeCount).isEqualTo(0);
        assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
        assertFalse(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
    }

    @Test
    public void testTvSupport() {
        resetTestVariables();
        mShouldDispatchActiveSource = true;
        mTvSystemAudioModeSupport = true;

        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
                new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
        mTestLooper.dispatchAll();

        assertThat(mMsgRequestActiveSourceCount).isEqualTo(1);
        assertThat(mMsgSetSystemAudioModeCount).isEqualTo(1);
        assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
        assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());

        assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress).isEqualTo(1002);

    }

    @Test
    public void testKnownActiveSource() {
        resetTestVariables();
        mTvSystemAudioModeSupport = true;
        mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress = 1001;

        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
                new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
        mTestLooper.dispatchAll();

        assertThat(mMsgRequestActiveSourceCount).isEqualTo(0);
        assertThat(mMsgSetSystemAudioModeCount).isEqualTo(1);
        assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
        assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
    }

    @Test
    public void testRetry() {
        resetTestVariables();
        mTvSystemAudioModeSupport = true;
        mShouldDispatchActiveSource = true;
        mTryCountBeforeSucceed = 3;
        assertThat(mTryCountBeforeSucceed)
                .isAtMost(SystemAudioInitiationActionFromAvr.MAX_RETRY_COUNT);
        assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress)
                .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS);

        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
                new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem));
        mTestLooper.dispatchAll();

        assertThat(mMsgRequestActiveSourceCount).isEqualTo(4);
        assertThat(mMsgSetSystemAudioModeCount).isEqualTo(4);
        assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1);
        assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated());
    }

    private void resetTestVariables() {
        mMsgRequestActiveSourceCount = 0;
        mMsgSetSystemAudioModeCount = 0;
        mQueryTvSystemAudioModeSupportCount = 0;
        mTryCountBeforeSucceed = 0;
        mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress =
                Constants.INVALID_PHYSICAL_ADDRESS;
    }
}