diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt index 0456751ae8d..2c279b77258 100644 --- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt @@ -15,11 +15,19 @@ import kotlinx.parcelize.Parcelize sealed interface CallType : NodeInputs, Parcelable { @Parcelize - data class ExternalUrl(val url: String) : CallType + data class ExternalUrl(val url: String) : CallType { + override fun toString(): String { + return "ExternalUrl" + } + } @Parcelize data class RoomCall( val sessionId: SessionId, val roomId: RoomId, - ) : CallType + ) : CallType { + override fun toString(): String { + return "RoomCall(sessionId=$sessionId, roomId=$roomId)" + } + } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt index 8550f06f3d2..5ee52920e73 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt @@ -49,6 +49,7 @@ import kotlinx.coroutines.launch import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import timber.log.Timber import java.util.UUID class CallScreenPresenter @AssistedInject constructor( @@ -211,6 +212,7 @@ class CallScreenPresenter @AssistedInject constructor( theme = theme, ).getOrThrow() callWidgetDriver.value = result.driver + Timber.d("Call widget driver initialized for sessionId: ${inputs.sessionId}, roomId: ${inputs.roomId}") result.url } } @@ -221,10 +223,12 @@ class CallScreenPresenter @AssistedInject constructor( private fun HandleMatrixClientSyncState() { val coroutineScope = rememberCoroutineScope() DisposableEffect(Unit) { - val client = (callType as? CallType.RoomCall)?.sessionId?.let { - matrixClientsProvider.getOrNull(it) - } ?: return@DisposableEffect onDispose { } + val roomCallType = callType as? CallType.RoomCall ?: return@DisposableEffect onDispose {} + val client = matrixClientsProvider.getOrNull(roomCallType.sessionId) ?: return@DisposableEffect onDispose { + Timber.w("No MatrixClient found for sessionId, can't send call notification: ${roomCallType.sessionId}") + } coroutineScope.launch { + Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}") client.syncService().syncState .collect { state -> if (state == SyncState.Running) { @@ -235,6 +239,7 @@ class CallScreenPresenter @AssistedInject constructor( } } onDispose { + Timber.d("Stopped observing sync state in-call for sessionId: ${roomCallType.sessionId}") // Make sure we mark the call as ended in the app state appForegroundStateService.updateIsInCallState(false) } @@ -242,12 +247,29 @@ class CallScreenPresenter @AssistedInject constructor( } private suspend fun MatrixClient.notifyCallStartIfNeeded(roomId: RoomId) { - if (!notifiedCallStart) { - val activeRoomForSession = activeRoomsHolder.getActiveRoomMatching(sessionId, roomId) - val sendCallNotificationResult = activeRoomForSession?.sendCallNotificationIfNeeded() - ?: getJoinedRoom(roomId)?.use { it.sendCallNotificationIfNeeded() } - sendCallNotificationResult?.onSuccess { notifiedCallStart = true } + if (notifiedCallStart) return + + val activeRoomForSession = activeRoomsHolder.getActiveRoomMatching(sessionId, roomId) + val sendCallNotificationResult = if (activeRoomForSession != null) { + Timber.d("Notifying call start for room $roomId. Has room call: ${activeRoomForSession.info().hasRoomCall}") + activeRoomForSession.sendCallNotificationIfNeeded() + } else { + // Instantiate the room from the session and roomId and send the notification + getJoinedRoom(roomId)?.use { room -> + Timber.d("Notifying call start for room $roomId. Has room call: ${room.info().hasRoomCall}") + room.sendCallNotificationIfNeeded() + } ?: run { + Timber.w("No room found for session $sessionId and room $roomId, skipping call notification.") + return + } } + + sendCallNotificationResult.fold( + onSuccess = { notifiedCallStart = true }, + onFailure = { error -> + Timber.e(error, "Failed to send call notification for room $roomId.") + } + ) } private fun parseMessage(message: String): WidgetMessage? { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt index 25e856444c1..aabc3338a5d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt @@ -134,11 +134,13 @@ internal fun CallScreenView( AsyncData.Uninitialized, is AsyncData.Loading -> ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait)) - is AsyncData.Failure -> + is AsyncData.Failure -> { + Timber.e(state.urlState.error, "WebView failed to load URL: ${state.urlState.error.message}") ErrorDialog( content = state.urlState.error.message.orEmpty(), onSubmit = { state.eventSink(CallScreenEvents.Hangup) }, ) + } is AsyncData.Success -> Unit } } @@ -242,6 +244,20 @@ private fun WebView.setup( ConsoleMessage.MessageLevel.WARNING -> Log.WARN else -> Log.DEBUG } + + val message = buildString { + append(consoleMessage.sourceId()) + append(":") + append(consoleMessage.lineNumber()) + append(" ") + append(consoleMessage.message()) + } + + if (message.contains("password=")) { + // Avoid logging any messages that contain "password" to prevent leaking sensitive information + return true + } + Timber.tag("WebView").log( priority = priority, message = buildString { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index 4c44fb29d96..cec8a81c588 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -97,6 +97,8 @@ class ElementCallActivity : pictureInPicturePresenter.setPipView(this) + Timber.d("Created ElementCallActivity with call type: ${webViewTarget.value}") + setContent { val pipState = pictureInPicturePresenter.present() ListenToAndroidEvents(pipState) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 57a02de56aa..d007c01a468 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -181,6 +181,9 @@ class DefaultActiveCallManager @Inject constructor( Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring") return } + + Timber.tag(tag).d("Hung up call: $callType") + cancelIncomingCallNotification() if (activeWakeLock?.isHeld == true) { Timber.tag(tag).d("Releasing partial wakelock after hang up") @@ -191,6 +194,8 @@ class DefaultActiveCallManager @Inject constructor( } override suspend fun joinedCall(callType: CallType) = mutex.withLock { + Timber.tag(tag).d("Joined call: $callType") + cancelIncomingCallNotification() if (activeWakeLock?.isHeld == true) { Timber.tag(tag).d("Releasing partial wakelock after joining call") diff --git a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt index dbcf39766b8..dc0faf26f11 100644 --- a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt +++ b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt @@ -46,14 +46,18 @@ class RoomCallStatePresenter @Inject constructor( (currentCall as? CurrentCall.RoomCall)?.roomId == room.roomId } } - val callState = when { - isAvailable.not() -> RoomCallState.Unavailable - roomInfo.hasRoomCall -> RoomCallState.OnGoing( - canJoinCall = canJoinCall, - isUserInTheCall = isUserInTheCall, - isUserLocallyInTheCall = isUserLocallyInTheCall, - ) - else -> RoomCallState.StandBy(canStartCall = canJoinCall) + val callState by remember { + derivedStateOf { + when { + isAvailable.not() -> RoomCallState.Unavailable + roomInfo.hasRoomCall -> RoomCallState.OnGoing( + canJoinCall = canJoinCall, + isUserInTheCall = isUserInTheCall, + isUserLocallyInTheCall = isUserLocallyInTheCall, + ) + else -> RoomCallState.StandBy(canStartCall = canJoinCall) + } + } } return callState }