Fixed compil with latest SDK from master that has more @Nullable tags

This commit is contained in:
Sylvain Berfini 2020-07-07 11:41:33 +02:00
parent 704df9d583
commit 8e9aa6117c
42 changed files with 238 additions and 166 deletions

View file

@ -155,7 +155,7 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
private fun isCreateButtonEnabled(): Boolean { private fun isCreateButtonEnabled(): Boolean {
val usernameRegexp = corePreferences.config.getString("assistant", "username_regex", "^[a-z0-9+_.\\-]*\$") val usernameRegexp = corePreferences.config.getString("assistant", "username_regex", "^[a-z0-9+_.\\-]*\$")
return isPhoneNumberOk() && return isPhoneNumberOk() && usernameRegexp != null &&
(useUsername.value == false || (useUsername.value == false ||
username.value.orEmpty().matches(Regex(usernameRegexp)) && username.value.orEmpty().matches(Regex(usernameRegexp)) &&
username.value.orEmpty().isNotEmpty() && username.value.orEmpty().isNotEmpty() &&

View file

@ -58,10 +58,12 @@ class QrCodeViewModel : ViewModel() {
} }
} }
val first = coreContext.core.videoDevicesList[0] val first = coreContext.core.videoDevicesList.firstOrNull()
if (first != null) {
Log.i("[QR Code] Using first camera found: $first") Log.i("[QR Code] Using first camera found: $first")
coreContext.core.videoDevice = first coreContext.core.videoDevice = first
} }
}
fun switchCamera() { fun switchCamera() {
coreContext.switchCamera() coreContext.switchCamera()

View file

@ -97,8 +97,9 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd
} }
fun removeFromConference() { fun removeFromConference() {
if (call.conference != null) { val conference = call.conference
call.conference.removeParticipant(call.remoteAddress) if (conference != null) {
conference.removeParticipant(call.remoteAddress)
if (call.core.conferenceSize <= 1) call.core.leaveConference() if (call.core.conferenceSize <= 1) call.core.leaveConference()
} }
} }

View file

