Show contacts consolidated presence if not offline + updated EXPIRE for sip.linphone.org from 1 year to 1 month

This commit is contained in:
Sylvain Berfini 2023-02-07 15:45:32 +01:00
parent 536bfd0020
commit dc65d27603
35 changed files with 537 additions and 55 deletions

View file

@ -3,11 +3,11 @@
<section name="proxy_default_values">
<entry name="avpf" overwrite="true">1</entry>
<entry name="dial_escape_plus" overwrite="true">0</entry>
<entry name="publish" overwrite="true">0</entry>
<entry name="publish" overwrite="true">1</entry>
<entry name="quality_reporting_collector" overwrite="true">sip:voip-metrics@sip.linphone.org;transport=tls</entry>
<entry name="quality_reporting_enabled" overwrite="true">1</entry>
<entry name="quality_reporting_interval" overwrite="true">180</entry>
<entry name="reg_expires" overwrite="true">31536000</entry>
<entry name="reg_expires" overwrite="true">2629800</entry>
<entry name="reg_identity" overwrite="true">sip:?@sip.linphone.org</entry>
<entry name="reg_proxy" overwrite="true">&lt;sip:sip.linphone.org;transport=tls&gt;</entry>
<entry name="reg_route" overwrite="true">&lt;sip:sip.linphone.org;transport=tls&gt;</entry>

View file

@ -43,6 +43,7 @@ class ChatRoomData(private val chatRoom: ChatRoom) : ContactDataInterface {
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
override val showGroupChatAvatar: Boolean
get() = conferenceChatRoom && !oneToOneChatRoom
override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
override val coroutineScope: CoroutineScope = coreContext.coroutineScope
val unreadMessagesCount = MutableLiveData<Int>()
@ -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
}
}
}
}

View file

@ -595,12 +595,6 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
}
}
binding.setTitleClickListener {
binding.sipUri.visibility = if (!viewModel.oneToOneChatRoom ||
binding.sipUri.visibility == View.VISIBLE
) View.GONE else View.VISIBLE
}
binding.setMenuClickListener {
showPopupMenu(chatRoom)
}

View file

@ -35,6 +35,7 @@ import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.TimestampUtils
class ChatRoomViewModelFactory(private val chatRoom: ChatRoom) :
ViewModelProvider.NewInstanceFactory() {
@ -51,6 +52,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
override val showGroupChatAvatar: Boolean
get() = conferenceChatRoom && !oneToOneChatRoom
override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
override val coroutineScope: CoroutineScope = viewModelScope
val subject = MutableLiveData<String>()
@ -67,7 +69,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
val securityLevelContentDescription = MutableLiveData<Int>()
val peerSipUri = MutableLiveData<String>()
val lastPresenceInfo = MutableLiveData<String>()
val ephemeralEnabled = MutableLiveData<Boolean>()
@ -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 &&

View file

@ -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<Friend> = MutableLiveData<Friend>()
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
override val coroutineScope: CoroutineScope = coreContext.coroutineScope
val firstName = MutableLiveData<String>()
@ -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 ?: ""

View file

@ -54,6 +54,7 @@ class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifier
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
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
}
}

View file

@ -92,6 +92,14 @@ class AccountSettingsFragment : GenericSettingFragment<SettingsAccountFragmentBi
}
}
viewModel.publishPresenceToggledEvent.observe(
viewLifecycleOwner
) {
it.consume {
sharedViewModel.publishPresenceToggled.value = true
}
}
view.doOnPreDraw {
// Notifies fragment is ready to be drawn
sharedViewModel.accountSettingsFragmentOpenedEvent.value = Event(true)

View file

@ -74,6 +74,10 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
MutableLiveData<Event<Boolean>>()
}
val publishPresenceToggledEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val displayUsernameInsteadOfIdentity = corePreferences.replaceSipUriByUsername
private var accountToDelete: Account? = null
@ -436,6 +440,16 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
}
val limeServerUrl = MutableLiveData<String>()
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<Boolean>()
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() {

View file

@ -74,6 +74,12 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
viewModel.updateAccountsList()
}
sharedViewModel.publishPresenceToggled.observe(
viewLifecycleOwner
) {
viewModel.refreshConsolidatedPresence()
}
viewModel.accountsSettingsListener = object : SettingListenerStub() {
override fun onAccountClicked(identity: String) {
val args = Bundle()

View file

@ -43,6 +43,8 @@ class SideMenuViewModel : ViewModel() {
val accounts = MutableLiveData<ArrayList<AccountSettingsViewModel>>()
val presenceStatus = MutableLiveData<ConsolidatedPresence>()
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()

View file

@ -88,9 +88,17 @@ class SharedMainViewModel : ViewModel() {
/* Accounts */
val defaultAccountChanged = MutableLiveData<Boolean>()
val defaultAccountChanged: MutableLiveData<Boolean> by lazy {
MutableLiveData<Boolean>()
}
val accountRemoved = MutableLiveData<Boolean>()
val accountRemoved: MutableLiveData<Boolean> by lazy {
MutableLiveData<Boolean>()
}
val publishPresenceToggled: MutableLiveData<Boolean> by lazy {
MutableLiveData<Boolean>()
}
val accountSettingsFragmentOpenedEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()

View file

@ -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<ConsolidatedPresence>
val coroutineScope: CoroutineScope
}
@ -46,10 +49,12 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte
final override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
final override val displayName: MutableLiveData<String> = MutableLiveData<String>()
final override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
final override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
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<Friend> = MutableLiveData<Friend>()
final override val displayName: MutableLiveData<String> = MutableLiveData<String>()
final override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
final override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
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
}
}
}
}

View file

@ -29,6 +29,7 @@ class ContactSelectionData(private val searchResult: SearchResult) : ContactData
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
override val presenceStatus: MutableLiveData<ConsolidatedPresence> = MutableLiveData<ConsolidatedPresence>()
override val coroutineScope: CoroutineScope = coreContext.coroutineScope
val isDisabled: MutableLiveData<Boolean> 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()
}
}

View file

@ -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}")

View file

@ -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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Activity>()
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()
}
}
}
}
}
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/led_registration_in_progress"/>
<size android:width="15dp" android:height="15dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/led_registered"/>
<size android:width="15dp" android:height="15dp"/>
</shape>

View file

@ -7,6 +7,7 @@
<import type="android.view.View" />
<import type="android.view.Gravity" />
<import type="com.google.android.flexbox.JustifyContent" />
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="contextMenuClickListener"
type="android.view.View.OnLongClickListener" />
@ -75,6 +76,18 @@
android:gravity="center"
android:visibility="@{data.chatMessage.outgoing || selectionListViewModel.isEditionEnabled ? View.GONE : (data.hideAvatar ? View.INVISIBLE : View.VISIBLE)}" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginStart="35dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:contentDescription="@{data.presenceStatus == ConsolidatedPresence.Online ? @string/content_description_presence_online : @string/content_description_presence_offline}"
android:src="@{data.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{data.chatMessage.outgoing || selectionListViewModel.isEditionEnabled || data.hideAvatar || data.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<LinearLayout
android:id="@+id/background"
backgroundImage="@{data.backgroundRes, default=@drawable/chat_bubble_outgoing_full}"

View file

@ -5,9 +5,7 @@
<data>
<import type="android.view.View"/>
<variable
name="titleClickListener"
type="android.view.View.OnClickListener"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="menuClickListener"
type="android.view.View.OnClickListener"/>
@ -77,7 +75,6 @@
android:paddingEnd="5dp">
<org.linphone.views.MarqueeTextView
android:onClick="@{titleClickListener}"
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.name ?? viewModel.displayName) : viewModel.subject, default=`Lorem Ipsum Dolor Sit Amet`}"
style="@style/toolbar_small_title_font"
android:layout_width="wrap_content"
@ -86,17 +83,17 @@
<org.linphone.views.MarqueeTextView
android:text="@{viewModel.participants, default=`John Doe`}"
android:visibility="@{viewModel.oneToOneChatRoom ? View.GONE : View.VISIBLE}"
android:visibility="@{viewModel.oneToOneChatRoom ? View.GONE : View.VISIBLE, default=gone}"
style="@style/toolbar_small_title_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true" />
<org.linphone.views.MarqueeTextView
android:id="@+id/sip_uri"
android:text="@{viewModel.peerSipUri, default=`sip:john.doe@sip.linphone.org`}"
android:visibility="gone"
style="@style/sip_uri_small_font"
android:id="@+id/last_seen_at"
android:text="@{viewModel.lastPresenceInfo, default=`Online`}"
android:visibility="@{viewModel.oneToOneChatRoom &amp;&amp; viewModel.presenceStatus != ConsolidatedPresence.Offline ? View.VISIBLE : View.GONE}"
style="@style/chat_last_seen_at_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true" />

View file

@ -4,6 +4,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="data"
type="org.linphone.activities.main.chat.data.DevicesListGroupData" />
@ -29,6 +30,7 @@
android:layout_centerVertical="true">
<ImageView
android:id="@+id/avatar_icon"
coilContact="@{data}"
android:layout_width="@dimen/contact_avatar_size"
android:layout_height="@dimen/contact_avatar_size"
@ -38,6 +40,18 @@
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar_icon"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:contentDescription="@{data.presenceStatus == ConsolidatedPresence.Online ? @string/content_description_presence_online : @string/content_description_presence_offline}"
android:src="@{data.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{data.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"

View file

@ -4,6 +4,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ChatRoomSecurityLevel"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="removeClickListener"
type="android.view.View.OnClickListener"/>
@ -27,6 +28,7 @@
android:layout_centerVertical="true">
<ImageView
android:id="@+id/avatar_icon"
coilContact="@{data}"
android:layout_width="@dimen/contact_avatar_size"
android:layout_height="@dimen/contact_avatar_size"
@ -36,6 +38,18 @@
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar_icon"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:contentDescription="@{data.presenceStatus == ConsolidatedPresence.Online ? @string/content_description_presence_online : @string/content_description_presence_offline}"
android:src="@{data.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{data.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<ImageView
android:visibility="@{isEncrypted &amp;&amp; data.securityLevel == ChatRoomSecurityLevel.ClearText ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="20dp"

View file

@ -3,6 +3,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="data"
type="org.linphone.activities.main.chat.data.ImdnParticipantData" />
@ -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"/>
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginBottom="-3dp"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:contentDescription="@{data.presenceStatus == ConsolidatedPresence.Online ? @string/content_description_presence_online : @string/content_description_presence_offline}"
android:src="@{data.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{data.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<TextView
android:id="@+id/time"

View file

@ -4,6 +4,7 @@
<data>
<import type="android.view.View" />
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="clickListener"
type="android.view.View.OnClickListener" />
@ -50,6 +51,19 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:contentDescription="@{data.presenceStatus == ConsolidatedPresence.Online ? @string/content_description_presence_online : @string/content_description_presence_offline}"
android:src="@{data.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{data.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}"
app:layout_constraintStart_toStartOf="@id/avatar"
app:layout_constraintBottom_toBottomOf="@id/avatar"/>
<ImageView
android:id="@+id/securityLevel"
android:layout_width="20dp"

View file

@ -5,6 +5,7 @@
<data>
<import type="android.view.View" />
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="deleteClickListener"
type="android.view.View.OnClickListener"/>
@ -82,14 +83,31 @@
android:orientation="vertical"
android:paddingTop="20dp">
<ImageView
android:id="@+id/avatar"
android:layout_width="@dimen/contact_avatar_big_size"
android:layout_height="@dimen/contact_avatar_big_size"
coilContactBig="@{viewModel}"
android:background="@drawable/generated_avatar_bg"
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/avatar"
android:layout_width="@dimen/contact_avatar_big_size"
android:layout_height="@dimen/contact_avatar_big_size"
coilContactBig="@{viewModel}"
android:background="@drawable/generated_avatar_bg"
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<ImageView
android:layout_width="@dimen/contact_presence_big_badge_size"
android:layout_height="@dimen/contact_presence_big_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginStart="70dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:src="@{viewModel.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{viewModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
</RelativeLayout>
<TextView
android:text="@{viewModel.contact.name ?? viewModel.displayName}"

View file

@ -3,6 +3,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="clickListener"
type="android.view.View.OnClickListener"/>
@ -43,6 +44,17 @@
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:src="@{viewModel.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{viewModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<LinearLayout
android:id="@+id/right"
android:layout_width="wrap_content"

View file

@ -3,6 +3,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="clickListener"
type="android.view.View.OnClickListener"/>
@ -31,6 +32,7 @@
android:layout_centerVertical="true">
<ImageView
android:id="@+id/avatar_icon"
coilContact="@{data}"
android:layout_width="@dimen/contact_avatar_size"
android:layout_height="@dimen/contact_avatar_size"
@ -40,6 +42,18 @@
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar_icon"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:contentDescription="@{data.presenceStatus == ConsolidatedPresence.Online ? @string/content_description_presence_online : @string/content_description_presence_offline}"
android:src="@{data.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{data.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<ImageView
android:visibility="@{data.hasLimeX3DHCapability ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="20dp"

View file

@ -5,6 +5,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="newContactClickListener"
type="android.view.View.OnClickListener"/>
@ -83,15 +84,32 @@
android:paddingTop="10dp"
android:paddingBottom="5dp">
<ImageView
android:id="@+id/avatar"
android:layout_marginTop="10dp"
android:layout_width="@dimen/contact_avatar_big_size"
android:layout_height="@dimen/contact_avatar_big_size"
coilContactBig="@{viewModel}"
android:background="@drawable/generated_avatar_bg"
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/avatar"
android:layout_marginTop="10dp"
android:layout_width="@dimen/contact_avatar_big_size"
android:layout_height="@dimen/contact_avatar_big_size"
coilContactBig="@{viewModel}"
android:background="@drawable/generated_avatar_bg"
android:src="@drawable/voip_single_contact_avatar"
android:contentDescription="@null" />
<ImageView
android:layout_width="@dimen/contact_presence_big_badge_size"
android:layout_height="@dimen/contact_presence_big_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginStart="70dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:src="@{viewModel.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{viewModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
</RelativeLayout>
<TextView
android:text="@{viewModel.contact.name ?? viewModel.displayName}"

