/*
 * 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.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;

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

import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;

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

import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;

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

import java.util.ArrayList;

@SmallTest
@Presubmit
@RunWith(JUnit4.class)
/** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */
public class HdmiCecLocalDeviceAudioSystemTest {
    private static final HdmiCecMessage MESSAGE_REQUEST_SAD_LCPM =
            HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
                    ADDR_TV, ADDR_AUDIO_SYSTEM, new int[] {Constants.AUDIO_CODEC_LPCM});

    private static final int EMPTY_FLAGS = 0;

    private HdmiControlService mHdmiControlService;
    private HdmiCecController mHdmiCecController;
    private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
    private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
    private FakeNativeWrapper mNativeWrapper;
    private FakePowerManagerWrapper mPowerManager;
    private FakeAudioFramework mAudioFramework;
    private AudioManagerWrapper mAudioManager;
    private Looper mMyLooper;
    private TestLooper mTestLooper = new TestLooper();
    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
    private static final int SELF_PHYSICAL_ADDRESS = 0x2000;
    private static final int HDMI_1_PHYSICAL_ADDRESS = 0x2100;
    private static final int HDMI_2_PHYSICAL_ADDRESS = 0x2200;
    private static final int HDMI_3_PHYSICAL_ADDRESS = 0x2300;
    private int mInvokeDeviceEventState;
    private HdmiDeviceInfo mDeviceInfo;
    private boolean mArcSupport;
    private HdmiPortInfo[] mHdmiPortInfo;
    private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();

    @Before
    public void setUp() {
        Context context = InstrumentationRegistry.getTargetContext();
        mMyLooper = mTestLooper.getLooper();
        mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
        mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);

        mAudioFramework = new FakeAudioFramework();
        mAudioManager = mAudioFramework.getAudioManager();
        mHdmiControlService =
            new HdmiControlService(InstrumentationRegistry.getTargetContext(),
                    mLocalDeviceTypes, mAudioManager,
                    mAudioFramework.getAudioDeviceVolumeManager()) {
                @Override
                void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                    mDeviceInfo = device;
                    mInvokeDeviceEventState = status;
                }

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

                @Override
                boolean readBooleanSystemProperty(String key, boolean defVal) {
                    switch (key) {
                        case Constants.PROPERTY_ARC_SUPPORT:
                            return mArcSupport;
                        default:
                            return defVal;
                    }
                }

                @Override
                protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
                    // do nothing
                }
            };

        mHdmiControlService.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                HdmiControlManager.VOLUME_CONTROL_ENABLED);
        mMyLooper = mTestLooper.getLooper();
        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
        mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
        mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
            @Override
            protected int getPreferredAddress() {
                return ADDR_PLAYBACK_1;
            }
        };
        mHdmiCecLocalDeviceAudioSystem.init();
        mHdmiCecLocalDevicePlayback.init();
        mHdmiControlService.setIoLooper(mMyLooper);
        mNativeWrapper = new FakeNativeWrapper();
        mNativeWrapper.setPhysicalAddress(SELF_PHYSICAL_ADDRESS);
        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
        mHdmiControlService.setCecController(mHdmiCecController);
        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
        mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
        mLocalDevices.add(mHdmiCecLocalDevicePlayback);
        mHdmiCecLocalDeviceAudioSystem.setRoutingControlFeatureEnabled(true);
        mHdmiPortInfo = new HdmiPortInfo[4];
        mHdmiPortInfo[0] =
            new HdmiPortInfo.Builder(0, HdmiPortInfo.PORT_INPUT, SELF_PHYSICAL_ADDRESS)
                    .setCecSupported(true)
                    .setMhlSupported(false)
                    .setArcSupported(false)
                    .build();
        mHdmiPortInfo[1] =
            new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, HDMI_1_PHYSICAL_ADDRESS)
                    .setCecSupported(true)
                    .setMhlSupported(false)
                    .setArcSupported(false)
                    .build();
        mHdmiPortInfo[2] =
            new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, HDMI_2_PHYSICAL_ADDRESS)
                    .setCecSupported(true)
                    .setMhlSupported(false)
                    .setArcSupported(false)
                    .build();
        mHdmiPortInfo[3] =
            new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS)
                    .setCecSupported(true)
                    .setMhlSupported(false)
                    .setArcSupported(false)
                    .build();
        mNativeWrapper.setPortInfo(mHdmiPortInfo);
        mHdmiControlService.initService();
        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
        mPowerManager = new FakePowerManagerWrapper(context);
        mHdmiControlService.setPowerManager(mPowerManager);
        // No TV device interacts with AVR so system audio control won't be turned on here
        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
        mTestLooper.dispatchAll();
        mNativeWrapper.clearResultMessages();
        mArcSupport = true;
        mInvokeDeviceEventState = 0;
        mDeviceInfo = null;
    }

    @Test
    public void handleGiveAudioStatus_volume_10_mute_true() throws Exception {
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 10, EMPTY_FLAGS);
        mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_MUTE,
                EMPTY_FLAGS);
        mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, 20);
        int scaledVolume = VolumeControlAction.scaleToCecVolume(10,
                mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildReportAudioStatus(
                        ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true);
        HdmiCecMessage messageGive =
                HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive))
            .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
    }

    @Test
    @Ignore("b/120845532")
    public void handleGiveSystemAudioModeStatus_originalOff() throws Exception {
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildReportSystemAudioMode(
                        ADDR_AUDIO_SYSTEM, ADDR_TV, false);
        HdmiCecMessage messageGive =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
    }

    @Test
    public void handleRequestShortAudioDescriptor_featureDisabled() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
                MESSAGE_REQUEST_SAD_LCPM))
                .isEqualTo(Constants.ABORT_REFUSED);
    }

    @Test
    public void handleRequestShortAudioDescriptor_samOff() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
                MESSAGE_REQUEST_SAD_LCPM))
                .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
    }

    // Testing device has sadConfig.xml
    @Ignore("b/120845532")
    @Test
    public void handleRequestShortAudioDescriptor_noAudioDeviceInfo() throws Exception {
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildFeatureAbortCommand(
                        ADDR_AUDIO_SYSTEM,
                        ADDR_TV,
                        Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
                        Constants.ABORT_UNABLE_TO_DETERMINE);

        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
                MESSAGE_REQUEST_SAD_LCPM))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
    }

    @Test
    @Ignore("b/120845532")
    public void handleSetSystemAudioMode_setOn_orignalOff() throws Exception {
        mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
        HdmiCecMessage messageSet =
                HdmiCecMessageBuilder.buildSetSystemAudioMode(ADDR_TV, ADDR_AUDIO_SYSTEM, true);
        HdmiCecMessage messageGive =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
        // Check if originally off
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
            .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
        // Check if correctly turned on
        mNativeWrapper.clearResultMessages();
        expectedMessage =
            HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
        assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isFalse();
    }

    @Test
    @Ignore("b/120845532")
    public void handleSystemAudioModeRequest_turnOffByTv() throws Exception {
        assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isFalse();
        // Check if feature correctly turned off
        HdmiCecMessage messageGive =
                HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
        HdmiCecMessage messageRequestOff =
                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                        ADDR_TV, ADDR_AUDIO_SYSTEM, 2, false);
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildSetSystemAudioMode(
                        ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);

        mNativeWrapper.clearResultMessages();
        expectedMessage =
            HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
            .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
        assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isTrue();
    }

    @Test
    public void onStandbyAudioSystem_currentSystemAudioControlOn() throws Exception {
        // Set system audio control on first
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
        // Check if standby correctly turns off the feature
        mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF);
        mTestLooper.dispatchAll();
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildSetSystemAudioMode(
                        ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
        assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isTrue();
    }

    @Test
    public void systemAudioControlOnPowerOn_alwaysOn() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
        assertThat(
            mHdmiCecLocalDeviceAudioSystem.getActions(
                SystemAudioInitiationActionFromAvr.class))
            .isNotEmpty();
    }

    @Test
    public void systemAudioControlOnPowerOn_neverOn() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
        assertThat(
            mHdmiCecLocalDeviceAudioSystem.getActions(
                SystemAudioInitiationActionFromAvr.class))
            .isEmpty();
    }

    @Test
    public void systemAudioControlOnPowerOn_useLastState_off() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
        assertThat(
            mHdmiCecLocalDeviceAudioSystem.getActions(
                SystemAudioInitiationActionFromAvr.class))
            .isEmpty();
    }

    @Test
    public void systemAudioControlOnPowerOn_useLastState_on() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
        assertThat(
            mHdmiCecLocalDeviceAudioSystem.getActions(
                SystemAudioInitiationActionFromAvr.class))
            .isNotEmpty();
    }

    @Test
    public void systemAudioControlOnPowerOn_singleActionStarted() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
        mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
        assertThat(
                mHdmiCecLocalDeviceAudioSystem.getActions(
                        SystemAudioInitiationActionFromAvr.class))
                .hasSize(1);
    }

    @Test
    public void onSystemAudioControlFeatureSupportChanged_singleActionStarted() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
        mHdmiCecLocalDeviceAudioSystem.onSystemAudioControlFeatureSupportChanged(true);
        mHdmiCecLocalDeviceAudioSystem.onSystemAudioControlFeatureSupportChanged(true);
        assertThat(
                mHdmiCecLocalDeviceAudioSystem.getActions(
                        SystemAudioInitiationActionFromAvr.class))
                .hasSize(1);
    }

    @Test
    public void handleActiveSource_updateActiveSource() throws Exception {
        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
        ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
            .isTrue();
    }

    @Test
    public void systemAudioModeMuting_enabled() {
        mHdmiControlService.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
                HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED);
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
        assertThat(mHdmiControlService.getAudioManager().isStreamMute(
                AudioManager.STREAM_MUSIC)).isFalse();
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
        assertThat(mHdmiControlService.getAudioManager().isStreamMute(
                AudioManager.STREAM_MUSIC)).isTrue();
    }

    @Test
    public void systemAudioModeMuting_disabled() {
        mHdmiControlService.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
                HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED);
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
        assertThat(mHdmiControlService.getAudioManager().isStreamMute(
                AudioManager.STREAM_MUSIC)).isFalse();
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
        assertThat(mHdmiControlService.getAudioManager().isStreamMute(
                AudioManager.STREAM_MUSIC)).isFalse();
        mHdmiControlService.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
                HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED);
    }

    @Test
    public void terminateSystemAudioMode_systemAudioModeOff() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
        mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildSetSystemAudioMode(
                        ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
        mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
        assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isFalse();
        assertThat(mNativeWrapper.getResultMessages()).isEmpty();
    }

    @Test
    public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
        mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildSetSystemAudioMode(
                        ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
        mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
        assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isTrue();
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
    }

    @Test
    public void handleRequestArcInitiate_isNotDirectConnectedToTv() throws Exception {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
        mNativeWrapper.setPhysicalAddress(0x1100);
        mHdmiControlService.onHotplug(0x1100, true);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
                .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE);
    }

    @Test
    public void handleRequestArcInitiate_startArcInitiationActionFromAvr() throws Exception {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
        mNativeWrapper.setPhysicalAddress(0x1000);
        mHdmiControlService.onHotplug(0x1000, true);

        mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
            .isNotEmpty();
    }

    @Test
    public void handleRequestArcTerminate_arcIsOn_startTerminationActionFromAvr() throws Exception {
        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();

        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
        mHdmiCecLocalDeviceAudioSystem.removeAction(ArcTerminationActionFromAvr.class);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
            .isNotEmpty();
    }

    @Test
    @Ignore("b/120845532")
    public void handleRequestArcTerminate_arcIsNotOn() throws Exception {
        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildFeatureAbortCommand(
                        ADDR_AUDIO_SYSTEM,
                        ADDR_TV,
                        Constants.MESSAGE_REQUEST_ARC_TERMINATION,
                        Constants.ABORT_NOT_IN_CORRECT_MODE);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
    }

    @Test
    public void handleRequestArcTerminate_callbackIsPreserved() throws Exception {
        TestCallback callback = new TestCallback();

        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
                new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback));

        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
                .isEqualTo(Constants.HANDLED);

        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(
                ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback);
    }

    @Test
    public void handleRequestArcInit_arcIsNotSupported() throws Exception {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
        mArcSupport = false;

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message))
                .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE);
    }

    @Test
    public void setArcStatus() {
        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();

        mHdmiCecLocalDeviceAudioSystem.setArcStatus(false);
        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
    }

    @Test
    @Ignore("b/151150320")
    public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                        ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
                        SELF_PHYSICAL_ADDRESS, true);
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildFeatureAbortCommand(
                        ADDR_AUDIO_SYSTEM,
                        ADDR_TUNER_1,
                        Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
                        Constants.ABORT_REFUSED);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message))
                .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
    }

    @Test
    public void handleSystemAudioModeRequest_fromNonTV_tVSupport() {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                        ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
                        SELF_PHYSICAL_ADDRESS, true);
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildSetSystemAudioMode(
                        ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
        mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);


        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
    }

    @Test
    public void handleActiveSource_activeSourceFromTV_swithToArc() {
        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);

        ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
            .isEqualTo(expectedActiveSource);
    }

    @Test
    @Ignore("b/151150320")
    public void handleRoutingChange_currentActivePortIsHome() {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, SELF_PHYSICAL_ADDRESS);

        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, SELF_PHYSICAL_ADDRESS);
        ActiveSource expectedActiveSource = ActiveSource.of(ADDR_PLAYBACK_1, SELF_PHYSICAL_ADDRESS);
        int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
            .isEqualTo(expectedActiveSource);
        assertThat(mHdmiCecLocalDeviceAudioSystem.getRoutingPort())
            .isEqualTo(expectedLocalActivePort);
        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
    }

    @Test
    public void handleRoutingInformation_currentActivePortIsHDMI1() {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, SELF_PHYSICAL_ADDRESS);
        mHdmiCecLocalDeviceAudioSystem.setRoutingPort(mHdmiPortInfo[1].getId());
        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildRoutingInformation(
                        ADDR_AUDIO_SYSTEM, HDMI_1_PHYSICAL_ADDRESS);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
    }

    @Ignore("b/120845532")
    @Test
    public void handleRoutingChange_homeIsActive_playbackSendActiveSource() {
        HdmiCecMessage message =
                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000);

        HdmiCecMessage expectedMessage =
                HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);

        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message))
                .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();
        assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
    }

    @Test
    public void doNotWakeUpOnHotPlug_PlugIn() {
        mPowerManager.setInteractive(false);
        mHdmiCecLocalDeviceAudioSystem.onHotplug(0, true);
        assertThat(mPowerManager.isInteractive()).isFalse();
    }

    @Test
    public void doNotWakeUpOnHotPlug_PlugOut() {
        mPowerManager.setInteractive(false);
        mHdmiCecLocalDeviceAudioSystem.onHotplug(0, false);
        assertThat(mPowerManager.isInteractive()).isFalse();
    }

    @Test
    public void giveAudioStatus_volumeEnabled() {
        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                HdmiControlManager.VOLUME_CONTROL_ENABLED);
        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);

        int volume = mHdmiControlService.getAudioManager()
                .getStreamVolume(AudioManager.STREAM_MUSIC);
        boolean mute = mHdmiControlService.getAudioManager()
                .isStreamMute(AudioManager.STREAM_MUSIC);
        int maxVolume = mHdmiControlService.getAudioManager()
                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
        HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
                ADDR_TV, scaledVolume, mute);

        HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
                ADDR_AUDIO_SYSTEM);
        mNativeWrapper.clearResultMessages();
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus))
                        .isEqualTo(Constants.HANDLED);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).contains(expected);
    }

    @Test
    public void giveAudioStatus_volumeDisabled() {
        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                HdmiControlManager.VOLUME_CONTROL_DISABLED);
        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);

        int volume = mHdmiControlService.getAudioManager()
                .getStreamVolume(AudioManager.STREAM_MUSIC);
        boolean mute = mHdmiControlService.getAudioManager()
                .isStreamMute(AudioManager.STREAM_MUSIC);
        int maxVolume = mHdmiControlService.getAudioManager()
                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
        HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
                ADDR_TV, scaledVolume, mute);

        HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV,
                ADDR_AUDIO_SYSTEM);
        mNativeWrapper.clearResultMessages();
        assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus))
                .isEqualTo(Constants.ABORT_REFUSED);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
    }

    @Test
    public void reportAudioStatus_volumeEnabled() {
        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                HdmiControlManager.VOLUME_CONTROL_ENABLED);
        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);

        int volume = mHdmiControlService.getAudioManager()
                .getStreamVolume(AudioManager.STREAM_MUSIC);
        boolean mute = mHdmiControlService.getAudioManager()
                .isStreamMute(AudioManager.STREAM_MUSIC);
        int maxVolume = mHdmiControlService.getAudioManager()
                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
        HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
                ADDR_TV, scaledVolume, mute);

        mNativeWrapper.clearResultMessages();
        mHdmiCecLocalDeviceAudioSystem.reportAudioStatus(ADDR_TV);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).contains(expected);
    }

    @Test
    public void reportAudioStatus_volumeDisabled() {
        mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
                HdmiControlManager.VOLUME_CONTROL_DISABLED);
        mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);

        int volume = mHdmiControlService.getAudioManager()
                .getStreamVolume(AudioManager.STREAM_MUSIC);
        boolean mute = mHdmiControlService.getAudioManager()
                .isStreamMute(AudioManager.STREAM_MUSIC);
        int maxVolume = mHdmiControlService.getAudioManager()
                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
        HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM,
                ADDR_TV, scaledVolume, mute);

        mNativeWrapper.clearResultMessages();
        mHdmiCecLocalDeviceAudioSystem.reportAudioStatus(ADDR_TV);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
    }

    @Test
    public void setActiveSource_localDevice_playback() {
        mHdmiControlService.setActiveSource(
                mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
                SELF_PHYSICAL_ADDRESS,
                "HdmiControlServiceTest");

        assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress)
                .isEqualTo(mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress());
        assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
                SELF_PHYSICAL_ADDRESS);
        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
    }

    @Test
    public void setActiveSource_localDevice_audio() {
        mHdmiControlService.setActiveSource(
                mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
                SELF_PHYSICAL_ADDRESS,
                "HdmiControlServiceTest");

        assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress)
                .isEqualTo(mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress());
        assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
                SELF_PHYSICAL_ADDRESS);
        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isTrue();
    }

    @Test
    public void setActiveSource_remoteDevice() {
        mHdmiControlService.setActiveSource(Constants.ADDR_TV, 0x0000, "HdmiControlServiceTest");

        assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
                Constants.ADDR_TV);
        assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(0x000);
        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
    }

    @Test
    public void setActiveSource_nonCecDevice() {
        mHdmiControlService.setActiveSource(Constants.ADDR_INVALID, 0x1234,
                "HdmiControlServiceTest");

        assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
                Constants.ADDR_INVALID);
        assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(0x1234);
        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
    }

    @Test
    public void setActiveSource_unknown() {
        mHdmiControlService.setActiveSource(Constants.ADDR_INVALID,
                Constants.INVALID_PHYSICAL_ADDRESS, "HdmiControlServiceTest");

        assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
                Constants.ADDR_INVALID);
        assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
                Constants.INVALID_PHYSICAL_ADDRESS);
        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
        assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
    }

    @Test
    @Ignore("b/151150320")
    public void oneTouchPlay() {
        mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
            @Override
            public void onComplete(int result) {
            }
        });
        mTestLooper.dispatchAll();

        HdmiCecMessage textViewOn_fromPlayback = HdmiCecMessageBuilder.buildTextViewOn(
                mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(), ADDR_TV);
        HdmiCecMessage activeSource_fromPlayback = HdmiCecMessageBuilder.buildActiveSource(
                mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
                SELF_PHYSICAL_ADDRESS);
        HdmiCecMessage systemAudioModeRequest_fromPlayback =
                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                        mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(),
                        ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true);
        HdmiCecMessage textViewOn_fromAudioSystem = HdmiCecMessageBuilder.buildTextViewOn(
                mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(), ADDR_TV);
        HdmiCecMessage activeSource_fromAudioSystem = HdmiCecMessageBuilder.buildActiveSource(
                mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
                SELF_PHYSICAL_ADDRESS);
        HdmiCecMessage systemAudioModeRequest_fromAudioSystem =
                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
                        mHdmiCecLocalDeviceAudioSystem.getDeviceInfo().getLogicalAddress(),
                        ADDR_AUDIO_SYSTEM, SELF_PHYSICAL_ADDRESS, true);
        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn_fromPlayback);
        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource_fromPlayback);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
                systemAudioModeRequest_fromPlayback);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn_fromAudioSystem);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource_fromAudioSystem);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
                systemAudioModeRequest_fromAudioSystem);
    }

    private static class TestCallback extends IHdmiControlCallback.Stub {
        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();

        @Override
        public void onComplete(int result) {
            mCallbackResult.add(result);
        }
    }
}
