From dc65d2760331d56c84835c45959f5bc6f2bfa9f9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 7 Feb 2023 15:45:32 +0100 Subject: [PATCH] Show contacts consolidated presence if not offline + updated EXPIRE for sip.linphone.org from 1 year to 1 month --- .../assets/assistant_linphone_default_values | 4 +- .../activities/main/chat/data/ChatRoomData.kt | 11 +- .../chat/fragments/DetailChatRoomFragment.kt | 6 - .../main/chat/viewmodels/ChatRoomViewModel.kt | 48 +++++-- .../main/contact/data/ContactEditorData.kt | 7 + .../contact/viewmodels/ContactViewModel.kt | 7 + .../fragments/AccountSettingsFragment.kt | 8 ++ .../viewmodels/AccountSettingsViewModel.kt | 15 +++ .../sidemenu/fragments/SideMenuFragment.kt | 6 + .../sidemenu/viewmodels/SideMenuViewModel.kt | 7 + .../main/viewmodels/SharedMainViewModel.kt | 12 +- .../linphone/contact/ContactDataInterface.kt | 26 +++- .../linphone/contact/ContactSelectionData.kt | 25 +++- .../org/linphone/contact/ContactsManager.kt | 2 +- .../java/org/linphone/core/CoreContext.kt | 42 ++++++ .../org/linphone/utils/ActivityMonitor.kt | 121 ++++++++++++++++++ app/src/main/res/drawable/led_away.xml | 5 + app/src/main/res/drawable/led_online.xml | 5 + .../res/layout/chat_message_list_cell.xml | 13 ++ .../res/layout/chat_room_detail_fragment.xml | 15 +-- .../layout/chat_room_devices_group_cell.xml | 14 ++ .../chat_room_group_info_participant_cell.xml | 14 ++ .../chat_room_imdn_participant_cell.xml | 18 ++- .../main/res/layout/chat_room_list_cell.xml | 14 ++ .../res/layout/contact_detail_fragment.xml | 34 +++-- app/src/main/res/layout/contact_list_cell.xml | 12 ++ .../res/layout/contact_selection_cell.xml | 14 ++ .../res/layout/history_detail_fragment.xml | 36 ++++-- app/src/main/res/layout/history_list_cell.xml | 12 ++ .../res/layout/settings_account_fragment.xml | 6 + .../main/res/layout/side_menu_fragment.xml | 12 ++ app/src/main/res/values-fr/strings.xml | 7 + app/src/main/res/values/dimen.xml | 2 + app/src/main/res/values/strings.xml | 7 + app/src/main/res/values/styles.xml | 5 + 35 files changed, 537 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/org/linphone/utils/ActivityMonitor.kt create mode 100644 app/src/main/res/drawable/led_away.xml create mode 100644 app/src/main/res/drawable/led_online.xml diff --git a/app/src/main/assets/assistant_linphone_default_values b/app/src/main/assets/assistant_linphone_default_values index df1f378c1..e9724cd0c 100644 --- a/app/src/main/assets/assistant_linphone_default_values +++ b/app/src/main/assets/assistant_linphone_default_values @@ -3,11 +3,11 @@
1 0 - 0 + 1 sip:voip-metrics@sip.linphone.org;transport=tls 1 180 - 31536000 + 2629800 sip:?@sip.linphone.org <sip:sip.linphone.org;transport=tls> <sip:sip.linphone.org;transport=tls> diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt index 20ee5621b..3312a9087 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt @@ -43,6 +43,7 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface { override val securityLevel: MutableLiveData = MutableLiveData() override val showGroupChatAvatar: Boolean get() = conferenceChatRoom && !oneToOneChatRoom + override val presenceStatus: MutableLiveData = MutableLiveData() override val coroutineScope: CoroutineScope = coreContext.coroutineScope val unreadMessagesCount = MutableLiveData() @@ -79,6 +80,7 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface { init { unreadMessagesCount.value = chatRoom.unreadMessagesCount + presenceStatus.value = ConsolidatedPresence.Offline subject.value = chatRoom.subject updateSecurityIcon() @@ -135,7 +137,14 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface { } } if (remoteAddress != null) { - contact.value = coreContext.contactsManager.findContactByAddress(remoteAddress) + val friend = coreContext.contactsManager.findContactByAddress(remoteAddress) + if (friend != null) { + contact.value = friend!! + presenceStatus.value = friend.consolidatedPresence + friend.addListener { + presenceStatus.value = it.consolidatedPresence + } + } } } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index 541ce6475..2df29dcb4 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -595,12 +595,6 @@ class DetailChatRoomFragment : MasterFragment = MutableLiveData() override val showGroupChatAvatar: Boolean get() = conferenceChatRoom && !oneToOneChatRoom + override val presenceStatus: MutableLiveData = MutableLiveData() override val coroutineScope: CoroutineScope = viewModelScope val subject = MutableLiveData() @@ -67,7 +69,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf val securityLevelContentDescription = MutableLiveData() - val peerSipUri = MutableLiveData() + val lastPresenceInfo = MutableLiveData() val ephemeralEnabled = MutableLiveData() @@ -230,6 +232,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf } fun contactLookup() { + presenceStatus.value = ConsolidatedPresence.Offline displayName.value = when { basicChatRoom -> LinphoneUtils.getDisplayName( chatRoom.peerAddress @@ -304,7 +307,42 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf private fun searchMatchingContact() { val remoteAddress = getRemoteAddress() if (remoteAddress != null) { - contact.value = coreContext.contactsManager.findContactByAddress(remoteAddress) + val friend = coreContext.contactsManager.findContactByAddress(remoteAddress) + if (friend != null) { + contact.value = friend!! + presenceStatus.value = friend.consolidatedPresence + computeLastSeenLabel(friend) + friend.addListener { + presenceStatus.value = it.consolidatedPresence + computeLastSeenLabel(friend) + } + } + } + } + + private fun computeLastSeenLabel(friend: Friend) { + if (friend.consolidatedPresence == ConsolidatedPresence.Online) { + lastPresenceInfo.value = AppUtils.getString(R.string.chat_room_presence_online) + return + } + + val timestamp = friend.presenceModel?.timestamp ?: -1 + lastPresenceInfo.value = when { + TimestampUtils.isToday(timestamp) -> { + val time = TimestampUtils.timeToString(timestamp, timestampInSecs = true) + val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online_today) + "$text $time" + } + TimestampUtils.isYesterday(timestamp) -> { + val time = TimestampUtils.timeToString(timestamp, timestampInSecs = true) + val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online_yesterday) + "$text $time" + } + else -> { + val date = TimestampUtils.toString(timestamp, onlyDate = true, shortDate = false, hideYear = true) + val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online) + "$text $date" + } } } @@ -354,12 +392,6 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf private fun updateParticipants() { val participants = chatRoom.participants - peerSipUri.value = if (oneToOneChatRoom && !basicChatRoom) { - participants.firstOrNull()?.address?.asStringUriOnly() - ?: chatRoom.peerAddress.asStringUriOnly() - } else { - chatRoom.peerAddress.asStringUriOnly() - } oneParticipantOneDevice = oneToOneChatRoom && chatRoom.me?.devices?.size == 1 && diff --git a/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt index f64f07d26..fdef62159 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt @@ -32,6 +32,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.contact.* import org.linphone.core.ChatRoomSecurityLevel +import org.linphone.core.ConsolidatedPresence import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.utils.AppUtils @@ -42,6 +43,7 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface { override val contact: MutableLiveData = MutableLiveData() override val displayName: MutableLiveData = MutableLiveData() override val securityLevel: MutableLiveData = MutableLiveData() + override val presenceStatus: MutableLiveData = MutableLiveData() override val coroutineScope: CoroutineScope = coreContext.coroutineScope val firstName = MutableLiveData() @@ -66,8 +68,13 @@ class ContactEditorData(val friend: Friend?) : ContactDataInterface { if (friend != null) { contact.value = friend!! displayName.value = friend.name ?: "" + presenceStatus.value = friend.consolidatedPresence + friend.addListener { + presenceStatus.value = it.consolidatedPresence + } } else { displayName.value = "" + presenceStatus.value = ConsolidatedPresence.Offline } organization.value = friend?.organization ?: "" diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt index e57ef514a..bd6911cc4 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt @@ -54,6 +54,7 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier override val contact: MutableLiveData = MutableLiveData() override val displayName: MutableLiveData = MutableLiveData() override val securityLevel: MutableLiveData = MutableLiveData() + override val presenceStatus: MutableLiveData = MutableLiveData() override val coroutineScope: CoroutineScope = viewModelScope var fullName = "" @@ -142,10 +143,16 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier contact.postValue(friend) displayName.postValue(friend.name) isNativeContact.postValue(friend.refKey != null) + presenceStatus.postValue(friend.consolidatedPresence) } else { contact.value = friend displayName.value = friend.name isNativeContact.value = friend.refKey != null + presenceStatus.value = friend.consolidatedPresence + } + + friend.addListener { + presenceStatus.value = it.consolidatedPresence } } diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt index f4fb8208d..5ee6034dc 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt @@ -92,6 +92,14 @@ class AccountSettingsFragment : GenericSettingFragment>() } + val publishPresenceToggledEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val displayUsernameInsteadOfIdentity = corePreferences.replaceSipUriByUsername private var accountToDelete: Account? = null @@ -436,6 +440,16 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( } val limeServerUrl = MutableLiveData() + val publishPresenceListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + val params = account.params.clone() + params.isPublishEnabled = newValue + account.params = params + publishPresenceToggledEvent.value = Event(true) + } + } + val publishPresence = MutableLiveData() + init { update() account.addListener(listener) @@ -496,6 +510,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel( limeServerUrl.value = params.limeServerUrl hideLinkPhoneNumber.value = corePreferences.hideLinkPhoneNumber || params.identityAddress?.domain != corePreferences.defaultDomain + publishPresence.value = params.isPublishEnabled } private fun initTransportList() { diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt index b9449e764..8bd98bc41 100644 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt @@ -74,6 +74,12 @@ class SideMenuFragment : GenericFragment() { viewModel.updateAccountsList() } + sharedViewModel.publishPresenceToggled.observe( + viewLifecycleOwner + ) { + viewModel.refreshConsolidatedPresence() + } + viewModel.accountsSettingsListener = object : SettingListenerStub() { override fun onAccountClicked(identity: String) { val args = Bundle() diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt index 288c8831f..1f07fedb6 100644 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt @@ -43,6 +43,8 @@ class SideMenuViewModel : ViewModel() { val accounts = MutableLiveData>() + val presenceStatus = MutableLiveData() + lateinit var accountsSettingsListener: SettingListenerStub private val listener: CoreListenerStub = object : CoreListenerStub() { @@ -69,6 +71,7 @@ class SideMenuViewModel : ViewModel() { LinphoneUtils.isRemoteConferencingAvailable() coreContext.core.addListener(listener) updateAccountsList() + refreshConsolidatedPresence() } override fun onCleared() { @@ -78,6 +81,10 @@ class SideMenuViewModel : ViewModel() { super.onCleared() } + fun refreshConsolidatedPresence() { + presenceStatus.value = coreContext.core.consolidatedPresence + } + fun updateAccountsList() { defaultAccountFound.value = false // Do not assume a default account will still be found defaultAccountViewModel.value?.destroy() diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt index 9db0a6c70..052027802 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt @@ -88,9 +88,17 @@ class SharedMainViewModel : ViewModel() { /* Accounts */ - val defaultAccountChanged = MutableLiveData() + val defaultAccountChanged: MutableLiveData by lazy { + MutableLiveData() + } - val accountRemoved = MutableLiveData() + val accountRemoved: MutableLiveData by lazy { + MutableLiveData() + } + + val publishPresenceToggled: MutableLiveData by lazy { + MutableLiveData() + } val accountSettingsFragmentOpenedEvent: MutableLiveData> by lazy { MutableLiveData>() diff --git a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt index 90171dd7c..3d0fa893a 100644 --- a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt +++ b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt @@ -26,6 +26,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.activities.main.viewmodels.MessageNotifierViewModel import org.linphone.core.Address import org.linphone.core.ChatRoomSecurityLevel +import org.linphone.core.ConsolidatedPresence import org.linphone.core.Friend import org.linphone.utils.LinphoneUtils @@ -39,6 +40,8 @@ interface ContactDataInterface { val showGroupChatAvatar: Boolean get() = false + val presenceStatus: MutableLiveData + val coroutineScope: CoroutineScope } @@ -46,10 +49,12 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte final override val contact: MutableLiveData = MutableLiveData() final override val displayName: MutableLiveData = MutableLiveData() final override val securityLevel: MutableLiveData = MutableLiveData() + final override val presenceStatus: MutableLiveData = MutableLiveData() final override val coroutineScope: CoroutineScope = coreContext.coroutineScope init { securityLevel.value = ChatRoomSecurityLevel.ClearText + presenceStatus.value = ConsolidatedPresence.Offline contactLookup() } @@ -59,9 +64,13 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte private fun contactLookup() { displayName.value = LinphoneUtils.getDisplayName(sipAddress) - val c = coreContext.contactsManager.findContactByAddress(sipAddress) - if (c != null) { - contact.value = c!! + val friend = coreContext.contactsManager.findContactByAddress(sipAddress) + if (friend != null) { + contact.value = friend!! + presenceStatus.value = friend.consolidatedPresence + friend.addListener { + presenceStatus.value = it.consolidatedPresence + } } } } @@ -70,15 +79,24 @@ abstract class GenericContactViewModel(private val sipAddress: Address) : Messag final override val contact: MutableLiveData = MutableLiveData() final override val displayName: MutableLiveData = MutableLiveData() final override val securityLevel: MutableLiveData = MutableLiveData() + final override val presenceStatus: MutableLiveData = MutableLiveData() final override val coroutineScope: CoroutineScope = viewModelScope init { securityLevel.value = ChatRoomSecurityLevel.ClearText + presenceStatus.value = ConsolidatedPresence.Offline contactLookup() } private fun contactLookup() { displayName.value = LinphoneUtils.getDisplayName(sipAddress) - contact.value = coreContext.contactsManager.findContactByAddress(sipAddress) + val friend = coreContext.contactsManager.findContactByAddress(sipAddress) + if (friend != null) { + contact.value = friend!! + presenceStatus.value = friend.consolidatedPresence + friend.addListener { + presenceStatus.value = it.consolidatedPresence + } + } } } diff --git a/app/src/main/java/org/linphone/contact/ContactSelectionData.kt b/app/src/main/java/org/linphone/contact/ContactSelectionData.kt index 73ba13cae..61e78a7ac 100644 --- a/app/src/main/java/org/linphone/contact/ContactSelectionData.kt +++ b/app/src/main/java/org/linphone/contact/ContactSelectionData.kt @@ -29,6 +29,7 @@ class ContactSelectionData(private val searchResult: SearchResult) : ContactData override val contact: MutableLiveData = MutableLiveData() override val displayName: MutableLiveData = MutableLiveData() override val securityLevel: MutableLiveData = MutableLiveData() + override val presenceStatus: MutableLiveData = MutableLiveData() override val coroutineScope: CoroutineScope = coreContext.coroutineScope val isDisabled: MutableLiveData by lazy { @@ -57,6 +58,7 @@ class ContactSelectionData(private val searchResult: SearchResult) : ContactData init { isDisabled.value = false isSelected.value = false + presenceStatus.value = ConsolidatedPresence.Offline searchMatchingContact() } @@ -65,14 +67,31 @@ class ContactSelectionData(private val searchResult: SearchResult) : ContactData if (friend != null) { contact.value = friend!! displayName.value = friend.name + presenceStatus.value = friend.consolidatedPresence + friend.addListener { + presenceStatus.value = it.consolidatedPresence + } } else { val address = searchResult.address if (address != null) { - contact.value = coreContext.contactsManager.findContactByAddress(address) + val found = coreContext.contactsManager.findContactByAddress(address) + if (found != null) { + contact.value = found!! + presenceStatus.value = found.consolidatedPresence + found.addListener { + presenceStatus.value = it.consolidatedPresence + } + } displayName.value = LinphoneUtils.getDisplayName(address) } else if (searchResult.phoneNumber != null) { - contact.value = - coreContext.contactsManager.findContactByPhoneNumber(searchResult.phoneNumber.orEmpty()) + val found = coreContext.contactsManager.findContactByPhoneNumber(searchResult.phoneNumber.orEmpty()) + if (found != null) { + contact.value = found!! + presenceStatus.value = found.consolidatedPresence + found.addListener { + presenceStatus.value = it.consolidatedPresence + } + } displayName.value = searchResult.phoneNumber.orEmpty() } } diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt index 294a2c24e..723ddc93c 100644 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contact/ContactsManager.kt @@ -281,7 +281,7 @@ class ContactsManager(private val context: Context) { @Synchronized private fun refreshContactOnPresenceReceived(friend: Friend) { - Log.d("[Contacts Manager] Received presence information for contact $friend") + Log.d("[Contacts Manager] Received presence information for contact [${friend.name}]: [${friend.consolidatedPresence}]") if (corePreferences.storePresenceInNativeContact && PermissionHelper.get().hasWriteContactsPermission()) { if (friend.refKey != null) { Log.i("[Contacts Manager] Storing presence in native contact ${friend.refKey}") diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 6c10f52ca..eadb05351 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -19,6 +19,7 @@ */ package org.linphone.core +import android.app.Application import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -127,6 +128,8 @@ class CoreContext( private var previousCallState = Call.State.Idle private lateinit var phoneStateListener: PhoneStateInterface + private val activityMonitor = ActivityMonitor() + private val listener: CoreListenerStub = object : CoreListenerStub() { override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) { Log.i("[Context] Global state changed [$state]") @@ -309,6 +312,8 @@ class CoreContext( stopped = false _lifecycleRegistry.currentState = Lifecycle.State.CREATED + + (context as Application).registerActivityLifecycleCallbacks(activityMonitor) Log.i("[Context] Ready") } @@ -374,6 +379,24 @@ class CoreContext( stopped = true _lifecycleRegistry.currentState = Lifecycle.State.DESTROYED loggingService.removeListener(loggingServiceListener) + + (context as Application).unregisterActivityLifecycleCallbacks(activityMonitor) + } + + fun onForeground() { + // If presence publish is disabled and we call core.setConsolidatedPresence, it will enabled it! + if (core.defaultAccount?.params?.isPublishEnabled == true) { + Log.i("[Context] App is in foreground, setting consolidated presence to Online") + core.consolidatedPresence = ConsolidatedPresence.Online + } + } + + fun onBackground() { + // If presence publish is disabled and we call core.setConsolidatedPresence, it will enabled it! + if (core.defaultAccount?.params?.isPublishEnabled == true) { + Log.i("[Context] App is in background, setting consolidated presence to Busy") + core.consolidatedPresence = ConsolidatedPresence.Busy + } } private fun configureCore() { @@ -411,11 +434,30 @@ class CoreContext( computeUserAgent() + val fiveOneMigrationRequired = core.config.getBool("app", "migration_5.1", true) + core.config.setBool("app", "migration_5.1", false) + for (account in core.accountList) { if (account.params.identityAddress?.domain == corePreferences.defaultDomain) { var paramsChanged = false val params = account.params.clone() + if (fiveOneMigrationRequired) { + val newExpire = 2629800 // 1 month + if (account.params.expires != newExpire) { + Log.i("[Context] Updating expire on proxy config ${params.identityAddress?.asString()} from ${account.params.expires} to newExpire") + params.expires = newExpire + paramsChanged = true + } + + // Enable presence publish/subscribe for new feature + if (!account.params.isPublishEnabled) { + Log.i("[Context] Enabling presence publish on proxy config ${params.identityAddress?.asString()}") + params.isPublishEnabled = true + paramsChanged = true + } + } + // Ensure conference factory URI is set on sip.linphone.org proxy configs if (account.params.conferenceFactoryUri == null) { val uri = corePreferences.conferenceServerUri diff --git a/app/src/main/java/org/linphone/utils/ActivityMonitor.kt b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt new file mode 100644 index 000000000..20f4fe482 --- /dev/null +++ b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.utils + +import android.app.Activity +import android.app.Application.ActivityLifecycleCallbacks +import android.os.Bundle +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.service.AndroidDispatcher +import org.linphone.core.tools.service.CoreManager + +class ActivityMonitor : ActivityLifecycleCallbacks { + private val activities = ArrayList() + private var mActive = false + private var mRunningActivities = 0 + private var mLastChecker: InactivityChecker? = null + + @Synchronized + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + if (!activities.contains(activity)) activities.add(activity) + } + + override fun onActivityStarted(activity: Activity) { + } + + @Synchronized + override fun onActivityResumed(activity: Activity) { + if (!activities.contains(activity)) { + activities.add(activity) + } + mRunningActivities++ + checkActivity() + } + + @Synchronized + override fun onActivityPaused(activity: Activity) { + if (!activities.contains(activity)) { + activities.add(activity) + } else { + mRunningActivities-- + checkActivity() + } + } + + override fun onActivityStopped(activity: Activity) { + } + + @Synchronized + override fun onActivityDestroyed(activity: Activity) { + activities.remove(activity) + } + + private fun startInactivityChecker() { + if (mLastChecker != null) mLastChecker!!.cancel() + AndroidDispatcher.dispatchOnUIThreadAfter( + InactivityChecker().also { mLastChecker = it }, + 2000 + ) + } + + private fun checkActivity() { + if (mRunningActivities == 0) { + if (mActive) startInactivityChecker() + } else if (mRunningActivities > 0) { + if (!mActive) { + mActive = true + onForegroundMode() + } + if (mLastChecker != null) { + mLastChecker!!.cancel() + mLastChecker = null + } + } + } + + private fun onBackgroundMode() { + coreContext.onBackground() + } + + private fun onForegroundMode() { + coreContext.onForeground() + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + internal inner class InactivityChecker : Runnable { + private var isCanceled = false + fun cancel() { + isCanceled = true + } + + override fun run() { + if (CoreManager.isReady()) { + synchronized(CoreManager.instance()) { + if (!isCanceled) { + if (mRunningActivities == 0 && mActive) { + mActive = false + onBackgroundMode() + } + } + } + } + } + } +} diff --git a/app/src/main/res/drawable/led_away.xml b/app/src/main/res/drawable/led_away.xml new file mode 100644 index 000000000..53b2810ea --- /dev/null +++ b/app/src/main/res/drawable/led_away.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/led_online.xml b/app/src/main/res/drawable/led_online.xml new file mode 100644 index 000000000..7c701e958 --- /dev/null +++ b/app/src/main/res/drawable/led_online.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/chat_message_list_cell.xml b/app/src/main/res/layout/chat_message_list_cell.xml index 26afd8fd2..34ca38da7 100644 --- a/app/src/main/res/layout/chat_message_list_cell.xml +++ b/app/src/main/res/layout/chat_message_list_cell.xml @@ -7,6 +7,7 @@ + @@ -75,6 +76,18 @@ android:gravity="center" android:visibility="@{data.chatMessage.outgoing || selectionListViewModel.isEditionEnabled ? View.GONE : (data.hideAvatar ? View.INVISIBLE : View.VISIBLE)}" /> + + - + @@ -77,7 +75,6 @@ android:paddingEnd="5dp"> diff --git a/app/src/main/res/layout/chat_room_devices_group_cell.xml b/app/src/main/res/layout/chat_room_devices_group_cell.xml index 36bcffc25..c4ab8802d 100644 --- a/app/src/main/res/layout/chat_room_devices_group_cell.xml +++ b/app/src/main/res/layout/chat_room_devices_group_cell.xml @@ -4,6 +4,7 @@ + @@ -29,6 +30,7 @@ android:layout_centerVertical="true"> + + + @@ -27,6 +28,7 @@ android:layout_centerVertical="true"> + + + @@ -18,12 +19,25 @@ android:id="@+id/avatar" android:layout_width="@dimen/contact_avatar_size" android:layout_height="@dimen/contact_avatar_size" + android:layout_centerVertical="true" android:layout_marginRight="10dp" coilContact="@{data}" android:background="@drawable/generated_avatar_bg" android:src="@drawable/voip_single_contact_avatar" - android:contentDescription="@null" - android:layout_centerVertical="true"/> + android:contentDescription="@null"/> + + + @@ -50,6 +51,19 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" /> + + + @@ -82,14 +83,31 @@ android:orientation="vertical" android:paddingTop="20dp"> - + + + + + + + + @@ -43,6 +44,17 @@ android:src="@drawable/voip_single_contact_avatar" android:contentDescription="@null" /> + + + @@ -31,6 +32,7 @@ android:layout_centerVertical="true"> + + + @@ -83,15 +84,32 @@ android:paddingTop="10dp" android:paddingBottom="5dp"> - + + + + + + + + + + + + + @@ -68,6 +69,17 @@ android:src="@drawable/avatar_border" android:visibility="@{viewModel.defaultAccountFound ? View.VISIBLE : View.GONE}" /> + + Capture sauvegardée : %s Vous avez reçu un message vocal Message vocal + La personne est en ligne + La personne est hors ligne + Publier la présence + En ligne + En ligne aujourd\'hui à + En ligne hier à + En ligne le \ No newline at end of file diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 5fa444a83..19ba6bf14 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -82,4 +82,6 @@ 10dp 35dp 5dp + 12dp + 25dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 481bf9be9..894449b0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -233,6 +233,10 @@ Please wait for first download to finish before starting a new one You have received a voice message Voice message + Online + Online today at + Online yesterday at + Online on No recordings @@ -702,6 +706,7 @@ Conference factory URI Audio/video conference factory URI E2E encryption keys server URL + Publish presence information Default layout @@ -890,4 +895,6 @@ Toggle meeting information details visibility Group call participants Export recording + User is online + User is offline diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 8d2def36a..c35fb4fad 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -422,6 +422,11 @@ + +