From b185de70ca1e38c53cfac723f36db8f2066c3d47 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 19 Jan 2024 15:03:21 +0100 Subject: [PATCH] Added media encryption statistics when long clicking media encryption icon on top of active call --- .../ui/call/fragment/ActiveCallFragment.kt | 30 +++++ ...MediaEncryptionStatisticsDialogFragment.kt | 73 ++++++++++++ .../ui/call/model/CallMediaEncryptionModel.kt | 107 ++++++++++++++++++ .../ui/call/viewmodel/CurrentCallViewModel.kt | 15 +++ app/src/main/res/drawable/chart_bar.xml | 9 ++ .../layout-land/call_actions_bottom_sheet.xml | 42 +++++-- .../res/layout/call_actions_bottom_sheet.xml | 31 +++++ .../main/res/layout/call_active_fragment.xml | 7 +- .../call_media_encryption_stats_menu.xml | 83 ++++++++++++++ app/src/main/res/values/strings.xml | 9 ++ 10 files changed, 398 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/call/fragment/MediaEncryptionStatisticsDialogFragment.kt create mode 100644 app/src/main/java/org/linphone/ui/call/model/CallMediaEncryptionModel.kt create mode 100644 app/src/main/res/drawable/chart_bar.xml create mode 100644 app/src/main/res/layout/call_media_encryption_stats_menu.xml 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 20b48aecf..429789fe7 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 @@ -35,11 +35,13 @@ import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController import androidx.window.layout.FoldingFeature import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.CallActiveFragmentBinding import org.linphone.ui.call.CallActivity +import org.linphone.ui.call.model.CallMediaEncryptionModel import org.linphone.ui.call.model.ZrtpSasConfirmationDialogModel import org.linphone.ui.call.viewmodel.CallsViewModel import org.linphone.ui.call.viewmodel.CurrentCallViewModel @@ -63,6 +65,8 @@ class ActiveCallFragment : GenericCallFragment() { private var zrtpSasDialog: Dialog? = null + private var bottomSheetDialog: BottomSheetDialogFragment? = null + // For moving video preview purposes private var previewX: Float = 0f @@ -152,6 +156,10 @@ class ActiveCallFragment : GenericCallFragment() { findNavController().navigate(action) } + binding.setCallStatisticsClickListener { + showCallStatistics() + } + sharedViewModel = requireActivity().run { ViewModelProvider(this)[SharedCallViewModel::class.java] } @@ -272,6 +280,12 @@ class ActiveCallFragment : GenericCallFragment() { } } } + + callViewModel.showMediaEncryptionStatisticsEvent.observe(viewLifecycleOwner) { + it.consume { model -> + showMediaEncryptionStatistics(model) + } + } } @SuppressLint("ClickableViewAccessibility") @@ -295,6 +309,9 @@ class ActiveCallFragment : GenericCallFragment() { zrtpSasDialog?.dismiss() zrtpSasDialog = null + bottomSheetDialog?.dismiss() + bottomSheetDialog = null + binding.localPreviewVideoSurface.setOnTouchListener(null) } @@ -317,4 +334,17 @@ class ActiveCallFragment : GenericCallFragment() { set.applyTo(constraintLayout) } + + private fun showCallStatistics() { + // TODO + } + + private fun showMediaEncryptionStatistics(model: CallMediaEncryptionModel) { + val modalBottomSheet = MediaEncryptionStatisticsDialogFragment(model) + modalBottomSheet.show( + requireActivity().supportFragmentManager, + MediaEncryptionStatisticsDialogFragment.TAG + ) + bottomSheetDialog = modalBottomSheet + } } 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 new file mode 100644 index 000000000..110c799b8 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/call/fragment/MediaEncryptionStatisticsDialogFragment.kt @@ -0,0 +1,73 @@ +/* + * 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.CallMediaEncryptionStatsMenuBinding +import org.linphone.ui.call.model.CallMediaEncryptionModel + +@UiThread +class MediaEncryptionStatisticsDialogFragment( + private val model: CallMediaEncryptionModel, + private val onDismiss: (() -> Unit)? = null +) : BottomSheetDialogFragment() { + companion object { + const val TAG = "MediaEncryptionStatisticsDialogFragment" + } + + 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 = CallMediaEncryptionStatsMenuBinding.inflate(layoutInflater) + + view.model = model + + return view.root + } +} diff --git a/app/src/main/java/org/linphone/ui/call/model/CallMediaEncryptionModel.kt b/app/src/main/java/org/linphone/ui/call/model/CallMediaEncryptionModel.kt new file mode 100644 index 000000000..81003a79f --- /dev/null +++ b/app/src/main/java/org/linphone/ui/call/model/CallMediaEncryptionModel.kt @@ -0,0 +1,107 @@ +/* + * 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 org.linphone.R +import org.linphone.core.Call +import org.linphone.core.MediaEncryption +import org.linphone.core.StreamType +import org.linphone.utils.AppUtils + +class CallMediaEncryptionModel @WorkerThread constructor(call: Call) { + val mediaEncryption = MutableLiveData() + + val isMediaEncryptionZrtp = MutableLiveData() + val zrtpCipher = MutableLiveData() + val zrtpKeyAgreement = MutableLiveData() + val zrtpHash = MutableLiveData() + val zrtpAuthTag = MutableLiveData() + val zrtpAuthSas = MutableLiveData() + + init { + isMediaEncryptionZrtp.postValue(false) + + val stats = call.getStats(StreamType.Audio) + if (stats != null) { + // ZRTP stats are only available when authentication token isn't null ! + if (call.currentParams.mediaEncryption == MediaEncryption.ZRTP && call.authenticationToken != null) { + isMediaEncryptionZrtp.postValue(true) + + if (stats.isZrtpKeyAgreementAlgoPostQuantum) { + mediaEncryption.postValue( + AppUtils.getFormattedString( + R.string.call_stats_media_encryption, + AppUtils.getString( + R.string.call_stats_media_encryption_zrtp_post_quantum + ) + ) + ) + } else { + mediaEncryption.postValue( + AppUtils.getFormattedString( + R.string.call_stats_media_encryption, + call.currentParams.mediaEncryption.name + ) + ) + } + + zrtpCipher.postValue( + AppUtils.getFormattedString( + R.string.call_stats_zrtp_cipher_algo, + stats.zrtpCipherAlgo + ) + ) + zrtpKeyAgreement.postValue( + AppUtils.getFormattedString( + R.string.call_stats_zrtp_key_agreement_algo, + stats.zrtpKeyAgreementAlgo + ) + ) + zrtpHash.postValue( + AppUtils.getFormattedString( + R.string.call_stats_zrtp_hash_algo, + stats.zrtpHashAlgo + ) + ) + zrtpAuthTag.postValue( + AppUtils.getFormattedString( + R.string.call_stats_zrtp_auth_tag_algo, + stats.zrtpAuthTagAlgo + ) + ) + zrtpAuthSas.postValue( + AppUtils.getFormattedString( + R.string.call_stats_zrtp_sas_algo, + stats.zrtpSasAlgo + ) + ) + } else { + mediaEncryption.postValue( + AppUtils.getFormattedString( + R.string.call_stats_media_encryption, + call.currentParams.mediaEncryption.name + ) + ) + } + } + } +} 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 9d2c6cd8a..72f361729 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 @@ -41,6 +41,7 @@ import org.linphone.core.MediaDirection import org.linphone.core.MediaEncryption 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.ConferenceModel import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.ui.main.history.model.NumpadModel @@ -135,6 +136,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() { MutableLiveData>>() } + val showMediaEncryptionStatisticsEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + // Conference val conferenceModel = ConferenceModel() @@ -652,6 +657,16 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() { } } + @UiThread + fun showMediaEncryptionStatisticsIfPossible(): Boolean { + coreContext.postOnCoreThread { + val model = CallMediaEncryptionModel(currentCall) + showMediaEncryptionStatisticsEvent.postValue(Event(model)) + } + + return true + } + @WorkerThread private fun showZrtpSasDialog(authToken: String) { val upperCaseAuthToken = authToken.uppercase(Locale.getDefault()) diff --git a/app/src/main/res/drawable/chart_bar.xml b/app/src/main/res/drawable/chart_bar.xml new file mode 100644 index 000000000..6916f393e --- /dev/null +++ b/app/src/main/res/drawable/chart_bar.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-land/call_actions_bottom_sheet.xml b/app/src/main/res/layout-land/call_actions_bottom_sheet.xml index cd38f9ef3..12e721cff 100644 --- a/app/src/main/res/layout-land/call_actions_bottom_sheet.xml +++ b/app/src/main/res/layout-land/call_actions_bottom_sheet.xml @@ -12,7 +12,7 @@ name="callsListClickListener" type="View.OnClickListener" /> + + @@ -212,7 +227,7 @@ android:layout_height="wrap_content" android:paddingBottom="15dp" android:text="@string/call_action_show_dialer" - app:layout_constraintEnd_toStartOf="@id/chat_label" + app:layout_constraintEnd_toStartOf="@id/stats_label" app:layout_constraintStart_toEndOf="@id/calls_list_label" app:layout_constraintTop_toBottomOf="@id/numpad" /> @@ -223,10 +238,23 @@ android:layout_height="wrap_content" android:paddingBottom="15dp" android:text="@string/call_action_show_messages" + android:visibility="gone" app:layout_constraintEnd_toStartOf="@id/pause_call_label" app:layout_constraintStart_toEndOf="@id/numpad_label" app:layout_constraintTop_toBottomOf="@id/chat" /> + + + @@ -123,12 +126,28 @@ android:padding="@dimen/call_button_icon_padding" android:src="@drawable/chat_teardrop_text" android:background="@drawable/shape_round_in_call_disabled_button_background" + android:visibility="gone" app:tint="?attr/color_grey_500" app:layout_constraintDimensionRatio="1:1" app:layout_constraintTop_toBottomOf="@id/transfer_label" app:layout_constraintStart_toStartOf="@id/transfer" app:layout_constraintEnd_toEndOf="@id/transfer"/> + + + + + @@ -195,6 +198,7 @@ + bind:callsListClickListener="@{callsListClickListener}" + bind:callStatisticsClickListener="@{callStatisticsClickListener}"/> + + + + + + + + + + + + + + + + + + + + + + + + \ 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 302e858ac..83f76f09c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -498,6 +498,7 @@ Calls list Dialer Messages + Statistics Pause Resume Record @@ -524,6 +525,14 @@ Headset Headphones + Media encryption: %s + Post Quantum ZRTP + Cipher algorithm: %s + Key agreement algorithm: %s + Hash algorithm: %s + Authentication algorithm: %s + SAS algorithm: %s + Share invitation Waiting for other participants… Screen share