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

Commit c7737bee authored by Marvin Ramin's avatar Marvin Ramin
Browse files

Add retry to SystemAudioAutoInitiationAction

In cases where audio devices don't respond to the system audio status
request in time this can bring the system audio status of the TV and
audio device out of sync.

Add a retry to attempt gettingthe system audio mode status of the
audio device again.

Bug: 156579158
Bug: 176920796
Test: atest SystemAudioAutoInitiationAction
Change-Id: Ifedf1b71aecb02897ac4b06f21cd0b2cffb6f8c1
parent a954e2dc
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.server.hdmi;

import android.hardware.tv.cec.V1_0.SendMessageResult;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;

/**
@@ -30,6 +32,14 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction {
    // <Give System Audio Mode Status> to AV Receiver.
    private static final int STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS = 1;

    @VisibleForTesting
    static final int RETRIES_ON_TIMEOUT = 1;

    // On some audio devices the <System Audio Mode Status> message can be delayed as the device
    // is just waking up. Retry the <Give System Audio Mode Status> message to ensure we properly
    // initialize system audio.
    private int mRetriesOnTimeOut = RETRIES_ON_TIMEOUT;

    SystemAudioAutoInitiationAction(HdmiCecLocalDevice source, int avrAddress) {
        super(source);
        mAvrAddress = avrAddress;
@@ -100,6 +110,13 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction {

        switch (mState) {
            case STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS:
                if (mRetriesOnTimeOut > 0) {
                    mRetriesOnTimeOut--;
                    addTimer(mState, HdmiConfig.TIMEOUT_MS);
                    sendGiveSystemAudioModeStatus();
                    return;
                }

                handleSystemAudioModeStatusTimeout();
                break;
        }
+343 −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 com.android.server.hdmi;


import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;

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

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.hdmi.HdmiPortInfo;
import android.media.AudioManager;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
import android.os.Looper;
import android.os.PowerManager;
import android.os.test.TestLooper;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;

/**
 * Test for {@link SystemAudioAutoInitiationAction}.
 */
@SmallTest
@RunWith(JUnit4.class)
public class SystemAudioAutoInitiationActionTest {

    private Context mContextSpy;
    private HdmiControlService mHdmiControlService;
    private FakeNativeWrapper mNativeWrapper;

    private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;

    private TestLooper mTestLooper = new TestLooper();
    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
    private int mPhysicalAddress;

