diff --git a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt index 531530514..94570dab5 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt +++ b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt @@ -29,6 +29,7 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import java.util.* import org.linphone.R import org.linphone.activities.call.viewmodels.CallsViewModel import org.linphone.activities.call.viewmodels.ControlsViewModel @@ -47,6 +48,8 @@ class ControlsFragment : Fragment() { private lateinit var controlsViewModel: ControlsViewModel private lateinit var sharedViewModel: SharedCallViewModel + private var dialog: Dialog? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -87,7 +90,11 @@ class ControlsFragment : Fragment() { callsViewModel.callUpdateEvent.observe(viewLifecycleOwner, Observer { it.consume { call -> - showCallUpdateDialog(call) + if (call.state == Call.State.StreamsRunning) { + dialog?.dismiss() + } else if (call.state == Call.State.UpdatedByRemote) { + showCallUpdateDialog(call) + } } }) @@ -132,18 +139,18 @@ class ControlsFragment : Fragment() { private fun showCallUpdateDialog(call: Call) { val viewModel = DialogViewModel(AppUtils.getString(R.string.call_video_update_requested_dialog)) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) + dialog = DialogUtils.getDialog(requireContext(), viewModel) viewModel.showCancelButton({ callsViewModel.answerCallUpdateRequest(call, false) - dialog.dismiss() + dialog?.dismiss() }, getString(R.string.dialog_decline)) viewModel.showOkButton({ callsViewModel.answerCallUpdateRequest(call, true) - dialog.dismiss() + dialog?.dismiss() }, getString(R.string.dialog_accept)) - dialog.show() + dialog?.show() } } diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt index afb5048ed..4040ae733 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/CallViewModel.kt @@ -22,6 +22,11 @@ package org.linphone.activities.call.viewmodels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import java.util.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.contact.GenericContactViewModel import org.linphone.core.Call @@ -54,6 +59,8 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd MutableLiveData>() } + private var timer: Timer? = null + private val listener = object : CallListenerStub() { override fun onStateChanged(call: Call, state: Call.State, message: String) { if (call != this@CallViewModel.call) return @@ -61,6 +68,7 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd isPaused.value = state == Call.State.Paused if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { + timer?.cancel() callEndedEvent.value = Event(true) if (state == Call.State.Error) { @@ -68,6 +76,13 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd } } else if (call.state == Call.State.Connected) { callConnectedEvent.value = Event(true) + } else if (call.state == Call.State.StreamsRunning) { + // Stop call update timer once user has accepted or declined call update + timer?.cancel() + } else if (call.state == Call.State.UpdatedByRemote) { + // User has 30 secs to accept or decline call update + // Dialog to accept or decline is handled by CallsViewModel & ControlsFragment + startTimer(call) } } } @@ -103,4 +118,20 @@ open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAd if (call.core.conferenceSize <= 1) call.core.leaveConference() } } + + private fun startTimer(call: Call) { + timer?.cancel() + + timer = Timer("Call update timeout") + timer?.schedule(object : TimerTask() { + override fun run() { + // Decline call update + viewModelScope.launch { + withContext(Dispatchers.Main) { + coreContext.answerCallUpdateRequest(call, false) + } + } + } + }, 30000) + } } diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt index 79c79e056..ed844adf5 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/CallsViewModel.kt @@ -76,17 +76,20 @@ class CallsViewModel : ViewModel() { } else if (state == Call.State.Resuming) { removeCallFromPausedListIfPresent(call) } else if (call.state == Call.State.UpdatedByRemote) { - // If the correspondent proposes video while audio call, + // If the correspondent asks to turn on video while audio call, // defer update until user has chosen whether to accept it or not val remoteVideo = call.remoteParams?.videoEnabled() ?: false val localVideo = call.currentParams.videoEnabled() val autoAccept = call.core.videoActivationPolicy.automaticallyAccept if (remoteVideo && !localVideo && !autoAccept) { call.deferUpdate() - // TODO: start 30 secs timer and decline update if no answer when it triggers callUpdateEvent.value = Event(call) } } else { + if (state == Call.State.StreamsRunning) { + callUpdateEvent.value = Event(call) + } + if (call.conference != null) { addCallToConferenceListIfNotAlreadyInIt(call) } else { @@ -126,16 +129,7 @@ class CallsViewModel : ViewModel() { } fun answerCallUpdateRequest(call: Call, accept: Boolean) { - val core = call.core - val params = core.createCallParams(call) - - if (accept) { - params?.enableVideo(true) - core.enableVideoCapture(true) - core.enableVideoDisplay(true) - } - - call.acceptUpdate(params) + coreContext.answerCallUpdateRequest(call, accept) } fun pauseConference() { diff --git a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt index fcab5db3d..453cc0cba 100644 --- a/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt +++ b/app/src/main/java/org/linphone/activities/call/viewmodels/ControlsFadingViewModel.kt @@ -22,6 +22,10 @@ package org.linphone.activities.call.viewmodels import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.* import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.core.AudioDevice @@ -119,8 +123,12 @@ class ControlsFadingViewModel : ViewModel() { timer = Timer("Hide UI controls scheduler") timer?.schedule(object : TimerTask() { override fun run() { - val videoEnabled = coreContext.isVideoCallOrConferenceActive() - areControlsHidden.postValue(videoEnabled) + viewModelScope.launch { + withContext(Dispatchers.Main) { + val videoEnabled = coreContext.isVideoCallOrConferenceActive() + areControlsHidden.postValue(videoEnabled) + } + } } }, 3000) } diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index dbcbcd451..e3c03c288 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -299,6 +299,18 @@ class CoreContext(val context: Context, coreConfig: Config) { /* Call related functions */ + fun answerCallUpdateRequest(call: Call, accept: Boolean) { + val params = core.createCallParams(call) + + if (accept) { + params?.enableVideo(true) + core.enableVideoCapture(true) + core.enableVideoDisplay(true) + } + + call.acceptUpdate(params) + } + fun answerCall(call: Call) { Log.i("[Context] Answering call $call") val params = core.createCallParams(call)