diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index e2285880e..f90bc966d 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -218,7 +218,6 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() { updateCallDuration() Log.i("$TAG Call is ending, go to ended call fragment") // Show that call was ended for a few seconds, then leave - // TODO FIXME: do not show it when call is being ended due to user terminating the call goToEndedCallEvent.postValue(Event(true)) } } else { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index 1da2abb3a..b57260b24 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -29,9 +29,13 @@ import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub +import org.linphone.core.ConferenceScheduler +import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.EventLog import org.linphone.core.Factory import org.linphone.core.Friend +import org.linphone.core.Participant +import org.linphone.core.ParticipantInfo import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ParticipantModel import org.linphone.ui.main.contacts.model.ContactAvatarModel @@ -181,6 +185,33 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { } } + private val conferenceSchedulerListener = object : ConferenceSchedulerListenerStub() { + override fun onStateChanged( + conferenceScheduler: ConferenceScheduler, + state: ConferenceScheduler.State + ) { + Log.i("$TAG Conference scheduler state is $state") + if (state == ConferenceScheduler.State.Ready) { + conferenceScheduler.removeListener(this) + + val conferenceAddress = conferenceScheduler.info?.uri + if (conferenceAddress != null) { + Log.i( + "$TAG Conference info created, address is ${conferenceAddress.asStringUriOnly()}" + ) + coreContext.startCall(conferenceAddress) + } else { + Log.e("$TAG Conference info URI is null!") + // TODO: notify error to user + } + } else if (state == ConferenceScheduler.State.Error) { + conferenceScheduler.removeListener(this) + Log.e("$TAG Failed to create group call!") + // TODO: notify error to user + } + } + } + init { expandParticipants.value = true } @@ -287,8 +318,8 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { @UiThread fun call() { coreContext.postOnCoreThread { core -> - if (LinphoneUtils.isChatRoomAGroup(chatRoom)) { - // TODO: group chat room call + if (LinphoneUtils.isChatRoomAGroup(chatRoom) && chatRoom.participants.size >= 2) { + createGroupCall() } else { val firstParticipant = chatRoom.participants.firstOrNull() val address = firstParticipant?.address @@ -567,4 +598,40 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { participants.postValue(participantsList) } + + @WorkerThread + private fun createGroupCall() { + val core = coreContext.core + val account = core.defaultAccount + if (account == null) { + Log.e( + "$TAG No default account found, can't create group call!" + ) + return + } + + val conferenceInfo = Factory.instance().createConferenceInfo() + conferenceInfo.organizer = account.params.identityAddress + conferenceInfo.subject = subject.value + + val participants = arrayOfNulls(chatRoom.participants.size) + var index = 0 + for (participant in chatRoom.participants) { + val info = Factory.instance().createParticipantInfo(participant.address) + // For meetings, all participants must have Speaker role + info?.role = Participant.Role.Speaker + participants[index] = info + index += 1 + } + conferenceInfo.setParticipantInfos(participants) + + Log.i( + "$TAG Creating group call with subject ${subject.value} and ${participants.size} participant(s)" + ) + val conferenceScheduler = core.createConferenceScheduler() + conferenceScheduler.addListener(conferenceSchedulerListener) + conferenceScheduler.account = account + // Will trigger the conference creation/update automatically + conferenceScheduler.info = conferenceInfo + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index 8a30c4b4d..db5b0d83c 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -30,9 +30,13 @@ import org.linphone.core.ChatMessage import org.linphone.core.ChatMessageReaction import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub +import org.linphone.core.ConferenceScheduler +import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.EventLog import org.linphone.core.Factory import org.linphone.core.Friend +import org.linphone.core.Participant +import org.linphone.core.ParticipantInfo import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.EventLogModel import org.linphone.ui.main.chat.model.MessageModel @@ -280,6 +284,33 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { } } + private val conferenceSchedulerListener = object : ConferenceSchedulerListenerStub() { + override fun onStateChanged( + conferenceScheduler: ConferenceScheduler, + state: ConferenceScheduler.State + ) { + Log.i("$TAG Conference scheduler state is $state") + if (state == ConferenceScheduler.State.Ready) { + conferenceScheduler.removeListener(this) + + val conferenceAddress = conferenceScheduler.info?.uri + if (conferenceAddress != null) { + Log.i( + "$TAG Conference info created, address is ${conferenceAddress.asStringUriOnly()}" + ) + coreContext.startCall(conferenceAddress) + } else { + Log.e("$TAG Conference info URI is null!") + // TODO: notify error to user + } + } else if (state == ConferenceScheduler.State.Error) { + conferenceScheduler.removeListener(this) + Log.e("$TAG Failed to create group call!") + // TODO: notify error to user + } + } + } + init { searchBarVisible.value = false isUserScrollingUp.value = false @@ -415,8 +446,8 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { @UiThread fun startCall() { coreContext.postOnCoreThread { core -> - if (LinphoneUtils.isChatRoomAGroup(chatRoom)) { - // TODO: group chat room call + if (LinphoneUtils.isChatRoomAGroup(chatRoom) && chatRoom.participants.size >= 2) { + createGroupCall() } else { val firstParticipant = chatRoom.participants.firstOrNull() val address = firstParticipant?.address @@ -736,4 +767,40 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { composingLabel.postValue("") } } + + @WorkerThread + private fun createGroupCall() { + val core = coreContext.core + val account = core.defaultAccount + if (account == null) { + Log.e( + "$TAG No default account found, can't create group call!" + ) + return + } + + val conferenceInfo = Factory.instance().createConferenceInfo() + conferenceInfo.organizer = account.params.identityAddress + conferenceInfo.subject = subject.value + + val participants = arrayOfNulls(chatRoom.participants.size) + var index = 0 + for (participant in chatRoom.participants) { + val info = Factory.instance().createParticipantInfo(participant.address) + // For meetings, all participants must have Speaker role + info?.role = Participant.Role.Speaker + participants[index] = info + index += 1 + } + conferenceInfo.setParticipantInfos(participants) + + Log.i( + "$TAG Creating group call with subject ${subject.value} and ${participants.size} participant(s)" + ) + val conferenceScheduler = core.createConferenceScheduler() + conferenceScheduler.addListener(conferenceSchedulerListener) + conferenceScheduler.account = account + // Will trigger the conference creation/update automatically + conferenceScheduler.info = conferenceInfo + } } diff --git a/app/src/main/res/layout/call_ended_fragment.xml b/app/src/main/res/layout/call_ended_fragment.xml index 0a81fd230..4c23bc07d 100644 --- a/app/src/main/res/layout/call_ended_fragment.xml +++ b/app/src/main/res/layout/call_ended_fragment.xml @@ -34,7 +34,6 @@ android:layout_height="@dimen/call_top_bar_text_height" android:layout_marginStart="10dp" android:text="@string/call_ended" - android:visibility="@{viewModel.conferenceModel.isCurrentCallInConference ? View.GONE : View.VISIBLE}" app:layout_constraintStart_toEndOf="@id/back" app:layout_constraintTop_toTopOf="@id/back" app:layout_constraintBottom_toBottomOf="@id/back"/>