diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
index eb64de6ec..70a35499f 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
@@ -107,6 +107,17 @@ class ActiveCallFragment : GenericCallFragment() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { }
}
+ private val callStatsBottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
+ override fun onStateChanged(bottomSheet: View, newState: Int) {
+ if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
+ val callStatsBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
+ callStatsBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+ }
+ }
+
+ override fun onSlide(bottomSheet: View, slideOffset: Float) { }
+ }
+
private val actionsBottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
@@ -159,6 +170,7 @@ class ActiveCallFragment : GenericCallFragment() {
binding.viewModel = callViewModel
binding.callsViewModel = callsViewModel
binding.numpadModel = callViewModel.numpadModel
+ binding.callStatsModel = callViewModel.callStatsModel
val actionsBottomSheetBehavior = BottomSheetBehavior.from(binding.bottomBar.root)
actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
@@ -168,6 +180,10 @@ class ActiveCallFragment : GenericCallFragment() {
numpadBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
numpadBottomSheetBehavior.addBottomSheetCallback(numpadBottomSheetCallback)
+ val callStatsBottomSheetBehavior = BottomSheetBehavior.from(binding.callStats.root)
+ callStatsBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+ callStatsBottomSheetBehavior.addBottomSheetCallback(callStatsBottomSheetCallback)
+
binding.setBackClickListener {
requireActivity().finish()
}
@@ -183,7 +199,9 @@ class ActiveCallFragment : GenericCallFragment() {
}
binding.setCallStatisticsClickListener {
- showCallStatistics()
+ actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
+ numpadBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
+ callStatsBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
sharedViewModel = requireActivity().run {
@@ -205,6 +223,7 @@ class ActiveCallFragment : GenericCallFragment() {
Log.i("$TAG Switching full screen mode to ${if (hide) "ON" else "OFF"}")
sharedViewModel.toggleFullScreenEvent.value = Event(hide)
numpadBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+ callStatsBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
callViewModel.showZrtpSasDialogEvent.observe(viewLifecycleOwner) {
@@ -371,10 +390,6 @@ class ActiveCallFragment : GenericCallFragment() {
set.applyTo(constraintLayout)
}
- private fun showCallStatistics() {
- // TODO
- }
-
private fun showMediaEncryptionStatistics(model: CallMediaEncryptionModel) {
val modalBottomSheet = MediaEncryptionStatisticsDialogFragment(model)
modalBottomSheet.show(
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/AudioDevicesMenuDialogFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/AudioDevicesMenuDialogFragment.kt
index 73ccc25ff..7816fe9d9 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/AudioDevicesMenuDialogFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/AudioDevicesMenuDialogFragment.kt
@@ -30,7 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.R
-import org.linphone.databinding.CallAudioDevicesMenuBinding
+import org.linphone.databinding.CallAudioDevicesBottomSheetBinding
import org.linphone.ui.call.model.AudioDeviceModel
@UiThread
@@ -68,7 +68,8 @@ class AudioDevicesMenuDialogFragment(
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- val view = CallAudioDevicesMenuBinding.inflate(layoutInflater)
+ val view = CallAudioDevicesBottomSheetBinding.inflate(layoutInflater)
+ view.lifecycleOwner = viewLifecycleOwner
for (device in devicesList) {
device.dismissDialog = {
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt
index 99ec08a8f..18741dc35 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt
@@ -63,6 +63,7 @@ class CallMenuDialogFragment(
savedInstanceState: Bundle?
): View {
val view = CallsListLongPressMenuBinding.inflate(layoutInflater)
+ view.lifecycleOwner = viewLifecycleOwner
view.setHangUpClickListener {
callModel.hangUp()
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/CallStatisticsDialogFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/CallStatisticsDialogFragment.kt
new file mode 100644
index 000000000..d903bc309
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/call/fragment/CallStatisticsDialogFragment.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010-2023 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.ui.call.fragment
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.UiThread
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import org.linphone.databinding.CallStatsBottomSheetBindingImpl
+import org.linphone.ui.call.model.CallStatsModel
+
+@UiThread
+class CallStatisticsDialogFragment(
+ private val model: CallStatsModel,
+ private val onDismiss: (() -> Unit)? = null
+) : BottomSheetDialogFragment() {
+ companion object {
+ const val TAG = "CallStatisticsDialogFragment"
+ }
+
+ override fun onCancel(dialog: DialogInterface) {
+ onDismiss?.invoke()
+ super.onCancel(dialog)
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ onDismiss?.invoke()
+ super.onDismiss(dialog)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
+ // Makes sure all menu entries are visible,
+ // required for landscape mode (otherwise only first item is visible)
+ dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ return dialog
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val view = CallStatsBottomSheetBindingImpl.inflate(layoutInflater)
+ view.lifecycleOwner = viewLifecycleOwner
+
+ view.model = model
+
+ return view.root
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ConferenceLayoutMenuDialogFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ConferenceLayoutMenuDialogFragment.kt
index 8fd179ac4..7ad9736fb 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/ConferenceLayoutMenuDialogFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/ConferenceLayoutMenuDialogFragment.kt
@@ -29,7 +29,7 @@ import androidx.annotation.UiThread
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import org.linphone.databinding.CallConferenceLayoutMenuBinding
+import org.linphone.databinding.CallConferenceLayoutBottomSheetBinding
import org.linphone.ui.call.model.ConferenceModel
@UiThread
@@ -64,7 +64,8 @@ class ConferenceLayoutMenuDialogFragment(
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- val view = CallConferenceLayoutMenuBinding.inflate(layoutInflater)
+ val view = CallConferenceLayoutBottomSheetBinding.inflate(layoutInflater)
+ view.lifecycleOwner = viewLifecycleOwner
view.viewModel = conferenceModel
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/MediaEncryptionStatisticsDialogFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/MediaEncryptionStatisticsDialogFragment.kt
index 110c799b8..ce758eada 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/MediaEncryptionStatisticsDialogFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/MediaEncryptionStatisticsDialogFragment.kt
@@ -29,7 +29,7 @@ import androidx.annotation.UiThread
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import org.linphone.databinding.CallMediaEncryptionStatsMenuBinding
+import org.linphone.databinding.CallMediaEncryptionStatsBottomSheetBinding
import org.linphone.ui.call.model.CallMediaEncryptionModel
@UiThread
@@ -64,7 +64,8 @@ class MediaEncryptionStatisticsDialogFragment(
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- val view = CallMediaEncryptionStatsMenuBinding.inflate(layoutInflater)
+ val view = CallMediaEncryptionStatsBottomSheetBinding.inflate(layoutInflater)
+ view.lifecycleOwner = viewLifecycleOwner
view.model = model
diff --git a/app/src/main/java/org/linphone/ui/call/model/CallStatsModel.kt b/app/src/main/java/org/linphone/ui/call/model/CallStatsModel.kt
new file mode 100644
index 000000000..efd5f9f4b
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/call/model/CallStatsModel.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2010-2023 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.ui.call.model
+
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.MutableLiveData
+import kotlin.math.roundToInt
+import org.linphone.R
+import org.linphone.core.Call
+import org.linphone.core.CallStats
+import org.linphone.core.StreamType
+import org.linphone.utils.AppUtils
+
+class CallStatsModel @WorkerThread constructor() {
+ val audioCodec = MutableLiveData()
+ val audioBandwidth = MutableLiveData()
+
+ val isVideoEnabled = MutableLiveData()
+ val videoCodec = MutableLiveData()
+ val videoBandwidth = MutableLiveData()
+ val videoResolution = MutableLiveData()
+ val videoFps = MutableLiveData()
+
+ @WorkerThread
+ fun update(call: Call, stats: CallStats?) {
+ stats ?: return
+ isVideoEnabled.postValue(call.params.isVideoEnabled)
+
+ when (stats.type) {
+ StreamType.Audio -> {
+ val payloadType = call.currentParams.usedAudioPayloadType
+ val clockRate = (payloadType?.clockRate ?: 0) / 1000
+ val codecLabel = AppUtils.getFormattedString(
+ R.string.call_stats_codec_label,
+ "${payloadType?.mimeType}/$clockRate kHz"
+ )
+ audioCodec.postValue(codecLabel)
+
+ val uploadBandwidth = stats.uploadBandwidth.roundToInt()
+ val downloadBandwidth = stats.downloadBandwidth.roundToInt()
+ val bandwidthLabel = AppUtils.getFormattedString(
+ R.string.call_stats_bandwidth_label,
+ "↑ $uploadBandwidth kbits/s ↓ $downloadBandwidth kbits/s"
+ )
+ audioBandwidth.postValue(bandwidthLabel)
+ }
+ StreamType.Video -> {
+ val payloadType = call.currentParams.usedVideoPayloadType
+ val clockRate = (payloadType?.clockRate ?: 0) / 1000
+ val codecLabel = AppUtils.getFormattedString(
+ R.string.call_stats_codec_label,
+ "${payloadType?.mimeType}/$clockRate kHz"
+ )
+ videoCodec.postValue(codecLabel)
+
+ val uploadBandwidth = stats.uploadBandwidth.roundToInt()
+ val downloadBandwidth = stats.downloadBandwidth.roundToInt()
+ val bandwidthLabel = AppUtils.getFormattedString(
+ R.string.call_stats_bandwidth_label,
+ "↑ $uploadBandwidth kbits/s ↓ $downloadBandwidth kbits/s"
+ )
+ videoBandwidth.postValue(bandwidthLabel)
+
+ val sentResolution = call.currentParams.sentVideoDefinition?.name
+ val receivedResolution = call.currentParams.receivedVideoDefinition?.name
+ val resolutionLabel = AppUtils.getFormattedString(
+ R.string.call_stats_resolution_label,
+ "↑ $sentResolution ↓ $receivedResolution"
+ )
+ videoResolution.postValue(resolutionLabel)
+
+ val sentFps = call.currentParams.sentFramerate.roundToInt()
+ val receivedFps = call.currentParams.receivedFramerate.roundToInt()
+ val fpsLabel = AppUtils.getFormattedString(
+ R.string.call_stats_fps_label,
+ "↑ $sentFps ↓ $receivedFps"
+ )
+ videoFps.postValue(fpsLabel)
+ }
+ else -> {}
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantDeviceModel.kt b/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantDeviceModel.kt
index e79c91e08..1a6600838 100644
--- a/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantDeviceModel.kt
+++ b/app/src/main/java/org/linphone/ui/call/model/ConferenceParticipantDeviceModel.kt
@@ -116,7 +116,7 @@ class ConferenceParticipantDeviceModel @WorkerThread constructor(
if (streamType == StreamType.Video) {
isVideoAvailable.postValue(available)
if (available) {
- updateWindowId(textureView)
+ updateWindowId()
}
}
}
@@ -182,20 +182,26 @@ class ConferenceParticipantDeviceModel @WorkerThread constructor(
)
textureView = view
coreContext.postOnCoreThread {
- updateWindowId(textureView)
+ updateWindowId()
}
}
@WorkerThread
- private fun updateWindowId(windowId: Any?) {
- Log.i(
- "$$TAG Setting participant [${device.address.asStringUriOnly()}] window ID [$windowId]"
- )
- // SDK does it but it's a bit better this way, prevents going to participants map in PlatformHelper for nothing
- if (isMe) {
- coreContext.core.nativePreviewWindowId = windowId
+ private fun updateWindowId() {
+ if (::textureView.isInitialized) {
+ Log.i(
+ "$$TAG Setting participant [${device.address.asStringUriOnly()}] window ID [$textureView]"
+ )
+ // SDK does it but it's a bit better this way, prevents going to participants map in PlatformHelper for nothing
+ if (isMe) {
+ coreContext.core.nativePreviewWindowId = textureView
+ } else {
+ device.nativeVideoWindowId = textureView
+ }
} else {
- device.nativeVideoWindowId = windowId
+ Log.e(
+ "$TAG TextureView for participant [${device.address.asStringUriOnly()}] wasn't initialized yet!"
+ )
}
}
}
diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt
index 02e1dc8df..7d8e9e7b7 100644
--- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt
@@ -40,6 +40,7 @@ import org.linphone.core.Address
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
+import org.linphone.core.CallStats
import org.linphone.core.ChatRoom.SecurityLevel
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
@@ -49,6 +50,7 @@ import org.linphone.core.StreamType
import org.linphone.core.tools.Log
import org.linphone.ui.call.model.AudioDeviceModel
import org.linphone.ui.call.model.CallMediaEncryptionModel
+import org.linphone.ui.call.model.CallStatsModel
import org.linphone.ui.call.model.ConferenceModel
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.ui.main.history.model.NumpadModel
@@ -102,6 +104,8 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
val hideVideo = MutableLiveData()
+ val callStatsModel = CallStatsModel()
+
val incomingCallTitle: MutableLiveData by lazy {
MutableLiveData()
}
@@ -198,6 +202,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
isRemoteRecordingEvent.postValue(Event(Pair(recording, displayedName.value.orEmpty())))
}
+ override fun onStatsUpdated(call: Call, stats: CallStats) {
+ callStatsModel.update(call, stats)
+ }
+
@WorkerThread
override fun onStateChanged(call: Call, state: Call.State, message: String) {
Log.i("$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]")
@@ -768,6 +776,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
terminatedByUsed = false
currentCall = call
+ callStatsModel.update(call, call.audioStats)
call.addListener(callListener)
if (call.conference != null) {
diff --git a/app/src/main/res/color/in_call_button_color.xml b/app/src/main/res/color/in_call_button_color.xml
new file mode 100644
index 000000000..cfea5c512
--- /dev/null
+++ b/app/src/main/res/color/in_call_button_color.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_squircle_main2_400_border.xml b/app/src/main/res/drawable/shape_squircle_in_call_button_background.xml
similarity index 57%
rename from app/src/main/res/drawable/shape_squircle_main2_400_border.xml
rename to app/src/main/res/drawable/shape_squircle_in_call_button_background.xml
index a08105337..16c67b6ca 100644
--- a/app/src/main/res/drawable/shape_squircle_main2_400_border.xml
+++ b/app/src/main/res/drawable/shape_squircle_in_call_button_background.xml
@@ -1,5 +1,6 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/call_media_encryption_stats_menu.xml b/app/src/main/res/layout-land/call_media_encryption_stats_bottom_sheet.xml
similarity index 100%
rename from app/src/main/res/layout-land/call_media_encryption_stats_menu.xml
rename to app/src/main/res/layout-land/call_media_encryption_stats_bottom_sheet.xml
diff --git a/app/src/main/res/layout-land/call_stats_bottom_sheet.xml b/app/src/main/res/layout-land/call_stats_bottom_sheet.xml
new file mode 100644
index 000000000..31c565e8d
--- /dev/null
+++ b/app/src/main/res/layout-land/call_stats_bottom_sheet.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/call_active_conference_fragment.xml b/app/src/main/res/layout/call_active_conference_fragment.xml
index 0780301f2..4eab9a486 100644
--- a/app/src/main/res/layout/call_active_conference_fragment.xml
+++ b/app/src/main/res/layout/call_active_conference_fragment.xml
@@ -220,7 +220,7 @@
android:paddingBottom="12dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
- android:background="@drawable/shape_squircle_main2_400_border"
+ android:background="@drawable/shape_squircle_in_call_button_background"
android:text="@string/conference_share_link_title"
android:textSize="18sp"
android:textColor="@color/gray_main2_400"
diff --git a/app/src/main/res/layout/call_active_fragment.xml b/app/src/main/res/layout/call_active_fragment.xml
index fc4858fce..6e5615985 100644
--- a/app/src/main/res/layout/call_active_fragment.xml
+++ b/app/src/main/res/layout/call_active_fragment.xml
@@ -27,6 +27,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/call_audio_devices_menu.xml b/app/src/main/res/layout/call_audio_devices_bottom_sheet.xml
similarity index 100%
rename from app/src/main/res/layout/call_audio_devices_menu.xml
rename to app/src/main/res/layout/call_audio_devices_bottom_sheet.xml
diff --git a/app/src/main/res/layout/call_conference_layout_menu.xml b/app/src/main/res/layout/call_conference_layout_bottom_sheet.xml
similarity index 100%
rename from app/src/main/res/layout/call_conference_layout_menu.xml
rename to app/src/main/res/layout/call_conference_layout_bottom_sheet.xml
diff --git a/app/src/main/res/layout/call_media_encryption_stats_menu.xml b/app/src/main/res/layout/call_media_encryption_stats_bottom_sheet.xml
similarity index 100%
rename from app/src/main/res/layout/call_media_encryption_stats_menu.xml
rename to app/src/main/res/layout/call_media_encryption_stats_bottom_sheet.xml
diff --git a/app/src/main/res/layout/call_stats_bottom_sheet.xml b/app/src/main/res/layout/call_stats_bottom_sheet.xml
new file mode 100644
index 000000000..12f86f295
--- /dev/null
+++ b/app/src/main/res/layout/call_stats_bottom_sheet.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 b6fdd19b8..b984c22ab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -548,6 +548,13 @@
Headset
Headphones
+ Audio
+ Codec: %s
+ Bandwidth: %s
+ Video
+ Resolution: %s
+ FPS: %s
+
Media encryption: %s
Post Quantum ZRTP
Cipher algorithm: %s