Migrated deprecated chat room & conference scheduler APIs

This commit is contained in:
Sylvain Berfini 2024-10-22 11:21:30 +02:00
parent 50cb162bd3
commit 257352927d
15 changed files with 248 additions and 78 deletions

View file

@ -25,6 +25,8 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Address
import org.linphone.core.ConferenceParams
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
class NotificationBroadcastReceiver : BroadcastReceiver() { class NotificationBroadcastReceiver : BroadcastReceiver() {
@ -110,7 +112,15 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
return@postOnCoreThread return@postOnCoreThread
} }
val room = core.searchChatRoom(null, localAddress, remoteAddress, arrayOfNulls(0)) val params: ConferenceParams? = null
val room = core.searchChatRoom(
params,
localAddress,
remoteAddress,
arrayOfNulls<Address>(
0
)
)
if (room == null) { if (room == null) {
Log.e( Log.e(
"$TAG Couldn't find conversation for remote address [$remoteSipAddress] and local address [$localIdentity]" "$TAG Couldn't find conversation for remote address [$remoteSipAddress] and local address [$localIdentity]"

View file

@ -42,7 +42,8 @@ import org.linphone.core.CallStats
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.ChatRoomParams import org.linphone.core.Conference
import org.linphone.core.ConferenceParams
import org.linphone.core.Core import org.linphone.core.Core
import org.linphone.core.CoreListenerStub import org.linphone.core.CoreListenerStub
import org.linphone.core.MediaDirection import org.linphone.core.MediaDirection
@ -711,7 +712,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
else -> device.deviceName else -> device.deviceName
} }
val currentDevice = currentCall.outputAudioDevice val currentDevice = currentCall.outputAudioDevice
val isCurrentlyInUse = device.type == currentDevice?.type && device.deviceName == currentDevice?.deviceName val isCurrentlyInUse = device.type == currentDevice?.type && device.deviceName == currentDevice.deviceName
val model = AudioDeviceModel(device, name, device.type, isCurrentlyInUse, true) { val model = AudioDeviceModel(device, name, device.type, isCurrentlyInUse, true) {
// onSelected // onSelected
coreContext.postOnCoreThread { coreContext.postOnCoreThread {
@ -1281,7 +1282,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
val params = getChatRoomParams(call) ?: return // TODO: show error to user val params = getChatRoomParams(call) ?: return // TODO: show error to user
val conversation = core.createChatRoom(params, localAddress, participants) val conversation = core.createChatRoom(params, localAddress, participants)
if (conversation != null) { if (conversation != null) {
if (params.backend == ChatRoom.Backend.FlexisipChat) { if (params.chatParams?.backend == ChatRoom.Backend.FlexisipChat) {
if (conversation.state == ChatRoom.State.Created) { if (conversation.state == ChatRoom.State.Created) {
val id = LinphoneUtils.getChatRoomId(conversation) val id = LinphoneUtils.getChatRoomId(conversation)
Log.i("$TAG 1-1 conversation [$id] has been created") Log.i("$TAG 1-1 conversation [$id] has been created")
@ -1323,37 +1324,39 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() {
} }
@WorkerThread @WorkerThread
private fun getChatRoomParams(call: Call): ChatRoomParams? { private fun getChatRoomParams(call: Call): ConferenceParams? {
val localAddress = call.callLog.localAddress val localAddress = call.callLog.localAddress
val remoteAddress = call.remoteAddress val remoteAddress = call.remoteAddress
val core = call.core val core = call.core
val account = LinphoneUtils.getAccountForAddress(localAddress) ?: LinphoneUtils.getDefaultAccount() ?: return null val account = LinphoneUtils.getAccountForAddress(localAddress) ?: LinphoneUtils.getDefaultAccount() ?: return null
val params: ChatRoomParams = core.createDefaultChatRoomParams() val params = coreContext.core.createConferenceParams(call.conference)
params.isChatEnabled = true
params.isGroupEnabled = false params.isGroupEnabled = false
params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject)
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default val chatParams = params.chatParams ?: return null
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
val sameDomain = remoteAddress.domain == corePreferences.defaultDomain && remoteAddress.domain == account.params.domain val sameDomain = remoteAddress.domain == corePreferences.defaultDomain && remoteAddress.domain == account.params.domain
if (account.params.instantMessagingEncryptionMandatory && sameDomain) { if (account.params.instantMessagingEncryptionMandatory && sameDomain) {
Log.i( Log.i(
"$TAG Account is in secure mode & domain matches, requesting E2E encryption" "$TAG Account is in secure mode & domain matches, requesting E2E encryption"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else if (!account.params.instantMessagingEncryptionMandatory) { } else if (!account.params.instantMessagingEncryptionMandatory) {
if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME is available, requesting E2E encryption" "$TAG Account is in interop mode but LIME is available, requesting E2E encryption"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else { } else {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME isn't available, disabling E2E encryption" "$TAG Account is in interop mode but LIME isn't available, disabling E2E encryption"
) )
params.backend = ChatRoom.Backend.Basic chatParams.backend = ChatRoom.Backend.Basic
params.isEncryptionEnabled = false params.securityLevel = Conference.SecurityLevel.None
} }
} else { } else {
Log.e( Log.e(

View file

@ -26,7 +26,9 @@ import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.core.Address
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ConferenceParams
import org.linphone.core.ConferenceScheduler import org.linphone.core.ConferenceScheduler
import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.ConferenceSchedulerListenerStub
import org.linphone.core.Factory import org.linphone.core.Factory
@ -149,11 +151,12 @@ abstract class AbstractConversationViewModel : GenericViewModel() {
if (localAddress != null && remoteAddress != null) { if (localAddress != null && remoteAddress != null) {
Log.i("$TAG Searching for conversation in Core using local & peer SIP addresses") Log.i("$TAG Searching for conversation in Core using local & peer SIP addresses")
val params: ConferenceParams? = null
val found = core.searchChatRoom( val found = core.searchChatRoom(
null, params,
localAddress, localAddress,
remoteAddress, remoteAddress,
arrayOfNulls( arrayOfNulls<Address>(
0 0
) )
) )
@ -229,7 +232,7 @@ abstract class AbstractConversationViewModel : GenericViewModel() {
Log.i( Log.i(
"$TAG Creating group call with subject ${conferenceInfo.subject} and ${participants.size} participant(s)" "$TAG Creating group call with subject ${conferenceInfo.subject} and ${participants.size} participant(s)"
) )
val conferenceScheduler = core.createConferenceScheduler() val conferenceScheduler = LinphoneUtils.createConferenceScheduler(account)
conferenceScheduler.addListener(conferenceSchedulerListener) conferenceScheduler.addListener(conferenceSchedulerListener)
conferenceScheduler.account = account conferenceScheduler.account = account
// Will trigger the conference creation/update automatically // Will trigger the conference creation/update automatically

View file

@ -29,7 +29,7 @@ import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.ChatRoomParams import org.linphone.core.Conference
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
@ -189,29 +189,31 @@ class ConversationForwardMessageViewModel @UiThread constructor() : AddressSelec
operationInProgress.postValue(true) operationInProgress.postValue(true)
val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() val params = coreContext.core.createConferenceParams(null)
params.isChatEnabled = true
params.isGroupEnabled = false params.isGroupEnabled = false
params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject)
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default val chatParams = params.chatParams ?: return
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain
if (account.params.instantMessagingEncryptionMandatory && sameDomain) { if (account.params.instantMessagingEncryptionMandatory && sameDomain) {
Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation") Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation")
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else if (!account.params.instantMessagingEncryptionMandatory) { } else if (!account.params.instantMessagingEncryptionMandatory) {
if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME is available, creating a E2E conversation" "$TAG Account is in interop mode but LIME is available, creating a E2E conversation"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else { } else {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
) )
params.backend = ChatRoom.Backend.Basic chatParams.backend = ChatRoom.Backend.Basic
params.isEncryptionEnabled = false params.securityLevel = Conference.SecurityLevel.None
} }
} else { } else {
Log.e( Log.e(
@ -238,7 +240,7 @@ class ConversationForwardMessageViewModel @UiThread constructor() : AddressSelec
) )
val chatRoom = core.createChatRoom(params, localAddress, participants) val chatRoom = core.createChatRoom(params, localAddress, participants)
if (chatRoom != null) { if (chatRoom != null) {
if (params.backend == ChatRoom.Backend.FlexisipChat) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) {
if (chatRoom.state == ChatRoom.State.Created) { if (chatRoom.state == ChatRoom.State.Created) {
val id = LinphoneUtils.getChatRoomId(chatRoom) val id = LinphoneUtils.getChatRoomId(chatRoom)
Log.i("$TAG 1-1 conversation [$id] has been created") Log.i("$TAG 1-1 conversation [$id] has been created")

View file

@ -29,7 +29,7 @@ import org.linphone.R
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.ChatRoomParams import org.linphone.core.Conference
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.ui.main.viewmodel.AddressSelectionViewModel import org.linphone.ui.main.viewmodel.AddressSelectionViewModel
import org.linphone.utils.AppUtils import org.linphone.utils.AppUtils
@ -112,11 +112,14 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM
operationInProgress.postValue(true) operationInProgress.postValue(true)
val groupChatRoomSubject = subject.value.orEmpty() val groupChatRoomSubject = subject.value.orEmpty()
val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() val params = coreContext.core.createConferenceParams(null)
params.isChatEnabled = true
params.isGroupEnabled = true params.isGroupEnabled = true
params.subject = groupChatRoomSubject params.subject = groupChatRoomSubject
params.backend = ChatRoom.Backend.FlexisipChat params.securityLevel = Conference.SecurityLevel.EndToEnd
params.isEncryptionEnabled = true val chatParams = params.chatParams ?: return@postOnCoreThread
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
chatParams.backend = ChatRoom.Backend.FlexisipChat
val participants = arrayListOf<Address>() val participants = arrayListOf<Address>()
for (participant in selection.value.orEmpty()) { for (participant in selection.value.orEmpty()) {
@ -131,7 +134,7 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM
participants.toArray(participantsArray) participants.toArray(participantsArray)
) )
if (chatRoom != null) { if (chatRoom != null) {
if (params.backend == ChatRoom.Backend.FlexisipChat) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) {
if (chatRoom.state == ChatRoom.State.Created) { if (chatRoom.state == ChatRoom.State.Created) {
val id = LinphoneUtils.getChatRoomId(chatRoom) val id = LinphoneUtils.getChatRoomId(chatRoom)
Log.i( Log.i(
@ -188,29 +191,31 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM
operationInProgress.postValue(true) operationInProgress.postValue(true)
val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() val params = coreContext.core.createConferenceParams(null)
params.isChatEnabled = true
params.isGroupEnabled = false params.isGroupEnabled = false
params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject)
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default val chatParams = params.chatParams ?: return
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain
if (account.params.instantMessagingEncryptionMandatory && sameDomain) { if (account.params.instantMessagingEncryptionMandatory && sameDomain) {
Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation") Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation")
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else if (!account.params.instantMessagingEncryptionMandatory) { } else if (!account.params.instantMessagingEncryptionMandatory) {
if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME is available, creating a E2E conversation" "$TAG Account is in interop mode but LIME is available, creating a E2E conversation"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else { } else {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
) )
params.backend = ChatRoom.Backend.Basic chatParams.backend = ChatRoom.Backend.Basic
params.isEncryptionEnabled = false params.securityLevel = Conference.SecurityLevel.None
} }
} else { } else {
Log.e( Log.e(
@ -232,7 +237,7 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM
) )
val chatRoom = core.createChatRoom(params, localAddress, participants) val chatRoom = core.createChatRoom(params, localAddress, participants)
if (chatRoom != null) { if (chatRoom != null) {
if (params.backend == ChatRoom.Backend.FlexisipChat) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) {
if (chatRoom.state == ChatRoom.State.Created) { if (chatRoom.state == ChatRoom.State.Created) {
val id = LinphoneUtils.getChatRoomId(chatRoom) val id = LinphoneUtils.getChatRoomId(chatRoom)
Log.i("$TAG 1-1 conversation [$id] has been created") Log.i("$TAG 1-1 conversation [$id] has been created")

View file

@ -36,7 +36,7 @@ import org.linphone.core.Address
import org.linphone.core.Call import org.linphone.core.Call
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.ChatRoomParams import org.linphone.core.Conference
import org.linphone.core.Core import org.linphone.core.Core
import org.linphone.core.CoreListenerStub import org.linphone.core.CoreListenerStub
import org.linphone.core.Friend import org.linphone.core.Friend
@ -501,31 +501,33 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() {
"$TAG Looking for existing conversation between [$localSipUri] and [$remoteSipUri]" "$TAG Looking for existing conversation between [$localSipUri] and [$remoteSipUri]"
) )
val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() val params = coreContext.core.createConferenceParams(null)
params.isChatEnabled = true
params.isGroupEnabled = false params.isGroupEnabled = false
params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject)
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default val chatParams = params.chatParams ?: return
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain
if (account.params.instantMessagingEncryptionMandatory && sameDomain) { if (account.params.instantMessagingEncryptionMandatory && sameDomain) {
Log.i( Log.i(
"$TAG Account is in secure mode & domain matches, creating a E2E conversation" "$TAG Account is in secure mode & domain matches, creating a E2E conversation"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else if (!account.params.instantMessagingEncryptionMandatory) { } else if (!account.params.instantMessagingEncryptionMandatory) {
if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME is available, creating a E2E conversation" "$TAG Account is in interop mode but LIME is available, creating a E2E conversation"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else { } else {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
) )
params.backend = ChatRoom.Backend.Basic chatParams.backend = ChatRoom.Backend.Basic
params.isEncryptionEnabled = false params.securityLevel = Conference.SecurityLevel.None
} }
} else { } else {
Log.e( Log.e(
@ -554,7 +556,7 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() {
operationInProgress.postValue(true) operationInProgress.postValue(true)
val chatRoom = core.createChatRoom(params, localAddress, participants) val chatRoom = core.createChatRoom(params, localAddress, participants)
if (chatRoom != null) { if (chatRoom != null) {
if (params.backend == ChatRoom.Backend.FlexisipChat) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) {
if (chatRoom.state == ChatRoom.State.Created) { if (chatRoom.state == ChatRoom.State.Created) {
val id = LinphoneUtils.getChatRoomId(chatRoom) val id = LinphoneUtils.getChatRoomId(chatRoom)
Log.i("$TAG 1-1 conversation [$id] has been created") Log.i("$TAG 1-1 conversation [$id] has been created")

View file

@ -28,7 +28,7 @@ import org.linphone.R
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomListenerStub import org.linphone.core.ChatRoomListenerStub
import org.linphone.core.ChatRoomParams import org.linphone.core.Conference
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.ui.GenericViewModel import org.linphone.ui.GenericViewModel
import org.linphone.ui.main.history.model.CallLogHistoryModel import org.linphone.ui.main.history.model.CallLogHistoryModel
@ -184,31 +184,33 @@ class HistoryViewModel @UiThread constructor() : GenericViewModel() {
"$TAG Looking for existing conversation between [$localSipUri] and [$remoteSipUri]" "$TAG Looking for existing conversation between [$localSipUri] and [$remoteSipUri]"
) )
val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() val params = coreContext.core.createConferenceParams(null)
params.isChatEnabled = true
params.isGroupEnabled = false params.isGroupEnabled = false
params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject) params.subject = AppUtils.getString(R.string.conversation_one_to_one_hidden_subject)
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default val chatParams = params.chatParams ?: return@postOnCoreThread
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain val sameDomain = remote.domain == corePreferences.defaultDomain && remote.domain == account.params.domain
if (account.params.instantMessagingEncryptionMandatory && sameDomain) { if (account.params.instantMessagingEncryptionMandatory && sameDomain) {
Log.i( Log.i(
"$TAG Account is in secure mode & domain matches, creating a E2E conversation" "$TAG Account is in secure mode & domain matches, creating a E2E conversation"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else if (!account.params.instantMessagingEncryptionMandatory) { } else if (!account.params.instantMessagingEncryptionMandatory) {
if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME is available, creating a E2E conversation" "$TAG Account is in interop mode but LIME is available, creating a E2E conversation"
) )
params.backend = ChatRoom.Backend.FlexisipChat chatParams.backend = ChatRoom.Backend.FlexisipChat
params.isEncryptionEnabled = true params.securityLevel = Conference.SecurityLevel.EndToEnd
} else { } else {
Log.i( Log.i(
"$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
) )
params.backend = ChatRoom.Backend.Basic chatParams.backend = ChatRoom.Backend.Basic
params.isEncryptionEnabled = false params.securityLevel = Conference.SecurityLevel.None
} }
} else { } else {
Log.e( Log.e(
@ -237,7 +239,7 @@ class HistoryViewModel @UiThread constructor() : GenericViewModel() {
operationInProgress.postValue(true) operationInProgress.postValue(true)
val chatRoom = core.createChatRoom(params, localAddress, participants) val chatRoom = core.createChatRoom(params, localAddress, participants)
if (chatRoom != null) { if (chatRoom != null) {
if (params.backend == ChatRoom.Backend.FlexisipChat) { if (chatParams.backend == ChatRoom.Backend.FlexisipChat) {
if (chatRoom.state == ChatRoom.State.Created) { if (chatRoom.state == ChatRoom.State.Created) {
val id = LinphoneUtils.getChatRoomId(chatRoom) val id = LinphoneUtils.getChatRoomId(chatRoom)
Log.i("$TAG 1-1 conversation [$id] has been created") Log.i("$TAG 1-1 conversation [$id] has been created")

View file

@ -234,7 +234,7 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() {
Log.i( Log.i(
"$TAG Creating group call with subject ${subject.value} and ${participants.size} participant(s)" "$TAG Creating group call with subject ${subject.value} and ${participants.size} participant(s)"
) )
val conferenceScheduler = core.createConferenceScheduler() val conferenceScheduler = LinphoneUtils.createConferenceScheduler(account)
conferenceScheduler.addListener(conferenceSchedulerListener) conferenceScheduler.addListener(conferenceSchedulerListener)
conferenceScheduler.account = account conferenceScheduler.account = account
// Will trigger the conference creation/update automatically // Will trigger the conference creation/update automatically

View file

@ -35,6 +35,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.R import org.linphone.R
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.MeetingsListFragmentBinding import org.linphone.databinding.MeetingsListFragmentBinding
import org.linphone.ui.GenericActivity
import org.linphone.ui.main.fragment.AbstractMainFragment import org.linphone.ui.main.fragment.AbstractMainFragment
import org.linphone.ui.main.history.model.ConfirmationDialogModel import org.linphone.ui.main.history.model.ConfirmationDialogModel
import org.linphone.ui.main.meetings.adapter.MeetingsListAdapter import org.linphone.ui.main.meetings.adapter.MeetingsListAdapter
@ -59,6 +60,8 @@ class MeetingsListFragment : AbstractMainFragment() {
private var bottomSheetDialog: BottomSheetDialogFragment? = null private var bottomSheetDialog: BottomSheetDialogFragment? = null
private var meetingViewModelBeingCancelled: MeetingModel? = null
private val dataObserver = object : AdapterDataObserver() { private val dataObserver = object : AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0 && adapter.itemCount == itemCount) { if (positionStart == 0 && adapter.itemCount == itemCount) {
@ -161,6 +164,24 @@ class MeetingsListFragment : AbstractMainFragment() {
listViewModel.fetchInProgress.value = false listViewModel.fetchInProgress.value = false
} }
listViewModel.conferenceCancelledEvent.observe(viewLifecycleOwner) {
it.consume {
Log.i("$TAG Meeting has been cancelled successfully, deleting it")
(requireActivity() as GenericActivity).showGreenToast(
getString(R.string.meeting_info_cancelled_toast),
R.drawable.trash_simple
)
meetingViewModelBeingCancelled?.delete()
meetingViewModelBeingCancelled = null
listViewModel.applyFilter()
(requireActivity() as GenericActivity).showGreenToast(
getString(R.string.meeting_info_deleted_toast),
R.drawable.trash_simple
)
}
}
adapter.meetingLongClickedEvent.observe(viewLifecycleOwner) { adapter.meetingLongClickedEvent.observe(viewLifecycleOwner) {
it.consume { model -> it.consume { model ->
val modalBottomSheet = MeetingsMenuDialogFragment( val modalBottomSheet = MeetingsMenuDialogFragment(
@ -168,12 +189,17 @@ class MeetingsListFragment : AbstractMainFragment() {
adapter.resetSelection() adapter.resetSelection()
}, },
{ // onDelete { // onDelete
if (model.isOrganizer()) { if (model.isOrganizer() && !model.isCancelled) {
showCancelMeetingDialog(model) showCancelMeetingDialog(model)
} else { } else {
Log.i("$TAG Deleting meeting [${model.id}]") Log.i("$TAG Deleting meeting [${model.id}]")
model.delete() model.delete()
listViewModel.applyFilter() listViewModel.applyFilter()
(requireActivity() as GenericActivity).showGreenToast(
getString(R.string.meeting_info_deleted_toast),
R.drawable.trash_simple
)
} }
} }
) )
@ -296,16 +322,20 @@ class MeetingsListFragment : AbstractMainFragment() {
Log.i("$TAG Deleting meeting [${meetingModel.id}]") Log.i("$TAG Deleting meeting [${meetingModel.id}]")
meetingModel.delete() meetingModel.delete()
listViewModel.applyFilter() listViewModel.applyFilter()
dialog.dismiss() dialog.dismiss()
(requireActivity() as GenericActivity).showGreenToast(
getString(R.string.meeting_info_deleted_toast),
R.drawable.trash_simple
)
} }
} }
model.confirmEvent.observe(viewLifecycleOwner) { model.confirmEvent.observe(viewLifecycleOwner) {
it.consume { it.consume {
Log.i("$TAG Cancelling meeting [${meetingModel.id}]") Log.i("$TAG Cancelling meeting [${meetingModel.id}]")
meetingViewModelBeingCancelled = meetingModel
listViewModel.cancelMeeting(meetingModel.conferenceInfo) listViewModel.cancelMeeting(meetingModel.conferenceInfo)
meetingModel.delete()
listViewModel.applyFilter()
dialog.dismiss() dialog.dismiss()
} }
} }

View file

@ -25,7 +25,6 @@ import androidx.lifecycle.MutableLiveData
import java.util.TimeZone import java.util.TimeZone
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.core.ChatRoom
import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceInfo
import org.linphone.core.ConferenceScheduler import org.linphone.core.ConferenceScheduler
import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.ConferenceSchedulerListenerStub
@ -36,6 +35,7 @@ import org.linphone.ui.GenericViewModel
import org.linphone.ui.main.meetings.model.ParticipantModel import org.linphone.ui.main.meetings.model.ParticipantModel
import org.linphone.ui.main.meetings.model.TimeZoneModel import org.linphone.ui.main.meetings.model.TimeZoneModel
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class MeetingViewModel @UiThread constructor() : GenericViewModel() { class MeetingViewModel @UiThread constructor() : GenericViewModel() {
@ -90,12 +90,12 @@ class MeetingViewModel @UiThread constructor() : GenericViewModel() {
Log.i( Log.i(
"$TAG Conference ${conferenceScheduler.info?.subject} cancelled" "$TAG Conference ${conferenceScheduler.info?.subject} cancelled"
) )
val chatRoomParams = coreContext.core.createDefaultChatRoomParams() val params = LinphoneUtils.getChatRoomParamsToCancelMeeting()
chatRoomParams.isGroupEnabled = false if (params != null) {
chatRoomParams.backend = ChatRoom.Backend.FlexisipChat conferenceScheduler.sendInvitations(params)
chatRoomParams.isEncryptionEnabled = true } else {
chatRoomParams.subject = "Meeting cancelled" // Won't be used operationInProgress.postValue(false)
conferenceScheduler.sendInvitations(chatRoomParams) // Send cancel ICS }
} else if (state == ConferenceScheduler.State.Error) { } else if (state == ConferenceScheduler.State.Error) {
operationInProgress.postValue(false) operationInProgress.postValue(false)
} }
@ -185,7 +185,9 @@ class MeetingViewModel @UiThread constructor() : GenericViewModel() {
if (::conferenceInfo.isInitialized) { if (::conferenceInfo.isInitialized) {
Log.i("$TAG Cancelling conference information [$conferenceInfo]") Log.i("$TAG Cancelling conference information [$conferenceInfo]")
operationInProgress.postValue(true) operationInProgress.postValue(true)
val conferenceScheduler = core.createConferenceScheduler() val conferenceScheduler = LinphoneUtils.createConferenceScheduler(
LinphoneUtils.getDefaultAccount()
)
conferenceScheduler.addListener(conferenceSchedulerListener) conferenceScheduler.addListener(conferenceSchedulerListener)
conferenceScheduler.cancelConference(conferenceInfo) conferenceScheduler.cancelConference(conferenceInfo)
} }

View file

@ -23,13 +23,18 @@ import androidx.annotation.UiThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Address
import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceInfo
import org.linphone.core.ConferenceScheduler
import org.linphone.core.ConferenceSchedulerListenerStub
import org.linphone.core.Core import org.linphone.core.Core
import org.linphone.core.CoreListenerStub import org.linphone.core.CoreListenerStub
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.ui.main.meetings.model.MeetingListItemModel import org.linphone.ui.main.meetings.model.MeetingListItemModel
import org.linphone.ui.main.meetings.model.MeetingModel import org.linphone.ui.main.meetings.model.MeetingModel
import org.linphone.ui.main.viewmodel.AbstractMainViewModel import org.linphone.ui.main.viewmodel.AbstractMainViewModel
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() { class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() {
@ -41,6 +46,12 @@ class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() {
val fetchInProgress = MutableLiveData<Boolean>() val fetchInProgress = MutableLiveData<Boolean>()
val operationInProgress = MutableLiveData<Boolean>()
val conferenceCancelledEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
private val coreListener = object : CoreListenerStub() { private val coreListener = object : CoreListenerStub() {
@WorkerThread @WorkerThread
override fun onConferenceInfoReceived(core: Core, conferenceInfo: ConferenceInfo) { override fun onConferenceInfoReceived(core: Core, conferenceInfo: ConferenceInfo) {
@ -49,7 +60,51 @@ class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() {
} }
} }
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) {
Log.i(
"$TAG Conference ${conferenceScheduler.info?.subject} cancelled"
)
val params = LinphoneUtils.getChatRoomParamsToCancelMeeting()
if (params != null) {
conferenceScheduler.sendInvitations(params)
} else {
operationInProgress.postValue(false)
}
} else if (state == ConferenceScheduler.State.Error) {
operationInProgress.postValue(false)
}
}
override fun onInvitationsSent(
conferenceScheduler: ConferenceScheduler,
failedInvitations: Array<out Address>?
) {
if (failedInvitations?.isNotEmpty() == true) {
for (address in failedInvitations) {
Log.e(
"$TAG Conference cancelled ICS wasn't sent to participant ${address.asStringUriOnly()}"
)
}
} else {
Log.i(
"$TAG Conference cancelled ICS successfully sent to all participants"
)
}
conferenceScheduler.removeListener(this)
operationInProgress.postValue(false)
conferenceCancelledEvent.postValue(Event(true))
}
}
init { init {
operationInProgress.value = false
fetchInProgress.value = true fetchInProgress.value = true
coreContext.postOnCoreThread { core -> coreContext.postOnCoreThread { core ->
@ -79,7 +134,11 @@ class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() {
fun cancelMeeting(conferenceInfo: ConferenceInfo) { fun cancelMeeting(conferenceInfo: ConferenceInfo) {
coreContext.postOnCoreThread { core -> coreContext.postOnCoreThread { core ->
Log.w("$TAG Cancelling conference info [${conferenceInfo.uri?.asStringUriOnly()}]") Log.w("$TAG Cancelling conference info [${conferenceInfo.uri?.asStringUriOnly()}]")
val conferenceScheduler = core.createConferenceScheduler() operationInProgress.postValue(true)
val conferenceScheduler = LinphoneUtils.createConferenceScheduler(
LinphoneUtils.getDefaultAccount()
)
conferenceScheduler.addListener(conferenceSchedulerListener)
conferenceScheduler.cancelConference(conferenceInfo) conferenceScheduler.cancelConference(conferenceInfo)
} }
} }

View file

@ -30,6 +30,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.core.Address import org.linphone.core.Address
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.Conference
import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceInfo
import org.linphone.core.ConferenceScheduler import org.linphone.core.ConferenceScheduler
import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.ConferenceSchedulerListenerStub
@ -41,6 +42,7 @@ import org.linphone.ui.GenericViewModel
import org.linphone.ui.main.meetings.model.TimeZoneModel import org.linphone.ui.main.meetings.model.TimeZoneModel
import org.linphone.ui.main.model.SelectedAddressModel import org.linphone.ui.main.model.SelectedAddressModel
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() {
@ -129,11 +131,15 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() {
if (sendInvitations.value == true) { if (sendInvitations.value == true) {
Log.i("$TAG User asked for invitations to be sent, let's do it") Log.i("$TAG User asked for invitations to be sent, let's do it")
val chatRoomParams = coreContext.core.createDefaultChatRoomParams()
val chatRoomParams = coreContext.core.createConferenceParams(null)
chatRoomParams.isChatEnabled = true
chatRoomParams.isGroupEnabled = false chatRoomParams.isGroupEnabled = false
chatRoomParams.backend = ChatRoom.Backend.FlexisipChat
chatRoomParams.isEncryptionEnabled = true
chatRoomParams.subject = "Meeting invitation" // Won't be used chatRoomParams.subject = "Meeting invitation" // Won't be used
val chatParams = chatRoomParams.chatParams ?: return
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
chatParams.backend = ChatRoom.Backend.FlexisipChat
chatRoomParams.securityLevel = Conference.SecurityLevel.EndToEnd
conferenceScheduler.sendInvitations(chatRoomParams) conferenceScheduler.sendInvitations(chatRoomParams)
} else { } else {
Log.i("$TAG User didn't asked for invitations to be sent") Log.i("$TAG User didn't asked for invitations to be sent")
@ -459,7 +465,7 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() {
conferenceInfo.setParticipantInfos(participantsInfoArray) conferenceInfo.setParticipantInfos(participantsInfoArray)
if (!::conferenceScheduler.isInitialized) { if (!::conferenceScheduler.isInitialized) {
conferenceScheduler = core.createConferenceScheduler() conferenceScheduler = LinphoneUtils.createConferenceScheduler(localAccount)
conferenceScheduler.addListener(conferenceSchedulerListener) conferenceScheduler.addListener(conferenceSchedulerListener)
} }
@ -511,7 +517,9 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() {
conferenceInfo.setParticipantInfos(participantsInfoArray) conferenceInfo.setParticipantInfos(participantsInfoArray)
if (!::conferenceScheduler.isInitialized) { if (!::conferenceScheduler.isInitialized) {
conferenceScheduler = core.createConferenceScheduler() conferenceScheduler = LinphoneUtils.createConferenceScheduler(
LinphoneUtils.getDefaultAccount()
)
conferenceScheduler.addListener(conferenceSchedulerListener) conferenceScheduler.addListener(conferenceSchedulerListener)
} }

View file

@ -41,7 +41,10 @@ import org.linphone.core.Call.Status
import org.linphone.core.CallLog import org.linphone.core.CallLog
import org.linphone.core.ChatMessage import org.linphone.core.ChatMessage
import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom
import org.linphone.core.Conference
import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceInfo
import org.linphone.core.ConferenceParams
import org.linphone.core.ConferenceScheduler
import org.linphone.core.Core import org.linphone.core.Core
import org.linphone.core.Factory import org.linphone.core.Factory
import org.linphone.core.Friend import org.linphone.core.Friend
@ -213,6 +216,39 @@ class LinphoneUtils {
return core.defaultAccount?.params?.audioVideoConferenceFactoryAddress != null return core.defaultAccount?.params?.audioVideoConferenceFactoryAddress != null
} }
@WorkerThread
fun createConferenceScheduler(account: Account?): ConferenceScheduler {
if (!account?.params?.ccmpServerUrl.isNullOrEmpty()) {
Log.i(
"$TAG CCMP server URL has been set in Account's params, using CCMP conference scheduler"
)
return coreContext.core.createConferenceSchedulerWithType(
account,
ConferenceScheduler.Type.CCMP
)
}
Log.i(
"$TAG CCMP server URL hasn't been set in Account's params, using SIP conference scheduler"
)
return coreContext.core.createConferenceSchedulerWithType(
account,
ConferenceScheduler.Type.SIP
)
}
@WorkerThread
fun getChatRoomParamsToCancelMeeting(): ConferenceParams? {
val chatRoomParams = coreContext.core.createConferenceParams(null)
chatRoomParams.isChatEnabled = true
chatRoomParams.isGroupEnabled = false
chatRoomParams.subject = "Meeting invitation" // Won't be used
val chatParams = chatRoomParams.chatParams ?: return null
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
chatParams.backend = ChatRoom.Backend.FlexisipChat
chatRoomParams.securityLevel = Conference.SecurityLevel.EndToEnd
return chatRoomParams
}
@WorkerThread @WorkerThread
fun arePushNotificationsAvailable(core: Core): Boolean { fun arePushNotificationsAvailable(core: Core): Boolean {
if (!core.isPushNotificationAvailable) { if (!core.isPushNotificationAvailable) {

View file

@ -103,6 +103,10 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<include
layout="@layout/operation_in_progress"
bind:visibility="@{viewModel.operationInProgress}" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView

View file

@ -103,6 +103,10 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<include
layout="@layout/operation_in_progress"
bind:visibility="@{viewModel.operationInProgress}" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView