From 00fbfcc490b880b34138fd7fa5f8556dc04143cc Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 8 Jan 2025 12:59:23 +0100 Subject: [PATCH] Added early media advanced settings + added video views in incoming/outgoing call fragments to see/capture early media video if any --- .../java/org/linphone/core/CorePreferences.kt | 14 +++ .../java/org/linphone/ui/call/CallActivity.kt | 2 +- .../fragment/ActiveConferenceCallFragment.kt | 37 +------ .../ui/call/fragment/ActiveCallFragment.kt | 42 ++------ .../ui/call/fragment/GenericCallFragment.kt | 48 +++++++++ .../ui/call/fragment/IncomingCallFragment.kt | 14 +++ .../ui/call/fragment/OutgoingCallFragment.kt | 27 +++++ .../ui/call/viewmodel/CurrentCallViewModel.kt | 13 ++- .../ui/call/viewmodel/SharedCallViewModel.kt | 4 + .../settings/viewmodel/SettingsViewModel.kt | 24 +++++ .../layout-land/call_incoming_fragment.xml | 98 ++++++++++++------- .../layout-land/call_outgoing_fragment.xml | 33 ++++++- .../res/layout/call_incoming_fragment.xml | 84 +++++++++++----- .../res/layout/call_outgoing_fragment.xml | 29 ++++++ .../res/layout/settings_advanced_fragment.xml | 69 +++++++++++-- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 17 files changed, 398 insertions(+), 144 deletions(-) diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index 20891c000..1c7aecf2c 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -126,6 +126,20 @@ class CorePreferences config.setBool("app", "show_confirmation_dialog_zrtp_trust_call", value) } + @get:WorkerThread @set:WorkerThread + var acceptEarlyMedia: Boolean + get() = config.getBool("sip", "incoming_calls_early_media", false) + set(value) { + config.setBool("sip", "incoming_calls_early_media", value) + } + + @get:WorkerThread @set:WorkerThread + var allowOutgoingEarlyMedia: Boolean + get() = config.getBool("misc", "real_early_media", false) + set(value) { + config.setBool("misc", "real_early_media", value) + } + // Conversation related var markConversationAsReadWhenDismissingMessageNotification: Boolean diff --git a/app/src/main/java/org/linphone/ui/call/CallActivity.kt b/app/src/main/java/org/linphone/ui/call/CallActivity.kt index f9f61f498..0c3a22ab0 100644 --- a/app/src/main/java/org/linphone/ui/call/CallActivity.kt +++ b/app/src/main/java/org/linphone/ui/call/CallActivity.kt @@ -492,7 +492,7 @@ class CallActivity : GenericActivity() { } private fun hideUI(hide: Boolean) { - Log.i("$TAG Switching full screen mode to ${if (hide) "ON" else "OFF"}") + Log.i("$TAG Switching full screen mode to [${if (hide) "ON" else "OFF"}]") val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView) if (hide) { windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) diff --git a/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt b/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt index 7cf980f2a..9b262c046 100644 --- a/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/conference/fragment/ActiveConferenceCallFragment.kt @@ -19,14 +19,12 @@ */ package org.linphone.ui.call.conference.fragment -import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Bundle import android.os.SystemClock import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.activity.OnBackPressedCallback @@ -78,33 +76,6 @@ class ActiveConferenceCallFragment : GenericCallFragment() { override fun onSlide(bottomSheet: View, slideOffset: Float) { } } - // For moving video preview purposes - - private var previewX: Float = 0f - private var previewY: Float = 0f - - private val previewTouchListener = View.OnTouchListener { view, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - previewX = view.x - event.rawX - previewY = view.y - event.rawY - true - } - MotionEvent.ACTION_MOVE -> { - view.animate() - .x(event.rawX + previewX) - .y(event.rawY + previewY) - .setDuration(0) - .start() - true - } - else -> { - view.performClick() - false - } - } - } - private val backPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { val actionsBottomSheetBehavior = BottomSheetBehavior.from(binding.bottomBar.root) @@ -329,23 +300,21 @@ class ActiveConferenceCallFragment : GenericCallFragment() { ) } - @SuppressLint("ClickableViewAccessibility") override fun onResume() { super.onResume() - coreContext.postOnCoreThread { - binding.localPreviewVideoSurface.setOnTouchListener(previewTouchListener) + setupVideoPreview(binding.localPreviewVideoSurface) + coreContext.postOnCoreThread { // Need to be done manually callViewModel.updateCallDuration() } } - @SuppressLint("ClickableViewAccessibility") override fun onPause() { super.onPause() - binding.localPreviewVideoSurface.setOnTouchListener(null) + cleanVideoPreview(binding.localPreviewVideoSurface) } private fun updateHingeRelatedConstraints(feature: FoldingFeature) { 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 8d71e4d56..00ea78e4d 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 @@ -19,12 +19,10 @@ */ package org.linphone.ui.call.fragment -import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle import android.os.SystemClock import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.animation.Animation @@ -67,33 +65,6 @@ class ActiveCallFragment : GenericCallFragment() { private var zrtpSasDialog: Dialog? = null - // For moving video preview purposes - - private var previewX: Float = 0f - private var previewY: Float = 0f - - private val previewTouchListener = View.OnTouchListener { view, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - previewX = view.x - event.rawX - previewY = view.y - event.rawY - true - } - MotionEvent.ACTION_MOVE -> { - view.animate() - .x(event.rawX + previewX) - .y(event.rawY + previewY) - .setDuration(0) - .start() - true - } - else -> { - view.performClick() - false - } - } - } - private val actionsBottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { if (newState == BottomSheetBehavior.STATE_EXPANDED) { @@ -253,7 +224,7 @@ class ActiveCallFragment : GenericCallFragment() { } callViewModel.fullScreenMode.observe(viewLifecycleOwner) { hide -> - Log.i("$TAG Switching full screen mode to ${if (hide) "ON" else "OFF"}") + 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 @@ -420,14 +391,14 @@ class ActiveCallFragment : GenericCallFragment() { ) } - @SuppressLint("ClickableViewAccessibility") override fun onResume() { super.onResume() - coreContext.postOnCoreThread { core -> - core.nativeVideoWindowId = binding.remoteVideoSurface + setupVideoPreview(binding.localPreviewVideoSurface) - binding.localPreviewVideoSurface.setOnTouchListener(previewTouchListener) + coreContext.postOnCoreThread { core -> + Log.i("$TAG Fragment resuming, setting native video window ID") + core.nativeVideoWindowId = binding.remoteVideoSurface // Need to be done manually callViewModel.updateCallDuration() @@ -442,14 +413,13 @@ class ActiveCallFragment : GenericCallFragment() { } } - @SuppressLint("ClickableViewAccessibility") override fun onPause() { super.onPause() zrtpSasDialog?.dismiss() zrtpSasDialog = null - binding.localPreviewVideoSurface.setOnTouchListener(null) + cleanVideoPreview(binding.localPreviewVideoSurface) } private fun updateHingeRelatedConstraints(feature: FoldingFeature) { diff --git a/app/src/main/java/org/linphone/ui/call/fragment/GenericCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/GenericCallFragment.kt index f380d0c30..7a4e3bedb 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/GenericCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/GenericCallFragment.kt @@ -19,11 +19,14 @@ */ package org.linphone.ui.call.fragment +import android.annotation.SuppressLint import android.os.Bundle +import android.view.MotionEvent import android.view.View import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider import org.linphone.ui.GenericFragment +import org.linphone.ui.call.view.RoundCornersTextureView import org.linphone.ui.call.viewmodel.SharedCallViewModel @UiThread @@ -34,6 +37,34 @@ abstract class GenericCallFragment : GenericFragment() { protected lateinit var sharedViewModel: SharedCallViewModel + // For moving video preview purposes + private val videoPreviewTouchListener = View.OnTouchListener { view, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + sharedViewModel.videoPreviewX = view.x - event.rawX + sharedViewModel.videoPreviewY = view.y - event.rawY + true + } + MotionEvent.ACTION_UP -> { + sharedViewModel.videoPreviewX = view.translationX + sharedViewModel.videoPreviewY = view.translationY + true + } + MotionEvent.ACTION_MOVE -> { + view.animate() + .x(event.rawX + sharedViewModel.videoPreviewX) + .y(event.rawY + sharedViewModel.videoPreviewY) + .setDuration(0) + .start() + true + } + else -> { + view.performClick() + false + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -41,4 +72,21 @@ abstract class GenericCallFragment : GenericFragment() { ViewModelProvider(this)[SharedCallViewModel::class.java] } } + + @SuppressLint("ClickableViewAccessibility") + protected fun setupVideoPreview(localPreviewVideoSurface: RoundCornersTextureView) { + // To restore video preview position if possible + localPreviewVideoSurface.animate() + .x(sharedViewModel.videoPreviewX) + .y(sharedViewModel.videoPreviewY) + .setDuration(0) + .start() + + localPreviewVideoSurface.setOnTouchListener(videoPreviewTouchListener) + } + + @SuppressLint("ClickableViewAccessibility") + protected fun cleanVideoPreview(localPreviewVideoSurface: RoundCornersTextureView) { + localPreviewVideoSurface.setOnTouchListener(null) + } } diff --git a/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt index 8dcdcdff1..2f9050bba 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/IncomingCallFragment.kt @@ -26,11 +26,16 @@ import android.view.ViewGroup import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.Log import org.linphone.databinding.CallIncomingFragmentBinding import org.linphone.ui.call.viewmodel.CurrentCallViewModel @UiThread class IncomingCallFragment : GenericCallFragment() { + companion object { + private const val TAG = "[Incoming Call Fragment]" + } + private lateinit var binding: CallIncomingFragmentBinding private lateinit var callViewModel: CurrentCallViewModel @@ -53,6 +58,15 @@ class IncomingCallFragment : GenericCallFragment() { binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel + + callViewModel.isIncomingEarlyMedia.observe(viewLifecycleOwner) { earlyMedia -> + if (earlyMedia) { + coreContext.postOnCoreThread { core -> + Log.i("$TAG Incoming early-media call, setting video surface") + core.nativeVideoWindowId = binding.remoteVideoSurface + } + } + } } override fun onResume() { diff --git a/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt index df952a431..a3d561b5f 100644 --- a/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/call/fragment/OutgoingCallFragment.kt @@ -25,11 +25,17 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.Log import org.linphone.databinding.CallOutgoingFragmentBinding import org.linphone.ui.call.viewmodel.CurrentCallViewModel @UiThread class OutgoingCallFragment : GenericCallFragment() { + companion object { + private const val TAG = "[Outgoing Call Fragment]" + } + private lateinit var binding: CallOutgoingFragmentBinding private lateinit var callViewModel: CurrentCallViewModel @@ -52,5 +58,26 @@ class OutgoingCallFragment : GenericCallFragment() { binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = callViewModel + + callViewModel.isOutgoingEarlyMedia.observe(viewLifecycleOwner) { earlyMedia -> + if (earlyMedia) { + coreContext.postOnCoreThread { core -> + Log.i("$TAG Outgoing early-media call with video, setting preview surface") + core.nativePreviewWindowId = binding.localPreviewVideoSurface + } + } + } + } + + override fun onResume() { + super.onResume() + + setupVideoPreview(binding.localPreviewVideoSurface) + } + + override fun onPause() { + super.onPause() + + cleanVideoPreview(binding.localPreviewVideoSurface) } } 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 92b6c340a..43c4652f5 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 @@ -87,10 +87,14 @@ class CurrentCallViewModel val videoUpdateInProgress = MutableLiveData() + val isIncomingEarlyMedia = MutableLiveData() + val isOutgoing = MutableLiveData() val isOutgoingRinging = MutableLiveData() + val isOutgoingEarlyMedia = MutableLiveData() + val isRecordingEnabled = MutableLiveData() val isRecording = MutableLiveData() @@ -420,6 +424,8 @@ class CurrentCallViewModel message: String ) { isOutgoingRinging.postValue(call.state == Call.State.OutgoingRinging) + isIncomingEarlyMedia.postValue(call.state == Call.State.IncomingEarlyMedia) + isOutgoingEarlyMedia.postValue(call.state == Call.State.OutgoingEarlyMedia) if (::currentCall.isInitialized) { if (call != currentCall) { @@ -1129,10 +1135,13 @@ class CurrentCallViewModel updateOutputAudioDevice(audioDevice) isOutgoing.postValue(call.dir == Call.Dir.Outgoing) - isOutgoingRinging.postValue(call.state == Call.State.OutgoingRinging) + val state = call.state + isOutgoingRinging.postValue(state == Call.State.OutgoingRinging) + isIncomingEarlyMedia.postValue(state == Call.State.IncomingEarlyMedia) + isOutgoingEarlyMedia.postValue(state == Call.State.OutgoingEarlyMedia) isPaused.postValue(isCallPaused()) - isPausedByRemote.postValue(call.state == Call.State.PausedByRemote) + isPausedByRemote.postValue(state == Call.State.PausedByRemote) canBePaused.postValue(canCallBePaused()) val address = call.callLog.remoteAddress diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/SharedCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/SharedCallViewModel.kt index c84e01c82..a551b151f 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/SharedCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/SharedCallViewModel.kt @@ -31,4 +31,8 @@ class SharedCallViewModel val toggleFullScreenEvent = MutableLiveData>() val foldingState = MutableLiveData() + + // For moving video preview purposes + var videoPreviewX: Float = 0f + var videoPreviewY: Float = 0f } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index 53e7bd3ea..3a6b4a819 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -187,6 +187,8 @@ class SettingsViewModel val mediaEncryptionLabels = arrayListOf() private val mediaEncryptionValues = arrayListOf() val mediaEncryptionMandatory = MutableLiveData() + val acceptEarlyMedia = MutableLiveData() + val allowOutgoingEarlyMedia = MutableLiveData() val expandAudioDevices = MutableLiveData() val inputAudioDeviceIndex = MutableLiveData() @@ -671,6 +673,8 @@ class SettingsViewModel } mediaEncryptionMandatory.postValue(core.isMediaEncryptionMandatory) + acceptEarlyMedia.postValue(corePreferences.acceptEarlyMedia) + allowOutgoingEarlyMedia.postValue(corePreferences.allowOutgoingEarlyMedia) } @UiThread @@ -696,6 +700,26 @@ class SettingsViewModel } } + @UiThread + fun toggleAcceptEarlyMedia() { + val newValue = acceptEarlyMedia.value == false + + coreContext.postOnCoreThread { core -> + corePreferences.acceptEarlyMedia = newValue + acceptEarlyMedia.postValue(newValue) + } + } + + @UiThread + fun toggleAllowOutgoingEarlyMedia() { + val newValue = allowOutgoingEarlyMedia.value == false + + coreContext.postOnCoreThread { core -> + corePreferences.allowOutgoingEarlyMedia = newValue + allowOutgoingEarlyMedia.postValue(newValue) + } + } + @UiThread fun updateDeviceName() { coreContext.postOnCoreThread { diff --git a/app/src/main/res/layout-land/call_incoming_fragment.xml b/app/src/main/res/layout-land/call_incoming_fragment.xml index 3a98572aa..26859bbc7 100644 --- a/app/src/main/res/layout-land/call_incoming_fragment.xml +++ b/app/src/main/res/layout-land/call_incoming_fragment.xml @@ -15,44 +15,6 @@ android:layout_height="match_parent" android:background="@color/gray_900"> - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/call_outgoing_fragment.xml b/app/src/main/res/layout-land/call_outgoing_fragment.xml index ccd8e5202..81f9c0de9 100644 --- a/app/src/main/res/layout-land/call_outgoing_fragment.xml +++ b/app/src/main/res/layout-land/call_outgoing_fragment.xml @@ -98,6 +98,21 @@ + + + + - - - - + + + + + + + + + + + + + + + + - + app:layout_constraintEnd_toStartOf="@id/accept_early_media_switch"/> + + + + + + + app:layout_constraintTop_toBottomOf="@id/allow_outgoing_early_media_switch"/> URL du serveur de partage de fichier Chiffrement du média Rendre le chiffrement du média obligatoire + Accepter l\'early media + Autoriser l\'early media pour les appels sortants URL de configuration distante Télécharger & appliquer Périphériques audio diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index afd3668bb..a8d3e036a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -296,6 +296,8 @@ File sharing server URL Media encryption Media encryption mandatory + Accept early media + Allow outgoing early media Remote provisioning URL Download & apply Audio devices