[Feature] Receive error event when device manager throws any error (#385)

This commit is contained in:
Mohtasim 2022-08-19 16:17:38 -07:00
Родитель fdb97aa35f
Коммит a36c79567e
11 изменённых файлов: 88 добавлений и 19 удалений

Просмотреть файл

@ -1,3 +1,6 @@
# Release History
[Communication UI Calling Library CHANGELOG](docs/CHANGELOG_UI_CALLING.md)
### Features
- New error code 'unknownError' introduced for ambiguous errors such as device manager error

Просмотреть файл

@ -4,13 +4,13 @@
package com.azure.android.communication.mocking
import androidx.annotation.GuardedBy
import com.azure.android.communication.calling.CallState
import com.azure.android.communication.calling.CameraFacing
import com.azure.android.communication.calling.MediaStreamType
import com.azure.android.communication.calling.ParticipantState
import com.azure.android.communication.calling.PropertyChangedListener
import com.azure.android.communication.calling.RemoteVideoStreamsUpdatedListener
import com.azure.android.communication.calling.VideoDeviceType
import com.azure.android.communication.calling.ParticipantState
import com.azure.android.communication.calling.MediaStreamType
import com.azure.android.communication.calling.CallState
import com.azure.android.communication.calling.RemoteVideoStreamsUpdatedListener
import com.azure.android.communication.calling.PropertyChangedListener
import com.azure.android.communication.ui.calling.models.ParticipantInfoModel
import com.azure.android.communication.ui.calling.models.StreamType
import com.azure.android.communication.ui.calling.models.VideoStreamModel
@ -137,7 +137,9 @@ internal class TestCallingSDK(private val callEvents: CallEvents, coroutineConte
emitRemoteParticipantFlow()
}
override fun setupCall() {}
override fun setupCall(): CompletableFuture<Void> {
return completedNullFuture()
}
override fun dispose() {}
override fun turnOnVideoAsync(): CompletableFuture<LocalVideoStream> {

Просмотреть файл

@ -14,6 +14,7 @@ internal class ErrorCode : ExpandableStringEnum<ErrorCode?>() {
val TURN_CAMERA_OFF_FAILED = fromString("turnCameraOffFailed")
val TURN_MIC_ON_FAILED = fromString("turnMicOnFailed")
val TURN_MIC_OFF_FAILED = fromString("turnMicOffFailed")
val UNKNOWN_ERROR = fromString("unknownError")
private fun fromString(name: String): ErrorCode {
return fromString(name, ErrorCode::class.java)

Просмотреть файл

@ -10,6 +10,7 @@ import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.SWIT
import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.TOKEN_EXPIRED
import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.TURN_CAMERA_OFF_FAILED
import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.TURN_CAMERA_ON_FAILED
import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.UNKNOWN_ERROR
import com.azure.android.communication.ui.calling.models.CallCompositeErrorCode
import com.azure.android.communication.ui.calling.models.CallCompositeErrorEvent
import com.azure.android.communication.ui.calling.models.CallCompositeEventCode
@ -118,6 +119,9 @@ internal class ErrorHandler(
TOKEN_EXPIRED -> {
return CallCompositeErrorCode.TOKEN_EXPIRED
}
UNKNOWN_ERROR -> {
return CallCompositeErrorCode.UNKNOWN_ERROR
}
CALL_JOIN_FAILED -> {
return CallCompositeErrorCode.CALL_JOIN_FAILED
}

Просмотреть файл

@ -27,12 +27,16 @@ public final class CallCompositeErrorCode extends ExpandableStringEnum<CallCompo
*/
public static final CallCompositeErrorCode TOKEN_EXPIRED = fromString("tokenExpired");
/**
* Dispatched when camera failed to start, stop or switch
*/
public static final CallCompositeErrorCode CAMERA_FAILURE = fromString("cameraFailure");
/***
* Dispatched when composite falls under any ambiguous state such as device manager instance error
*/
public static final CallCompositeErrorCode UNKNOWN_ERROR = fromString("unknownError");
/**
* Creates or finds a {@link CallCompositeErrorCode} from its string representation.
*

Просмотреть файл

@ -9,6 +9,7 @@ import com.azure.android.communication.ui.calling.redux.state.AudioOperationalSt
import com.azure.android.communication.ui.calling.redux.state.CameraDeviceSelectionStatus
internal sealed class LocalParticipantAction : Action {
class DeviceManagerFetchFailed(val error: CallCompositeError) : LocalParticipantAction()
class CameraPreviewOnRequested : LocalParticipantAction()
class CameraPreviewOnTriggered : LocalParticipantAction()
class CameraPreviewOnSucceeded(var videoStreamID: String) : LocalParticipantAction()

Просмотреть файл

@ -199,7 +199,15 @@ internal class CallingMiddlewareActionHandlerImpl(
}
override fun setupCall(store: Store<ReduxState>) {
callingService.setupCall()
callingService.setupCall().handle { _, error: Throwable? ->
if (error != null) {
store.dispatch(
ErrorAction.FatalErrorOccurred(
FatalError(error, ErrorCode.UNKNOWN_ERROR)
)
)
}
}
}
override fun startCall(store: Store<ReduxState>) {

Просмотреть файл

@ -102,8 +102,8 @@ internal class CallingService(
return callingSdk.resume()
}
fun setupCall() {
callingSdk.setupCall()
fun setupCall(): CompletableFuture<Void> {
return callingSdk.setupCall()
}
fun dispose() {

Просмотреть файл

@ -4,13 +4,13 @@
package com.azure.android.communication.ui.calling.service.sdk
import android.view.View
import com.azure.android.communication.calling.CameraFacing
import com.azure.android.communication.calling.CreateViewOptions
import com.azure.android.communication.calling.MediaStreamType
import com.azure.android.communication.calling.ParticipantState
import com.azure.android.communication.calling.PropertyChangedListener
import com.azure.android.communication.calling.RemoteVideoStreamsUpdatedListener
import com.azure.android.communication.calling.MediaStreamType
import com.azure.android.communication.calling.CameraFacing
import com.azure.android.communication.calling.VideoDeviceType
import com.azure.android.communication.calling.CreateViewOptions
import com.azure.android.communication.ui.calling.models.ParticipantInfoModel
import com.azure.android.communication.ui.calling.redux.state.AudioState
import com.azure.android.communication.ui.calling.redux.state.CameraDeviceSelectionStatus
@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.SharedFlow
*/
internal interface CallingSDK {
// Internal helpers. Refactor these out further.
fun setupCall()
fun setupCall(): CompletableFuture<Void>
fun dispose()
// Interactions.

Просмотреть файл

@ -136,20 +136,29 @@ internal class CallingSDKWrapper(
cleanupResources()
}
override fun setupCall() {
override fun setupCall(): CompletableFuture<Void> {
if (callClient == null) {
val callClientOptions = CallClientOptions().also {
it.setTags(configuration.callConfig?.diagnosticConfig?.tags, logger)
}
callClient = CallClient(callClientOptions)
}
createDeviceManager()
val setupCallCompletableFuture: CompletableFuture<Void> = CompletableFuture()
createDeviceManager().handle { _, error: Throwable? ->
if (error != null) {
setupCallCompletableFuture.completeExceptionally(error)
} else {
setupCallCompletableFuture.complete(null)
}
}
return setupCallCompletableFuture
}
override fun startCall(
cameraState: CameraState,
audioState: AudioState,
): CompletableFuture<Void> {
val startCallCompletableFuture = CompletableFuture<Void>()
createCallAgent().thenAccept { agent: CallAgent ->
val audioOptions = AudioOptions()
@ -357,7 +366,7 @@ internal class CallingSDKWrapper(
return deviceManagerCompletableFuture!!
}
private fun createDeviceManager() {
private fun createDeviceManager(): CompletableFuture<DeviceManager> {
val deviceManagerCompletableFuture = getDeviceManagerCompletableFuture()
if (deviceManagerCompletableFuture.isCompletedExceptionally ||
@ -378,18 +387,20 @@ internal class CallingSDKWrapper(
CompletableFuture.allOf(
deviceManagerCompletableFuture,
)
return deviceManagerCompletableFuture
}
private fun initializeCameras(): CompletableFuture<Void> {
if (camerasInitializedCompletableFuture == null) {
camerasInitializedCompletableFuture = CompletableFuture<Void>()
getDeviceManagerCompletableFuture().whenComplete { deviceManager: DeviceManager, _: Throwable? ->
getDeviceManagerCompletableFuture().whenComplete { deviceManager: DeviceManager?, error: Throwable? ->
completeCamerasInitializedCompletableFuture()
videoDevicesUpdatedListener =
VideoDevicesUpdatedListener {
completeCamerasInitializedCompletableFuture()
}
deviceManager.addOnCamerasUpdatedListener(videoDevicesUpdatedListener)
deviceManager?.addOnCamerasUpdatedListener(videoDevicesUpdatedListener)
}
}

Просмотреть файл

@ -34,6 +34,7 @@ import com.azure.android.communication.ui.calling.redux.state.ReduxState
import com.azure.android.communication.ui.calling.service.CallingService
import com.azure.android.communication.ui.ACSBaseTestCoroutine
import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.CALL_END_FAILED
import com.azure.android.communication.ui.calling.error.ErrorCode.Companion.UNKNOWN_ERROR
import com.azure.android.communication.ui.calling.models.CallCompositeEventCode.Companion.CALL_DECLINED
import com.azure.android.communication.ui.calling.models.CallCompositeEventCode.Companion.CALL_EVICTED
import com.azure.android.communication.ui.helper.UnconfinedTestContextProvider
@ -475,6 +476,40 @@ internal class CallingMiddlewareActionHandlerUnitTest : ACSBaseTestCoroutine() {
)
}
@ExperimentalCoroutinesApi
@Test
fun callingMiddlewareActionHandler_setupCall_fails_then_dispatchFatalError() =
runScopedTest {
val appState = AppReduxState("")
appState.callState = CallingState(CallingStatus.NONE)
val setupCallCompletableFuture: CompletableFuture<Void> = CompletableFuture()
val mockCallingService: CallingService = mock {
on { setupCall() } doReturn setupCallCompletableFuture
}
val handler = CallingMiddlewareActionHandlerImpl(
mockCallingService,
UnconfinedTestContextProvider()
)
val mockAppStore = mock<AppStore<ReduxState>> {
on { dispatch(any()) } doAnswer { }
}
val exception = Exception("test")
handler.setupCall(mockAppStore)
setupCallCompletableFuture.completeExceptionally(exception)
// assert
verify(mockAppStore, times(1)).dispatch(
argThat { action ->
action is ErrorAction.FatalErrorOccurred &&
action.error.fatalError == exception && action.error.errorCode == ErrorCode.UNKNOWN_ERROR
}
)
}
@ExperimentalCoroutinesApi
@Test
fun callingMiddlewareActionHandler_startCall_fails_then_dispatchFatalError() =