/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.utils

import com.amazonaws.services.chime.sdk.BuildConfig
import org.junit.Assert
import org.junit.Test

class DeviceUtilsTest {
    @Test
    fun `sdkName should return "amazon-chime-sdk-android"`() {
        Assert.assertEquals("amazon-chime-sdk-android", DeviceUtils.sdkName)
    }

    @Test
    fun `osName should return "Android"`() {
        Assert.assertEquals("Android", DeviceUtils.osName)
    }

    @Test
    fun `osVersion should return "Android"`() {
        Assert.assertEquals(android.os.Build.VERSION.RELEASE, DeviceUtils.osVersion)
    }

    @Test
    fun `sdkVersion should return value same as BuildConfig`() {
        Assert.assertEquals(BuildConfig.VERSION_NAME, DeviceUtils.sdkVersion)
    }

    @Test
    fun `deviceName should return value same as vendor + model`() {
        Assert.assertEquals("${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}", DeviceUtils.deviceName)
    }

    @Test
    fun `deviceModel should return value same as model`() {
        Assert.assertEquals(android.os.Build.MODEL, DeviceUtils.deviceModel)
    }

    @Test
    fun `deviceManufacturer should return value same as vendor`() {
        Assert.assertEquals(android.os.Build.MANUFACTURER, DeviceUtils.deviceManufacturer)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.utils

import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import java.io.InputStream
import java.io.OutputStream
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Before
import org.junit.Test

@ExperimentalCoroutinesApi
class HttpUtilsTest {
    val body = "hello"

    @MockK
    private lateinit var connectionMock: HttpURLConnection

    @MockK
    private lateinit var outputStreamMock: OutputStream

    @MockK
    private lateinit var inputStreamMock: InputStream

    @MockK
    private lateinit var urlMock: URL

    @Before
    fun setUp() {
        MockKAnnotations.init(this, relaxUnitFun = true)

        every { inputStreamMock.read(any()) } returns -1
        every { inputStreamMock.read(any(), any(), any()) } returns -1
        every { connectionMock.requestMethod = any() } returns Unit
        every { connectionMock.doInput = any() } returns Unit
        every { connectionMock.doOutput = any() } returns Unit
        every { connectionMock.setRequestProperty(any(), any()) } returns Unit
        every { connectionMock.outputStream } returns outputStreamMock
        every { connectionMock.inputStream } returns inputStreamMock
        every { connectionMock.errorStream } returns inputStreamMock
        every { urlMock.openConnection() } returns connectionMock
    }

    @Test
    fun `HttpUtils should return responseCode 400`() {
        every { (connectionMock).responseCode } returns 400
        runBlocking {
            val result = HttpUtils.post(urlMock, body)

            Assert.assertEquals(400, result.httpException?.errorCode)
        }
    }

    @Test
    fun `HttpUtils should return responseCode 200`() {
        every { (connectionMock).responseCode } returns 200

        runBlocking {
            val result = HttpUtils.post(urlMock, body)

            Assert.assertNotNull(result.data)
            Assert.assertNull(result.httpException)
        }
    }

    @Test
    fun `HttpUtils should return httpException when exception is thrown`() {
        every { (connectionMock).responseCode } returns 200
        every { connectionMock.outputStream } returns null

        runBlocking {
            val result = HttpUtils.post(urlMock, body)

            Assert.assertNull(result.data)
            Assert.assertNotNull(result.httpException)
            Assert.assertNotNull(result.httpException?.errorReason)
        }
    }

    @Test
    fun `HttpUtils should retry if it is in the retryableSet`() {
        every { (connectionMock).responseCode } returns 400
        val retry = 5

        runBlocking {
            HttpUtils.post(urlMock, body, DefaultBackOffRetry(retry, 0, setOf(400)))

            verify(exactly = retry) { urlMock.openConnection() }
        }
    }

    @Test
    fun `HttpUtils should not retry if it is not in the retryableSet`() {
        every { (connectionMock).responseCode } returns 400
        val retry = 5

        runBlocking {
            HttpUtils.post(urlMock, body, DefaultBackOffRetry(retry, 0, setOf(404)))

            verify(exactly = 1) { urlMock.openConnection() }
        }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.utils

import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class ObserverUtilsTest {
    @MockK
    private lateinit var mockObserver: MockObserver

    class MockObserver {
        fun mockFn() {
        }
    }

    private val testDispatcher = TestCoroutineDispatcher()
    private val mockObservers = ConcurrentSet.createConcurrentSet<MockObserver>()

    @Before
    fun setUp() {
        mockkStatic(System::class)
        Dispatchers.setMain(testDispatcher)
        mockObserver = mockk(relaxed = true)
        mockObservers.add(mockObserver)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun `forEachObserver should call function that is passed in`() {
        ObserverUtils.notifyObserverOnMainThread(mockObservers) { it.mockFn() }

        verify { mockObserver.mockFn() }
    }

    @Test
    fun `forEachObserver should call for each observer that is added concurrently`() = runBlockingTest {
        launch {
            for (index in 0..49) {
                val mockObserver: MockObserver = mockk(relaxed = true)
                mockObservers.add(mockObserver)
            }
        }
        val deferred = async {
            for (index in 0..48) {
                val mockObserver: MockObserver = mockk(relaxed = true)
                mockObservers.add(mockObserver)
            }
        }
        deferred.await()
        ObserverUtils.notifyObserverOnMainThread(mockObservers) { it.mockFn() }

        assertEquals(mockObservers.size, 100)
        verify {
            for (observer in mockObservers) {
                observer.mockFn()
            }
        }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.utils

import java.util.TimeZone
import org.junit.Assert
import org.junit.Test

class TimezoneUtilsTest {
    @Test
    fun `getUtcOffset should return correctly formatted positive Utc offset`() {
        Assert.assertEquals("+05:30", TimezoneUtils.getUtcOffset(TimeZone.getTimeZone("Asia/Calcutta")))
    }

    @Test
    fun `getUtcOffset should return correctly formatted negative Utc offset`() {
        Assert.assertEquals("-06:00", TimezoneUtils.getUtcOffset(TimeZone.getTimeZone("America/Costa_Rica")))
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.video

import android.content.Context
import com.amazonaws.services.chime.sdk.meetings.audiovideo.contentshare.ContentShareObserver
import com.amazonaws.services.chime.sdk.meetings.audiovideo.contentshare.ContentShareStatus
import com.amazonaws.services.chime.sdk.meetings.audiovideo.contentshare.ContentShareStatusCode
import com.amazonaws.services.chime.sdk.meetings.internal.contentshare.DefaultContentShareVideoClientObserver
import com.amazonaws.services.chime.sdk.meetings.internal.metric.ClientMetricsCollector
import com.amazonaws.services.chime.sdk.meetings.session.URLRewriter
import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import com.xodee.client.video.VideoClient
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test

class DefaultContentShareVideoClientObserverTest {
    @MockK
    private lateinit var mockContext: Context

    @MockK
    private lateinit var mockLogger: Logger

    @MockK
    private lateinit var mockTurnRequestParams: TURNRequestParams

    @MockK
    private lateinit var mockClientMetricsCollector: ClientMetricsCollector

    @MockK
    private lateinit var mockURLRewriter: URLRewriter

    @InjectMockKs
    private lateinit var testContentShareVideoClientObserver: DefaultContentShareVideoClientObserver

    @MockK
    private lateinit var mockContentShareObserver: ContentShareObserver

    @MockK
    private lateinit var mockVideoClient: VideoClient

    private val testDispatcher = TestCoroutineDispatcher()

    private val videoClientStatus = 0

    @Before
    fun setUp() {
        mockkStatic(System::class)
        every { System.loadLibrary(any()) } just runs
        Dispatchers.setMain(testDispatcher)
        MockKAnnotations.init(this, relaxUnitFun = true)
        testContentShareVideoClientObserver.subscribeToVideoClientStateChange(
            mockContentShareObserver
        )
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun `didFail should notify observer of onContentShareStopped event with VideoServiceFailed status`() {
        testContentShareVideoClientObserver.didFail(
            mockVideoClient,
            videoClientStatus,
            VideoClient.VIDEO_CLIENT_STATUS_CALL_AT_CAPACITY
        )

        verify(exactly = 1) {
            mockContentShareObserver.onContentShareStopped(
                ContentShareStatus(
                    ContentShareStatusCode.VideoServiceFailed
                )
            )
        }
    }

    @Test
    fun `didFail should call clientMetricsCollector to clean metrics`() {
        testContentShareVideoClientObserver.didFail(
            mockVideoClient,
            videoClientStatus,
            VideoClient.VIDEO_CLIENT_STATUS_CALL_AT_CAPACITY
        )

        verify { mockClientMetricsCollector.processContentShareVideoClientMetrics(emptyMap()) }
    }

    @Test
    fun `didStop should notify observer of onContentShareStopped event with OK status`() {
        testContentShareVideoClientObserver.didStop(mockVideoClient)

        verify(exactly = 1) {
            mockContentShareObserver.onContentShareStopped(
                ContentShareStatus(
                    ContentShareStatusCode.OK
                )
            )
        }
    }

    @Test
    fun `didStop should call clientMetricsCollector to clean metrics`() {
        testContentShareVideoClientObserver.didStop(mockVideoClient)

        verify { mockClientMetricsCollector.processContentShareVideoClientMetrics(emptyMap()) }
    }

    @Test
    fun `onMetrics should call clientMetricsCollector to process video metrics`() {
        testContentShareVideoClientObserver.onMetrics(intArrayOf(1), doubleArrayOf(1.0))

        verify { mockClientMetricsCollector.processContentShareVideoClientMetrics(any()) }
    }

    @Test
    fun `onTurnURIsReceived should call urlRewriter for each uri passed in`() {
        val uri1 = "one"
        val uri2 = "two"
        val uri3 = "three"
        every { mockURLRewriter(uri1) } returns uri1
        every { mockURLRewriter(uri2) } returns uri2
        every { mockURLRewriter(uri3) } returns uri3
        val turnUris = listOf<String>(uri1, uri2, uri3)
        val outUris = testContentShareVideoClientObserver.onTurnURIsReceived(turnUris)

        verify(exactly = 3) { mockURLRewriter(any()) }
        assert(outUris.equals(turnUris))
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.video

import android.content.Context
import android.content.pm.PackageInfo
import android.hardware.camera2.CameraManager
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.util.Log
import com.amazonaws.services.chime.sdk.meetings.TestConstant
import com.amazonaws.services.chime.sdk.meetings.analytics.EventAnalyticsController
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.LocalVideoConfiguration
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoSource
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.capture.DefaultCameraCaptureSource
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCore
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCoreFactory
import com.amazonaws.services.chime.sdk.meetings.session.MeetingSessionConfiguration
import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import com.xodee.client.video.VideoClient
import io.mockk.MockKAnnotations
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkConstructor
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.slot
import io.mockk.verify
import java.security.InvalidParameterException
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test

class DefaultVideoClientControllerTest {
    private val messageTopic = "topic"
    private val messageData = "data"
    private val messageLifetimeMs = 3000

    private lateinit var mockContext: Context

    @MockK
    private lateinit var mockPackageInfo: PackageInfo

    @MockK
    private lateinit var mockLogger: Logger

    @MockK
    private lateinit var mockVideoClientStateController: VideoClientStateController

    @MockK
    private lateinit var mockVideoClientObserver: VideoClientObserver

    @MockK
    private lateinit var mockConfiguration: MeetingSessionConfiguration

    @MockK
    private lateinit var mockVideoClient: VideoClient

    @MockK
    private lateinit var mockVideoClientFactory: DefaultVideoClientFactory

    @MockK
    private lateinit var mockEglCoreFactory: EglCoreFactory

    @MockK
    private lateinit var eventAnalyticsController: EventAnalyticsController

    @MockK(relaxed = true)
    private lateinit var mockEglCore: EglCore

    @InjectMockKs
    private lateinit var testVideoClientController: DefaultVideoClientController

    @MockK
    private lateinit var mockVideoSource: VideoSource

    private lateinit var mockCameraManager: CameraManager

    private lateinit var mockLooper: Looper

    @Before
    fun setUp() {
        mockkStatic(System::class, Log::class, VideoClient::class)
        every { Log.d(any(), any()) } returns 0
        every { System.loadLibrary(any()) } just runs

        mockContext = mockk()
        mockCameraManager = mockk(relaxed = true)
        mockLooper = mockk()
        every { mockContext.getSystemService(any()) } returns mockCameraManager

        mockkConstructor(HandlerThread::class)
        every { anyConstructed<HandlerThread>().looper } returns mockLooper
        every { anyConstructed<HandlerThread>().run() } just runs
        mockkConstructor(Handler::class)
        every { anyConstructed<Handler>().looper } returns mockLooper
        mockkConstructor(DefaultCameraCaptureSource::class)
        every { anyConstructed<DefaultCameraCaptureSource>().start() } just runs
        every { anyConstructed<DefaultCameraCaptureSource>().stop() } just runs
        val slot = slot<Runnable>()
        every { anyConstructed<Handler>().post(capture(slot)) } answers {
            slot.captured.run()
            true
        }

        MockKAnnotations.init(this, relaxUnitFun = true)
        every { mockVideoClient.sendDataMessage(any(), any(), any()) } just runs
        every { mockVideoClient.setExternalVideoSource(any(), any()) } just runs
        every { mockVideoClient.setSending(any()) } just runs
        every { mockVideoClientFactory.getVideoClient(mockVideoClientObserver) } returns mockVideoClient
        every { mockEglCoreFactory.createEglCore() } returns mockEglCore
    }

    @Test
    fun `start should call VideoClientStateController start`() {
        testVideoClientController.start()

        verify { mockVideoClientStateController.start() }
    }

    @Test
    fun `stopAndDestroy should call VideoClientStateController stop and release EglCore`() {
        runBlockingTest {
            testVideoClientController.stopAndDestroy()
        }

        coVerify(exactly = 1, timeout = TestConstant.globalScopeTimeoutMs) { mockVideoClientStateController.stop() }
        coVerify(exactly = 1, timeout = TestConstant.globalScopeTimeoutMs) { mockEglCore.release() }
    }

    @Test
    fun `startLocalVideo should start camera capture and then video client`() {
        every { mockVideoClientStateController.canAct(any()) } returns true

        testVideoClientController.startLocalVideo()

        verify { anyConstructed<DefaultCameraCaptureSource>().start() }
        verify { mockVideoClient.setExternalVideoSource(any(), any()) }
        verify { mockVideoClient.setSending(true) }
    }

    @Test
    fun `startLocalVideo should start camera capture and setMaxBitRateKbps with given video config`() {
        every { mockVideoClientStateController.canAct(any()) } returns true
        val localVideoConfig = LocalVideoConfiguration(600)

        testVideoClientController.startLocalVideo(localVideoConfig)

        verify { anyConstructed<DefaultCameraCaptureSource>().start() }
        verify { mockVideoClient.setExternalVideoSource(any(), any()) }
        verify { mockVideoClient.setSending(true) }
        verify { mockVideoClient.setMaxBitRateKbps(600) }
    }

    @Test
    fun `startLocalVideo should not start camera capture and then video client`() {
        every { mockVideoClientStateController.canAct(any()) } returns true

        testVideoClientController.startLocalVideo(mockVideoSource)

        verify(exactly = 0) { anyConstructed<DefaultCameraCaptureSource>().start() }
        verify { mockVideoClient.setExternalVideoSource(any(), any()) }
        verify { mockVideoClient.setSending(true) }
    }

    @Test
    fun `startLocalVideo should not start camera capture and setMaxBitRateKbps with given video config`() {
        every { mockVideoClientStateController.canAct(any()) } returns true
        val localVideoConfig = LocalVideoConfiguration(600)

        testVideoClientController.startLocalVideo(mockVideoSource, localVideoConfig)

        verify(exactly = 0) { anyConstructed<DefaultCameraCaptureSource>().start() }
        verify { mockVideoClient.setExternalVideoSource(any(), any()) }
        verify { mockVideoClient.setSending(true) }
        verify { mockVideoClient.setMaxBitRateKbps(600) }
    }

    @Test
    fun `stopLocalVideo should stop camera capture source if startLocalVideo was called with no source`() {
        every { mockVideoClientStateController.canAct(any()) } returns true

        testVideoClientController.startLocalVideo()
        testVideoClientController.stopLocalVideo()

        verify { anyConstructed<DefaultCameraCaptureSource>().stop() }
        verify { mockVideoClient.setSending(false) }
    }

    @Test
    fun `sendDataMessage should not call videoClient when state is not started`() {
        every { mockVideoClientStateController.canAct(VideoClientState.STARTED) } returns false

        testVideoClientController.sendDataMessage(messageTopic, messageData, messageLifetimeMs)

        verify(exactly = 0) { mockVideoClient.sendDataMessage(any(), any(), any()) }
    }

    @Test
    fun `sendDataMessage should call videoClient when state is started`() {
        every { mockVideoClientStateController.canAct(VideoClientState.STARTED) } returns true

        testVideoClientController.sendDataMessage(messageTopic, messageData, messageLifetimeMs)

        verify(exactly = 1) { mockVideoClient.sendDataMessage(any(), any(), any()) }
    }

    @Test(expected = InvalidParameterException::class)
    fun `sendDataMessage should throw exception when topic is invalid`() {
        every { mockVideoClientStateController.canAct(VideoClientState.STARTED) } returns true

        testVideoClientController.sendDataMessage("!@#$%", messageData, messageLifetimeMs)

        verify(exactly = 0) { mockVideoClient.sendDataMessage(any(), any(), any()) }
    }

    @Test(expected = InvalidParameterException::class)
    fun `sendDataMessage should throw exception when data is oversize`() {
        every { mockVideoClientStateController.canAct(VideoClientState.STARTED) } returns true

        testVideoClientController.sendDataMessage(messageTopic, ByteArray(2049), messageLifetimeMs)

        verify(exactly = 0) { mockVideoClient.sendDataMessage(any(), any(), any()) }
    }

    @Test(expected = InvalidParameterException::class)
    fun `sendDataMessage should throw exception when lifetime ms is negative`() {
        every { mockVideoClientStateController.canAct(VideoClientState.STARTED) } returns true

        testVideoClientController.sendDataMessage(messageTopic, messageData, -1)

        verify(exactly = 0) { mockVideoClient.sendDataMessage(any(), any(), any()) }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.video

import android.content.Context
import com.amazonaws.services.chime.sdk.meetings.audiovideo.AudioVideoObserver
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoFrame
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoRotation
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoTileController
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameI420Buffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameTextureBuffer
import com.amazonaws.services.chime.sdk.meetings.internal.metric.ClientMetricsCollector
import com.amazonaws.services.chime.sdk.meetings.realtime.datamessage.DataMessageObserver
import com.amazonaws.services.chime.sdk.meetings.session.MeetingSessionStatus
import com.amazonaws.services.chime.sdk.meetings.session.MeetingSessionStatusCode
import com.amazonaws.services.chime.sdk.meetings.session.URLRewriter
import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import com.xodee.client.audio.audioclient.AudioClient
import com.xodee.client.video.DataMessage
import com.xodee.client.video.VideoClient
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.slot
import io.mockk.verify
import java.nio.ByteBuffer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test

class DefaultVideoClientObserverTest {
    @MockK
    private lateinit var mockAudioVideoObserver: AudioVideoObserver

    @MockK
    private lateinit var mockVideoTileController: VideoTileController

    @MockK
    private lateinit var mockDataMessageObserver: DataMessageObserver

    @MockK
    private lateinit var mockAnotherDataMessageObserver: DataMessageObserver

    @MockK
    private lateinit var mockContext: Context

    @MockK
    private lateinit var mockLogger: Logger

    @MockK
    private lateinit var mockMetricsCollector: ClientMetricsCollector

    @MockK
    private lateinit var mockVideoClientStateController: VideoClientStateController

    @MockK
    private lateinit var mockDefaultUrlRewriter: URLRewriter

    @MockK
    private lateinit var mockVideoClient: VideoClient

    @MockK(relaxed = true)
    private lateinit var mockMediaVideoFrame: com.xodee.client.video.VideoFrame

    @MockK(relaxed = true)
    private lateinit var mockMediaVideoFrameI420Buffer: com.xodee.client.video.VideoFrameI420Buffer

    @MockK(relaxed = true)
    private lateinit var mockMediaVideoFrameTextureBuffer: com.xodee.client.video.VideoFrameTextureBuffer

    // Required even if not queried
    private val testDataY = ByteBuffer.allocateDirect(0)
    private val testDataU = ByteBuffer.allocateDirect(0)
    private val testDataV = ByteBuffer.allocateDirect(0)

    private val testTimestamp: Long = 1
    private val testRotation = VideoRotation.Rotation90

    private lateinit var testVideoClientObserver: DefaultVideoClientObserver

    private val turnRequestParams =
        TURNRequestParams(
            "meetingId",
            "signalingUrl",
            "turnControlUrl",
            "joinToken"
        )

    private val videoClientSuccessCode = 0
    private val testMessage = "Hello world"
    private val testProfileId = "aliceId"
    private val testVideoId = 1
    private val testDispatcher = TestCoroutineDispatcher()

    private val topic = "topic1"
    private val anotherTopic = "topic2"
    private val dataMessageWithTopic = DataMessage(
        10000,
        topic,
        "hello".toByteArray(),
        "attendeeId",
        "externalId",
        false
    )
    private val dataMessageWithAnotherTopic = DataMessage(
        10000,
        anotherTopic,
        "hello".toByteArray(),
        "attendeeId",
        "externalId",
        false
    )
    private val uri1 = "one"
    private val uri2 = "two"
    private val uri3 = "three"
    private val turnUris = listOf<String>(uri1, uri2, uri3)

    @Before
    fun setUp() {
        mockkStatic(System::class)
        every { System.loadLibrary(any()) } just runs
        Dispatchers.setMain(testDispatcher)
        MockKAnnotations.init(this, relaxUnitFun = true)
        testVideoClientObserver =
            DefaultVideoClientObserver(
                mockContext,
                mockLogger,
                turnRequestParams,
                mockMetricsCollector,
                mockVideoClientStateController,
                mockDefaultUrlRewriter
            )
        testVideoClientObserver.subscribeToVideoClientStateChange(mockAudioVideoObserver)
        testVideoClientObserver.subscribeToVideoTileChange(mockVideoTileController)

        every { mockMediaVideoFrameI420Buffer.dataY } returns testDataY
        every { mockMediaVideoFrameI420Buffer.dataU } returns testDataU
        every { mockMediaVideoFrameI420Buffer.dataV } returns testDataV
        every { mockMediaVideoFrame.timestampNs } returns testTimestamp
        every { mockMediaVideoFrame.rotation } returns 90
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun `isConnecting should notify added observers about video session connecting event`() {
        testVideoClientObserver.isConnecting(mockVideoClient)

        verify { mockAudioVideoObserver.onVideoSessionStartedConnecting() }
    }

    @Test
    fun `didConnect should notify added observers about video session start when control status is OK`() {
        testVideoClientObserver.didConnect(mockVideoClient, videoClientSuccessCode)

        verify { mockAudioVideoObserver.onVideoSessionStarted(any()) }
    }

    @Test
    fun `didConnect should notify added observers about video session start with error status when control status is NOT OK`() {
        testVideoClientObserver.didConnect(
            mockVideoClient,
            VideoClient.VIDEO_CLIENT_STATUS_CALL_AT_CAPACITY_VIEW_ONLY
        )

        verify {
            mockAudioVideoObserver.onVideoSessionStarted(
                MeetingSessionStatus(
                    MeetingSessionStatusCode.VideoAtCapacityViewOnly
                )
            )
        }
    }

    @Test
    fun `didFail should notify added observers about video session failure event`() {
        testVideoClientObserver.didFail(
            mockVideoClient,
            videoClientSuccessCode,
            VideoClient.VIDEO_CLIENT_STATUS_CALL_AT_CAPACITY
        )

        verify {
            mockAudioVideoObserver.onVideoSessionStopped(
                MeetingSessionStatus(
                    MeetingSessionStatusCode.VideoServiceFailed
                )
            )
        }
    }

    @Test
    fun `didStop should notify added observers about video session stop event`() {
        testVideoClientObserver.didStop(mockVideoClient)

        verify {
            mockAudioVideoObserver.onVideoSessionStopped(
                MeetingSessionStatus(
                    MeetingSessionStatusCode.OK
                )
            )
        }
    }

    @Test
    fun `didReceiveFrame should notify added observers about frame receive event and maintains timestamp and rotation`() {
        every { mockMediaVideoFrame.buffer } returns mockMediaVideoFrameI420Buffer
        testVideoClientObserver.didReceiveFrame(
            mockVideoClient,
            mockMediaVideoFrame,
            testProfileId,
            testVideoId,
            VideoClient.VIDEO_CLIENT_NO_PAUSE,
            testVideoId
        )

        val slot = slot<VideoFrame>()
        verify { mockVideoTileController.onReceiveFrame(capture(slot), any(), any(), any()) }
        Assert.assertEquals(slot.captured.rotation, testRotation)
        Assert.assertEquals(slot.captured.timestampNs, testTimestamp)
    }

    @Test
    fun `didReceiveFrame should convert Media I420 frame to SDK I420 frame`() {
        every { mockMediaVideoFrame.buffer } returns mockMediaVideoFrameI420Buffer
        testVideoClientObserver.didReceiveFrame(
                mockVideoClient,
                mockMediaVideoFrame,
                testProfileId,
                testVideoId,
                VideoClient.VIDEO_CLIENT_NO_PAUSE,
                testVideoId
        )

        val slot = slot<VideoFrame>()
        verify { mockVideoTileController.onReceiveFrame(capture(slot), any(), any(), any()) }
        assert(slot.captured.buffer is VideoFrameI420Buffer)
    }

    @Test
    fun `didReceiveFrame should convert Media texture frame buffer to SDK texture frame buffer`() {
        every { mockMediaVideoFrame.buffer } returns mockMediaVideoFrameTextureBuffer
        testVideoClientObserver.didReceiveFrame(
                mockVideoClient,
                mockMediaVideoFrame,
                testProfileId,
                testVideoId,
                VideoClient.VIDEO_CLIENT_NO_PAUSE,
                testVideoId
        )

        val slot = slot<VideoFrame>()
        verify { mockVideoTileController.onReceiveFrame(capture(slot), any(), any(), any()) }
        assert(slot.captured.buffer is VideoFrameTextureBuffer)
    }

    @Test
    fun `onMetrics should call clientMetricsCollector to process video metrics`() {
        testVideoClientObserver.onMetrics(intArrayOf(1), doubleArrayOf(1.0))

        verify { mockMetricsCollector.processVideoClientMetrics(any()) }
    }

    @Test
    fun `onLogMessage should call logger`() {
        testVideoClientObserver.onLogMessage(AudioClient.L_ERROR, testMessage)

        verify { mockLogger.error(any(), testMessage) }
    }

    @Test
    fun `onLogMessage should NOT call logger when message is null`() {
        testVideoClientObserver.onLogMessage(AudioClient.L_FATAL, null)

        verify(exactly = 0) { mockLogger.error(any(), any()) }
    }

    @Test
    fun `onLogMessage should NOT call logger when log level is non error or non fatal`() {
        testVideoClientObserver.onLogMessage(AudioClient.L_INFO, testMessage)

        verify(exactly = 0) { mockLogger.error(any(), testMessage) }
    }

    @Test
    fun `unsubscribeFromVideoClientStateChange should result in no notification`() {
        testVideoClientObserver.unsubscribeFromVideoClientStateChange(mockAudioVideoObserver)

        testVideoClientObserver.isConnecting(mockVideoClient)

        verify(exactly = 0) { mockAudioVideoObserver.onVideoSessionStartedConnecting() }
    }

    @Test
    fun `unsubscribeFromVideoTile should result in no notification`() {
        every { mockMediaVideoFrame.buffer } returns mockMediaVideoFrameI420Buffer

        testVideoClientObserver.unsubscribeFromVideoTileChange(mockVideoTileController)

        testVideoClientObserver.didReceiveFrame(
            mockVideoClient,
            mockMediaVideoFrame,
            testProfileId,
            testVideoId,
            VideoClient.VIDEO_CLIENT_NO_PAUSE,
            testVideoId
        )

        verify(exactly = 0) { mockVideoTileController.onReceiveFrame(any(), any(), any(), any()) }
    }

    @Test
    fun `onDataMessageReceived should notify observers that subscribe to the topic`() {
        testVideoClientObserver.subscribeToReceiveDataMessage(topic, mockDataMessageObserver)
        testVideoClientObserver.subscribeToReceiveDataMessage(anotherTopic, mockAnotherDataMessageObserver)

        testVideoClientObserver.onDataMessageReceived(arrayOf(dataMessageWithTopic))

        verify(exactly = 1) { mockDataMessageObserver.onDataMessageReceived(any()) }
    }

    @Test
    fun `onDataMessageReceived should notify all observers that subscribe to the topic`() {
        testVideoClientObserver.subscribeToReceiveDataMessage(topic, mockDataMessageObserver)
        testVideoClientObserver.subscribeToReceiveDataMessage(topic, mockAnotherDataMessageObserver)

        testVideoClientObserver.onDataMessageReceived(arrayOf(dataMessageWithTopic))

        verify(exactly = 1) { mockDataMessageObserver.onDataMessageReceived(any()) }
        verify(exactly = 1) { mockAnotherDataMessageObserver.onDataMessageReceived(any()) }
    }

    @Test
    fun `onDataMessageReceived should notify observers for the following message when the first message's topic is not subscribed to`() {
        testVideoClientObserver.subscribeToReceiveDataMessage(topic, mockDataMessageObserver)

        testVideoClientObserver.onDataMessageReceived(arrayOf(dataMessageWithAnotherTopic, dataMessageWithTopic))

        verify(exactly = 1) { mockDataMessageObserver.onDataMessageReceived(any()) }
    }

    @Test
    fun `onTurnURIsReceived should call urlRewriter for each uri passed in`() {
        every { mockDefaultUrlRewriter(uri1) } returns uri1
        every { mockDefaultUrlRewriter(uri2) } returns uri2
        every { mockDefaultUrlRewriter(uri3) } returns uri3
        val outUris = testVideoClientObserver.onTurnURIsReceived(turnUris)

        verify(exactly = 3) { mockDefaultUrlRewriter(any()) }
        assert(outUris.equals(turnUris))
    }

    @Test
    fun `cameraSendIsAvailable should notify onCameraSendAvailabilityUpdated of available is false`() {
        testVideoClientObserver.cameraSendIsAvailable(mockVideoClient, false)

        verify { mockAudioVideoObserver.onCameraSendAvailabilityUpdated(false) }
    }

    @Test
    fun `cameraSendIsAvailable should notify onCameraSendAvailabilityUpdated of available is true`() {
        testVideoClientObserver.cameraSendIsAvailable(mockVideoClient, true)

        verify { mockAudioVideoObserver.onCameraSendAvailabilityUpdated(true) }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.video

import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class DefaultVideoClientStateControllerTest {
    @MockK
    private lateinit var mockLogger: Logger

    @MockK
    private lateinit var mockVideoClientLifecycleHandler: VideoClientLifecycleHandler

    private lateinit var testVideoClientStateController: DefaultVideoClientStateController

    @Before
    fun setUp() {
        MockKAnnotations.init(this, relaxUnitFun = true)
        testVideoClientStateController =
            DefaultVideoClientStateController(
                mockLogger
            )
        testVideoClientStateController.bindLifecycleHandler(mockVideoClientLifecycleHandler)
    }

    @Test
    fun `start should initialize and start video client when video client state is uninitialized`() {
        testVideoClientStateController.start()

        verify { mockVideoClientLifecycleHandler.initializeVideoClient() }
        verify { mockVideoClientLifecycleHandler.startVideoClient() }
    }

    @Test
    fun `start should only start video client when video client state is initialized`() {
        testVideoClientStateController.updateState(VideoClientState.INITIALIZED)

        testVideoClientStateController.start()

        verify(exactly = 0) { mockVideoClientLifecycleHandler.initializeVideoClient() }
        verify { mockVideoClientLifecycleHandler.startVideoClient() }
    }

    @Test
    fun `start should do nothing when video client state is started`() {
        testVideoClientStateController.updateState(VideoClientState.STARTED)

        testVideoClientStateController.start()

        verify(exactly = 0) { mockVideoClientLifecycleHandler.initializeVideoClient() }
        verify(exactly = 0) { mockVideoClientLifecycleHandler.startVideoClient() }
    }

    @Test
    fun `start should only start video client when video client state is stopped`() {
        testVideoClientStateController.updateState(VideoClientState.STOPPED)

        testVideoClientStateController.start()

        verify(exactly = 0) { mockVideoClientLifecycleHandler.initializeVideoClient() }
        verify { mockVideoClientLifecycleHandler.startVideoClient() }
    }

    @Test
    fun `stop should do nothing when video client state is uninitialized`() {
        testVideoClientStateController.stop()

        verify(exactly = 0) { mockVideoClientLifecycleHandler.stopVideoClient() }
        verify(exactly = 0) { mockVideoClientLifecycleHandler.destroyVideoClient() }
    }

    @Test
    fun `stop should only destroy video client when video client state is initialized`() {
        testVideoClientStateController.updateState(VideoClientState.INITIALIZED)

        testVideoClientStateController.stop()

        verify(exactly = 0) { mockVideoClientLifecycleHandler.stopVideoClient() }
        verify { mockVideoClientLifecycleHandler.destroyVideoClient() }
    }

    @Test
    fun `stop should stop and destroy video client when video client state is started`() {
        testVideoClientStateController.updateState(VideoClientState.STARTED)

        testVideoClientStateController.stop()

        verify { mockVideoClientLifecycleHandler.stopVideoClient() }
        verify { mockVideoClientLifecycleHandler.destroyVideoClient() }
    }

    @Test
    fun `stop should only destroy video client when video client state is stopped`() {
        testVideoClientStateController.updateState(VideoClientState.STOPPED)

        testVideoClientStateController.stop()

        verify(exactly = 0) { mockVideoClientLifecycleHandler.stopVideoClient() }
        verify { mockVideoClientLifecycleHandler.destroyVideoClient() }
    }

    @Test
    fun `canAct should return false when video client state is less than minimum required state`() {
        val testOutput: Boolean =
            testVideoClientStateController.canAct(VideoClientState.INITIALIZED)

        assertFalse(testOutput)
    }

    @Test
    fun `canAct should return true when video client state is equal to minimum required state`() {
        testVideoClientStateController.updateState(VideoClientState.INITIALIZED)

        val testOutput: Boolean =
            testVideoClientStateController.canAct(VideoClientState.INITIALIZED)

        assertTrue(testOutput)
    }

    @Test
    fun `canAct should return true when video client state is greater than minimum required state`() {
        testVideoClientStateController.updateState(VideoClientState.STARTED)

        val testOutput: Boolean =
            testVideoClientStateController.canAct(VideoClientState.INITIALIZED)

        assertTrue(testOutput)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.video

import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoContentHint
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoFrame
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoRotation
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoSource
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameBuffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameI420Buffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameRGBABuffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameTextureBuffer
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.slot
import io.mockk.verify
import java.lang.Exception
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class VideoSourceAdapterTest {
    @MockK
    private lateinit var mockSdkVideoSource: VideoSource

    @MockK
    private lateinit var mockVideoFrame: VideoFrame

    @MockK
    private lateinit var mockSdkVideoFrameBuffer: VideoFrameBuffer

    @MockK
    private lateinit var mockSdkVideoFrameI420Buffer: VideoFrameI420Buffer

    @MockK
    private lateinit var mockSdkVideoFrameRGBABuffer: VideoFrameRGBABuffer

    @MockK
    private lateinit var mockSdkVideoFrameTextureBuffer: VideoFrameTextureBuffer

    @MockK
    private lateinit var mockMediaVideoSink: com.xodee.client.video.VideoSink

    private val testSDKContentHint = VideoContentHint.Motion
    private val testMediaContentHint = com.xodee.client.video.ContentHint.MOTION

    private val testTimestamp: Long = 1
    private val testRotation = VideoRotation.Rotation90

    @Before
    fun setUp() {
        MockKAnnotations.init(this, relaxUnitFun = true, relaxed = true)
        every { mockSdkVideoSource.contentHint } returns testSDKContentHint
        every { mockVideoFrame.timestampNs } returns testTimestamp
        every { mockVideoFrame.rotation } returns testRotation
    }

    @Test
    fun `Content hint is passed through`() {
        val adapter = VideoSourceAdapter()
        adapter.source = mockSdkVideoSource

        assertEquals(adapter.contentHint, testMediaContentHint)
    }

    @Test
    fun `Timestamp and rotation is passed through`() {
        val adapter = VideoSourceAdapter()
        adapter.source = mockSdkVideoSource
        adapter.addSink(mockMediaVideoSink)

        adapter.onVideoFrameReceived(VideoFrame(testTimestamp, mockSdkVideoFrameTextureBuffer, testRotation))

        val slot = slot<com.xodee.client.video.VideoFrame>()
        verify(exactly = 1) { mockMediaVideoSink.onFrameCaptured(capture(slot)) }
        assertEquals(slot.captured.rotation, testRotation.degrees)
        assertEquals(slot.captured.timestampNs, testTimestamp)
    }

    @Test
    fun `Passing a generic SDK buffer results in an exception`() {
        val adapter = VideoSourceAdapter()
        adapter.source = mockSdkVideoSource
        adapter.addSink(mockMediaVideoSink)

        var exceptionThrown = false
        try {
            adapter.onVideoFrameReceived(VideoFrame(testTimestamp, mockSdkVideoFrameBuffer, testRotation))
        } catch (e: Exception) {
            exceptionThrown = true
        }
        assert(exceptionThrown)
    }

    @Test
    fun `Passing an I420 SDK buffer results in an I420 Media buffer`() {
        val adapter = VideoSourceAdapter()
        adapter.source = mockSdkVideoSource
        adapter.addSink(mockMediaVideoSink)

        adapter.onVideoFrameReceived(VideoFrame(testTimestamp, mockSdkVideoFrameI420Buffer, testRotation))

        val slot = slot<com.xodee.client.video.VideoFrame>()
        verify(exactly = 1) { mockMediaVideoSink.onFrameCaptured(capture(slot)) }
        assert(slot.captured.buffer is com.xodee.client.video.VideoFrameI420Buffer)
    }

    @Test
    fun `Passing an RGBA SDK buffer results in an RGBA Media buffer`() {
        val adapter = VideoSourceAdapter()
        adapter.source = mockSdkVideoSource
        adapter.addSink(mockMediaVideoSink)

        adapter.onVideoFrameReceived(VideoFrame(testTimestamp, mockSdkVideoFrameRGBABuffer, testRotation))

        val slot = slot<com.xodee.client.video.VideoFrame>()
        verify(exactly = 1) { mockMediaVideoSink.onFrameCaptured(capture(slot)) }
        assert(slot.captured.buffer is com.xodee.client.video.VideoFrameRGBABuffer)
    }

    @Test
    fun `Passing a texture SDK buffer results in a texture Media buffer`() {
        val adapter = VideoSourceAdapter()
        adapter.source = mockSdkVideoSource
        adapter.addSink(mockMediaVideoSink)

        adapter.onVideoFrameReceived(VideoFrame(testTimestamp, mockSdkVideoFrameTextureBuffer, testRotation))

        val slot = slot<com.xodee.client.video.VideoFrame>()
        verify(exactly = 1) { mockMediaVideoSink.onFrameCaptured(capture(slot)) }
        assert(slot.captured.buffer is com.xodee.client.video.VideoFrameTextureBuffer)
    }
}


package com.amazonaws.services.chime.sdk.meetings.internal.video.gl

import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLContext
import android.opengl.EGLDisplay
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.DefaultEglCore
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.DefaultEglCoreFactory
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCore
import com.amazonaws.services.chime.sdk.meetings.utils.RefCountDelegate
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.mockkConstructor
import io.mockk.mockkStatic
import io.mockk.slot
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test

class DefaultEglCoreFactoryTest {
    @MockK
    private lateinit var mockDisplay: EGLDisplay

    @MockK
    private lateinit var mockContext: EGLContext

    @MockK
    private var rootEglCore: EglCore? = null

    @MockK
    private lateinit var mockConfig: EGLConfig

    @MockK
    private lateinit var mockDefaultEglCore: DefaultEglCore

    @MockK
    private var refCountDelegate: RefCountDelegate? = null

    @InjectMockKs
    private lateinit var factory: DefaultEglCoreFactory

    @Before
    fun setUp() {
        mockkStatic(EGL14::class)
        mockkConstructor(DefaultEglCore::class)
        MockKAnnotations.init(this, relaxUnitFun = true)
        every { EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY) } returns mockDisplay
        every { EGL14.eglInitialize(any(), any(), any(), any(), any()) } returns true
        every { EGL14.eglQueryContext(any(), any(), any(), any(), any()) } returns true
        val slot = slot<Array<EGLConfig>>()
        every { EGL14.eglChooseConfig(any(), any(), any(), capture(slot), any(), any(), any(), any()) } answers {
            slot.captured[0] = mockConfig
            true
        }
        factory = DefaultEglCoreFactory(mockContext)
    }

    @Test
    fun `createEglCore should create rootEglCore`() {
        factory.createEglCore()

        assertNotNull(rootEglCore)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.video.gl

import android.graphics.Matrix
import android.graphics.SurfaceTexture
import android.opengl.EGL14
import android.opengl.EGLSurface
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoFrame
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameI420Buffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameRGBABuffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameTextureBuffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCore
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCoreFactory
import com.xodee.client.video.YuvUtil
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkConstructor
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.slot
import io.mockk.verify
import java.nio.ByteBuffer
import kotlinx.coroutines.android.HandlerDispatcher
import org.junit.Before
import org.junit.Test

class DefaultGlVideoFrameDrawerTest {

    @MockK
    private lateinit var mockEglCoreFactory: EglCoreFactory

    private lateinit var mockHandlerDispatcher: HandlerDispatcher

    @MockK(relaxed = true)
    private lateinit var mockEglCore: EglCore

    private lateinit var testGlVideoFrameDrawer: DefaultGlVideoFrameDrawer

    private lateinit var mockLooper: Looper

    @MockK(relaxed = true)
    private lateinit var mockMatrix: Matrix

    @MockK(relaxed = true)
    private lateinit var mockEglSurface: EGLSurface

    private val testWidth = 10
    private val testHeight = 10

    private val testTextureId = 1

    // Required even if not queried
    private val testDataY = ByteBuffer.allocateDirect(0)
    private val testDataU = ByteBuffer.allocateDirect(0)
    private val testDataV = ByteBuffer.allocateDirect(0)

    private val testStrideY = 1
    private val testStrideU = 2
    private val testStrideV = 3

    private val testViewportX = 4
    private val testViewportY = 5
    private val testViewportWidth = 6
    private val testViewportHeight = 7

    @Before
    fun setUp() {
        mockLooper = mockk()
        mockkConstructor(HandlerThread::class)
        every { anyConstructed<HandlerThread>().looper } returns mockLooper
        every { mockLooper.quit() } just runs

        mockHandlerDispatcher = mockk()
        mockkConstructor(Handler::class)
        every { anyConstructed<Handler>().looper } returns mockLooper
        val slot = slot<Runnable>()
        every { anyConstructed<Handler>().post(capture(slot)) } answers {
            slot.captured.run()
            true
        }

        // Previous mocks need to be done before constructor call
        MockKAnnotations.init(this, relaxUnitFun = true)

        every { mockEglCoreFactory.createEglCore() } returns mockEglCore

        mockkStatic(EGL14::class)
        every { EGL14.eglCreatePbufferSurface(any(), any(), any(), any()) } returns mockEglSurface

        mockkStatic(GLES20::class)

        mockkConstructor(SurfaceTexture::class)

        mockkObject(GlUtil)
        every { GlUtil.createProgram(any(), any()) } returns 1

        mockkStatic(YuvUtil::class)
        every { YuvUtil.copyPlane(any(), any(), any(), any(), any(), any()) } just runs

        testGlVideoFrameDrawer = DefaultGlVideoFrameDrawer()
    }

    @Test
    fun `drawFrame with TEXTURE_2D calls appropriate OpenGLES function and sets viewport as expected`() {
        val testFrame = VideoFrame(0,
                VideoFrameTextureBuffer(
                        testWidth,
                        testHeight,
                        testTextureId,
                        null,
                        VideoFrameTextureBuffer.Type.TEXTURE_2D,
                        Runnable {})
        )

        testGlVideoFrameDrawer.drawFrame(
                testFrame,
                testViewportX,
                testViewportY,
                testViewportWidth,
                testViewportHeight,
                null)

        verify { GLES20.glActiveTexture(GLES20.GL_TEXTURE0) }
        verify { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, testTextureId) }
        verify { GLES20.glViewport(testViewportX, testViewportY, testViewportWidth, testViewportHeight) }
    }

    @Test
    fun `drawFrame with TEXTURE_OES calls appropriate OpenGLES function and sets viewport as expected`() {
        val testFrame = VideoFrame(0,
                VideoFrameTextureBuffer(
                        testWidth,
                        testHeight,
                        testTextureId,
                        null,
                        VideoFrameTextureBuffer.Type.TEXTURE_OES,
                        Runnable {})
        )

        testGlVideoFrameDrawer.drawFrame(
                testFrame,
                testViewportX,
                testViewportY,
                testViewportWidth,
                testViewportHeight,
                null)

        verify { GLES20.glActiveTexture(GLES20.GL_TEXTURE0) }
        verify { GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, testTextureId) }
        verify { GLES20.glViewport(testViewportX, testViewportY, testViewportWidth, testViewportHeight) }
    }

    @Test
    fun `drawFrame with YUV buffer calls appropriate OpenGLES function and sets viewport as expected`() {
        val testFrame = VideoFrame(0,
                VideoFrameI420Buffer(
                        testWidth,
                        testHeight,
                        testDataY,
                        testDataU,
                        testDataV,
                        testStrideY,
                        testStrideU,
                        testStrideV,
                        Runnable {})
        )

        testGlVideoFrameDrawer.drawFrame(
                testFrame,
                testViewportX,
                testViewportY,
                testViewportWidth,
                testViewportHeight,
                null)

        verify { GLES20.glActiveTexture(GLES20.GL_TEXTURE0) }
        verify { GLES20.glActiveTexture(GLES20.GL_TEXTURE1) }
        verify { GLES20.glActiveTexture(GLES20.GL_TEXTURE2) }
        verify { GLES20.glViewport(testViewportX, testViewportY, testViewportWidth, testViewportHeight) }
    }

    @Test
    fun `drawFrame with RGB buffer calls appropriate OpenGLES function and sets viewport as expected`() {
        val testFrame = VideoFrame(0,
                VideoFrameRGBABuffer(
                        testWidth,
                        testHeight,
                        testDataY,
                        testStrideY,
                        Runnable {})
        )

        testGlVideoFrameDrawer.drawFrame(
                testFrame,
                testViewportX,
                testViewportY,
                testViewportWidth,
                testViewportHeight,
                null)

        verify { GLES20.glActiveTexture(GLES20.GL_TEXTURE0) }
        verify { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, any()) }
        verify { GLES20.glViewport(testViewportX, testViewportY, testViewportWidth, testViewportHeight) }
    }

    @Test
    fun `release deletes used program to release resources`() {
        val testFrame = VideoFrame(0,
                VideoFrameRGBABuffer(
                        testWidth,
                        testHeight,
                        testDataY,
                        testStrideY,
                        Runnable {})
        )

        testGlVideoFrameDrawer.drawFrame(
                testFrame,
                testViewportX,
                testViewportY,
                testViewportWidth,
                testViewportHeight,
                null)
        testGlVideoFrameDrawer.release()

        verify { GLES20.glDeleteProgram(1) }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.realtime

import com.amazonaws.services.chime.sdk.meetings.internal.audio.AudioClientController
import com.amazonaws.services.chime.sdk.meetings.internal.audio.AudioClientObserver
import com.amazonaws.services.chime.sdk.meetings.internal.video.VideoClientController
import com.amazonaws.services.chime.sdk.meetings.internal.video.VideoClientObserver
import com.amazonaws.services.chime.sdk.meetings.realtime.datamessage.DataMessageObserver
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class DefaultRealtimeControllerTest {
    private val messageTopic = "topic"
    private val messageData = "data"
    private val messageLifetimeMs = 3000

    @MockK
    private lateinit var mockRealtimeObserver: RealtimeObserver

    @MockK
    private lateinit var mockDataMessageObserver: DataMessageObserver

    @MockK
    private lateinit var mockTranscriptEventObserver: TranscriptEventObserver

    @MockK
    private lateinit var audioClientObserver: AudioClientObserver

    @MockK
    private lateinit var audioClientController: AudioClientController

    @MockK
    private lateinit var videoClientController: VideoClientController

    @MockK
    private lateinit var videoClientObserver: VideoClientObserver

    @InjectMockKs
    private lateinit var realtimeController: DefaultRealtimeController

    @Before
    fun setup() = MockKAnnotations.init(this, relaxUnitFun = true)

    @Test
    fun `realtimeLocalMute should call audioClientController setMute with true and return the status`() {
        every { audioClientController.setMute(true) } returns true
        assertTrue(realtimeController.realtimeLocalMute())
        verify { audioClientController.setMute(true) }
    }

    @Test
    fun `realtimeLocalUnmute should call audioClientController setMute with false and return the status`() {
        every { audioClientController.setMute(false) } returns true
        assertTrue(realtimeController.realtimeLocalUnmute())
        verify { audioClientController.setMute(false) }
    }

    @Test
    fun `addRealtimeObserver should call audioClientController subscribeToRealTimeEvents with given observer`() {
        realtimeController.addRealtimeObserver(mockRealtimeObserver)
        verify { audioClientObserver.subscribeToRealTimeEvents(mockRealtimeObserver) }
    }

    @Test
    fun `removeRealtimeObserver should call audioClientController unsubscribeFromRealTimeEvents with given observer`() {
        realtimeController.removeRealtimeObserver(mockRealtimeObserver)
        verify { audioClientObserver.unsubscribeFromRealTimeEvents(mockRealtimeObserver) }
    }

    @Test
    fun `realtimeSendDataMessage should call videoClientController sendDataMessage with given data`() {
        realtimeController.realtimeSendDataMessage(messageTopic, messageData, messageLifetimeMs)
        verify { videoClientController.sendDataMessage(messageTopic, messageData, messageLifetimeMs) }
    }

    @Test
    fun `addRealtimeDataMessageObserver should call videoClientObserver subscribeToReceiveDataMessage with given observer`() {
        realtimeController.addRealtimeDataMessageObserver(messageTopic, mockDataMessageObserver)
        verify { videoClientObserver.subscribeToReceiveDataMessage(messageTopic, mockDataMessageObserver) }
    }

    @Test
    fun `removeRealtimeDataMessageObserverFromTopic should call videoClientObserver unsubscribeFromReceiveDataMessage with given topic`() {
        realtimeController.removeRealtimeDataMessageObserverFromTopic(messageTopic)
        verify { videoClientObserver.unsubscribeFromReceiveDataMessage(messageTopic) }
    }

    @Test
    fun `addRealtimeTranscriptEventObserver should call audioClientObserver subscribeToTranscriptEvent with given observer`() {
        realtimeController.addRealtimeTranscriptEventObserver(mockTranscriptEventObserver)
        verify { audioClientObserver.subscribeToTranscriptEvent(mockTranscriptEventObserver) }
    }

    @Test
    fun `removeRealtimeTranscriptEventObserver should call audioClientObserver unsubscribeFromTranscriptEvent with given observer`() {
        realtimeController.removeRealtimeTranscriptEventObserver(mockTranscriptEventObserver)
        verify { audioClientObserver.unsubscribeFromTranscriptEvent(mockTranscriptEventObserver) }
    }

    @Test
    fun `realtimeSetVoiceFocusEnabled(true) should call audioClientController setVoiceFocusEnabled with true and return the status`() {
        every { audioClientController.setVoiceFocusEnabled(true) } returns true
        assertTrue(realtimeController.realtimeSetVoiceFocusEnabled(true))
        verify { audioClientController.setVoiceFocusEnabled(true) }
    }

    @Test
    fun `realtimeIsVoiceFocusEnabled() should call audioClientController IsVoiceFocusEnabled and return the status true`() {
        every { audioClientController.isVoiceFocusEnabled() } returns true
        assertTrue(realtimeController.realtimeIsVoiceFocusEnabled())
        verify { audioClientController.isVoiceFocusEnabled() }
    }

    @Test
    fun `realtimeSetVoiceFocusEnabled(false) should call audioClientController setVoiceFocusEnabled with false and return the status`() {
        every { audioClientController.setVoiceFocusEnabled(false) } returns true
        assertTrue(realtimeController.realtimeSetVoiceFocusEnabled(false))
        verify { audioClientController.setVoiceFocusEnabled(false) }
    }

    @Test
    fun `realtimeIsVoiceFocusEnabled() should call audioClientController IsVoiceFocusEnabled and return the status false`() {
        every { audioClientController.isVoiceFocusEnabled() } returns false
        assertFalse(realtimeController.realtimeIsVoiceFocusEnabled())
        verify { audioClientController.isVoiceFocusEnabled() }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.realtime.datamessage

import com.google.gson.Gson
import org.junit.Assert.assertEquals
import org.junit.Test

class DataMessageTest {
    @Test
    fun `text should return data as a string`() {
        val dataString = "hello"
        val dataBytes = dataString.toByteArray()
        val dataMessage = DataMessage(
            10000,
            "topic",
            dataBytes,
            "attendeeId",
            "externalId",
            false)

        assertEquals(dataString, dataMessage.text())
    }

    @Test
    fun `fromJson should return data as a given type`() {
        val obj = CustomerDataClass("hello", 1)
        val dataBytes = Gson().toJson(obj).toByteArray()
        val dataMessage = DataMessage(
            10000,
            "topic",
            dataBytes,
            "attendeeId",
            "externalId",
            false
        )

        val objFromJson = dataMessage.fromJson(CustomerDataClass::class.java)

        assertEquals(obj, objFromJson)
    }

    data class CustomerDataClass(val string: String, val number: Int)
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.session

import android.content.Context
import android.content.Intent
import android.content.res.AssetManager
import android.hardware.camera2.CameraManager
import android.media.AudioManager
import android.os.HandlerThread
import android.os.Looper
import android.util.Log
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCoreFactory
import com.amazonaws.services.chime.sdk.meetings.ingestion.EventReporterFactory
import com.amazonaws.services.chime.sdk.meetings.internal.audio.AudioClientFactory
import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import com.xodee.client.audio.audioclient.AudioClient
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockkClass
import io.mockk.mockkConstructor
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.runs
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test

class DefaultMeetingSessionTest {
    @MockK
    private lateinit var configuration: MeetingSessionConfiguration

    @MockK
    private lateinit var contentConfiguration: MeetingSessionConfiguration

    @MockK
    private lateinit var logger: Logger

    @MockK
    private lateinit var context: Context

    @MockK
    private lateinit var mockAudioClient: AudioClient

    @MockK
    private lateinit var assetManager: AssetManager

    @MockK
    private lateinit var mockEglCoreFactory: EglCoreFactory

    @MockK
    private lateinit var mockLooper: Looper

    @MockK
    private lateinit var eventReporterFactory: EventReporterFactory

    private lateinit var meetingSession: DefaultMeetingSession

    @Before
    fun setup() {
        // Mock Log.d first because initializing the AudioClient mock appears to fail otherwise
        mockkStatic(System::class, Log::class)
        every { System.loadLibrary(any()) } just runs
        every { Log.d(any(), any()) } returns 0
        MockKAnnotations.init(this, relaxed = true)
        every { context.assets } returns assetManager
        every { context.registerReceiver(any(), any()) } returns mockkClass(Intent::class)
        val audioManager = mockkClass(AudioManager::class)
        every { audioManager.mode = any() } returns Unit
        every { audioManager.mode } returns AudioManager.MODE_NORMAL
        every { audioManager.isSpeakerphoneOn } returns true
        val cameraManager = mockkClass(CameraManager::class)
        every { cameraManager.cameraIdList } returns emptyArray()
        every { context.getSystemService(Context.AUDIO_SERVICE) } returns audioManager
        every { context.getSystemService(Context.CAMERA_SERVICE) } returns cameraManager
        mockkObject(AudioClientFactory.Companion)
        every { AudioClientFactory.getAudioClient(any(), any()) } returns mockAudioClient
        every { logger.info(any(), any()) } just runs
        every { configuration.createContentShareMeetingSessionConfiguration() } returns contentConfiguration
        every { eventReporterFactory.createEventReporter() } returns null
        mockkConstructor(HandlerThread::class)
        every { anyConstructed<HandlerThread>().looper } returns mockLooper
        every { anyConstructed<HandlerThread>().run() } just runs

        meetingSession = DefaultMeetingSession(configuration, logger, context, mockEglCoreFactory, eventReporterFactory)
    }

    @Test
    fun `constructor should return non-null instance`() {
        assertNotNull(meetingSession)
    }

    @Test
    fun `audioVideo should return non-null instance`() {
        assertNotNull(meetingSession.audioVideo)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.session

import io.mockk.spyk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

class MeetingSessionConfigurationTest {
    // Meeting
    private val externalMeetingId = "I am the meeting"
    private val mediaRegion = "us-east-1"
    private val meetingId = "meetingId"

    // Attendee
    private val attendeeId = "attendeeId"
    private val externalUserId = "Alice"
    private val joinToken = "joinToken"

    // MediaPlacement
    private val audioFallbackURL = "audioFallbackURL"
    private val audioHostURL = "audioHostURL"
    private val turnControlURL = "turnControlURL"
    private val signalingURL = "signalingURL"
    private val ingestionURL = "ingestionURL"

    @Test
    fun `constructor should return object with data from parameters`() {
        val meetingSessionConfiguration = MeetingSessionConfiguration(
            CreateMeetingResponse(
                Meeting(
                    externalMeetingId,
                    MediaPlacement(audioFallbackURL, audioHostURL, signalingURL, turnControlURL, ingestionURL),
                    mediaRegion,
                    meetingId
                )
            ), CreateAttendeeResponse(Attendee(attendeeId, externalUserId, joinToken))
        )

        assertEquals(meetingId, meetingSessionConfiguration.meetingId)
        assertEquals(audioHostURL, meetingSessionConfiguration.urls.audioHostURL)
        assertEquals(audioFallbackURL, meetingSessionConfiguration.urls.audioFallbackURL)
        assertEquals(turnControlURL, meetingSessionConfiguration.urls.turnControlURL)
        assertEquals(signalingURL, meetingSessionConfiguration.urls.signalingURL)
        assertEquals(attendeeId, meetingSessionConfiguration.credentials.attendeeId)
        assertEquals(joinToken, meetingSessionConfiguration.credentials.joinToken)
        assertEquals(ingestionURL, meetingSessionConfiguration.urls.ingestionURL)
    }

    @Test
    fun `constructor should return null externalMeetingId when not provided through Meeting`() {
        val meetingSessionConfiguration = MeetingSessionConfiguration(
            CreateMeetingResponse(
                Meeting(
                    null,
                    MediaPlacement(audioFallbackURL, audioHostURL, signalingURL, turnControlURL),
                    mediaRegion,
                    meetingId
                )
            ), CreateAttendeeResponse(Attendee(attendeeId, externalUserId, joinToken))
        )

        assertNull(meetingSessionConfiguration.externalMeetingId)
    }

    @Test
    fun `constructor should return null ingestionUrl when not provided through Meeting`() {
        val meetingSessionConfiguration = MeetingSessionConfiguration(
            CreateMeetingResponse(
                Meeting(
                    null,
                    MediaPlacement(audioFallbackURL, audioHostURL, signalingURL, turnControlURL),
                    mediaRegion,
                    meetingId
                )
            ), CreateAttendeeResponse(Attendee(attendeeId, externalUserId, joinToken))
        )

        assertNull(meetingSessionConfiguration.urls.ingestionURL)
    }

    @Test
    fun `constructor should return null externalMeetingId when not provided through constructor`() {
        val creds = MeetingSessionCredentials(
            attendeeId,
            externalUserId,
            joinToken
        )
        val urls = MeetingSessionURLs(
            audioFallbackURL,
            audioHostURL,
            turnControlURL,
            signalingURL,
            ::defaultUrlRewriter
        )
        val meetingSessionConfiguration = MeetingSessionConfiguration(
            meetingId,
            creds,
            urls
        )

        assertNull(meetingSessionConfiguration.externalMeetingId)
    }

    @Test
    fun `UrlRewriter should replace given urls`() {
        val mockRewrite = spyk(::mockUrlRewriter)
        val meetingSessionConfiguration = MeetingSessionConfiguration(
            CreateMeetingResponse(
                Meeting(
                    externalMeetingId,
                    MediaPlacement(audioFallbackURL, audioHostURL, signalingURL, turnControlURL),
                    mediaRegion,
                    meetingId
                )
            ), CreateAttendeeResponse(Attendee(attendeeId, externalUserId, joinToken)),
            mockRewrite)

        assertEquals(replaceString(audioHostURL),
            meetingSessionConfiguration.urls.audioHostURL)
        assertEquals(replaceString(audioFallbackURL),
            meetingSessionConfiguration.urls.audioFallbackURL)
        assertEquals(replaceString(turnControlURL),
            meetingSessionConfiguration.urls.turnControlURL)
        assertEquals(replaceString(signalingURL),
            meetingSessionConfiguration.urls.signalingURL)
    }

    private fun replaceString(str: String): String {
        return str.replace("URL", "Hello")
    }

    private fun mockUrlRewriter(url: String): String {
        return replaceString(url)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.session

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

class MeetingSessionStatusCodeTest {
    @Test
    fun `from should return MeetingSessionStatusCode when defined value`() {
        assertEquals(
            MeetingSessionStatusCode.OK,
            MeetingSessionStatusCode.from(0)
        )
        assertEquals(
            MeetingSessionStatusCode.AudioJoinedFromAnotherDevice,
            MeetingSessionStatusCode.from(2)
        )
        assertEquals(
            MeetingSessionStatusCode.AudioCallEnded,
            MeetingSessionStatusCode.from(6)
        )
        assertEquals(
            MeetingSessionStatusCode.AudioDisconnected,
            MeetingSessionStatusCode.from(9)
        )
    }

    @Test
    fun `from should return null when undefined value`() {
        assertNull(MeetingSessionStatusCode.from(-99))
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.session

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test

class MeetingSessionStatusTest {
    private val testStatusCode = MeetingSessionStatusCode.OK

    @Test
    fun `constructor should return object with value from param`() {
        val testOutput = MeetingSessionStatus(testStatusCode)

        assertNotNull(testOutput)
        assertEquals(testStatusCode, testOutput.statusCode)
    }

    @Test
    fun `secondary constructor should return object with non null status code when using defined values`() {
        val testOutput = MeetingSessionStatus(0)

        assertNotNull(testOutput)
        assertEquals(testStatusCode, testOutput.statusCode)
    }

    @Test
    fun `secondary constructor should return status with null status code when using undefined values`() {
        val testOutput = MeetingSessionStatus(-99)

        assertNotNull(testOutput)
        assertNull("actual: ${testOutput.statusCode}", testOutput.statusCode)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.utils

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test

class DefaultModalityTest {
    private val id = "abc"
    private val modalityType = ModalityType.Content
    private val idWithModality = "abc#" + modalityType.value
    private val idWithOnlySeparator = DefaultModality.MODALITY_SEPARATOR

    @Test
    fun `id should return expected value`() {
        assertEquals(id, DefaultModality(id).id())
        assertEquals(idWithModality, DefaultModality(idWithModality).id())
        assertEquals(idWithOnlySeparator, DefaultModality(idWithOnlySeparator).id())
        assertEquals("", DefaultModality("").id())
    }

    @Test
    fun `base should return expected value`() {
        assertEquals(id, DefaultModality(id).base())
        assertEquals(id, DefaultModality(idWithModality).base())
        assertEquals("", DefaultModality(idWithOnlySeparator).base())
        assertEquals("", DefaultModality("").base())
    }

    @Test
    fun `modality should return expected value`() {
        assertNull(DefaultModality(id).modality())
        assertEquals(modalityType, DefaultModality(idWithModality).modality())
        assertNull(DefaultModality(idWithOnlySeparator).modality())
        assertNull(DefaultModality("").modality())
    }

    @Test
    fun `hasModality should return expected value`() {
        assertFalse(DefaultModality(id).hasModality(modalityType))
        assertTrue(DefaultModality(idWithModality).hasModality(modalityType))
        assertFalse(DefaultModality(idWithOnlySeparator).hasModality(modalityType))
        assertFalse(DefaultModality("").hasModality(modalityType))
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.utils

import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import java.lang.Exception
import org.junit.Before
import org.junit.Test

// For some reason, this test won't be recognized as a test if it is called `RefCountDelegateTest`
class RefCountTest {

    @MockK
    private lateinit var mockCallback: Runnable

    @Before
    fun setUp() {
        MockKAnnotations.init(this, relaxUnitFun = true)
    }

    @Test
    fun `release should trigger release callback on newly created delegate`() {
        val refCountDelegate = RefCountDelegate(mockCallback)
        refCountDelegate.release()

        verify(exactly = 1) { mockCallback.run() }
    }

    @Test
    fun `release should not trigger release callback on delegate after retain is called`() {
        val refCountDelegate = RefCountDelegate(mockCallback)
        refCountDelegate.retain()
        refCountDelegate.release()

        verify(exactly = 0) { mockCallback.run() }
    }

    @Test
    fun `release should trigger release callback if release is called the same amount of times as retain + 1`() {
        val refCountDelegate = RefCountDelegate(mockCallback)
        refCountDelegate.retain()
        refCountDelegate.retain()
        refCountDelegate.retain()
        refCountDelegate.release()
        refCountDelegate.release()
        refCountDelegate.release()

        refCountDelegate.retain()
        refCountDelegate.release()
        refCountDelegate.retain()
        refCountDelegate.release()

        refCountDelegate.release()

        verify(exactly = 1) { mockCallback.run() }
    }

    @Test
    fun `additional releases should throw exception and not retrigger callback`() {
        val refCountDelegate = RefCountDelegate(mockCallback)

        refCountDelegate.release()

        var exceptionThrown = false
        try {
            refCountDelegate.release()
        } catch (e: Exception) {
            exceptionThrown = true
        }

        assert(exceptionThrown)
        verify(exactly = 1) { mockCallback.run() }
    }

    @Test
    fun `additional retains should throw exception`() {
        val refCountDelegate = RefCountDelegate(mockCallback)

        refCountDelegate.release()

        var exceptionThrown = false
        try {
            refCountDelegate.retain()
        } catch (e: Exception) {
            exceptionThrown = true
        }

        assert(exceptionThrown)
        verify(exactly = 1) { mockCallback.run() }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.utils

import java.io.FileInputStream
import java.util.Properties
import org.junit.Assert.assertEquals
import org.junit.Test

class VersioningTest {
    @Test
    fun `sdkVersion should return version string matching with properties file`() {
        val sdkVersion = Versioning.sdkVersion()
        val file = FileInputStream("version.properties")
        val versionProp = Properties()

        versionProp.load(file)
        val version = "${versionProp.getProperty("versionMajor")}.${versionProp.getProperty("versionMinor")}.${versionProp.getProperty("versionPatch")}"

        assertEquals(sdkVersion, version)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.utils.logger

import android.util.Log
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class ConsoleLoggerTest {
    private val testTag = "tag"
    private val testInfoMsg = "info"
    private val testDebugMsg = "debug"
    private val testWarnMsg = "warn"
    private val testErrorMsg = "error"
    private val testVerboseMsg = "verbose"

    @Before
    fun setup() {
        mockkStatic(Log::class)
        every { Log.v(any(), any()) } returns 0
        every { Log.d(any(), any()) } returns 0
        every { Log.i(any(), any()) } returns 0
        every { Log.w(any(), any<String>()) } returns 0
        every { Log.e(any(), any()) } returns 0
    }

    @Test
    fun `constructor should use default log level when no parameters`() {
        val logger = ConsoleLogger()

        assertEquals(LogLevel.INFO, logger.getLogLevel())
    }

    @Test
    fun `constructor should use the log level when given parameter`() {
        val logger = ConsoleLogger(LogLevel.VERBOSE)

        assertEquals(LogLevel.VERBOSE, logger.getLogLevel())
    }

    @Test
    fun `logger should log nothing when log level is OFF`() {
        val logger = ConsoleLogger(LogLevel.OFF)

        logger.info(testTag, testInfoMsg)
        logger.error(testTag, testErrorMsg)

        verify(exactly = 0) { Log.i(any(), any()) }
        verify(exactly = 0) { Log.e(any(), any()) }
    }

    @Test
    fun `logger should skip messages of log levels lower than configured log level`() {
        val logger = ConsoleLogger()

        logger.verbose(testTag, testVerboseMsg)
        logger.debug(testTag, testDebugMsg)
        logger.info(testTag, testInfoMsg)
        logger.warn(testTag, testWarnMsg)
        logger.error(testTag, testErrorMsg)

        verify(exactly = 0) { Log.v(any(), any()) }
        verify(exactly = 0) { Log.d(any(), any()) }
        verify(exactly = 1) { Log.i(any(), any()) }
        verify(exactly = 1) { Log.w(testTag, testWarnMsg) }
        verify(exactly = 1) { Log.e(testTag, testErrorMsg) }
    }

    @Test
    fun `logger should log messages of all log levels greater than or equal to configured log level`() {
        val logger = ConsoleLogger()
        logger.setLogLevel(LogLevel.VERBOSE)

        logger.verbose(testTag, testVerboseMsg)
        logger.debug(testTag, testDebugMsg)
        logger.info(testTag, testInfoMsg)
        logger.warn(testTag, testWarnMsg)
        logger.error(testTag, testErrorMsg)

        verify(exactly = 1) { Log.v(testTag, testVerboseMsg) }
        verify(exactly = 1) { Log.d(testTag, testDebugMsg) }
        verify(exactly = 1) { Log.i(testTag, testInfoMsg) }
        verify(exactly = 1) { Log.w(testTag, testWarnMsg) }
        verify(exactly = 1) { Log.e(testTag, testErrorMsg) }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdkdemo.activity

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatSpinner
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModelProvider
import com.amazonaws.services.chime.sdk.meetings.audiovideo.AudioVideoConfiguration
import com.amazonaws.services.chime.sdk.meetings.audiovideo.audio.AudioMode
import com.amazonaws.services.chime.sdk.meetings.internal.utils.DefaultBackOffRetry
import com.amazonaws.services.chime.sdk.meetings.internal.utils.HttpUtils
import com.amazonaws.services.chime.sdk.meetings.utils.Versioning
import com.amazonaws.services.chime.sdk.meetings.utils.logger.ConsoleLogger
import com.amazonaws.services.chime.sdk.meetings.utils.logger.LogLevel
import com.amazonaws.services.chime.sdkdemo.R
import com.amazonaws.services.chime.sdkdemo.fragment.DebugSettingsFragment
import com.amazonaws.services.chime.sdkdemo.model.DebugSettingsViewModel
import com.amazonaws.services.chime.sdkdemo.utils.encodeURLParam
import com.amazonaws.services.chime.sdkdemo.utils.showToast
import java.net.URL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class HomeActivity : AppCompatActivity() {
    private val logger = ConsoleLogger(LogLevel.INFO)
    private val uiScope = CoroutineScope(Dispatchers.Main)

    private val MEETING_REGION = "us-east-1"
    private val TAG = "MeetingHomeActivity"
    private val WEBRTC_PERMISSION_REQUEST_CODE = 1

    private val WEBRTC_PERM = arrayOf(
        Manifest.permission.MODIFY_AUDIO_SETTINGS,
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.CAMERA
    )

    private var meetingEditText: EditText? = null
    private var nameEditText: EditText? = null
    private var audioMode: AppCompatSpinner? = null
    private var authenticationProgressBar: ProgressBar? = null
    private var meetingID: String? = null
    private var yourName: String? = null
    private var testUrl: String = ""
    private var audioModes = listOf("Stereo/48KHz Audio", "Mono/48KHz Audio", "Mono/16KHz Audio")
    private lateinit var audioVideoConfig: AudioVideoConfiguration
    private lateinit var debugSettingsViewModel: DebugSettingsViewModel

    companion object {
        const val MEETING_RESPONSE_KEY = "MEETING_RESPONSE"
        const val MEETING_ID_KEY = "MEETING_ID"
        const val NAME_KEY = "NAME"
        const val MEETING_ENDPOINT_KEY = "MEETING_ENDPOINT_URL"
        const val AUDIO_MODE_KEY = "AUDIO_MODE"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        meetingEditText = findViewById(R.id.editMeetingId)
        nameEditText = findViewById(R.id.editName)
        audioMode = findViewById(R.id.audioModeSpinner)
        authenticationProgressBar = findViewById(R.id.progressAuthentication)
        debugSettingsViewModel = ViewModelProvider(this).get(DebugSettingsViewModel::class.java)

        audioMode?.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, audioModes)
        findViewById<Button>(R.id.buttonContinue)?.setOnClickListener {
            joinMeeting()
        }
        findViewById<Button>(R.id.buttonDebugSettings)?.setOnClickListener { showDebugSettings() }

        val versionText: TextView = findViewById(R.id.versionText) as TextView
        versionText.text = "${getString(R.string.version_prefix)}${Versioning.sdkVersion()}"
    }

    private fun showDebugSettings() {
        var debugSettingsFragment = DebugSettingsFragment()
        debugSettingsFragment.show(supportFragmentManager, TAG)
    }

    private fun joinMeeting() {
        when (audioMode?.selectedItemPosition ?: 0) {
            0 -> audioVideoConfig = AudioVideoConfiguration(audioMode = AudioMode.Stereo48K)
            1 -> audioVideoConfig = AudioVideoConfiguration(audioMode = AudioMode.Mono48K)
            2 -> audioVideoConfig = AudioVideoConfiguration(audioMode = AudioMode.Mono16K)
        }

        meetingID = meetingEditText?.text.toString().trim().replace("\\s+".toRegex(), "+")
        yourName = nameEditText?.text.toString().trim().replace("\\s+".toRegex(), "+")
        testUrl = getTestUrl()

        if (meetingID.isNullOrBlank()) {
            showToast(getString(R.string.user_notification_meeting_id_invalid))
        } else if (yourName.isNullOrBlank()) {
            showToast(getString(R.string.user_notification_attendee_name_invalid))
        } else {
            if (hasPermissionsAlready()) {
                authenticate(testUrl, meetingID, yourName)
            } else {
                ActivityCompat.requestPermissions(this, WEBRTC_PERM, WEBRTC_PERMISSION_REQUEST_CODE)
            }
        }
    }

    private fun getTestUrl(): String {
        val endpointUrl = debugSettingsViewModel.endpointUrl.value
        return if (endpointUrl.isNullOrEmpty()) getString(R.string.test_url) else endpointUrl
    }

    private fun hasPermissionsAlready(): Boolean {
        return WEBRTC_PERM.all {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissionsList: Array<String>,
        grantResults: IntArray
    ) {
        when (requestCode) {
            WEBRTC_PERMISSION_REQUEST_CODE -> {
                val isMissingPermission: Boolean =
                    grantResults.isEmpty() || grantResults.any { PackageManager.PERMISSION_GRANTED != it }

                if (isMissingPermission) {
                    showToast(getString(R.string.user_notification_permission_error))
                    return
                }
                authenticate(testUrl, meetingID, yourName)
            }
        }
    }

    private fun authenticate(
        meetingUrl: String,
        meetingId: String?,
        attendeeName: String?
    ) =
        uiScope.launch {
            logger.info(
                TAG,
                "Joining meeting. meetingUrl: $meetingUrl, meetingId: $meetingId, attendeeName: $attendeeName"
            )
            if (!meetingUrl.startsWith("http")) {
                showToast(getString(R.string.user_notification_meeting_url_error))
            } else {
                authenticationProgressBar?.visibility = View.VISIBLE

                val primaryMeetingId = debugSettingsViewModel.primaryMeetingId.value
                val meetingResponseJson: String? = joinMeeting(meetingUrl, meetingId, attendeeName, primaryMeetingId)

                authenticationProgressBar?.visibility = View.INVISIBLE

                if (meetingResponseJson == null) {
                    showToast(getString(R.string.user_notification_meeting_start_error))
                } else {
                    val intent = Intent(applicationContext, MeetingActivity::class.java).apply {
                        putExtras(
                            bundleOf(
                                MEETING_RESPONSE_KEY to meetingResponseJson,
                                MEETING_ID_KEY to meetingId,
                                NAME_KEY to attendeeName,
                                MEETING_ENDPOINT_KEY to meetingUrl,
                                AUDIO_MODE_KEY to audioVideoConfig.audioMode.value
                            )
                        )
                    }
                    startActivity(intent)
                }
            }
        }

    private suspend fun joinMeeting(
        meetingUrl: String,
        meetingId: String?,
        attendeeName: String?,
        primaryMeetingId: String?
    ): String? {
        val meetingServerUrl = if (meetingUrl.endsWith("/")) meetingUrl else "$meetingUrl/"
        var url = "${meetingServerUrl}join?title=${encodeURLParam(meetingId)}&name=${encodeURLParam(
            attendeeName)}&region=${encodeURLParam(MEETING_REGION)}"
        if (!primaryMeetingId.isNullOrEmpty()) {
            url += "&primaryExternalMeetingId=${encodeURLParam(primaryMeetingId)}"
        }
        val response = HttpUtils.post(URL(url), "", DefaultBackOffRetry(), logger)
        return if (response.httpException == null) {
            response.data
        } else {
            logger.error(TAG, "Unable to join meeting. ${response.httpException}")
            null
        }
    }
}