From 04b5262f8d152b1478b1d4ba06c06138b7b05783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Mon, 11 Nov 2024 09:23:36 -0500 Subject: [PATCH] add connection monitor Gitlab: #1789 Change-Id: Ia74284dbc50401e8b8bddb321f535b8882e7c39d --- .../account/JamiAccountSummaryFragment.kt | 2 +- .../fragments/ConnectionMonitorFragment.kt | 180 ++++++++++++++++++ .../cx/ring/interfaces/AppBarStateListener.kt | 2 +- .../java/cx/ring/settings/SettingsFragment.kt | 16 +- .../baseline_private_connectivity_24.xml | 10 + .../main/res/drawable/baseline_radar_24.xml | 10 + .../baseline_settings_ethernet_24.xml | 12 ++ .../app/src/main/res/drawable/p2p_24.xml | 10 + .../res/layout/frag_connection_monitor.xml | 26 +++ .../app/src/main/res/layout/frag_settings.xml | 45 ++++- .../res/layout/item_device_connection.xml | 67 +++++++ .../src/main/res/layout/item_list_contact.xml | 75 ++++++++ .../main/res/values/strings_preferences.xml | 3 + .../net/jami/services/AccountService.kt | 45 +++++ 14 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt create mode 100644 jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml create mode 100644 jami-android/app/src/main/res/drawable/baseline_radar_24.xml create mode 100644 jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml create mode 100644 jami-android/app/src/main/res/drawable/p2p_24.xml create mode 100644 jami-android/app/src/main/res/layout/frag_connection_monitor.xml create mode 100644 jami-android/app/src/main/res/layout/item_device_connection.xml create mode 100644 jami-android/app/src/main/res/layout/item_list_contact.xml diff --git a/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt b/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt index f9f31ea0c..ebb05e614 100644 --- a/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt @@ -219,7 +219,7 @@ class JamiAccountSummaryFragment : mBinding?.appBar?.setLiftOnScrollTargetView(v) } - override fun onToolbarTitleChanged(title: String) { + override fun onToolbarTitleChanged(title: CharSequence) { mBinding?.toolbar?.title = title } //=============== AppBar management end =================== diff --git a/jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt b/jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt new file mode 100644 index 000000000..d29c7ddba --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2004-2024 Savoir-faire Linux Inc. + * + * 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 <https://www.gnu.org/licenses/>. + */ +package cx.ring.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import cx.ring.R +import cx.ring.databinding.FragConnectionMonitorBinding +import cx.ring.databinding.ItemDeviceConnectionBinding +import cx.ring.databinding.ItemListContactBinding +import cx.ring.interfaces.AppBarStateListener +import cx.ring.views.AvatarDrawable +import dagger.hilt.android.AndroidEntryPoint +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.model.ContactViewModel +import net.jami.services.AccountService +import net.jami.services.AccountService.ConnectionStatus +import net.jami.services.ContactService +import javax.inject.Inject + +@AndroidEntryPoint +class ConnectionMonitorFragment: Fragment() { + + @Inject + lateinit var service: AccountService + @Inject + lateinit var contactService: ContactService + + private var list: RecyclerView? = null + private val disposableBag = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragConnectionMonitorBinding.inflate(inflater, container, false).apply { + root.adapter = ConnectionAdapter() + list = root + }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + (parentFragment as? AppBarStateListener)?.apply { + onToolbarTitleChanged(getText(R.string.pref_connection_monitor)) + onAppBarScrollTargetViewChanged(list) + } + } + + data class DeviceConnectionViewModel( + val contact: ContactViewModel?, + val connection: AccountService.DeviceConnection? + ) + + class ConnectionAdapter(var connections: List<DeviceConnectionViewModel> = emptyList()): RecyclerView.Adapter<ConnectionAdapter.ConnectionViewHolder>() { + class ConnectionViewHolder(val binding: ItemListContactBinding?, val connBinding: ItemDeviceConnectionBinding?): RecyclerView.ViewHolder(binding?.root ?: connBinding!!.root) + + override fun getItemViewType(position: Int): Int = + if (connections[position].contact != null) 0 else 1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConnectionViewHolder = + if (viewType == 0) + ConnectionViewHolder(ItemListContactBinding.inflate(LayoutInflater.from(parent.context), parent, false), null) + else + ConnectionViewHolder(null, ItemDeviceConnectionBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + + override fun onBindViewHolder(holder: ConnectionViewHolder, position: Int) { + holder.connBinding?.let { + val connection = connections[position].connection!! + it.name.text = connection.device + it.device.text = connection.remoteAddress + it.device.isVisible = !connection.remoteAddress.isNullOrEmpty() + it.icon.setImageResource(when (connection.status) { + ConnectionStatus.Waiting -> 0 + ConnectionStatus.Connecting -> R.drawable.baseline_radar_24 + ConnectionStatus.ICE -> R.drawable.p2p_24 + ConnectionStatus.TLS -> R.drawable.baseline_private_connectivity_24 + ConnectionStatus.Connected -> R.drawable.baseline_private_connectivity_24 + }) + it.icon.imageTintList = when (connection.status) { + ConnectionStatus.Connected -> ContextCompat.getColorStateList(it.root.context, R.color.green_500) + else -> ContextCompat.getColorStateList(it.root.context, R.color.icon_color) + } + it.icon.contentDescription = connection.status.toString() + } + holder.binding?.let { + val contact = connections[position].contact!! + it.photo.setAvatar(AvatarDrawable.Builder() + .withContact(contact) + .withCircleCrop(true) + .build(it.root.context)) + it.convParticipant.text = contact.displayName + it.convLastItem.text = contact.displayUri + } + } + + fun setData(newConnections: List<DeviceConnectionViewModel>) { + val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int = connections.size + override fun getNewListSize(): Int = newConnections.size + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = connections[oldItemPosition] + val newItem = newConnections[newItemPosition] + return if (oldItem.contact != null && newItem.contact != null) { + oldItem.contact.contact.uri == newItem.contact.contact.uri + } else { + oldItem.connection?.id == newItem.connection?.id + } + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = connections[oldItemPosition] + val newItem = newConnections[newItemPosition] + return if (oldItem.contact != null && newItem.contact != null) { + oldItem.contact.contact.uri == newItem.contact.contact.uri + && oldItem.contact.displayName == newItem.contact.displayName + } else { + oldItem.connection?.id == newItem.connection?.id + && oldItem.connection?.status == newItem.connection?.status + } + } + + }) + connections = newConnections + diff.dispatchUpdatesTo(this) + } + override fun getItemCount(): Int = connections.size + } + + override fun onStart() { + super.onStart() + disposableBag.add((service.monitorConnections() + .switchMapSingle { (accountId, connections) -> + if (connections.isEmpty()) + Single.just(emptyList()) + else + Single.zip(connections + .map { (peer, connections) -> + contactService.getLoadedContact(accountId, peer).map { Pair(it, connections) } + }) { it.map { it as Pair<ContactViewModel, List<AccountService.DeviceConnection>> } } + } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + val adapter = list?.adapter as? ConnectionAdapter + adapter?.setData(it.map { (contact, connections) -> + val list = ArrayList<DeviceConnectionViewModel>(1 + connections.size) + list.add(DeviceConnectionViewModel(contact, null)) + connections.forEach { connection -> + list.add(DeviceConnectionViewModel(null, connection)) + } + list + }.flatten()) + })) + } + + override fun onStop() { + super.onStop() + disposableBag.clear() + } + +} \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt b/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt index 803e516e2..eea451654 100644 --- a/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt +++ b/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt @@ -4,6 +4,6 @@ import android.view.View /** An interface to be implemented by Activities or Fragments that host {@link com.google.android.material.appbar.AppBarLayout} */ interface AppBarStateListener { - fun onToolbarTitleChanged(title: String) + fun onToolbarTitleChanged(title: CharSequence) fun onAppBarScrollTargetViewChanged(v: View?) } \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt b/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt index 483047a1c..cc6b32a72 100644 --- a/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt @@ -34,6 +34,7 @@ import cx.ring.R import cx.ring.application.JamiApplication import cx.ring.client.LogsActivity import cx.ring.databinding.FragSettingsBinding +import cx.ring.fragments.ConnectionMonitorFragment import cx.ring.interfaces.AppBarStateListener import cx.ring.mvp.BaseSupportFragment import cx.ring.settings.extensionssettings.ExtensionDetails @@ -49,8 +50,6 @@ import net.jami.mvp.GenericView import net.jami.settings.SettingsPresenter import net.jami.settings.SettingsViewModel import net.jami.utils.DonationUtils -import net.jami.utils.DonationUtils.endDonationTimeMillis -import net.jami.utils.DonationUtils.startDonationTimeMillis @AndroidEntryPoint class SettingsFragment : @@ -130,6 +129,17 @@ class SettingsFragment : settingsLogs.setOnClickListener { v: View -> startActivity(Intent(v.context, LogsActivity::class.java)) } + connectionMonitor.setOnClickListener { v: View -> + val content = ConnectionMonitorFragment() + childFragmentManager + .beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(R.id.fragment_container, content, VIDEO_SETTINGS_TAG) + .addToBackStack(VIDEO_SETTINGS_TAG).commit() + fragmentContainer.isVisible = true + donateButton.isVisible = false + backPressedCallback.isEnabled = true + } toolbar.setNavigationOnClickListener { activity?.onBackPressedDispatcher?.onBackPressed() } @@ -293,7 +303,7 @@ class SettingsFragment : binding?.appBar?.setLiftOnScrollTargetView(v) } - override fun onToolbarTitleChanged(title: String) { + override fun onToolbarTitleChanged(title: CharSequence) { binding?.toolbar?.title = title } //=============== AppBar management end =================== diff --git a/jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml b/jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml new file mode 100644 index 000000000..2da477d0d --- /dev/null +++ b/jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,760Q374,760 295.5,691.5Q217,623 203,520L80,520L80,440L203,440Q217,337 295.5,268.5Q374,200 480,200Q586,200 664.5,268.5Q743,337 757,440L880,440L880,520L757,520Q743,623 664.5,691.5Q586,760 480,760ZM400,620L560,620Q577,620 588.5,608.5Q600,597 600,580L600,460Q600,443 588.5,431.5Q577,420 560,420L560,420L560,384Q560,349 537,324.5Q514,300 480,300Q447,300 423.5,323.5Q400,347 400,380L400,420L400,420Q383,420 371.5,431.5Q360,443 360,460L360,580Q360,597 371.5,608.5Q383,620 400,620ZM480,550Q467,550 458.5,541.5Q450,533 450,520Q450,507 458.5,498.5Q467,490 480,490Q493,490 501.5,498.5Q510,507 510,520Q510,533 501.5,541.5Q493,550 480,550ZM440,420L440,380Q440,363 451.5,351.5Q463,340 480,340Q497,340 508.5,351.5Q520,363 520,380L520,420L440,420Z"/> +</vector> diff --git a/jami-android/app/src/main/res/drawable/baseline_radar_24.xml b/jami-android/app/src/main/res/drawable/baseline_radar_24.xml new file mode 100644 index 000000000..a8ed1344e --- /dev/null +++ b/jami-android/app/src/main/res/drawable/baseline_radar_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q536,800 585.5,782.5Q635,765 676,733L619,676Q590,697 554.5,708.5Q519,720 480,720Q380,720 310,650Q240,580 240,480Q240,380 310,310Q380,240 480,240Q580,240 650,310Q720,380 720,480Q720,519 708,555Q696,591 675,620L732,677Q764,636 782,586Q800,536 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,640Q502,640 522.5,634.5Q543,629 561,618L500,557Q495,559 490,559.5Q485,560 480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,486 559.5,491.5Q559,497 557,502L617,562Q628,544 634,523.5Q640,503 640,480Q640,414 593,367Q546,320 480,320Q414,320 367,367Q320,414 320,480Q320,546 367,593Q414,640 480,640Z"/> +</vector> diff --git a/jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml b/jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml new file mode 100644 index 000000000..6309dcbf8 --- /dev/null +++ b/jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2L7,11v2zM17,11h-2v2h2v-2zM11,13h2v-2h-2v2zM17.77,5.48l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z" /> + +</vector> diff --git a/jami-android/app/src/main/res/drawable/p2p_24.xml b/jami-android/app/src/main/res/drawable/p2p_24.xml new file mode 100644 index 000000000..97e5faed4 --- /dev/null +++ b/jami-android/app/src/main/res/drawable/p2p_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,160Q80,127 103.5,103.5Q127,80 160,80L360,80Q393,80 416.5,103.5Q440,127 440,160L440,360L360,360L360,280L160,280L160,600L440,600L440,720Q440,753 416.5,776.5Q393,800 360,800L160,800ZM600,880Q567,880 543.5,856.5Q520,833 520,800L520,600L600,600L600,680L800,680L800,360L520,360L520,240Q520,207 543.5,183.5Q567,160 600,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,800Q880,833 856.5,856.5Q833,880 800,880L600,880ZM320,520Q303,520 291.5,508.5Q280,497 280,480Q280,463 291.5,451.5Q303,440 320,440Q337,440 348.5,451.5Q360,463 360,480Q360,497 348.5,508.5Q337,520 320,520ZM480,520Q463,520 451.5,508.5Q440,497 440,480Q440,463 451.5,451.5Q463,440 480,440Q497,440 508.5,451.5Q520,463 520,480Q520,497 508.5,508.5Q497,520 480,520ZM640,520Q623,520 611.5,508.5Q600,497 600,480Q600,463 611.5,451.5Q623,440 640,440Q657,440 668.5,451.5Q680,463 680,480Q680,497 668.5,508.5Q657,520 640,520Z"/> +</vector> diff --git a/jami-android/app/src/main/res/layout/frag_connection_monitor.xml b/jami-android/app/src/main/res/layout/frag_connection_monitor.xml new file mode 100644 index 000000000..36c1f6f2f --- /dev/null +++ b/jami-android/app/src/main/res/layout/frag_connection_monitor.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2024 Savoir-faire Linux Inc. + +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 <https://www.gnu.org/licenses/>. +--> +<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingVertical="8dp" + android:clipToPadding="false" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/item_device_connection" /> \ No newline at end of file diff --git a/jami-android/app/src/main/res/layout/frag_settings.xml b/jami-android/app/src/main/res/layout/frag_settings.xml index 293617745..84005e942 100644 --- a/jami-android/app/src/main/res/layout/frag_settings.xml +++ b/jami-android/app/src/main/res/layout/frag_settings.xml @@ -757,7 +757,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:id="@+id/system_diagnostics_image" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:contentDescription="@string/pref_persistNotification_summary" + android:contentDescription="@string/pref_logs_title" android:src="@drawable/baseline_article_24" android:layout_alignParentStart="true" android:layout_centerVertical="true" @@ -788,6 +788,49 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </RelativeLayout> + + <RelativeLayout + android:id="@+id/connection_monitor" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/padding_large"> + + <ImageView + android:id="@+id/connection_monitor_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/pref_connection_monitor_summary" + android:src="@drawable/baseline_settings_ethernet_24" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginEnd="32dp"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@+id/connection_monitor_image" + android:orientation="vertical"> + + <TextView + style="@style/ListPrimary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lines="1" + android:text="@string/pref_connection_monitor" /> + + <TextView + style="@style/ListSecondary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/pref_logs_summary" /> + + </LinearLayout> + + </RelativeLayout> + + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/jami-android/app/src/main/res/layout/item_device_connection.xml b/jami-android/app/src/main/res/layout/item_device_connection.xml new file mode 100644 index 000000000..3cea00bf0 --- /dev/null +++ b/jami-android/app/src/main/res/layout/item_device_connection.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2024 Savoir-faire Linux Inc. + +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 <https://www.gnu.org/licenses/>. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="56dp" + android:orientation="horizontal" + android:paddingHorizontal="16dp" + android:paddingVertical="8dp"> + + <ImageView + android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_centerVertical="true" + android:layout_marginStart="8dp" + android:layout_marginEnd="16dp" + android:src="@drawable/baseline_private_connectivity_24" + tools:tint="@color/green_500" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginEnd="8dp" + android:layout_toEndOf="@+id/icon" + android:orientation="vertical"> + + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="middle" + android:fontFamily="monospace" + android:maxLines="1" + android:singleLine="true" + android:textColor="@color/textColorSecondary" + android:textSize="@dimen/text_size_small" + tools:text="@tools:sample/full_names" /> + + <TextView + android:id="@+id/device" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:ellipsize="middle" + android:fontFamily="monospace" + android:maxLines="1" + android:singleLine="true" + android:textSize="@dimen/text_size_small" + tools:text="@tools:sample/us_zipcodes" /> + </LinearLayout> +</RelativeLayout> \ No newline at end of file diff --git a/jami-android/app/src/main/res/layout/item_list_contact.xml b/jami-android/app/src/main/res/layout/item_list_contact.xml new file mode 100644 index 000000000..45ebd8076 --- /dev/null +++ b/jami-android/app/src/main/res/layout/item_list_contact.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2024 Savoir-faire Linux Inc. + +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 <https://www.gnu.org/licenses/>. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="72dp" + android:background="@drawable/background_item_smartlist" + android:foreground="?attr/selectableItemBackground" + android:descendantFocusability="blocksDescendants" + android:paddingLeft="16dp" + android:paddingTop="8dp" + android:paddingRight="16dp" + android:paddingBottom="8dp" + android:layout_marginTop="8dp"> + + <cx.ring.views.AvatarView + android:id="@+id/photo" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_centerVertical="true" + android:layout_marginEnd="16dp" + tools:uri="@tools:sample/us_phones" + tools:avatar="@tools:sample/avatars" /> + + <LinearLayout + android:id="@+id/conv_info" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@+id/photo"> + + <TextView + android:id="@+id/conv_participant" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="middle" + android:gravity="start" + android:singleLine="true" + android:maxLines="1" + android:textAlignment="viewStart" + android:textColor="?attr/colorOnSurface" + android:textSize="@dimen/text_size_medium" + tools:text="@tools:sample/full_names" + android:paddingBottom="3dp"/> + + <TextView + android:id="@+id/conv_last_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="start" + android:maxLines="1" + android:textAlignment="viewStart" + android:textColor="@color/textColorSecondary" + android:textSize="@dimen/text_size_small" + tools:text="Ongoing call of 56 secs" /> + + </LinearLayout> + +</RelativeLayout> diff --git a/jami-android/app/src/main/res/values/strings_preferences.xml b/jami-android/app/src/main/res/values/strings_preferences.xml index 7efd70644..38a3ceea4 100644 --- a/jami-android/app/src/main/res/values/strings_preferences.xml +++ b/jami-android/app/src/main/res/values/strings_preferences.xml @@ -63,6 +63,9 @@ <string name="pref_logs_start">Start Logging</string> <string name="pref_logs_stop">Stop logging</string> + <string name="pref_connection_monitor">Connection monitor</string> + <string name="pref_connection_monitor_summary">Monitor peer to peer connections</string> + <string name="pref_typing_title">Enable typing indicators</string> <string name="pref_typing_summary">Send and receive typing indicators showing that a message is being typed.</string> diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt index 929d33316..87b85915b 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt @@ -1663,6 +1663,51 @@ class AccountService( } } + enum class ConnectionStatus(val value: Int) { + Connected(0), TLS(1), ICE(2), Connecting(3), Waiting(4); + companion object { + fun fromInt(state: Int) = ConnectionStatus.entries[state] + } + } + + data class DeviceConnection( + val accountId: String, + val id: String, + val device: String, + val status: ConnectionStatus, + val peer: String, + val remoteAddress: String? + ) + + fun monitorConnections(): Observable<Pair<String, List<Pair<String, List<DeviceConnection>>>>> = + currentAccountSubject + .switchMap { monitorConnections(it.accountId) } + + private fun monitorConnections(accountId: String): Observable<Pair<String, List<Pair<String, List<DeviceConnection>>>>> = + Observable.interval(0, 1, TimeUnit.SECONDS, scheduler) + .map { _ -> + Pair(accountId, JamiService.getConnectionList(accountId, "") + .mapNotNull { it: Map<String, String?> -> + val status = ConnectionStatus.fromInt(it["status"]?.toInt() ?: 4) + if (status == ConnectionStatus.Waiting || status == ConnectionStatus.Connecting) { + null + } else { + DeviceConnection( + accountId=accountId, + id=it["id"]!!, + device=it["device"]!!, + status=status, + peer=it["peer"]!!, + remoteAddress=it["remoteAddress"] + ) + } + } + .groupBy { it.peer } + .map { Pair(it.key, it.value) } + .sortedBy { it.first } + ) + } + companion object { private val TAG = AccountService::class.java.simpleName private const val VCARD_CHUNK_SIZE = 1000 -- GitLab