diff --git a/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt b/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt index c0cc4c727..ef9ff0566 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt @@ -25,6 +25,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.Address +import org.linphone.core.ConferenceParams import org.linphone.core.tools.Log class NotificationBroadcastReceiver : BroadcastReceiver() { @@ -110,7 +112,15 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { return@postOnCoreThread } - val room = core.searchChatRoom(null, localAddress, remoteAddress, arrayOfNulls(0)) + val params: ConferenceParams? = null + val room = core.searchChatRoom( + params, + localAddress, + remoteAddress, + arrayOfNulls
( + 0 + ) + ) if (room == null) { Log.e( "$TAG Couldn't find conversation for remote address [$remoteSipAddress] and local address [$localIdentity]" 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 81d5dc4ba..607b73040 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 @@ -42,7 +42,8 @@ import org.linphone.core.CallStats import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom 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.CoreListenerStub import org.linphone.core.MediaDirection @@ -711,7 +712,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() { else -> device.deviceName } 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) { // onSelected coreContext.postOnCoreThread { @@ -1281,7 +1282,7 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() { val params = getChatRoomParams(call) ?: return // TODO: show error to user val conversation = core.createChatRoom(params, localAddress, participants) if (conversation != null) { - if (params.backend == ChatRoom.Backend.FlexisipChat) { + if (params.chatParams?.backend == ChatRoom.Backend.FlexisipChat) { if (conversation.state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(conversation) Log.i("$TAG 1-1 conversation [$id] has been created") @@ -1323,37 +1324,39 @@ class CurrentCallViewModel @UiThread constructor() : GenericViewModel() { } @WorkerThread - private fun getChatRoomParams(call: Call): ChatRoomParams? { + private fun getChatRoomParams(call: Call): ConferenceParams? { val localAddress = call.callLog.localAddress val remoteAddress = call.remoteAddress val core = call.core 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.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 if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i( "$TAG Account is in secure mode & domain matches, requesting E2E encryption" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( "$TAG Account is in interop mode but LIME is available, requesting E2E encryption" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.i( "$TAG Account is in interop mode but LIME isn't available, disabling E2E encryption" ) - params.backend = ChatRoom.Backend.Basic - params.isEncryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.e( diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt index aeba594a5..c63d8c314 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/AbstractConversationViewModel.kt @@ -26,7 +26,9 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R +import org.linphone.core.Address import org.linphone.core.ChatRoom +import org.linphone.core.ConferenceParams import org.linphone.core.ConferenceScheduler import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.Factory @@ -149,11 +151,12 @@ abstract class AbstractConversationViewModel : GenericViewModel() { if (localAddress != null && remoteAddress != null) { Log.i("$TAG Searching for conversation in Core using local & peer SIP addresses") + val params: ConferenceParams? = null val found = core.searchChatRoom( - null, + params, localAddress, remoteAddress, - arrayOfNulls( + arrayOfNulls
( 0 ) ) @@ -229,7 +232,7 @@ abstract class AbstractConversationViewModel : GenericViewModel() { Log.i( "$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.account = account // Will trigger the conference creation/update automatically diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt index 4d891422d..56cb0a4d4 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationForwardMessageViewModel.kt @@ -29,7 +29,7 @@ import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers import org.linphone.core.Address import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub -import org.linphone.core.ChatRoomParams +import org.linphone.core.Conference import org.linphone.core.tools.Log import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel @@ -189,29 +189,31 @@ class ConversationForwardMessageViewModel @UiThread constructor() : AddressSelec operationInProgress.postValue(true) - val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() + val params = coreContext.core.createConferenceParams(null) + params.isChatEnabled = true params.isGroupEnabled = false 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 if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation") - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.i( "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" ) - params.backend = ChatRoom.Backend.Basic - params.isEncryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.e( @@ -238,7 +240,7 @@ class ConversationForwardMessageViewModel @UiThread constructor() : AddressSelec ) val chatRoom = core.createChatRoom(params, localAddress, participants) if (chatRoom != null) { - if (params.backend == ChatRoom.Backend.FlexisipChat) { + if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt index 37ed46d51..703b602e3 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt @@ -29,7 +29,7 @@ import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub -import org.linphone.core.ChatRoomParams +import org.linphone.core.Conference import org.linphone.core.tools.Log import org.linphone.ui.main.viewmodel.AddressSelectionViewModel import org.linphone.utils.AppUtils @@ -112,11 +112,14 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM operationInProgress.postValue(true) val groupChatRoomSubject = subject.value.orEmpty() - val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() + val params = coreContext.core.createConferenceParams(null) + params.isChatEnabled = true params.isGroupEnabled = true params.subject = groupChatRoomSubject - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + params.securityLevel = Conference.SecurityLevel.EndToEnd + val chatParams = params.chatParams ?: return@postOnCoreThread + chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default + chatParams.backend = ChatRoom.Backend.FlexisipChat val participants = arrayListOf
() for (participant in selection.value.orEmpty()) { @@ -131,7 +134,7 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM participants.toArray(participantsArray) ) if (chatRoom != null) { - if (params.backend == ChatRoom.Backend.FlexisipChat) { + if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(chatRoom) Log.i( @@ -188,29 +191,31 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM operationInProgress.postValue(true) - val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() + val params = coreContext.core.createConferenceParams(null) + params.isChatEnabled = true params.isGroupEnabled = false 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 if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i("$TAG Account is in secure mode & domain matches, creating a E2E conversation") - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.i( "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" ) - params.backend = ChatRoom.Backend.Basic - params.isEncryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.e( @@ -232,7 +237,7 @@ class StartConversationViewModel @UiThread constructor() : AddressSelectionViewM ) val chatRoom = core.createChatRoom(params, localAddress, participants) if (chatRoom != null) { - if (params.backend == ChatRoom.Backend.FlexisipChat) { + if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt index 9e91e4915..47226bf70 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactViewModel.kt @@ -36,7 +36,7 @@ import org.linphone.core.Address import org.linphone.core.Call import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub -import org.linphone.core.ChatRoomParams +import org.linphone.core.Conference import org.linphone.core.Core import org.linphone.core.CoreListenerStub import org.linphone.core.Friend @@ -501,31 +501,33 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() { "$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.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 if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i( "$TAG Account is in secure mode & domain matches, creating a E2E conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.i( "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" ) - params.backend = ChatRoom.Backend.Basic - params.isEncryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.e( @@ -554,7 +556,7 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() { operationInProgress.postValue(true) val chatRoom = core.createChatRoom(params, localAddress, participants) if (chatRoom != null) { - if (params.backend == ChatRoom.Backend.FlexisipChat) { + if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt index 87c2f8630..b3b39a9ff 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/HistoryViewModel.kt @@ -28,7 +28,7 @@ import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub -import org.linphone.core.ChatRoomParams +import org.linphone.core.Conference import org.linphone.core.tools.Log import org.linphone.ui.GenericViewModel 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]" ) - val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() + val params = coreContext.core.createConferenceParams(null) + params.isChatEnabled = true params.isGroupEnabled = false 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 if (account.params.instantMessagingEncryptionMandatory && sameDomain) { Log.i( "$TAG Account is in secure mode & domain matches, creating a E2E conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else if (!account.params.instantMessagingEncryptionMandatory) { if (LinphoneUtils.isEndToEndEncryptedChatAvailable(core)) { Log.i( "$TAG Account is in interop mode but LIME is available, creating a E2E conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.isEncryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.i( "$TAG Account is in interop mode but LIME isn't available, creating a SIP simple conversation" ) - params.backend = ChatRoom.Backend.Basic - params.isEncryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.e( @@ -237,7 +239,7 @@ class HistoryViewModel @UiThread constructor() : GenericViewModel() { operationInProgress.postValue(true) val chatRoom = core.createChatRoom(params, localAddress, participants) if (chatRoom != null) { - if (params.backend == ChatRoom.Backend.FlexisipChat) { + if (chatParams.backend == ChatRoom.Backend.FlexisipChat) { if (chatRoom.state == ChatRoom.State.Created) { val id = LinphoneUtils.getChatRoomId(chatRoom) Log.i("$TAG 1-1 conversation [$id] has been created") diff --git a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt index 009a620a6..ca5e15474 100644 --- a/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/history/viewmodel/StartCallViewModel.kt @@ -234,7 +234,7 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() { Log.i( "$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.account = account // Will trigger the conference creation/update automatically diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt index 70d24c29a..7b95219d1 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingsListFragment.kt @@ -35,6 +35,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.MeetingsListFragmentBinding +import org.linphone.ui.GenericActivity import org.linphone.ui.main.fragment.AbstractMainFragment import org.linphone.ui.main.history.model.ConfirmationDialogModel import org.linphone.ui.main.meetings.adapter.MeetingsListAdapter @@ -59,6 +60,8 @@ class MeetingsListFragment : AbstractMainFragment() { private var bottomSheetDialog: BottomSheetDialogFragment? = null + private var meetingViewModelBeingCancelled: MeetingModel? = null + private val dataObserver = object : AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { if (positionStart == 0 && adapter.itemCount == itemCount) { @@ -161,6 +164,24 @@ class MeetingsListFragment : AbstractMainFragment() { 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) { it.consume { model -> val modalBottomSheet = MeetingsMenuDialogFragment( @@ -168,12 +189,17 @@ class MeetingsListFragment : AbstractMainFragment() { adapter.resetSelection() }, { // onDelete - if (model.isOrganizer()) { + if (model.isOrganizer() && !model.isCancelled) { showCancelMeetingDialog(model) } else { Log.i("$TAG Deleting meeting [${model.id}]") model.delete() 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}]") meetingModel.delete() listViewModel.applyFilter() + dialog.dismiss() + (requireActivity() as GenericActivity).showGreenToast( + getString(R.string.meeting_info_deleted_toast), + R.drawable.trash_simple + ) } } model.confirmEvent.observe(viewLifecycleOwner) { it.consume { Log.i("$TAG Cancelling meeting [${meetingModel.id}]") + meetingViewModelBeingCancelled = meetingModel listViewModel.cancelMeeting(meetingModel.conferenceInfo) - meetingModel.delete() - listViewModel.applyFilter() dialog.dismiss() } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt index a8e5f597d..2b3f3b18c 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingViewModel.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.MutableLiveData import java.util.TimeZone import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.Address -import org.linphone.core.ChatRoom import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceScheduler 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.TimeZoneModel import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils import org.linphone.utils.TimestampUtils class MeetingViewModel @UiThread constructor() : GenericViewModel() { @@ -90,12 +90,12 @@ class MeetingViewModel @UiThread constructor() : GenericViewModel() { Log.i( "$TAG Conference ${conferenceScheduler.info?.subject} cancelled" ) - val chatRoomParams = coreContext.core.createDefaultChatRoomParams() - chatRoomParams.isGroupEnabled = false - chatRoomParams.backend = ChatRoom.Backend.FlexisipChat - chatRoomParams.isEncryptionEnabled = true - chatRoomParams.subject = "Meeting cancelled" // Won't be used - conferenceScheduler.sendInvitations(chatRoomParams) // Send cancel ICS + val params = LinphoneUtils.getChatRoomParamsToCancelMeeting() + if (params != null) { + conferenceScheduler.sendInvitations(params) + } else { + operationInProgress.postValue(false) + } } else if (state == ConferenceScheduler.State.Error) { operationInProgress.postValue(false) } @@ -185,7 +185,9 @@ class MeetingViewModel @UiThread constructor() : GenericViewModel() { if (::conferenceInfo.isInitialized) { Log.i("$TAG Cancelling conference information [$conferenceInfo]") operationInProgress.postValue(true) - val conferenceScheduler = core.createConferenceScheduler() + val conferenceScheduler = LinphoneUtils.createConferenceScheduler( + LinphoneUtils.getDefaultAccount() + ) conferenceScheduler.addListener(conferenceSchedulerListener) conferenceScheduler.cancelConference(conferenceInfo) } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt index ecfd24d9e..1f94bb15f 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/MeetingsListViewModel.kt @@ -23,13 +23,18 @@ import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.Address import org.linphone.core.ConferenceInfo +import org.linphone.core.ConferenceScheduler +import org.linphone.core.ConferenceSchedulerListenerStub import org.linphone.core.Core import org.linphone.core.CoreListenerStub import org.linphone.core.tools.Log import org.linphone.ui.main.meetings.model.MeetingListItemModel import org.linphone.ui.main.meetings.model.MeetingModel import org.linphone.ui.main.viewmodel.AbstractMainViewModel +import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils import org.linphone.utils.TimestampUtils class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() { @@ -41,6 +46,12 @@ class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() { val fetchInProgress = MutableLiveData() + val operationInProgress = MutableLiveData() + + val conferenceCancelledEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + private val coreListener = object : CoreListenerStub() { @WorkerThread 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? + ) { + 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 { + operationInProgress.value = false fetchInProgress.value = true coreContext.postOnCoreThread { core -> @@ -79,7 +134,11 @@ class MeetingsListViewModel @UiThread constructor() : AbstractMainViewModel() { fun cancelMeeting(conferenceInfo: ConferenceInfo) { coreContext.postOnCoreThread { core -> 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) } } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt index 59dc20a80..c9bd8eaa8 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/viewmodel/ScheduleMeetingViewModel.kt @@ -30,6 +30,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom +import org.linphone.core.Conference import org.linphone.core.ConferenceInfo import org.linphone.core.ConferenceScheduler 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.model.SelectedAddressModel import org.linphone.utils.Event +import org.linphone.utils.LinphoneUtils import org.linphone.utils.TimestampUtils class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { @@ -129,11 +131,15 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { if (sendInvitations.value == true) { 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.backend = ChatRoom.Backend.FlexisipChat - chatRoomParams.isEncryptionEnabled = true 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) } else { Log.i("$TAG User didn't asked for invitations to be sent") @@ -459,7 +465,7 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { conferenceInfo.setParticipantInfos(participantsInfoArray) if (!::conferenceScheduler.isInitialized) { - conferenceScheduler = core.createConferenceScheduler() + conferenceScheduler = LinphoneUtils.createConferenceScheduler(localAccount) conferenceScheduler.addListener(conferenceSchedulerListener) } @@ -511,7 +517,9 @@ class ScheduleMeetingViewModel @UiThread constructor() : GenericViewModel() { conferenceInfo.setParticipantInfos(participantsInfoArray) if (!::conferenceScheduler.isInitialized) { - conferenceScheduler = core.createConferenceScheduler() + conferenceScheduler = LinphoneUtils.createConferenceScheduler( + LinphoneUtils.getDefaultAccount() + ) conferenceScheduler.addListener(conferenceSchedulerListener) } diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 4e63b9c9c..c23347829 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -41,7 +41,10 @@ import org.linphone.core.Call.Status import org.linphone.core.CallLog import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom +import org.linphone.core.Conference import org.linphone.core.ConferenceInfo +import org.linphone.core.ConferenceParams +import org.linphone.core.ConferenceScheduler import org.linphone.core.Core import org.linphone.core.Factory import org.linphone.core.Friend @@ -213,6 +216,39 @@ class LinphoneUtils { 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 fun arePushNotificationsAvailable(core: Core): Boolean { if (!core.isPushNotificationAvailable) { diff --git a/app/src/main/res/layout-land/meetings_list_fragment.xml b/app/src/main/res/layout-land/meetings_list_fragment.xml index 7b1ba589e..cd9832939 100644 --- a/app/src/main/res/layout-land/meetings_list_fragment.xml +++ b/app/src/main/res/layout-land/meetings_list_fragment.xml @@ -103,6 +103,10 @@ + + + +