@ -52,10 +52,11 @@ class CallsViewModel : ViewModel() {
callPausedByRemote.value = state == Call.State.PausedByRemote callPausedByRemote.value = state == Call.State.PausedByRemote
isConferencePaused.value = !coreContext.core.isInConference isConferencePaused.value = !coreContext.core.isInConference
if (core.currentCall == null) { val currentCall = core.currentCall
if (currentCall == null) {
currentCallViewModel.value = null currentCallViewModel.value = null
} else if (currentCallViewModel.value == null) { } else if (currentCallViewModel.value == null) {
currentCallViewModel.value = CallViewModel(core.currentCall) currentCallViewModel.value = CallViewModel(currentCall)
} }
if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {

View file

@ -171,7 +171,7 @@ class ControlsViewModel : ViewModel() {
fun terminateCall() { fun terminateCall() {
val core = coreContext.core val core = coreContext.core
when { when {
core.currentCall != null -> core.currentCall.terminate() core.currentCall != null -> core.currentCall?.terminate()
core.isInConference -> core.terminateConference() core.isInConference -> core.terminateConference()
else -> core.terminateAllCalls() else -> core.terminateAllCalls()
} }
@ -180,13 +180,14 @@ class ControlsViewModel : ViewModel() {
fun toggleVideo() { fun toggleVideo() {
val core = coreContext.core val core = coreContext.core
val currentCall = core.currentCall val currentCall = core.currentCall
val conference = core.conference
if (core.conference != null && core.isInConference) { if (conference != null && core.isInConference) {
val params = core.createConferenceParams() val params = core.createConferenceParams()
val videoEnabled = core.conference.currentParams.videoEnabled() val videoEnabled = conference.currentParams.videoEnabled()
params.enableVideo(!videoEnabled) params.enableVideo(!videoEnabled)
Log.i("[Controls VM] Conference current param for video is $videoEnabled") Log.i("[Controls VM] Conference current param for video is $videoEnabled")
core.conference.updateParams(params) conference.updateParams(params)
} else if (currentCall != null) { } else if (currentCall != null) {
val state = currentCall.state val state = currentCall.state
if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error)
@ -194,7 +195,7 @@ class ControlsViewModel : ViewModel() {
isVideoUpdateInProgress.value = true isVideoUpdateInProgress.value = true
val params = core.createCallParams(currentCall) val params = core.createCallParams(currentCall)
params.enableVideo(!currentCall.currentParams.videoEnabled()) params?.enableVideo(!currentCall.currentParams.videoEnabled())
currentCall.update(params) currentCall.update(params)
} }
} }
@ -254,7 +255,7 @@ class ControlsViewModel : ViewModel() {
val conference = core.createConferenceWithParams(params) val conference = core.createConferenceWithParams(params)
for (call in core.calls) { for (call in core.calls) {
conference.addParticipant(call) conference?.addParticipant(call)
} }
toggleOptionsMenu() toggleOptionsMenu()
@ -341,8 +342,9 @@ class ControlsViewModel : ViewModel() {
private fun updateVideoAvailable() { private fun updateVideoAvailable() {
val core = coreContext.core val core = coreContext.core
val currentCall = core.currentCall
isVideoAvailable.value = (core.videoCaptureEnabled() || core.videoPreviewEnabled()) && isVideoAvailable.value = (core.videoCaptureEnabled() || core.videoPreviewEnabled()) &&
((core.currentCall != null && !core.currentCall.mediaInProgress()) || ((currentCall != null && !currentCall.mediaInProgress()) ||
(core.conference != null && core.isInConference)) (core.conference != null && core.isInConference))
} }

View file

@ -44,7 +44,7 @@ class IncomingCallViewModel(call: Call) : CallViewModel(call) {
init { init {
screenLocked.value = false screenLocked.value = false
inviteWithVideo.value = call.currentParams.videoEnabled() inviteWithVideo.value = call.currentParams.videoEnabled()
earlyMediaVideoEnabled.value = call.state == Call.State.IncomingEarlyMedia && call.currentParams?.videoEnabled() ?: false earlyMediaVideoEnabled.value = call.state == Call.State.IncomingEarlyMedia && call.currentParams.videoEnabled()
} }
fun answer(doAction: Boolean) { fun answer(doAction: Boolean) {

View file

@ -53,6 +53,7 @@ class StatItemViewModel(val type: StatType) : ViewModel() {
fun update(call: Call, stats: CallStats) { fun update(call: Call, stats: CallStats) {
val payloadType = if (stats.type == StreamType.Audio) call.currentParams.usedAudioPayloadType else call.currentParams.usedVideoPayloadType val payloadType = if (stats.type == StreamType.Audio) call.currentParams.usedAudioPayloadType else call.currentParams.usedVideoPayloadType
payloadType ?: return
value.value = when (type) { value.value = when (type) {
StatType.CAPTURE -> if (stats.type == StreamType.Audio) call.core.captureDevice else call.core.videoDevice StatType.CAPTURE -> if (stats.type == StreamType.Audio) call.core.captureDevice else call.core.videoDevice
StatType.PLAYBACK -> if (stats.type == StreamType.Audio) call.core.playbackDevice else call.core.videoDisplayFilter StatType.PLAYBACK -> if (stats.type == StreamType.Audio) call.core.playbackDevice else call.core.videoDisplayFilter

View file

@ -127,6 +127,7 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
with(binding) { with(binding) {
if (eventLog.type == EventLog.Type.ConferenceChatMessage) { if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
val chatMessage = eventLog.chatMessage val chatMessage = eventLog.chatMessage
chatMessage ?: return
val chatMessageViewModel = ChatMessageViewModel(chatMessage, contentClickedListener) val chatMessageViewModel = ChatMessageViewModel(chatMessage, contentClickedListener)
viewModel = chatMessageViewModel viewModel = chatMessageViewModel
@ -150,7 +151,7 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
val previousItem = getItem(adapterPosition - 1) val previousItem = getItem(adapterPosition - 1)
if (previousItem.type == EventLog.Type.ConferenceChatMessage) { if (previousItem.type == EventLog.Type.ConferenceChatMessage) {
val previousMessage = previousItem.chatMessage val previousMessage = previousItem.chatMessage
if (previousMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { if (previousMessage != null && previousMessage.fromAddress.weakEqual(chatMessage.fromAddress)) {
if (chatMessage.time - previousMessage.time < MAX_TIME_TO_GROUP_MESSAGES) { if (chatMessage.time - previousMessage.time < MAX_TIME_TO_GROUP_MESSAGES) {
hasPrevious = true hasPrevious = true
} }
@ -162,7 +163,7 @@ class ChatMessagesListAdapter(val selectionViewModel: ListTopBarViewModel) : Lif
val nextItem = getItem(adapterPosition + 1) val nextItem = getItem(adapterPosition + 1)
if (nextItem.type == EventLog.Type.ConferenceChatMessage) { if (nextItem.type == EventLog.Type.ConferenceChatMessage) {
val nextMessage = nextItem.chatMessage val nextMessage = nextItem.chatMessage
if (nextMessage.fromAddress.weakEqual(chatMessage.fromAddress)) { if (nextMessage != null && nextMessage.fromAddress.weakEqual(chatMessage.fromAddress)) {
if (nextMessage.time - chatMessage.time < MAX_TIME_TO_GROUP_MESSAGES) { if (nextMessage.time - chatMessage.time < MAX_TIME_TO_GROUP_MESSAGES) {
hasNext = true hasNext = true
} }
@ -314,8 +315,8 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLog>() {
): Boolean { ): Boolean {
return if (oldItem.type == EventLog.Type.ConferenceChatMessage && return if (oldItem.type == EventLog.Type.ConferenceChatMessage &&
newItem.type == EventLog.Type.ConferenceChatMessage) { newItem.type == EventLog.Type.ConferenceChatMessage) {
oldItem.chatMessage.time == newItem.chatMessage.time && oldItem.chatMessage?.time == newItem.chatMessage?.time &&
oldItem.chatMessage.isOutgoing == newItem.chatMessage.isOutgoing oldItem.chatMessage?.isOutgoing == newItem.chatMessage?.isOutgoing
} else oldItem.notifyId == newItem.notifyId } else oldItem.notifyId == newItem.notifyId
} }
@ -324,7 +325,7 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLog>() {
newItem: EventLog newItem: EventLog
): Boolean { ): Boolean {
return if (newItem.type == EventLog.Type.ConferenceChatMessage) { return if (newItem.type == EventLog.Type.ConferenceChatMessage) {
newItem.chatMessage.state == ChatMessage.State.Displayed newItem.chatMessage?.state == ChatMessage.State.Displayed
} else false } else false
} }
} }

View file

@ -73,7 +73,8 @@ class ChatRoomCreationContactsAdapter : LifecycleListAdapter<SearchResult, ChatR
selectedAddresses.observe(this@ViewHolder, Observer { selectedAddresses.observe(this@ViewHolder, Observer {
val selected = it.find { address -> val selected = it.find { address ->
if (searchResult.address != null) address.weakEqual(searchResult.address) else false val searchAddress = searchResult.address
if (searchAddress != null) address.weakEqual(searchAddress) else false
} }
searchResultViewModel.isSelected.value = selected != null searchResultViewModel.isSelected.value = selected != null
}) })
@ -91,7 +92,8 @@ class ChatRoomCreationContactsAdapter : LifecycleListAdapter<SearchResult, ChatR
viewModel: ChatRoomCreationContactViewModel, viewModel: ChatRoomCreationContactViewModel,
securityEnabled: Boolean securityEnabled: Boolean
) { ) {
val isMyself = securityEnabled && searchResult.address != null && coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(searchResult.address) ?: false val searchAddress = searchResult.address
val isMyself = securityEnabled && searchAddress != null && coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(searchAddress) ?: false
val limeCheck = !securityEnabled || (securityEnabled && searchResult.hasCapability(FriendCapability.LimeX3Dh)) val limeCheck = !securityEnabled || (securityEnabled && searchResult.hasCapability(FriendCapability.LimeX3Dh))
val groupCheck = !groupChatEnabled || (groupChatEnabled && searchResult.hasCapability(FriendCapability.GroupChat)) val groupCheck = !groupChatEnabled || (groupChatEnabled && searchResult.hasCapability(FriendCapability.GroupChat))
val disabled = if (searchResult.friend != null) !limeCheck || !groupCheck || isMyself else false // Generated entry from search filter val disabled = if (searchResult.friend != null) !limeCheck || !groupCheck || isMyself else false // Generated entry from search filter
@ -111,7 +113,9 @@ private class SearchResultDiffCallback : DiffUtil.ItemCallback<SearchResult>() {
oldItem: SearchResult, oldItem: SearchResult,
newItem: SearchResult newItem: SearchResult
): Boolean { ): Boolean {
return if (oldItem.address != null && newItem.address != null) oldItem.address.weakEqual(newItem.address) else false val oldAddress = oldItem.address
val newAddress = newItem.address
return if (oldAddress != null && newAddress != null) oldAddress.weakEqual(newAddress) else false
} }
override fun areContentsTheSame( override fun areContentsTheSame(

View file

@ -67,7 +67,7 @@ class ImdnFragment : Fragment() {
if (arguments != null) { if (arguments != null) {
val messageId = arguments?.getString("MessageId") val messageId = arguments?.getString("MessageId")
val message = chatRoom.findMessage(messageId) val message = if (messageId != null) chatRoom.findMessage(messageId) else null
if (message != null) { if (message != null) {
Log.i("[IMDN] Found message $message with id $messageId") Log.i("[IMDN] Found message $message with id $messageId")
viewModel = ViewModelProvider( viewModel = ViewModelProvider(

View file

@ -58,17 +58,18 @@ class ChatMessageContentViewModel(
init { init {
if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) {
downloadable.value = content.filePath.isEmpty() val filePath = content.filePath ?: ""
downloadable.value = filePath.isEmpty()
if (content.filePath.isNotEmpty()) { if (filePath.isNotEmpty()) {
Log.i("[Content] Found displayable content: ${content.filePath}") Log.i("[Content] Found displayable content: $filePath")
isImage.value = FileUtils.isExtensionImage(content.filePath) isImage.value = FileUtils.isExtensionImage(filePath)
isVideo.value = FileUtils.isExtensionVideo(content.filePath) isVideo.value = FileUtils.isExtensionVideo(filePath)
if (isVideo.value == true) { if (isVideo.value == true) {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
videoPreview.postValue(ImageUtils.getVideoPreview(content.filePath)) videoPreview.postValue(ImageUtils.getVideoPreview(filePath))
} }
} }
} }
@ -88,18 +89,22 @@ class ChatMessageContentViewModel(
} }
fun download() { fun download() {
if (content.isFileTransfer && (content.filePath == null || content.filePath.isEmpty())) { val filePath = content.filePath
val file = FileUtils.getFileStoragePath(content.name) if (content.isFileTransfer && (filePath == null || filePath.isEmpty())) {
val contentName = content.name
if (contentName != null) {
val file = FileUtils.getFileStoragePath(contentName)
content.filePath = file.path content.filePath = file.path
downloadEnabled.value = false downloadEnabled.value = false
Log.i("[Content] Started downloading ${content.name} into ${content.filePath}") Log.i("[Content] Started downloading $contentName into ${content.filePath}")
chatMessage.downloadContent(content) chatMessage.downloadContent(content)
} }
} }
}
fun openFile() { fun openFile() {
listener?.onContentClicked(content.filePath) listener?.onContentClicked(content.filePath.orEmpty())
} }
} }

View file

@ -101,8 +101,9 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
val isBasicChatRoom: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt()) val isBasicChatRoom: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt())
val message: ChatMessage = chatRoom.createEmptyMessage() val message: ChatMessage = chatRoom.createEmptyMessage()
if (textToSend.value.orEmpty().isNotEmpty()) { val toSend = textToSend.value
message.addTextContent(textToSend.value) if (toSend != null && toSend.isNotEmpty()) {
message.addTextContent(toSend)
} }
for (attachment in attachments.value.orEmpty()) { for (attachment in attachments.value.orEmpty()) {
@ -134,6 +135,6 @@ class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel()
fun transferMessage(chatMessage: ChatMessage) { fun transferMessage(chatMessage: ChatMessage) {
val message = chatRoom.createForwardMessage(chatMessage) val message = chatRoom.createForwardMessage(chatMessage)
message?.send() message.send()
} }
} }

View file

@ -63,7 +63,8 @@ class ChatMessageViewModel(
val ephemeralLifetime = MutableLiveData<String>() val ephemeralLifetime = MutableLiveData<String>()
val text: Spanned? by lazy { val text: Spanned? by lazy {
if (chatMessage.textContent != null) AppUtils.getTextWithHttpLinks(chatMessage.textContent) else null val textContent = chatMessage.textContent
if (textContent != null) AppUtils.getTextWithHttpLinks(textContent) else null
} }
private var countDownTimer: CountDownTimer? = null private var countDownTimer: CountDownTimer? = null

View file

@ -79,6 +79,7 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
if (eventLog.type == EventLog.Type.ConferenceChatMessage) { if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
val chatMessage = eventLog.chatMessage val chatMessage = eventLog.chatMessage
chatMessage ?: return
chatMessage.userData = position chatMessage.userData = position
chatMessage.addListener(chatMessageListener) chatMessage.addListener(chatMessageListener)
@ -101,6 +102,7 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
if (eventLog.type == EventLog.Type.ConferenceChatMessage) { if (eventLog.type == EventLog.Type.ConferenceChatMessage) {
val chatMessage = eventLog.chatMessage val chatMessage = eventLog.chatMessage
chatMessage ?: return
chatMessage.userData = position chatMessage.userData = position
chatMessage.addListener(chatMessageListener) chatMessage.addListener(chatMessageListener)
} }
@ -138,7 +140,9 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) { override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) {
Log.i("[Chat Messages] An ephemeral chat message has expired, removing it from event list") Log.i("[Chat Messages] An ephemeral chat message has expired, removing it from event list")
deleteMessage(eventLog.chatMessage) val chatMessage = eventLog.chatMessage
chatMessage ?: return
deleteMessage(chatMessage)
} }
override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) {
@ -160,7 +164,7 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
fun resendMessage(chatMessage: ChatMessage) { fun resendMessage(chatMessage: ChatMessage) {
val position: Int = chatMessage.userData as Int val position: Int = chatMessage.userData as Int
chatMessage.resend() chatMessage.send()
messageUpdatedEvent.value = Event(position) messageUpdatedEvent.value = Event(position)
} }

View file

@ -31,11 +31,8 @@ class ChatRoomCreationContactViewModel(private val searchResult: SearchResult) :
override val contact = MutableLiveData<Contact>() override val contact = MutableLiveData<Contact>()
override val displayName: String by lazy { override val displayName: String by lazy {
when { val address = searchResult.address
searchResult.friend != null -> searchResult.friend.name searchResult.friend?.name ?: if (address != null) LinphoneUtils.getDisplayName(address) else searchResult.phoneNumber.orEmpty()
searchResult.address != null -> LinphoneUtils.getDisplayName(searchResult.address)
else -> searchResult.phoneNumber
}
} }
val isDisabled: MutableLiveData<Boolean> by lazy { val isDisabled: MutableLiveData<Boolean> by lazy {
@ -47,14 +44,14 @@ class ChatRoomCreationContactViewModel(private val searchResult: SearchResult) :
} }
val isLinphoneUser: Boolean by lazy { val isLinphoneUser: Boolean by lazy {
searchResult.friend?.getPresenceModelForUriOrTel(searchResult.phoneNumber ?: searchResult.address.asStringUriOnly())?.basicStatus == PresenceBasicStatus.Open searchResult.friend?.getPresenceModelForUriOrTel(searchResult.phoneNumber ?: searchResult.address?.asStringUriOnly() ?: "")?.basicStatus == PresenceBasicStatus.Open
} }
val sipUri: String by lazy { val sipUri: String by lazy {
searchResult.phoneNumber ?: searchResult.address.asStringUriOnly() searchResult.phoneNumber ?: searchResult.address?.asStringUriOnly() ?: ""
} }
val address: Address by lazy { val address: Address? by lazy {
searchResult.address searchResult.address
} }
@ -70,11 +67,11 @@ class ChatRoomCreationContactViewModel(private val searchResult: SearchResult) :
} }
private fun searchMatchingContact() { private fun searchMatchingContact() {
if (searchResult.address != null) { val address = searchResult.address
contact.value = if (address != null) {
coreContext.contactsManager.findContactByAddress(searchResult.address) contact.value = coreContext.contactsManager.findContactByAddress(address)
} else if (searchResult.phoneNumber != null) { } else if (searchResult.phoneNumber != null) {
contact.value = coreContext.contactsManager.findContactByPhoneNumber(searchResult.phoneNumber) contact.value = coreContext.contactsManager.findContactByPhoneNumber(searchResult.phoneNumber.orEmpty())
} }
} }
} }

View file

@ -118,8 +118,9 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() {
} }
fun toggleSelectionForSearchResult(searchResult: SearchResult) { fun toggleSelectionForSearchResult(searchResult: SearchResult) {
if (searchResult.address != null) { val address = searchResult.address
toggleSelectionForAddress(searchResult.address) if (address != null) {
toggleSelectionForAddress(address)
} }
} }
@ -147,7 +148,7 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() {
val defaultProxyConfig = coreContext.core.defaultProxyConfig val defaultProxyConfig = coreContext.core.defaultProxyConfig
var room: ChatRoom? var room: ChatRoom?
val address = searchResult.address ?: coreContext.core.interpretUrl(searchResult.phoneNumber) val address = searchResult.address ?: coreContext.core.interpretUrl(searchResult.phoneNumber ?: "")
if (address == null) { if (address == null) {
Log.e("[Chat Room Creation] Can't get a valid address from search result $searchResult") Log.e("[Chat Room Creation] Can't get a valid address from search result $searchResult")
onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack) onErrorEvent.value = Event(R.string.chat_room_creation_failed_snack)
@ -168,23 +169,23 @@ class ChatRoomCreationViewModel : ErrorReportingViewModel() {
val participants = arrayOf(searchResult.address) val participants = arrayOf(searchResult.address)
// Use proxy config contact instead of identity because we need GRUU if FlexisipChat backend // Use proxy config contact instead of identity because we need GRUU if FlexisipChat backend
room = coreContext.core.searchChatRoom(params, defaultProxyConfig.contact, null, participants) room = coreContext.core.searchChatRoom(params, defaultProxyConfig?.contact, null, participants)
if (room == null) { if (room == null) {
Log.w("[Chat Room Creation] Couldn't find existing 1-1 chat room with remote ${searchResult.address.asStringUriOnly()}, encryption=$encrypted and local identity ${defaultProxyConfig.contact.asStringUriOnly()}") Log.w("[Chat Room Creation] Couldn't find existing 1-1 chat room with remote ${searchResult.address?.asStringUriOnly()}, encryption=$encrypted and local identity ${defaultProxyConfig?.contact?.asStringUriOnly()}")
// Use proxy config contact instead of identity because we need GRUU if FlexisipChat backend // Use proxy config contact instead of identity because we need GRUU if FlexisipChat backend
room = coreContext.core.createChatRoom(params, defaultProxyConfig.contact, participants) room = coreContext.core.createChatRoom(params, defaultProxyConfig?.contact, participants)
if (encrypted) { if (encrypted) {
room?.addListener(listener) room?.addListener(listener)
} else { } else {
if (room != null) { if (room != null) {
chatRoomCreatedEvent.value = Event(room) chatRoomCreatedEvent.value = Event(room)
} else { } else {
Log.e("[Chat Room Creation] Couldn't create chat room with remote ${searchResult.address.asStringUriOnly()} and local identity ${defaultProxyConfig.contact.asStringUriOnly()}") Log.e("[Chat Room Creation] Couldn't create chat room with remote ${searchResult.address?.asStringUriOnly()} and local identity ${defaultProxyConfig?.contact?.asStringUriOnly()}")
} }
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} }
} else { } else {
Log.i("[Chat Room Creation] Found existing 1-1 chat room with remote ${searchResult.address.asStringUriOnly()}, encryption=$encrypted and local identity ${defaultProxyConfig.contact.asStringUriOnly()}") Log.i("[Chat Room Creation] Found existing 1-1 chat room with remote ${searchResult.address?.asStringUriOnly()}, encryption=$encrypted and local identity ${defaultProxyConfig?.contact?.asStringUriOnly()}")
chatRoomCreatedEvent.value = Event(room) chatRoomCreatedEvent.value = Event(room)
waitForChatRoomCreation.value = false waitForChatRoomCreation.value = false
} }

View file

@ -49,7 +49,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactViewModelI
when { when {
chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt()) -> LinphoneUtils.getDisplayName(chatRoom.peerAddress) chatRoom.hasCapability(ChatRoomCapabilities.Basic.toInt()) -> LinphoneUtils.getDisplayName(chatRoom.peerAddress)
chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) -> LinphoneUtils.getDisplayName(chatRoom.participants.firstOrNull()?.address ?: chatRoom.peerAddress) chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) -> LinphoneUtils.getDisplayName(chatRoom.participants.firstOrNull()?.address ?: chatRoom.peerAddress)
chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) -> chatRoom.subject chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) -> chatRoom.subject.orEmpty()
else -> chatRoom.peerAddress.asStringUriOnly() else -> chatRoom.peerAddress.asStringUriOnly()
} }
} }

View file

@ -127,13 +127,13 @@ class ChatRoomsListViewModel : ErrorReportingViewModel() {
fun deleteChatRooms(chatRooms: ArrayList<ChatRoom>) { fun deleteChatRooms(chatRooms: ArrayList<ChatRoom>) {
chatRoomsToDeleteCount = chatRooms.size chatRoomsToDeleteCount = chatRooms.size
for (chatRoom in chatRooms) { for (chatRoom in chatRooms) {
for (eventLog in chatRoom.getHistoryMessageEvents(0).orEmpty()) { for (eventLog in chatRoom.getHistoryMessageEvents(0)) {
LinphoneUtils.deleteFilesAttachedToEventLog(eventLog) LinphoneUtils.deleteFilesAttachedToEventLog(eventLog)
} }
Compatibility.removeChatRoomShortcut(chatRoom) Compatibility.removeChatRoomShortcut(chatRoom)
chatRoom.addListener(chatRoomListener) chatRoom.addListener(chatRoomListener)
chatRoom.core?.deleteChatRoom(chatRoom) chatRoom.core.deleteChatRoom(chatRoom)
} }
} }

View file

@ -26,7 +26,7 @@ import org.linphone.core.ChatRoomSecurityLevel
import org.linphone.core.ParticipantDevice import org.linphone.core.ParticipantDevice
class DevicesListChildViewModel(private val device: ParticipantDevice) : ViewModel() { class DevicesListChildViewModel(private val device: ParticipantDevice) : ViewModel() {
val deviceName: String = device.name val deviceName: String = device.name.orEmpty()
val securityLevelIcon: Int by lazy { val securityLevelIcon: Int by lazy {
when (device.securityLevel) { when (device.securityLevel) {

View file

@ -23,7 +23,7 @@ import androidx.lifecycle.ViewModel
class EphemeralDurationViewModel( class EphemeralDurationViewModel(
val textResource: Int, val textResource: Int,
private val selectedDuration: Long, selectedDuration: Long,
private val duration: Long, private val duration: Long,
private val listener: DurationItemClicked private val listener: DurationItemClicked
) : ViewModel() { ) : ViewModel() {

View file

@ -139,7 +139,7 @@ class GroupInfoViewModel(val chatRoom: ChatRoom?) : ErrorReportingViewModel() {
val newSubject = subject.value.orEmpty() val newSubject = subject.value.orEmpty()
if (newSubject.isNotEmpty() && newSubject != chatRoom.subject) { if (newSubject.isNotEmpty() && newSubject != chatRoom.subject) {
Log.i("[Chat Room Group Info] Subject changed to $newSubject") Log.i("[Chat Room Group Info] Subject changed to $newSubject")
chatRoom.subject = newSubject chatRoom.setSubject(newSubject)
} }
// Removed participants // Removed participants

View file

@ -23,7 +23,7 @@ import org.linphone.contact.GenericContactViewModel
import org.linphone.core.ParticipantImdnState import org.linphone.core.ParticipantImdnState
import org.linphone.utils.TimestampUtils import org.linphone.utils.TimestampUtils
class ImdnParticipantViewModel(private val imdnState: ParticipantImdnState) : GenericContactViewModel(imdnState.participant.address) { class ImdnParticipantViewModel(imdnState: ParticipantImdnState) : GenericContactViewModel(imdnState.participant.address) {
val sipUri: String = imdnState.participant.address.asStringUriOnly() val sipUri: String = imdnState.participant.address.asStringUriOnly()
val time: String = TimestampUtils.toString(imdnState.stateChangeTime) val time: String = TimestampUtils.toString(imdnState.stateChangeTime)

View file

@ -98,9 +98,8 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
.commit() .commit()
} else { } else {
val friend = contact.friend ?: coreContext.core.createFriend() val friend = contact.friend ?: coreContext.core.createFriend()
if (friend != null) {
friend.edit() friend.edit()
friend.name = "${firstName.value.orEmpty()} ${lastName.value.orEmpty()}" friend.setName("${firstName.value.orEmpty()} ${lastName.value.orEmpty()}")
for (address in friend.addresses) { for (address in friend.addresses) {
friend.removeAddress(address) friend.removeAddress(address)
@ -114,8 +113,9 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
friend.removePhoneNumber(phone) friend.removePhoneNumber(phone)
} }
for (phone in numbers.value.orEmpty()) { for (phone in numbers.value.orEmpty()) {
if (phone.newValue.value?.isNotEmpty() == true) { val phoneNumber = phone.newValue.value
friend.addPhoneNumber(phone.newValue.value) if (phoneNumber?.isNotEmpty() == true) {
friend.addPhoneNumber(phoneNumber)
} }
} }
@ -129,8 +129,7 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactViewModelInt
if (contact.friend == null) { if (contact.friend == null) {
contact.friend = friend contact.friend = friend
coreContext.core.defaultFriendList.addLocalFriend(friend) coreContext.core.defaultFriendList?.addLocalFriend(friend)
}
} }
} }

View file

@ -178,7 +178,7 @@ class ContactViewModel(private val c: Contact) : ErrorReportingViewModel(), Cont
val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open
val contactAddress = presenceModel?.contact ?: number val contactAddress = presenceModel?.contact ?: number
val address = coreContext.core.interpretUrl(contactAddress) val address = coreContext.core.interpretUrl(contactAddress)
val isMe = coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false val isMe = if (address != null) coreContext.core.defaultProxyConfig?.identityAddress?.weakEqual(address) ?: false else false
val secureChatAllowed = !isMe && c.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false val secureChatAllowed = !isMe && c.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
val noa = ContactNumberOrAddressViewModel(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, listener = listener) val noa = ContactNumberOrAddressViewModel(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, listener = listener)
list.add(noa) list.add(noa)

View file

@ -65,7 +65,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
private val tickerChannel = ticker(1000, 1000) private val tickerChannel = ticker(1000, 1000)
private var player: Player private lateinit var player: Player
private val listener = PlayerListener { private val listener = PlayerListener {
Log.i("[Recording] End of file reached") Log.i("[Recording] End of file reached")
stop() stop()
@ -95,7 +95,9 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
} }
} }
} }
player = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null) val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null)
if (localPlayer != null) player = localPlayer
else Log.e("[Recording VM] Couldn't create local player!")
player.addListener(listener) player.addListener(listener)
} }

View file

@ -38,11 +38,11 @@ class AccountSettingsViewModelFactory(private val identity: String) :
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T { override fun <T : ViewModel?> create(modelClass: Class<T>): T {
for (proxy in coreContext.core.proxyConfigList) { for (proxy in coreContext.core.proxyConfigList) {
if (proxy.identityAddress.asStringUriOnly() == identity) { if (proxy.identityAddress?.asStringUriOnly() == identity) {
return AccountSettingsViewModel(proxy) as T return AccountSettingsViewModel(proxy) as T
} }
} }
return AccountSettingsViewModel(coreContext.core.defaultProxyConfig) as T return AccountSettingsViewModel(coreContext.core.defaultProxyConfig!!) as T
} }
} }
@ -115,7 +115,7 @@ class AccountSettingsViewModel(val proxyConfig: ProxyConfig) : GenericSettingsVi
val displayNameListener = object : SettingListenerStub() { val displayNameListener = object : SettingListenerStub() {
override fun onTextValueChanged(newValue: String) { override fun onTextValueChanged(newValue: String) {
proxyConfig.identityAddress.displayName = newValue proxyConfig.identityAddress?.displayName = newValue
} }
} }
// displayName mutable is above // displayName mutable is above
@ -138,10 +138,12 @@ class AccountSettingsViewModel(val proxyConfig: ProxyConfig) : GenericSettingsVi
private fun deleteProxyConfig(cfg: ProxyConfig) { private fun deleteProxyConfig(cfg: ProxyConfig) {
val authInfo = cfg.findAuthInfo() val authInfo = cfg.findAuthInfo()
if (authInfo != null) {
core.removeProxyConfig(cfg) core.removeProxyConfig(cfg)
core.removeAuthInfo(authInfo) core.removeAuthInfo(authInfo)
proxyConfigRemovedEvent.value = Event(true) proxyConfigRemovedEvent.value = Event(true)
} }
}
val deleteListener = object : SettingListenerStub() { val deleteListener = object : SettingListenerStub() {
override fun onClicked() { override fun onClicked() {
@ -207,7 +209,7 @@ class AccountSettingsViewModel(val proxyConfig: ProxyConfig) : GenericSettingsVi
val stunServerListener = object : SettingListenerStub() { val stunServerListener = object : SettingListenerStub() {
override fun onTextValueChanged(newValue: String) { override fun onTextValueChanged(newValue: String) {
proxyConfig.natPolicy.stunServer = newValue proxyConfig.natPolicy?.stunServer = newValue
if (newValue.isEmpty()) ice.value = false if (newValue.isEmpty()) ice.value = false
stunServer.value = newValue stunServer.value = newValue
} }
@ -216,7 +218,7 @@ class AccountSettingsViewModel(val proxyConfig: ProxyConfig) : GenericSettingsVi
val iceListener = object : SettingListenerStub() { val iceListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) { override fun onBoolValueChanged(newValue: Boolean) {
proxyConfig.natPolicy.enableIce(newValue) proxyConfig.natPolicy?.enableIce(newValue)
} }
} }
val ice = MutableLiveData<Boolean>() val ice = MutableLiveData<Boolean>()
@ -276,8 +278,11 @@ class AccountSettingsViewModel(val proxyConfig: ProxyConfig) : GenericSettingsVi
private fun update() { private fun update() {
isDefault.value = core.defaultProxyConfig == proxyConfig isDefault.value = core.defaultProxyConfig == proxyConfig
displayName.value = LinphoneUtils.getDisplayName(proxyConfig.identityAddress) val identityAddress = proxyConfig.identityAddress
identity.value = proxyConfig.identityAddress.asStringUriOnly() if (identityAddress != null) {
displayName.value = LinphoneUtils.getDisplayName(identityAddress)
identity.value = identityAddress.asStringUriOnly()
}
iconResource.value = when (proxyConfig.state) { iconResource.value = when (proxyConfig.state) {
RegistrationState.Ok -> R.drawable.led_connected RegistrationState.Ok -> R.drawable.led_connected
@ -292,14 +297,14 @@ class AccountSettingsViewModel(val proxyConfig: ProxyConfig) : GenericSettingsVi
else -> R.string.status_not_connected else -> R.string.status_not_connected
} }
userName.value = proxyConfig.identityAddress.username userName.value = proxyConfig.identityAddress?.username
userId.value = proxyConfig.findAuthInfo()?.userid userId.value = proxyConfig.findAuthInfo()?.userid
domain.value = proxyConfig.identityAddress.domain domain.value = proxyConfig.identityAddress?.domain
disable.value = !proxyConfig.registerEnabled() disable.value = !proxyConfig.registerEnabled()
pushNotification.value = proxyConfig.isPushNotificationAllowed pushNotification.value = proxyConfig.isPushNotificationAllowed
pushNotificationsAvailable.value = core.isPushNotificationAvailable pushNotificationsAvailable.value = core.isPushNotificationAvailable
proxy.value = proxyConfig.serverAddr proxy.value = proxyConfig.serverAddr
outboundProxy.value = proxyConfig.serverAddr == proxyConfig.route outboundProxy.value = proxyConfig.serverAddr == proxyConfig.routes.firstOrNull()
stunServer.value = proxyConfig.natPolicy?.stunServer stunServer.value = proxyConfig.natPolicy?.stunServer
ice.value = proxyConfig.natPolicy?.iceEnabled() ice.value = proxyConfig.natPolicy?.iceEnabled()
avpf.value = proxyConfig.avpfEnabled() avpf.value = proxyConfig.avpfEnabled()

View file

@ -168,6 +168,9 @@ class AudioSettingsViewModel : GenericSettingsViewModel() {
core.removeListener(listener) core.removeListener(listener)
when (status) { when (status) {
EcCalibratorStatus.InProgress -> {
echoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_started)
}
EcCalibratorStatus.DoneNoEcho -> { EcCalibratorStatus.DoneNoEcho -> {
echoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_no_echo) echoCalibration.value = prefs.getString(R.string.audio_settings_echo_cancellation_calibration_no_echo)
} }

View file

@ -50,7 +50,7 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() {
val useDualModeListener = object : SettingListenerStub() { val useDualModeListener = object : SettingListenerStub() {
override fun onBoolValueChanged(newValue: Boolean) { override fun onBoolValueChanged(newValue: Boolean) {
val tunnel = core.tunnel val tunnel = core.tunnel
tunnel.enableDualMode(newValue) tunnel?.enableDualMode(newValue)
} }
} }
val useDualMode = MutableLiveData<Boolean>() val useDualMode = MutableLiveData<Boolean>()
@ -77,7 +77,7 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() {
val modeListener = object : SettingListenerStub() { val modeListener = object : SettingListenerStub() {
override fun onListValueChanged(position: Int) { override fun onListValueChanged(position: Int) {
core.tunnel.mode = when (position) { core.tunnel?.mode = when (position) {
0 -> Tunnel.Mode.Disable 0 -> Tunnel.Mode.Disable
1 -> Tunnel.Mode.Enable 1 -> Tunnel.Mode.Enable
else -> Tunnel.Mode.Auto else -> Tunnel.Mode.Auto
@ -93,7 +93,7 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() {
hostnameUrl.value = config.host hostnameUrl.value = config.host
port.value = config.port port.value = config.port
useDualMode.value = tunnel.dualModeEnabled() useDualMode.value = tunnel?.dualModeEnabled()
hostnameUrl2.value = config.host2 hostnameUrl2.value = config.host2
port2.value = config.port2 port2.value = config.port2
@ -102,9 +102,9 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() {
private fun getTunnelConfig(): TunnelConfig { private fun getTunnelConfig(): TunnelConfig {
val tunnel = core.tunnel val tunnel = core.tunnel
val configs = tunnel.servers val configs = tunnel?.servers.orEmpty()
return if (configs.isNotEmpty()) { return if (configs.isNotEmpty()) {
configs[0] configs.first()
} else { } else {
Factory.instance().createTunnelConfig() Factory.instance().createTunnelConfig()
} }
@ -112,9 +112,9 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() {
private fun updateTunnelConfig(config: TunnelConfig) { private fun updateTunnelConfig(config: TunnelConfig) {
val tunnel = core.tunnel val tunnel = core.tunnel
tunnel.cleanServers() tunnel?.cleanServers()
if (config.host?.isNotEmpty() == true) { if (config.host?.isNotEmpty() == true) {
tunnel.addServer(config) tunnel?.addServer(config)
} }
} }
@ -125,7 +125,7 @@ class TunnelSettingsViewModel : GenericSettingsViewModel() {
labels.add(prefs.getString(R.string.tunnel_settings_auto_mode)) labels.add(prefs.getString(R.string.tunnel_settings_auto_mode))
modeLabels.value = labels modeLabels.value = labels
modeIndex.value = when (core.tunnel.mode) { modeIndex.value = when (core.tunnel?.mode) {
Tunnel.Mode.Disable -> 0 Tunnel.Mode.Disable -> 0
Tunnel.Mode.Enable -> 1 Tunnel.Mode.Enable -> 1
else -> 2 else -> 2

View file

@ -72,7 +72,7 @@ class VideoSettingsViewModel : GenericSettingsViewModel() {
val videoSizeListener = object : SettingListenerStub() { val videoSizeListener = object : SettingListenerStub() {
override fun onListValueChanged(position: Int) { override fun onListValueChanged(position: Int) {
core.preferredVideoDefinition = Factory.instance().createVideoDefinitionFromName(videoSizeLabels.value.orEmpty()[position]) core.setPreferredVideoDefinition(Factory.instance().createVideoDefinitionFromName(videoSizeLabels.value.orEmpty()[position]))
} }
} }
val videoSizeIndex = MutableLiveData<Int>() val videoSizeIndex = MutableLiveData<Int>()
@ -135,8 +135,10 @@ class VideoSettingsViewModel : GenericSettingsViewModel() {
if (index == -1) { if (index == -1) {
val firstDevice = cameraDeviceLabels.value.orEmpty().firstOrNull() val firstDevice = cameraDeviceLabels.value.orEmpty().firstOrNull()
Log.w("[Video Settings] Device not found in labels list: ${core.videoDevice}, replace it by $firstDevice") Log.w("[Video Settings] Device not found in labels list: ${core.videoDevice}, replace it by $firstDevice")
if (firstDevice != null) {
cameraDeviceIndex.value = 0 cameraDeviceIndex.value = 0
core.videoDevice = firstDevice core.videoDevice = firstDevice
}
} else { } else {
cameraDeviceIndex.value = index cameraDeviceIndex.value = index
} }
@ -146,11 +148,11 @@ class VideoSettingsViewModel : GenericSettingsViewModel() {
val labels = arrayListOf<String>() val labels = arrayListOf<String>()
for (size in Factory.instance().supportedVideoDefinitions) { for (size in Factory.instance().supportedVideoDefinitions) {
labels.add(size.name) labels.add(size.name.orEmpty())
} }
videoSizeLabels.value = labels videoSizeLabels.value = labels
videoSizeIndex.value = labels.indexOf(core.preferredVideoDefinition.name) videoSizeIndex.value = labels.indexOf(core.preferredVideoDefinition?.name)
} }
private fun initVideoPresetList() { private fun initVideoPresetList() {

View file

@ -71,8 +71,9 @@ open class StatusViewModel : ViewModel() {
core.addListener(listener) core.addListener(listener)
var state: RegistrationState = RegistrationState.None var state: RegistrationState = RegistrationState.None
if (core.defaultProxyConfig != null) { val defaultProxyConfig = core.defaultProxyConfig
state = core.defaultProxyConfig.state if (defaultProxyConfig != null) {
state = defaultProxyConfig.state
} }
updateDefaultProxyConfigRegistrationStatus(state) updateDefaultProxyConfigRegistrationStatus(state)
} }

View file

@ -61,6 +61,11 @@ class Api21Compatibility {
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath val filePath = content.filePath
if (filePath == null) {
Log.e("[Chat Message] Content doesn't have a file path!")
return false
}
val appName = AppUtils.getString(R.string.app_name) val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName" val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName"
val fileName = content.name val fileName = content.name
@ -92,6 +97,11 @@ class Api21Compatibility {
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath val filePath = content.filePath
if (filePath == null) {
Log.e("[Chat Message] Content doesn't have a file path!")
return false
}
val appName = AppUtils.getString(R.string.app_name) val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName" val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName"
val fileName = content.name val fileName = content.name
@ -124,6 +134,11 @@ class Api21Compatibility {
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath val filePath = content.filePath
if (filePath == null) {
Log.e("[Chat Message] Content doesn't have a file path!")
return false
}
val appName = AppUtils.getString(R.string.app_name) val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName" val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName"
val fileName = content.name val fileName = content.name

View file

@ -35,6 +35,11 @@ class Api29Compatibility {
companion object { companion object {
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath val filePath = content.filePath
if (filePath == null) {
Log.e("[Chat Message] Content doesn't have a file path!")
return false
}
val appName = AppUtils.getString(R.string.app_name) val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName" val relativePath = "${Environment.DIRECTORY_PICTURES}/$appName"
val fileName = content.name val fileName = content.name
@ -72,6 +77,11 @@ class Api29Compatibility {
suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath val filePath = content.filePath
if (filePath == null) {
Log.e("[Chat Message] Content doesn't have a file path!")
return false
}
val appName = AppUtils.getString(R.string.app_name) val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName" val relativePath = "${Environment.DIRECTORY_MOVIES}/$appName"
val fileName = content.name val fileName = content.name
@ -110,6 +120,11 @@ class Api29Compatibility {
suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean {
val filePath = content.filePath val filePath = content.filePath
if (filePath == null) {
Log.e("[Chat Message] Content doesn't have a file path!")
return false
}
val appName = AppUtils.getString(R.string.app_name) val appName = AppUtils.getString(R.string.app_name)
val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName" val relativePath = "${Environment.DIRECTORY_MUSIC}/$appName"
val fileName = content.name val fileName = content.name

View file

@ -54,7 +54,8 @@ class AsyncContactsLoader(private val context: Context) :
if (core.isFriendListSubscriptionEnabled) { if (core.isFriendListSubscriptionEnabled) {
val rls: String = corePreferences.rlsUri val rls: String = corePreferences.rlsUri
for (list in core.friendsLists) { for (list in core.friendsLists) {
if (list.rlsAddress == null || list.rlsAddress.asStringUriOnly() != rls) { val rlsAddress = list.rlsAddress
if (rlsAddress == null || rlsAddress.asStringUriOnly() != rls) {
Log.i("[Contacts Loader] Friend list RLS URI updated to: $rls") Log.i("[Contacts Loader] Friend list RLS URI updated to: $rls")
list.rlsUri = rls list.rlsUri = rls
} }

View file

@ -29,7 +29,6 @@ import android.os.AsyncTask
import android.os.AsyncTask.THREAD_POOL_EXECUTOR import android.os.AsyncTask.THREAD_POOL_EXECUTOR
import android.provider.ContactsContract import android.provider.ContactsContract
import android.util.Patterns import android.util.Patterns
import com.google.android.gms.tasks.Tasks.await
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences

View file

@ -175,7 +175,8 @@ class NativeContact(val nativeId: String, private val lookupKey: String? = null)
val friend = this.friend val friend = this.friend
if (friend != null) { if (friend != null) {
friend.edit() friend.edit()
friend.name = fullName val fn = fullName
if (fn != null) friend.setName(fn)
val vCard = friend.vcard val vCard = friend.vcard
if (vCard != null) { if (vCard != null) {

View file

@ -57,8 +57,8 @@ class CoreContext(val context: Context, coreConfig: Config) {
} }
val sdkVersion: String by lazy { val sdkVersion: String by lazy {
val sdkVersion = context.getString(org.linphone.R.string.linphone_sdk_version) val sdkVersion = context.getString(R.string.linphone_sdk_version)
val sdkBranch = context.getString(org.linphone.R.string.linphone_sdk_branch) val sdkBranch = context.getString(R.string.linphone_sdk_branch)
"$sdkVersion ($sdkBranch)" "$sdkVersion ($sdkBranch)"
} }
@ -250,12 +250,12 @@ class CoreContext(val context: Context, coreConfig: Config) {
computeUserAgent() computeUserAgent()
for (lpc in core.proxyConfigList) { for (lpc in core.proxyConfigList) {
if (lpc.identityAddress.domain == corePreferences.defaultDomain) { if (lpc.identityAddress?.domain == corePreferences.defaultDomain) {
// Ensure conference URI is set on sip.linphone.org proxy configs // Ensure conference URI is set on sip.linphone.org proxy configs
if (lpc.conferenceFactoryUri == null) { if (lpc.conferenceFactoryUri == null) {
lpc.edit() lpc.edit()
val uri = corePreferences.conferenceServerUri val uri = corePreferences.conferenceServerUri
Log.i("[Context] Setting conference factory on proxy config ${lpc.identityAddress.asString()} to default value: $uri") Log.i("[Context] Setting conference factory on proxy config ${lpc.identityAddress?.asString()} to default value: $uri")
lpc.conferenceFactoryUri = uri lpc.conferenceFactoryUri = uri
lpc.done() lpc.done()
} }
@ -277,11 +277,11 @@ class CoreContext(val context: Context, coreConfig: Config) {
private fun computeUserAgent() { private fun computeUserAgent() {
val deviceName: String = corePreferences.deviceName val deviceName: String = corePreferences.deviceName
val appName: String = context.resources.getString(org.linphone.R.string.app_name) val appName: String = context.resources.getString(R.string.app_name)
val androidVersion = org.linphone.BuildConfig.VERSION_NAME val androidVersion = org.linphone.BuildConfig.VERSION_NAME
val userAgent = "$appName/$androidVersion ($deviceName) LinphoneSDK" val userAgent = "$appName/$androidVersion ($deviceName) LinphoneSDK"
val sdkVersion = context.getString(org.linphone.R.string.linphone_sdk_version) val sdkVersion = context.getString(R.string.linphone_sdk_version)
val sdkBranch = context.getString(org.linphone.R.string.linphone_sdk_branch) val sdkBranch = context.getString(R.string.linphone_sdk_branch)
val sdkUserAgent = "$sdkVersion ($sdkBranch)" val sdkUserAgent = "$sdkVersion ($sdkBranch)"
core.setUserAgent(userAgent, sdkUserAgent) core.setUserAgent(userAgent, sdkUserAgent)
} }
@ -302,7 +302,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
fun answerCall(call: Call) { fun answerCall(call: Call) {
Log.i("[Context] Answering call $call") Log.i("[Context] Answering call $call")
val params = core.createCallParams(call) val params = core.createCallParams(call)
params.recordFile = LinphoneUtils.getRecordingFilePathForAddress(call.remoteAddress) params?.recordFile = LinphoneUtils.getRecordingFilePathForAddress(call.remoteAddress)
call.acceptWithParams(params) call.acceptWithParams(params)
} }
@ -356,11 +356,15 @@ class CoreContext(val context: Context, coreConfig: Config) {
val params = core.createCallParams(null) val params = core.createCallParams(null)
if (forceZRTP) { if (forceZRTP) {
params.mediaEncryption = MediaEncryption.ZRTP params?.mediaEncryption = MediaEncryption.ZRTP
} }
params.recordFile = LinphoneUtils.getRecordingFilePathForAddress(address) params?.recordFile = LinphoneUtils.getRecordingFilePathForAddress(address)
val call = core.inviteAddressWithParams(address, params) val call = if (params != null) {
core.inviteAddressWithParams(address, params)
} else {
core.inviteAddress(address)
}
Log.i("[Context] Starting call $call") Log.i("[Context] Starting call $call")
} }
@ -368,8 +372,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
val currentDevice = core.videoDevice val currentDevice = core.videoDevice
Log.i("[Context] Current camera device is $currentDevice") Log.i("[Context] Current camera device is $currentDevice")
val devices = core.videoDevicesList for (camera in core.videoDevicesList) {
for (camera in devices) {
if (camera != currentDevice && camera != "StaticImage: Static picture") { if (camera != currentDevice && camera != "StaticImage: Static picture") {
Log.i("[Context] New camera device will be $camera") Log.i("[Context] New camera device will be $camera")
core.videoDevice = camera core.videoDevice = camera
@ -390,12 +393,13 @@ class CoreContext(val context: Context, coreConfig: Config) {
} }
fun showSwitchCameraButton(): Boolean { fun showSwitchCameraButton(): Boolean {
return core.videoDevicesList.orEmpty().size > 2 // Count StaticImage camera return core.videoDevicesList.size > 2 // Count StaticImage camera
} }
fun isVideoCallOrConferenceActive(): Boolean { fun isVideoCallOrConferenceActive(): Boolean {
return if (core.conference != null && core.isInConference) { val conference = core.conference
core.conference.currentParams.videoEnabled() return if (conference != null && core.isInConference) {
conference.currentParams.videoEnabled()
} else { } else {
core.currentCall?.currentParams?.videoEnabled() ?: false core.currentCall?.currentParams?.videoEnabled() ?: false
} }

View file

@ -107,7 +107,7 @@ class CorePreferences constructor(private val context: Context) {
} }
var deviceName: String var deviceName: String
get() = config.getString("app", "device_name", Compatibility.getDeviceName(context)) get() = config.getString("app", "device_name", Compatibility.getDeviceName(context))!!
set(value) = config.setString("app", "device_name", value) set(value) = config.setString("app", "device_name", value)
var chatRoomShortcuts: Boolean var chatRoomShortcuts: Boolean
@ -207,10 +207,10 @@ class CorePreferences constructor(private val context: Context) {
/* Read only application settings previously in non_localizable_custom */ /* Read only application settings previously in non_localizable_custom */
val defaultDomain: String val defaultDomain: String
get() = config.getString("app", "default_domain", "sip.linphone.org") get() = config.getString("app", "default_domain", "sip.linphone.org")!!
val debugPopupCode: String val debugPopupCode: String
get() = config.getString("app", "debug_popup_magic", "#1234#") get() = config.getString("app", "debug_popup_magic", "#1234#")!!
val fetchContactsFromDefaultDirectory: Boolean val fetchContactsFromDefaultDirectory: Boolean
get() = config.getBool("app", "fetch_contacts_from_default_directory", true) get() = config.getBool("app", "fetch_contacts_from_default_directory", true)
@ -219,13 +219,13 @@ class CorePreferences constructor(private val context: Context) {
get() = config.getBool("app", "hide_contacts_without_presence", false) get() = config.getBool("app", "hide_contacts_without_presence", false)
val rlsUri: String val rlsUri: String
get() = config.getString("app", "rls_uri", "sip:rls@sip.linphone.org") get() = config.getString("app", "rls_uri", "sip:rls@sip.linphone.org")!!
val conferenceServerUri: String val conferenceServerUri: String
get() = config.getString("app", "default_conference_factory_uri", "sip:conference-factory@sip.linphone.org") get() = config.getString("app", "default_conference_factory_uri", "sip:conference-factory@sip.linphone.org")!!
val limeX3dhServerUrl: String val limeX3dhServerUrl: String
get() = config.getString("app", "default_lime_x3dh_server_url", "https://lime.linphone.org/lime-server/lime-server.php") get() = config.getString("app", "default_lime_x3dh_server_url", "https://lime.linphone.org/lime-server/lime-server.php")!!
val allowMultipleFilesAndTextInSameMessage: Boolean val allowMultipleFilesAndTextInSameMessage: Boolean
get() = config.getBool("app", "allow_multiple_files_and_text_in_same_message", true) get() = config.getBool("app", "allow_multiple_files_and_text_in_same_message", true)

View file

@ -35,6 +35,10 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
if (intent.action == NotificationsManager.INTENT_REPLY_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_MARK_AS_READ_ACTION) { if (intent.action == NotificationsManager.INTENT_REPLY_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_MARK_AS_READ_ACTION) {
val remoteSipAddress: String? = coreContext.notificationsManager.getSipUriForChatNotificationId(notificationId) val remoteSipAddress: String? = coreContext.notificationsManager.getSipUriForChatNotificationId(notificationId)
if (remoteSipAddress == null) {
Log.e("[Notification Broadcast Receiver] Couldn't find remote address $remoteSipAddress for notification id $notificationId")
return
}
val core: Core = coreContext.core val core: Core = coreContext.core
val remoteAddress = core.interpretUrl(remoteSipAddress) val remoteAddress = core.interpretUrl(remoteSipAddress)
@ -75,7 +79,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val remoteAddress: String? = coreContext.notificationsManager.getSipUriForCallNotificationId(notificationId) val remoteAddress: String? = coreContext.notificationsManager.getSipUriForCallNotificationId(notificationId)
val core: Core = coreContext.core val core: Core = coreContext.core
val call = core.findCallFromUri(remoteAddress) val call = if (remoteAddress != null) core.findCallFromUri(remoteAddress) else null
if (call == null) { if (call == null) {
Log.e("[Notification Broadcast Receiver] Couldn't find call from remote address $remoteAddress") Log.e("[Notification Broadcast Receiver] Couldn't find call from remote address $remoteAddress")
return return

View file

@ -122,7 +122,7 @@ class NotificationsManager(private val context: Context) {
Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> displayIncomingCallNotification(call) Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> displayIncomingCallNotification(call)
Call.State.End, Call.State.Error -> dismissCallNotification(call) Call.State.End, Call.State.Error -> dismissCallNotification(call)
Call.State.Released -> { Call.State.Released -> {
if (call.callLog?.status == Call.Status.Missed) { if (call.callLog.status == Call.Status.Missed) {
displayMissedCallNotification(call) displayMissedCallNotification(call)
} }
} }
@ -133,12 +133,12 @@ class NotificationsManager(private val context: Context) {
override fun onMessageReceived(core: Core, room: ChatRoom, message: ChatMessage) { override fun onMessageReceived(core: Core, room: ChatRoom, message: ChatMessage) {
if (message.isOutgoing) return if (message.isOutgoing) return
if (currentlyDisplayedChatRoomAddress == room.peerAddress?.asStringUriOnly()) { if (currentlyDisplayedChatRoomAddress == room.peerAddress.asStringUriOnly()) {
Log.i("[Notifications Manager] Chat room is currently displayed, do not notify received message") Log.i("[Notifications Manager] Chat room is currently displayed, do not notify received message")
return return
} }
if (message.errorInfo?.reason == Reason.UnsupportedContent) { if (message.errorInfo.reason == Reason.UnsupportedContent) {
Log.w("[Notifications Manager] Received message with unsupported content, do not notify") Log.w("[Notifications Manager] Received message with unsupported content, do not notify")
return return
} }
@ -500,7 +500,7 @@ class NotificationsManager(private val context: Context) {
} }
private fun dismissCallNotification(call: Call) { private fun dismissCallNotification(call: Call) {
val address = call.remoteAddress?.asStringUriOnly() val address = call.remoteAddress.asStringUriOnly()
val notifiable: Notifiable? = callNotificationsMap[address] val notifiable: Notifiable? = callNotificationsMap[address]
if (notifiable != null) { if (notifiable != null) {
cancel(notifiable.notificationId) cancel(notifiable.notificationId)
@ -536,10 +536,10 @@ class NotificationsManager(private val context: Context) {
val notifiable = getNotifiableForRoom(room) val notifiable = getNotifiableForRoom(room)
var text = "" var text = ""
if (message.hasTextContent()) text = message.textContent if (message.hasTextContent()) text = message.textContent.orEmpty()
else { else {
for (content in message.contents) { for (content in message.contents) {
text = content.name text += content.name
} }
} }
val notifiableMessage = NotifiableMessage(text, contact, displayName, message.time, senderAvatar = roundPicture) val notifiableMessage = NotifiableMessage(text, contact, displayName, message.time, senderAvatar = roundPicture)
@ -592,7 +592,7 @@ class NotificationsManager(private val context: Context) {
Log.i("[Notifications Manager] Updating message notification with reply for notification ${notifiable.notificationId}") Log.i("[Notifications Manager] Updating message notification with reply for notification ${notifiable.notificationId}")
val reply = NotifiableMessage( val reply = NotifiableMessage(
message.textContent, message.textContent.orEmpty(),
null, null,
notifiable.myself ?: LinphoneUtils.getDisplayName(message.fromAddress), notifiable.myself ?: LinphoneUtils.getDisplayName(message.fromAddress),
System.currentTimeMillis() System.currentTimeMillis()

View file

@ -354,7 +354,7 @@ fun addPrefixEditTextValidation(editText: EditText, enabled: Boolean) {
@BindingAdapter("assistantUsernameValidation") @BindingAdapter("assistantUsernameValidation")
fun addUsernameEditTextValidation(editText: EditText, enabled: Boolean) { fun addUsernameEditTextValidation(editText: EditText, enabled: Boolean) {
if (!enabled) return if (!enabled) return
val usernameRegexp = corePreferences.config.getString("assistant", "username_regex", "^[a-z0-9+_.\\-]*\$") val usernameRegexp = corePreferences.config.getString("assistant", "username_regex", "^[a-z0-9+_.\\-]*\$")!!
val usernameMaxLength = corePreferences.config.getInt("assistant", "username_max_length", 64) val usernameMaxLength = corePreferences.config.getInt("assistant", "username_max_length", 64)
editText.addTextChangedListener(object : TextWatcher { editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {

View file

@ -35,7 +35,7 @@ class LinphoneUtils {
private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss" private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss"
fun getDisplayName(address: Address): String { fun getDisplayName(address: Address): String {
return address.displayName ?: address.username return address.displayName ?: address.username ?: ""
} }
fun isLimeAvailable(): Boolean { fun isLimeAvailable(): Boolean {

View file

@ -175,7 +175,7 @@ class ShortcutsHelper(val context: Context) {
personsList.add(contact.getPerson()) personsList.add(contact.getPerson())
} }
} }
subject = chatRoom.subject subject = chatRoom.subject.orEmpty()
icon = IconCompat.createWithResource(context, R.drawable.chat_group_avatar) icon = IconCompat.createWithResource(context, R.drawable.chat_group_avatar)
} }