    @Mock
    private IPowerManager mIPowerManagerMock;
    @Mock
    private IThermalService mIThermalServiceMock;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));

        Looper myLooper = mTestLooper.getLooper();
        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
                mIThermalServiceMock, new Handler(myLooper));
        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
        when(mIPowerManagerMock.isInteractive()).thenReturn(true);

        mHdmiControlService = new HdmiControlService(mContextSpy) {
            @Override
            AudioManager getAudioManager() {
                return new AudioManager() {
                    @Override
                    public void setWiredDeviceConnectionState(
                            int type, int state, String address, String name) {
                        // Do nothing.
                    }
                };
            }

            @Override
            void wakeUp() {
            }

            @Override
            boolean isPowerStandby() {
                return false;
            }

            @Override
            protected PowerManager getPowerManager() {
                return powerManager;
            }

            @Override
            protected void writeStringSystemProperty(String key, String value) {
                // do nothing
            }
        };

        mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
        mHdmiCecLocalDeviceTv.init();
        mHdmiControlService.setIoLooper(myLooper);
        mNativeWrapper = new FakeNativeWrapper();
        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
        mHdmiControlService.setCecController(hdmiCecController);
        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
        mLocalDevices.add(mHdmiCecLocalDeviceTv);
        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
        hdmiPortInfos[0] =
                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
        hdmiPortInfos[1] =
                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
        mNativeWrapper.setPortInfo(hdmiPortInfos);
        mHdmiControlService.initService();
        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
        mPhysicalAddress = 0x0000;
        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
        mTestLooper.dispatchAll();
        mPhysicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
        mNativeWrapper.clearResultMessages();
    }

    private void setSystemAudioSetting(boolean on) {
        mHdmiCecLocalDeviceTv.setSystemAudioControlFeatureEnabled(on);
    }

    private void setTvHasSystemAudioChangeAction() {
        mHdmiCecLocalDeviceTv.addAndStartAction(
                new SystemAudioActionFromTv(mHdmiCecLocalDeviceTv, Constants.ADDR_AUDIO_SYSTEM,
                        true, null));
    }

    @Test
    public void testReceiveSystemAudioMode_systemAudioOn() {
        // Record that previous system audio mode is on.
        setSystemAudioSetting(true);

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);

        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);

        HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode(
                ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, true);
        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
        mTestLooper.dispatchAll();

        assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue();
    }

    @Test
    public void testReceiveSystemAudioMode_systemAudioOnAndImpossibleToChangeSystemAudio() {
        // Turn on system audio.
        setSystemAudioSetting(true);
        // Impossible to change system audio mode while SystemAudioActionFromTv is in progress.
        setTvHasSystemAudioChangeAction();

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);

        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);

        HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode(
                ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, true);
        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
        mTestLooper.dispatchAll();

        assertThat(mHdmiControlService.isSystemAudioActivated()).isFalse();
    }

    @Test
    public void testReceiveSystemAudioMode_systemAudioOnAndResponseOff() {
        // Record that previous system audio mode is on.
        setSystemAudioSetting(true);

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);

        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);

        HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode(
                ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, false);
        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
        mTestLooper.dispatchAll();

        assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty();
        SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions(
                SystemAudioActionFromTv.class).get(0);
        assertThat(resultingAction.mTargetAudioStatus).isTrue();
    }

    @Test
    public void testReceiveSystemAudioMode_settingOffAndResponseOn() {
        // Turn off system audio.
        setSystemAudioSetting(false);

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);

        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);

        HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode(
                ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, true);
        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
        mTestLooper.dispatchAll();

        assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty();
        SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions(
                SystemAudioActionFromTv.class).get(0);
        assertThat(resultingAction.mTargetAudioStatus).isFalse();
    }

    @Test
    public void testReceiveSystemAudioMode_settingOffAndResponseOff() {
        // Turn off system audio.
        setSystemAudioSetting(false);

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);

        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);

        HdmiCecMessage reportSystemAudioMode = HdmiCecMessageBuilder.buildReportSystemAudioMode(
                ADDR_AUDIO_SYSTEM, mHdmiCecLocalDeviceTv.mAddress, false);
        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
        mTestLooper.dispatchAll();

        assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isEmpty();
        assertThat(mHdmiControlService.isSystemAudioActivated()).isFalse();
    }

    @Test
    public void testTimeout_systemAudioOn_retries() {
        // Turn on system audio.
        setSystemAudioSetting(true);

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);

        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
        mNativeWrapper.clearResultMessages();

        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
        mTestLooper.dispatchAll();

        // Retry sends <Give System Audio Mode Status> again
        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
    }

    @Test
    public void testTimeout_systemAudioOn_allRetriesFail() {
        boolean targetStatus = true;
        // Turn on system audio.
        setSystemAudioSetting(targetStatus);

        HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
                ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceTv.addAndStartAction(action);
        mTestLooper.dispatchAll();

        HdmiCecMessage giveSystemAudioModeStatus =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
                        mHdmiCecLocalDeviceTv.mAddress, ADDR_AUDIO_SYSTEM);
        assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);

        for (int i = 0; i < RETRIES_ON_TIMEOUT; i++) {
            mNativeWrapper.clearResultMessages();

            // Target device doesn't respond within timeout
            mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
            mTestLooper.dispatchAll();

            // Retry sends <Give System Audio Mode Status> again
            assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
        }

        // Target device doesn't respond within timeouts
        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
        mTestLooper.dispatchAll();

        assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty();
        SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions(
                SystemAudioActionFromTv.class).get(0);
        assertThat(resultingAction.mTargetAudioStatus).isEqualTo(targetStatus);
    }
}