Added pause/resume action to long press menu in calls list + fixing issues with multi calls

This commit is contained in:
Sylvain Berfini 2023-09-25 11:54:19 +02:00
parent 51179c083c
commit a60c66ad33
16 changed files with 330 additions and 152 deletions

View file

@ -47,7 +47,6 @@ import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.model.isInSecureMode
import org.linphone.utils.ImageUtils
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.PhoneNumberUtils

View file

@ -74,7 +74,15 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
Log.i("$TAG Global state changed: $state")
Log.i("$TAG Global state changed [$state]")
}
override fun onConfiguringStatus(
core: Core,
status: Config.ConfiguringState?,
message: String?
) {
Log.i("$TAG Configuring state changed [$status]")
}
@WorkerThread

View file

@ -64,12 +64,22 @@ class CorePreferences @UiThread constructor(private val context: Context) {
// Calls settings
@get:WorkerThread @set:WorkerThread
var routeAudioToBluetoothIfAvailable: Boolean
get() = config.getBool("app", "route_audio_to_bluetooth_if_available", true)
set(value) {
config.setBool("app", "route_audio_to_bluetooth_if_available", value)
}
// This won't be done if bluetooth or wired headset is used
@get:WorkerThread @set:WorkerThread
var routeAudioToSpeakerWhenVideoIsEnabled: Boolean
get() = config.getBool("app", "route_audio_to_speaker_when_video_enabled", true)
set(value) {
config.setBool("app", "route_audio_to_speaker_when_video_enabled", value)
}
@get:WorkerThread @set:WorkerThread
var automaticallyStartCallRecording: Boolean
get() = config.getBool("app", "auto_start_call_record", false)
set(value) {
@ -105,9 +115,11 @@ class CorePreferences @UiThread constructor(private val context: Context) {
val thirdPartyDefaultValuesPath: String
get() = context.filesDir.absolutePath + "/assistant_third_party_default_values"
@get:AnyThread
private val ringtonesPath: String
get() = context.filesDir.absolutePath + "/share/sounds/linphone/rings/"
@get:AnyThread
val defaultRingtonePath: String
get() = ringtonesPath + "notes_of_the_optimistic.mkv"

View file

@ -25,8 +25,8 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Call
import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils
class NotificationBroadcastReceiver : BroadcastReceiver() {
companion object {
@ -61,9 +61,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
if (intent.action == NotificationsManager.INTENT_ANSWER_CALL_NOTIF_ACTION) {
coreContext.answerCall(call)
} else {
if (call.state == Call.State.IncomingReceived ||
call.state == Call.State.IncomingEarlyMedia
) {
if (LinphoneUtils.isCallIncoming(call.state)) {
coreContext.declineCall(call)
} else {
coreContext.terminateCall(call)

View file

@ -61,7 +61,6 @@ import org.linphone.core.Friend
import org.linphone.core.tools.Log
import org.linphone.ui.call.CallActivity
import org.linphone.utils.AppUtils
import org.linphone.utils.ImageUtils
import org.linphone.utils.LinphoneUtils
class NotificationsManager @MainThread constructor(private val context: Context) {

View file

@ -35,6 +35,7 @@ import org.linphone.core.Call
import org.linphone.core.CallListenerStub
import org.linphone.core.tools.Log
import org.linphone.utils.AudioRouteUtils
import org.linphone.utils.LinphoneUtils
class TelecomCallControlCallback constructor(
private val call: Call,
@ -207,7 +208,7 @@ class TelecomCallControlCallback constructor(
override suspend fun onAnswer(callType: Int): Boolean {
Log.i("$TAG We're asked to answer the call with type [$callType]")
coreContext.postOnCoreThread {
if (call.state == Call.State.IncomingReceived || call.state == Call.State.IncomingEarlyMedia) {
if (LinphoneUtils.isCallIncoming(call.state)) {
Log.i("$TAG Answering call")
coreContext.answerCall(call) // TODO: use call type
}

View file

@ -105,6 +105,40 @@ class CallActivity : AppCompatActivity() {
}
}
callViewModel.showLowWifiSignalEvent.observe(this) {
it.consume { show ->
if (show) {
showRedToast(
getString(R.string.toast_alert_low_wifi_signal),
R.drawable.wifi_low
)
} else {
hideRedToast()
showGreenToast(
getString(R.string.toast_alert_low_wifi_signal_cleared),
R.drawable.wifi_high
)
}
}
}
callViewModel.showLowCellularSignalEvent.observe(this) {
it.consume { show ->
if (show) {
showRedToast(
getString(R.string.toast_alert_low_cellular_signal),
R.drawable.cell_signal_low
)
} else {
hideRedToast()
showGreenToast(
getString(R.string.toast_alert_low_cellular_signal_cleared),
R.drawable.cell_signal_full
)
}
}
}
callsViewModel.showIncomingCallEvent.observe(this) {
it.consume {
val action = IncomingCallFragmentDirections.actionGlobalIncomingCallFragment()
@ -143,40 +177,6 @@ class CallActivity : AppCompatActivity() {
}
}
callsViewModel.showLowWifiSignalEvent.observe(this) {
it.consume { show ->
if (show) {
showRedToast(
getString(R.string.toast_alert_low_wifi_signal),
R.drawable.wifi_low
)
} else {
hideRedToast()
showGreenToast(
getString(R.string.toast_alert_low_wifi_signal_cleared),
R.drawable.wifi_high
)
}
}
}
callsViewModel.showLowCellularSignalEvent.observe(this) {
it.consume { show ->
if (show) {
showRedToast(
getString(R.string.toast_alert_low_cellular_signal),
R.drawable.cell_signal_low
)
} else {
hideRedToast()
showGreenToast(
getString(R.string.toast_alert_low_cellular_signal_cleared),
R.drawable.cell_signal_full
)
}
}
}
sharedViewModel.toggleFullScreenEvent.observe(this) {
it.consume { hide ->
hideUI(hide)

View file

@ -20,6 +20,7 @@
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
@ -61,6 +62,8 @@ class ActiveCallFragment : GenericCallFragment() {
private lateinit var callsViewModel: CallsViewModel
private var zrtpSasDialog: Dialog? = null
// For moving video preview purposes
private var previewX: Float = 0f
@ -181,6 +184,7 @@ class ActiveCallFragment : GenericCallFragment() {
}
dialog.show()
zrtpSasDialog = dialog
}
}
@ -253,6 +257,10 @@ class ActiveCallFragment : GenericCallFragment() {
@SuppressLint("ClickableViewAccessibility")
override fun onPause() {
super.onPause()
zrtpSasDialog?.dismiss()
zrtpSasDialog = null
binding.localPreviewVideoSurface.setOnTouchListener(null)
}

View file

@ -58,6 +58,13 @@ class CallMenuDialogFragment(
dismiss()
}
view.setPauseResumeClickListener {
callModel.togglePauseResume()
dismiss()
}
view.isPaused = callModel.isPaused.value == true
return view.root
}
}

View file

@ -24,23 +24,17 @@ import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Alert
import org.linphone.core.AlertListenerStub
import org.linphone.core.Call
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.tools.Log
import org.linphone.ui.call.model.CallModel
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class CallsViewModel @UiThread constructor() : ViewModel() {
companion object {
private const val TAG = "[Calls ViewModel]"
// Keys are hardcoded in SDK
private const val ALERT_NETWORK_TYPE_KEY = "network-type"
private const val ALERT_NETWORK_TYPE_WIFI = "wifi"
private const val ALERT_NETWORK_TYPE_CELLULAR = "mobile"
}
val calls = MutableLiveData<ArrayList<CallModel>>()
@ -55,37 +49,6 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
val noMoreCallEvent = MutableLiveData<Event<Boolean>>()
val showLowWifiSignalEvent = MutableLiveData<Event<Boolean>>()
val showLowCellularSignalEvent = MutableLiveData<Event<Boolean>>()
private val alertListener = object : AlertListenerStub() {
@WorkerThread
override fun onTerminated(alert: Alert) {
val remote = alert.call.remoteAddress.asStringUriOnly()
Log.w("$TAG Alert of type [${alert.type}] dismissed for call from [$remote]")
alert.removeListener(this)
if (alert.type == Alert.Type.QoSLowSignal) {
when (val signalType = alert.informations?.getString(ALERT_NETWORK_TYPE_KEY)) {
ALERT_NETWORK_TYPE_WIFI -> {
Log.i("$TAG Wi-Fi signal no longer low")
showLowWifiSignalEvent.postValue(Event(false))
}
ALERT_NETWORK_TYPE_CELLULAR -> {
Log.i("$TAG Cellular signal no longer low")
showLowCellularSignalEvent.postValue(Event(false))
}
else -> {
Log.w(
"$TAG Unexpected type of signal [$signalType] found in alert information"
)
}
}
}
}
}
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onLastCallEnded(core: Core) {
@ -100,6 +63,8 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
state: Call.State,
message: String
) {
Log.i("$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]")
// Update calls list if needed
val found = calls.value.orEmpty().find {
it.call == call
@ -125,10 +90,12 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
list.addAll(calls.value.orEmpty())
list.remove(found)
calls.postValue(list)
callsCount.postValue(list.size)
found.destroy()
}
}
// Update currently displayed fragment according to call state
if (call == core.currentCall || core.currentCall == null) {
Log.i(
"$TAG Current call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]"
@ -141,28 +108,20 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
}
}
}
}
@WorkerThread
override fun onNewAlertTriggered(core: Core, alert: Alert) {
val remote = alert.call.remoteAddress.asStringUriOnly()
Log.w("$TAG Alert of type [${alert.type}] triggered for call from [$remote]")
alert.addListener(alertListener)
if (alert.type == Alert.Type.QoSLowSignal) {
when (val networkType = alert.informations?.getString(ALERT_NETWORK_TYPE_KEY)) {
ALERT_NETWORK_TYPE_WIFI -> {
Log.i("$TAG Triggered low signal alert is for Wi-Fi")
showLowWifiSignalEvent.postValue(Event(true))
}
ALERT_NETWORK_TYPE_CELLULAR -> {
Log.i("$TAG Triggered low signal alert is for cellular")
showLowCellularSignalEvent.postValue(Event(true))
}
else -> {
Log.w(
"$TAG Unexpected type of signal [$networkType] found in alert information"
)
if (LinphoneUtils.isCallIncoming(call.state)) {
Log.i("$TAG Asking activity to show incoming call fragment")
showIncomingCallEvent.postValue(Event(true))
} else if (LinphoneUtils.isCallEnding(call.state)) {
if (core.callsNb > 0) {
val newCurrentCall = core.currentCall ?: core.calls.firstOrNull()
if (newCurrentCall != null) {
if (LinphoneUtils.isCallIncoming(newCurrentCall.state)) {
Log.i("$TAG Asking activity to show incoming call fragment")
showIncomingCallEvent.postValue(Event(true))
} else {
Log.i("$TAG Asking activity to show active call fragment")
goToActiveCallEvent.postValue(Event(true))
}
}
}
}
@ -183,6 +142,7 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
callsCount.postValue(list.size)
val currentCall = core.currentCall ?: core.calls.first()
Log.i("$TAG Current call is [${currentCall.remoteAddress.asStringUriOnly()}]")
when (currentCall.state) {
Call.State.Connected, Call.State.StreamsRunning, Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote, Call.State.UpdatedByRemote, Call.State.Updating -> {

View file

@ -28,10 +28,15 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.Locale
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.core.Alert
import org.linphone.core.AlertListenerStub
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.MediaDirection
import org.linphone.core.MediaEncryption
import org.linphone.core.tools.Log
@ -46,6 +51,11 @@ import org.linphone.utils.LinphoneUtils
class CurrentCallViewModel @UiThread constructor() : ViewModel() {
companion object {
private const val TAG = "[Current Call ViewModel]"
// Keys are hardcoded in SDK
private const val ALERT_NETWORK_TYPE_KEY = "network-type"
private const val ALERT_NETWORK_TYPE_WIFI = "wifi"
private const val ALERT_NETWORK_TYPE_CELLULAR = "mobile"
}
val contact = MutableLiveData<ContactAvatarModel>()
@ -115,7 +125,13 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<Boolean>>()
}
private lateinit var call: Call
// Alerts related
val showLowWifiSignalEvent = MutableLiveData<Event<Boolean>>()
val showLowCellularSignalEvent = MutableLiveData<Event<Boolean>>()
private lateinit var currentCall: Call
private val callListener = object : CallListenerStub() {
@WorkerThread
@ -124,24 +140,41 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
}
@WorkerThread
override fun onStateChanged(call: Call, state: Call.State?, message: String) {
if (CurrentCallViewModel@call != call) {
return
}
override fun onStateChanged(call: Call, state: Call.State, message: String) {
Log.i("$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]")
if (LinphoneUtils.isCallOutgoing(call.state)) {
isVideoEnabled.postValue(call.params.isVideoEnabled)
} else if (LinphoneUtils.isCallEnding(call.state)) {
// If current call is being terminated but there is at least one other call, switch
val core = call.core
val callsCount = core.callsNb
Log.i(
"$TAG Call is being ended, check for another current call (currently [$callsCount] calls)"
)
if (callsCount > 0) {
val newCurrentCall = core.currentCall ?: core.calls.firstOrNull()
if (newCurrentCall != null) {
Log.i(
"$TAG From now on current call will be [${newCurrentCall.remoteAddress.asStringUriOnly()}]"
)
configureCall(newCurrentCall)
} else {
Log.e("$TAG Failed to get a valid call to display!")
}
}
} else {
val videoEnabled = call.currentParams.isVideoEnabled
if (videoEnabled && isVideoEnabled.value == false) {
Log.i("$TAG Video enabled, routing audio to speaker")
AudioRouteUtils.routeAudioToSpeaker(call)
if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) {
Log.i("$TAG Video is now enabled, routing audio to speaker")
AudioRouteUtils.routeAudioToSpeaker(call)
}
}
isVideoEnabled.postValue(videoEnabled)
// Toggle full screen OFF when remote disables video
if (!videoEnabled && fullScreenMode.value == true) {
Log.w("$TAG Video is not longer enabled, leaving full screen mode")
fullScreenMode.postValue(false)
}
}
@ -154,6 +187,100 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
}
}
private val coreListener = object : CoreListenerStub() {
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State,
message: String
) {
if (::currentCall.isInitialized) {
if (call != currentCall) {
if (call == currentCall.core.currentCall) {
Log.w(
"$TAG Current call has changed, now is [${call.remoteAddress.asStringUriOnly()}] with state [$state]"
)
currentCall.removeListener(callListener)
configureCall(call)
} else if (LinphoneUtils.isCallIncoming(call.state)) {
Log.w(
"$TAG A call is being received [${call.remoteAddress.asStringUriOnly()}], using it as current call unless declined"
)
currentCall.removeListener(callListener)
configureCall(call)
}
}
} else {
Log.w(
"$TAG There was no current call (shouldn't be possible), using [${call.remoteAddress.asStringUriOnly()}] anyway"
)
configureCall(call)
}
}
@WorkerThread
override fun onNewAlertTriggered(core: Core, alert: Alert) {
val remote = alert.call.remoteAddress.asStringUriOnly()
Log.w("$TAG Alert of type [${alert.type}] triggered for call from [$remote]")
alert.addListener(alertListener)
if (alert.call != currentCall) {
Log.w("$TAG Terminated alert wasn't for current call, do not display it")
return
}
if (alert.type == Alert.Type.QoSLowSignal) {
when (val networkType = alert.informations?.getString(ALERT_NETWORK_TYPE_KEY)) {
ALERT_NETWORK_TYPE_WIFI -> {
Log.i("$TAG Triggered low signal alert is for Wi-Fi")
showLowWifiSignalEvent.postValue(Event(true))
}
ALERT_NETWORK_TYPE_CELLULAR -> {
Log.i("$TAG Triggered low signal alert is for cellular")
showLowCellularSignalEvent.postValue(Event(true))
}
else -> {
Log.w(
"$TAG Unexpected type of signal [$networkType] found in alert information"
)
}
}
}
}
}
private val alertListener = object : AlertListenerStub() {
@WorkerThread
override fun onTerminated(alert: Alert) {
val remote = alert.call.remoteAddress.asStringUriOnly()
Log.w("$TAG Alert of type [${alert.type}] dismissed for call from [$remote]")
alert.removeListener(this)
if (alert.call != currentCall) {
Log.w("$TAG Terminated alert wasn't for current call, do not display it")
return
}
if (alert.type == Alert.Type.QoSLowSignal) {
when (val signalType = alert.informations?.getString(ALERT_NETWORK_TYPE_KEY)) {
ALERT_NETWORK_TYPE_WIFI -> {
Log.i("$TAG Wi-Fi signal no longer low")
showLowWifiSignalEvent.postValue(Event(false))
}
ALERT_NETWORK_TYPE_CELLULAR -> {
Log.i("$TAG Cellular signal no longer low")
showLowCellularSignalEvent.postValue(Event(false))
}
else -> {
Log.w(
"$TAG Unexpected type of signal [$signalType] found in alert information"
)
}
}
}
}
}
init {
isVideoEnabled.value = false
isMicrophoneMuted.value = false
@ -161,10 +288,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
isActionsMenuExpanded.value = false
coreContext.postOnCoreThread { core ->
val currentCall = core.currentCall ?: core.calls.firstOrNull()
core.addListener(coreListener)
val call = core.currentCall ?: core.calls.firstOrNull()
if (currentCall != null) {
call = currentCall
if (call != null) {
Log.i("$TAG Found call [${call.remoteAddress.asStringUriOnly()}]")
configureCall(call)
} else {
@ -178,8 +305,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
{ digit -> // onDigitClicked
appendDigitToSearchBarEvent.value = Event(digit)
coreContext.postOnCoreThread {
Log.i("$TAG Sending DTMF [${digit.first()}]")
call.sendDtmf(digit.first())
if (::currentCall.isInitialized) {
Log.i("$TAG Sending DTMF [${digit.first()}]")
currentCall.sendDtmf(digit.first())
}
}
},
{ // OnBackspaceClicked
@ -194,9 +323,11 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
override fun onCleared() {
super.onCleared()
coreContext.postOnCoreThread {
if (::call.isInitialized) {
call.removeListener(callListener)
coreContext.postOnCoreThread { core ->
core.removeListener(coreListener)
if (::currentCall.isInitialized) {
currentCall.removeListener(callListener)
}
}
}
@ -204,9 +335,9 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@UiThread
fun answer() {
coreContext.postOnCoreThread {
if (::call.isInitialized) {
Log.i("$TAG Answering call [$call]")
coreContext.answerCall(call)
if (::currentCall.isInitialized) {
Log.i("$TAG Answering call [$currentCall]")
coreContext.answerCall(currentCall)
}
}
}
@ -214,9 +345,9 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@UiThread
fun hangUp() {
coreContext.postOnCoreThread {
if (::call.isInitialized) {
Log.i("$TAG Terminating call [${call.remoteAddress.asStringUriOnly()}]")
call.terminate()
if (::currentCall.isInitialized) {
Log.i("$TAG Terminating call [${currentCall.remoteAddress.asStringUriOnly()}]")
currentCall.terminate()
}
}
}
@ -224,8 +355,8 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@UiThread
fun updateZrtpSas(verified: Boolean) {
coreContext.postOnCoreThread {
if (::call.isInitialized) {
call.authenticationTokenVerified = verified
if (::currentCall.isInitialized) {
currentCall.authenticationTokenVerified = verified
}
}
}
@ -242,9 +373,9 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
}
coreContext.postOnCoreThread {
if (::call.isInitialized) {
call.microphoneMuted = !call.microphoneMuted
isMicrophoneMuted.postValue(call.microphoneMuted)
if (::currentCall.isInitialized) {
currentCall.microphoneMuted = !currentCall.microphoneMuted
isMicrophoneMuted.postValue(currentCall.microphoneMuted)
}
}
}
@ -267,12 +398,12 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
// onSelected
coreContext.postOnCoreThread {
Log.i("$TAG Selected audio device with ID [${device.id}]")
if (::call.isInitialized) {
if (::currentCall.isInitialized) {
when {
isHeadset -> AudioRouteUtils.routeAudioToHeadset(call)
isBluetooth -> AudioRouteUtils.routeAudioToBluetooth(call)
isSpeaker -> AudioRouteUtils.routeAudioToSpeaker(call)
else -> AudioRouteUtils.routeAudioToEarpiece(call)
isHeadset -> AudioRouteUtils.routeAudioToHeadset(currentCall)
isBluetooth -> AudioRouteUtils.routeAudioToBluetooth(currentCall)
isSpeaker -> AudioRouteUtils.routeAudioToSpeaker(currentCall)
else -> AudioRouteUtils.routeAudioToEarpiece(currentCall)
}
}
}
@ -288,11 +419,11 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
Log.i(
"$TAG Found less than two devices, simply switching between earpiece & speaker"
)
if (::call.isInitialized) {
if (::currentCall.isInitialized) {
if (routeAudioToSpeaker) {
AudioRouteUtils.routeAudioToSpeaker(call)
AudioRouteUtils.routeAudioToSpeaker(currentCall)
} else {
AudioRouteUtils.routeAudioToEarpiece(call)
AudioRouteUtils.routeAudioToEarpiece(currentCall)
}
}
}
@ -311,9 +442,9 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
}
coreContext.postOnCoreThread { core ->
if (::call.isInitialized) {
val params = core.createCallParams(call)
if (call.conference != null) {
if (::currentCall.isInitialized) {
val params = core.createCallParams(currentCall)
if (currentCall.conference != null) {
if (params?.isVideoEnabled == false) {
params.isVideoEnabled = true
params.videoDirection = MediaDirection.SendRecv
@ -330,7 +461,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
"$TAG Updating call with video enabled set to ${params?.isVideoEnabled}"
)
}
call.update(params)
currentCall.update(params)
}
}
}
@ -362,7 +493,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
private fun showZrtpSasDialog(authToken: String) {
val toRead: String
val toListen: String
when (call.dir) {
when (currentCall.dir) {
Call.Dir.Incoming -> {
toRead = authToken.substring(0, 2)
toListen = authToken.substring(2)
@ -377,10 +508,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@WorkerThread
private fun updateEncryption(): Boolean {
when (call.currentParams.mediaEncryption) {
when (currentCall.currentParams.mediaEncryption) {
MediaEncryption.ZRTP -> {
val authToken = call.authenticationToken
val deviceIsTrusted = call.authenticationTokenVerified && authToken != null
val authToken = currentCall.authenticationToken
val deviceIsTrusted = currentCall.authenticationTokenVerified && authToken != null
Log.i(
"$TAG Current call media encryption is ZRTP, auth token is ${if (deviceIsTrusted) "trusted" else "not trusted yet"}"
)
@ -404,6 +535,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@WorkerThread
private fun configureCall(call: Call) {
currentCall = call
call.addListener(callListener)
if (call.dir == Call.Dir.Incoming) {
@ -458,8 +590,8 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
@WorkerThread
fun updateCallDuration() {
if (::call.isInitialized) {
callDuration.postValue(call.duration)
if (::currentCall.isInitialized) {
callDuration.postValue(currentCall.duration)
}
}

View file

@ -110,6 +110,14 @@ class LinphoneUtils {
return address.displayName ?: address.username ?: address.asString()
}
@AnyThread
fun isCallIncoming(callState: Call.State): Boolean {
return when (callState) {
Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> true
else -> false
}
}
@AnyThread
fun isCallOutgoing(callState: Call.State): Boolean {
return when (callState) {
@ -126,6 +134,14 @@ class LinphoneUtils {
}
}
@AnyThread
fun isCallEnding(callState: Call.State): Boolean {
return when (callState) {
Call.State.End, Call.State.Error -> true
else -> false
}
}
@AnyThread
@IntegerRes
fun getIconResId(callStatus: Status, callDir: Dir): Int {

View file

@ -13,16 +13,17 @@
android:onClick="@{() -> model.onClicked()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray_main2_200">
android:background="@color/black">
<androidx.appcompat.widget.AppCompatTextView
style="@style/context_menu_action_label_style"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{model.name, default=`Piel 6 Ori`}"
android:background="@drawable/menu_item_background"
android:text="@{model.name, default=`Speaker`}"
android:textColor="@color/white"
android:layout_marginBottom="1dp"
android:drawableStart="@{model.isHeadset ? @drawable/headset : model.isBluetooth ? @drawable/bluetooth : model.isSpeaker ? @drawable/speaker_high : @drawable/ear, default=@drawable/speaker_high}"
android:drawableTint="@color/gray_main2_300"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View file

@ -12,6 +12,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/gray_500"
entries="@{devices}"
layout="@{@layout/call_audio_device_list_cell}">

View file

@ -4,9 +4,15 @@
<data>
<import type="android.view.View" />
<variable
name="pauseResumeClickListener"
type="View.OnClickListener" />
<variable
name="hangUpClickListener"
type="View.OnClickListener" />
<variable
name="isPaused"
type="Boolean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
@ -15,7 +21,37 @@
android:background="@color/gray_main2_200">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/delete"
android:id="@+id/pause"
android:onClick="@{pauseResumeClickListener}"
android:visibility="@{isPaused ? View.GONE : View.VISIBLE}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/call_action_pause_call"
style="@style/context_menu_action_label_style"
android:background="@drawable/menu_item_background"
android:layout_marginBottom="1dp"
android:drawableStart="@drawable/pause_call"
app:layout_constraintBottom_toTopOf="@id/hang_up"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/resume"
android:onClick="@{pauseResumeClickListener}"
android:visibility="@{isPaused ? View.VISIBLE : View.GONE, default=gone}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/call_action_resume_call"
style="@style/context_menu_action_label_style"
android:background="@drawable/menu_item_background"
android:layout_marginBottom="1dp"
android:drawableStart="@drawable/pause_call"
app:layout_constraintBottom_toTopOf="@id/hang_up"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/hang_up"
android:onClick="@{hangUpClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"

View file

@ -280,7 +280,7 @@
<string name="call_action_show_dialer">Dialer</string>
<string name="call_action_show_messages">Messages</string>
<string name="call_action_pause_call">Pause</string>
<string name="call_action_resume_call">Pause</string>
<string name="call_action_resume_call">Resume</string>
<string name="call_action_record_call">Record</string>
<string name="call_action_hang_up">Hang up</string>
<string name="call_state_outgoing_progress">In progress</string>