View file

@ -4,6 +4,7 @@
<data>
<import type="android.view.View" />
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="clickListener"
@ -92,6 +93,17 @@
android:background="@drawable/generated_avatar_bg"
android:src="@drawable/voip_single_contact_avatar" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginStart="25dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:src="@{viewModel.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{viewModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"

View file

@ -127,6 +127,12 @@
linphone:checked="@={viewModel.isDefault}"
linphone:enabled="@{!viewModel.isDefault}"/>
<include
layout="@layout/settings_widget_switch"
linphone:title="@{@string/account_setting_publish_presence_title}"
linphone:listener="@{viewModel.publishPresenceListener}"
linphone:checked="@={viewModel.publishPresence}"/>
<include
layout="@layout/settings_widget_basic"
linphone:title="@{@string/account_settings_link_phone_number_title}"

View file

@ -4,6 +4,7 @@
<data>
<import type="android.view.View"/>
<import type="org.linphone.core.ConsolidatedPresence"/>
<variable
name="selfPictureClickListener"
type="android.view.View.OnClickListener"/>
@ -68,6 +69,17 @@
android:src="@drawable/avatar_border"
android:visibility="@{viewModel.defaultAccountFound ? View.VISIBLE : View.GONE}" />
<ImageView
android:layout_width="@dimen/contact_presence_badge_size"
android:layout_height="@dimen/contact_presence_badge_size"
android:layout_alignBottom="@id/avatar"
android:layout_marginStart="45dp"
android:adjustViewBounds="true"
android:background="@drawable/led_background"
android:padding="2dp"
android:src="@{viewModel.presenceStatus == ConsolidatedPresence.Online ? @drawable/led_online : @drawable/led_away, default=@drawable/led_online}"
android:visibility="@{viewModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE, default=gone}" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"

View file

@ -758,4 +758,11 @@
<string name="call_screenshot_taken">Capture sauvegardée : %s</string>
<string name="chat_message_voice_recording_received_notification">Vous avez reçu un message vocal</string>
<string name="chat_message_voice_recording">Message vocal</string>
<string name="content_description_presence_online">La personne est en ligne</string>
<string name="content_description_presence_offline">La personne est hors ligne</string>
<string name="account_setting_publish_presence_title">Publier la présence</string>
<string name="chat_room_presence_online">En ligne</string>
<string name="chat_room_presence_last_seen_online_today">En ligne aujourd\'hui à</string>
<string name="chat_room_presence_last_seen_online_yesterday">En ligne hier à</string>
<string name="chat_room_presence_last_seen_online">En ligne le</string>
</resources>

View file

@ -82,4 +82,6 @@
<dimen name="mute_mic_grid_margin">10dp</dimen>
<dimen name="chat_message_sending_icons_size">35dp</dimen>
<dimen name="chat_message_sending_icons_margin">5dp</dimen>
<dimen name="contact_presence_badge_size">12dp</dimen>
<dimen name="contact_presence_big_badge_size">25dp</dimen>
</resources>

View file

@ -233,6 +233,10 @@
<string name="chat_message_download_already_in_progress">Please wait for first download to finish before starting a new one</string>
<string name="chat_message_voice_recording_received_notification">You have received a voice message</string>
<string name="chat_message_voice_recording">Voice message</string>
<string name="chat_room_presence_online">Online</string>
<string name="chat_room_presence_last_seen_online_today">Online today at</string>
<string name="chat_room_presence_last_seen_online_yesterday">Online yesterday at</string>
<string name="chat_room_presence_last_seen_online">Online on</string>
<!-- Recordings -->
<string name="recordings_empty_list">No recordings</string>
@ -702,6 +706,7 @@
<string name="account_setting_conference_factory_address">Conference factory URI</string>
<string name="account_setting_audio_video_conference_factory_address">Audio/video conference factory URI</string>
<string name="account_setting_end_to_end_encryption_keys_server_url">E2E encryption keys server URL</string>
<string name="account_setting_publish_presence_title">Publish presence information</string>
<!-- Conferences settings -->
<string name="conferences_settings_layout_title">Default layout</string>
@ -890,4 +895,6 @@
<string name="content_description_toggle_conference_info_details">Toggle meeting information details visibility</string>
<string name="content_description_conference_participants">Group call participants</string>
<string name="content_description_recording_export">Export recording</string>
<string name="content_description_presence_online">User is online</string>
<string name="content_description_presence_offline">User is offline</string>
</resources>

View file

@ -422,6 +422,11 @@
<!-- UI various fonts -->
<style name="chat_last_seen_at_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">?attr/primaryTextColor</item>
<item name="android:textSize">12sp</item>
</style>
<style name="accent_colored_title_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">?attr/accentColor</item>
<item name="android:textAllCaps">true</item>