diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt
index 7fd5072db..50757afa2 100644
--- a/app/src/main/java/org/linphone/core/CoreContext.kt
+++ b/app/src/main/java/org/linphone/core/CoreContext.kt
@@ -119,7 +119,20 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
) {
Log.i("$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]")
when (state) {
- Call.State.OutgoingProgress, Call.State.Connected -> {
+ Call.State.OutgoingProgress -> {
+ val conferenceInfo = core.findConferenceInformationFromUri(call.remoteAddress)
+ // Do not show outgoing call view for conference calls, wait for connected state
+ if (conferenceInfo == null) {
+ postOnMainThread {
+ showCallActivity()
+ }
+ } else {
+ Log.i(
+ "$TAG Call peer address matches known conference, delaying in-call UI until Connected state"
+ )
+ }
+ }
+ Call.State.Connected -> {
postOnMainThread {
showCallActivity()
}
diff --git a/app/src/main/java/org/linphone/ui/call/CallActivity.kt b/app/src/main/java/org/linphone/ui/call/CallActivity.kt
index 3f373fa93..93ff18acd 100644
--- a/app/src/main/java/org/linphone/ui/call/CallActivity.kt
+++ b/app/src/main/java/org/linphone/ui/call/CallActivity.kt
@@ -34,7 +34,6 @@ import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
-import androidx.navigation.fragment.findNavController
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
@@ -166,7 +165,7 @@ class CallActivity : AppCompatActivity() {
}
}
- callViewModel.goTEndedCallEvent.observe(this) {
+ callViewModel.goToEndedCallEvent.observe(this) {
it.consume {
val action = ActiveCallFragmentDirections.actionGlobalEndedCallFragment()
findNavController(R.id.call_nav_container).navigate(action)
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
index 928a31d69..8e3e61fdd 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
@@ -251,6 +251,14 @@ class ActiveCallFragment : GenericCallFragment() {
}
}
+ callViewModel.goToConferenceEvent.observe(viewLifecycleOwner) {
+ it.consume {
+ Log.i("$TAG Going to conference fragment")
+ val action = ActiveCallFragmentDirections.actionActiveCallFragmentToActiveConferenceCallFragment()
+ findNavController().navigate(action)
+ }
+ }
+
actionsBottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ActiveConferenceCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ActiveConferenceCallFragment.kt
new file mode 100644
index 000000000..1dc205f85
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/call/fragment/ActiveConferenceCallFragment.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.ui.call.fragment
+
+import android.os.Bundle
+import android.os.SystemClock
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProvider
+import org.linphone.databinding.CallActiveConferenceFragmentBinding
+import org.linphone.ui.call.viewmodel.CallsViewModel
+import org.linphone.ui.call.viewmodel.CurrentCallViewModel
+
+class ActiveConferenceCallFragment : GenericCallFragment() {
+ companion object {
+ private const val TAG = "[Active Conference Call Fragment]"
+ }
+
+ private lateinit var binding: CallActiveConferenceFragmentBinding
+
+ private lateinit var callViewModel: CurrentCallViewModel
+
+ private lateinit var callsViewModel: CallsViewModel
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = CallActiveConferenceFragmentBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ callViewModel = requireActivity().run {
+ ViewModelProvider(this)[CurrentCallViewModel::class.java]
+ }
+
+ callsViewModel = requireActivity().run {
+ ViewModelProvider(this)[CallsViewModel::class.java]
+ }
+
+ binding.lifecycleOwner = viewLifecycleOwner
+ binding.viewModel = callViewModel
+ binding.conferenceViewModel = callViewModel.conferenceModel
+ binding.callsViewModel = callsViewModel
+ binding.numpadModel = callViewModel.numpadModel
+
+ callViewModel.callDuration.observe(viewLifecycleOwner) { duration ->
+ binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
+ binding.chronometer.start()
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt b/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt
new file mode 100644
index 000000000..145af8603
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/call/model/ConferenceModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.linphone.ui.call.model
+
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.MutableLiveData
+import org.linphone.core.Call
+import org.linphone.core.Conference
+import org.linphone.core.tools.Log
+
+class ConferenceModel {
+ companion object {
+ private const val TAG = "[Conference ViewModel]"
+ }
+
+ val subject = MutableLiveData()
+
+ private lateinit var conference: Conference
+
+ @WorkerThread
+ fun configureFromCall(call: Call) {
+ val conf = call.conference ?: return
+ conference = conf
+
+ Log.i(
+ "$TAG Configuring conference with subject [${conference.subject}] from call [${call.callLog.callId}]"
+ )
+ subject.postValue(conference.subject)
+ }
+}
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 dfd4a8601..46429b508 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
@@ -43,6 +43,7 @@ import org.linphone.core.MediaDirection
import org.linphone.core.MediaEncryption
import org.linphone.core.tools.Log
import org.linphone.ui.call.model.AudioDeviceModel
+import org.linphone.ui.call.model.ConferenceModel
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.ui.main.history.model.NumpadModel
import org.linphone.utils.AppUtils
@@ -110,7 +111,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
MutableLiveData>()
}
- val goTEndedCallEvent: MutableLiveData> by lazy {
+ val goToEndedCallEvent: MutableLiveData> by lazy {
MutableLiveData>()
}
@@ -129,6 +130,14 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
MutableLiveData>>()
}
+ // Conference
+
+ val conferenceModel = ConferenceModel()
+
+ val goToConferenceEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
// Extras actions
val isActionsMenuExpanded = MutableLiveData()
@@ -201,13 +210,13 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
Log.e(
"$TAG Failed to get a valid call to display, go to ended call fragment"
)
- goTEndedCallEvent.postValue(Event(true))
+ goToEndedCallEvent.postValue(Event(true))
}
} else {
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
- goTEndedCallEvent.postValue(Event(true))
+ goToEndedCallEvent.postValue(Event(true))
}
} else {
val videoEnabled = call.currentParams.isVideoEnabled
@@ -224,6 +233,12 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
Log.w("$TAG Video is not longer enabled, leaving full screen mode")
fullScreenMode.postValue(false)
}
+
+ if (call.state == Call.State.Connected && call.conference != null) {
+ Log.i("$TAG Call is in Connected state and conference isn't null")
+ conferenceModel.configureFromCall(call)
+ goToConferenceEvent.postValue(Event(true))
+ }
}
isPaused.postValue(isCallPaused())
@@ -719,6 +734,11 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
currentCall = call
call.addListener(callListener)
+ if (call.conference != null) {
+ conferenceModel.configureFromCall(call)
+ goToConferenceEvent.postValue(Event(true))
+ }
+
if (call.dir == Call.Dir.Incoming) {
if (call.core.accountList.size > 1) {
val displayName = LinphoneUtils.getDisplayName(call.toAddress)
diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt
index bf3911337..402cd0608 100644
--- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt
+++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingWaitingRoomFragment.kt
@@ -109,6 +109,13 @@ class MeetingWaitingRoomFragment : GenericFragment() {
}
}
+ viewModel.conferenceCreatedEvent.observe(viewLifecycleOwner) {
+ it.consume {
+ Log.i("$TAG Conference was joined, leaving waiting room")
+ goBack()
+ }
+ }
+
if (!isCameraPermissionGranted()) {
viewModel.isVideoAvailable.value = false
Log.w("$TAG Camera permission wasn't granted yet, asking for it now")
diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt
index d9b06697b..55552974f 100644
--- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingWaitingRoomViewModel.kt
@@ -24,7 +24,10 @@ import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.core.Conference
import org.linphone.core.ConferenceInfo
+import org.linphone.core.Core
+import org.linphone.core.CoreListenerStub
import org.linphone.core.Factory
import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
@@ -53,11 +56,38 @@ class MeetingWaitingRoomViewModel @UiThread constructor() : ViewModel() {
val conferenceInfoFoundEvent = MutableLiveData>()
+ val conferenceCreatedEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
private lateinit var conferenceInfo: ConferenceInfo
+ private val coreListener = object : CoreListenerStub() {
+ override fun onConferenceStateChanged(
+ core: Core,
+ conference: Conference,
+ state: Conference.State?
+ ) {
+ Log.i("$TAG Conference state changed: [$state]")
+ if (conference.state == Conference.State.Created) {
+ conferenceCreatedEvent.postValue(Event(true))
+ }
+ }
+ }
+
+ init {
+ coreContext.postOnCoreThread { core ->
+ core.addListener(coreListener)
+ }
+ }
+
@UiThread
override fun onCleared() {
super.onCleared()
+
+ coreContext.postOnCoreThread { core ->
+ core.removeListener(coreListener)
+ }
}
@UiThread
diff --git a/app/src/main/res/layout/call_active_conference_fragment.xml b/app/src/main/res/layout/call_active_conference_fragment.xml
new file mode 100644
index 000000000..d1c9f8fc1
--- /dev/null
+++ b/app/src/main/res/layout/call_active_conference_fragment.xml
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/call_nav_graph.xml b/app/src/main/res/navigation/call_nav_graph.xml
index 3478a670f..870f803b4 100644
--- a/app/src/main/res/navigation/call_nav_graph.xml
+++ b/app/src/main/res/navigation/call_nav_graph.xml
@@ -66,6 +66,12 @@
app:enterAnim="@anim/slide_in"
app:popExitAnim="@anim/slide_out"
app:launchSingleTop="true" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c243b4e2d..f61588164 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -427,6 +427,8 @@
%s calls
%s paused calls
+ Waiting for other participants…
+
Account connection error