/*
 * Copyright (C) 2015 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.telecom.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;

import android.os.Process;
import android.os.RemoteException;
import android.telecom.CallAudioState;
import android.telecom.DisconnectCause;
import android.telecom.VideoProfile;

import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;

import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteAdapter;
import com.android.server.telecom.CallAudioRouteStateMachine;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;

import java.util.List;

/**
 * System tests for video-specific behavior in telecom.
 * TODO: Add unit tests which ensure that auto-speakerphone does not occur when using a wired
 * headset or a bluetooth headset.
 */
@RunWith(JUnit4.class)
public class VideoCallTests extends TelecomSystemTest {

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
    }

    @Override
    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * Tests to ensure an incoming video-call is automatically routed to the speakerphone when
     * the call is answered and neither a wired headset nor bluetooth headset are connected.
     */
    @MediumTest
    @Test
    public void testAutoSpeakerphoneIncomingBidirectional() throws Exception {
        // Start an incoming video call.
        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
                VideoProfile.STATE_BIDIRECTIONAL);

        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
    }

    /**
     * Tests to ensure an incoming receive-only video-call is answered in speakerphone mode.  Note
     * that this is not a scenario we would expect normally with the default dialer as it will
     * always answer incoming video calls as bi-directional.  It is, however, possible for a third
     * party dialer to answer an incoming video call a a one-way video call.
     */
    @MediumTest
    @Test
    public void testAutoSpeakerphoneIncomingReceiveOnly() throws Exception {
        // Start an incoming video call.
        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
                VideoProfile.STATE_RX_ENABLED);

        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
    }

    /**
     * Tests audio routing for an outgoing video call made with bidirectional video.  Expect to be
     * in speaker mode.
     */
    @MediumTest
    @Test
    public void testAutoSpeakerphoneOutgoingBidirectional() throws Exception {
        // Start an incoming video call.
        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
                VideoProfile.STATE_BIDIRECTIONAL, null);

        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
    }

    /**
     * Tests audio routing for an outgoing video call made with transmit only video.  Expect to be
     * in speaker mode.  Note: The default UI does not support making one-way video calls, but the
     * APIs do and a third party incall UI could choose to support that.
     */
    @MediumTest
    @Test
    public void testAutoSpeakerphoneOutgoingTransmitOnly() throws Exception {
        // Start an incoming video call.
        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
                VideoProfile.STATE_TX_ENABLED, null);

        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
    }

    /**
     * Tests audio routing for an outgoing video call made with transmit only video.  Expect to be
     * in speaker mode.  Note: The default UI does not support making one-way video calls, but the
     * APIs do and a third party incall UI could choose to support that.
     */
    @MediumTest
    @Test
    public void testNoAutoSpeakerphoneOnOutgoing() throws Exception {
        // Start an incoming video call.
        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
                VideoProfile.STATE_AUDIO_ONLY, null);

        verifyAudioRoute(CallAudioState.ROUTE_EARPIECE);
    }

    /**
     * Tests to ensure an incoming audio-only call is routed to the earpiece.
     */
    @MediumTest
    @Test
    public void testNoAutoSpeakerphoneOnIncoming() throws Exception {

        // Start an incoming video call.
        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
                VideoProfile.STATE_AUDIO_ONLY);

        verifyAudioRoute(CallAudioState.ROUTE_EARPIECE);
    }

    /**
     * Ensure that when an incoming video call is missed, the video state history still includes
     * video calling. This is important for the call log.
     */
    @LargeTest
    @Test
    public void testIncomingVideoCallMissedCheckVideoHistory() throws Exception {
        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();

        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.MISSED);

        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
    }

    /**
     * Ensure that when an incoming video call is rejected, the video state history still includes
     * video calling. This is important for the call log.
     */
    @LargeTest
    @Test
    public void testIncomingVideoCallRejectedCheckVideoHistory() throws Exception {
        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();

        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.REJECTED);

        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
    }


    /**
     * Ensure that when an outgoing video call is canceled, the video state history still includes
     * video calling. This is important for the call log.
     */
    @LargeTest
    @Test
    public void testOutgoingVideoCallCanceledCheckVideoHistory() throws Exception {
        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                mConnectionServiceFixtureA, Process.myUserHandle(),
                VideoProfile.STATE_BIDIRECTIONAL, null);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();

        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);

        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
    }

    /**
     * Ensure that when an outgoing video call is rejected, the video state history still includes
     * video calling. This is important for the call log.
     */
    @LargeTest
    @Test
    public void testOutgoingVideoCallRejectedCheckVideoHistory() throws Exception {
        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                mConnectionServiceFixtureA, Process.myUserHandle(),
                VideoProfile.STATE_BIDIRECTIONAL, null);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();

        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.REMOTE);

        assertTrue(VideoProfile.isVideo(call.getVideoStateHistory()));
    }

    /**
     * Ensure that when an outgoing video call is answered as audio only, the video state history
     * shows that the call was audio only. This is important for the call log.
     */
    @LargeTest
    @Test
    public void testOutgoingVideoCallAnsweredAsAudio() throws Exception {
        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                mConnectionServiceFixtureA, Process.myUserHandle(),
                VideoProfile.STATE_BIDIRECTIONAL, null);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();

        mConnectionServiceFixtureA.mConnectionById.get(ids.mConnectionId).videoState
                = VideoProfile.STATE_AUDIO_ONLY;
        mConnectionServiceFixtureA.sendSetVideoState(ids.mConnectionId);
        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);

        assertFalse(VideoProfile.isVideo(call.getVideoStateHistory()));
    }

    /**
     * Verifies that the
     * {@link android.telecom.InCallService#onCallAudioStateChanged(CallAudioState)} change is
     * called with an expected route and number of changes.
     *
     * @param expectedRoute The expected audio route on the latest change.
     */
    private void verifyAudioRoute(int expectedRoute) throws Exception {
        // Capture all onCallAudioStateChanged callbacks to InCall.
        CallAudioRouteAdapter cara = mTelecomSystem.getCallsManager()
                .getCallAudioManager().getCallAudioRouteAdapter();
        CallAudioModeStateMachine camsm = mTelecomSystem.getCallsManager()
                .getCallAudioManager().getCallAudioModeStateMachine();
        waitForHandlerAction(camsm.getHandler(), TEST_TIMEOUT);
        final boolean[] success = {true};
        cara.sendMessage(CallAudioRouteStateMachine.RUN_RUNNABLE, (Runnable) () -> {
            ArgumentCaptor<CallAudioState> callAudioStateArgumentCaptor = ArgumentCaptor.forClass(
                    CallAudioState.class);
            try {
                verify(mInCallServiceFixtureX.getTestDouble(), atLeastOnce())
                        .onCallAudioStateChanged(callAudioStateArgumentCaptor.capture());
            } catch (RemoteException e) {
                fail("Remote exception in InCallServiceFixture");
            }
            List<CallAudioState> changes = callAudioStateArgumentCaptor.getAllValues();
            assertEquals(expectedRoute, changes.get(changes.size() - 1).getRoute());
            success[0] = true;
        });
        waitForHandlerAction(cara.getAdapterHandler(), TEST_TIMEOUT);
        assertTrue(success[0]);
    }
}
