diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index e1a4d110b..c50e81b9f 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -265,6 +265,21 @@ class CoreContext "$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$currentState]" ) when (currentState) { + Call.State.IncomingReceived -> { + if (corePreferences.autoAnswerEnabled) { + val autoAnswerDelay = corePreferences.autoAnswerDelay + if (autoAnswerDelay == 0) { + Log.w("$TAG Auto answering call immediately") + answerCall(call) + } else { + Log.i("$TAG Scheduling auto answering in $autoAnswerDelay milliseconds") + postOnCoreThreadDelayed({ + Log.w("$TAG Auto answering call") + answerCall(call) + }, autoAnswerDelay.toLong()) + } + } + } Call.State.OutgoingInit -> { val conferenceInfo = core.findConferenceInformationFromUri(call.remoteAddress) // Do not show outgoing call view for conference calls, wait for connected state diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt index f373b45f4..5e05581f0 100644 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ b/app/src/main/java/org/linphone/core/CorePreferences.kt @@ -154,6 +154,20 @@ class CorePreferences config.setBool("misc", "real_early_media", value) } + @get:WorkerThread @set:WorkerThread + var autoAnswerEnabled: Boolean + get() = config.getBool("app", "auto_answer", false) + set(value) { + config.setBool("app", "auto_answer", value) + } + + @get:WorkerThread @set:WorkerThread + var autoAnswerDelay: Int + get() = config.getInt("app", "auto_answer_delay", 0) + set(value) { + config.setInt("app", "auto_answer_delay", value) + } + // Conversation related @get:WorkerThread @set:WorkerThread diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 100a25867..b90ebf8fe 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -189,8 +189,9 @@ class NotificationsManager state: Call.State?, message: String ) { - Log.i("$TAG Call state changed: [$state]") - when (state) { + val currentState = call.state + Log.i("$TAG Call state changed: [$currentState]") + when (currentState) { Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> { Log.i( "$TAG Showing incoming call notification for [${call.remoteAddress.asStringUriOnly()}]" @@ -205,14 +206,14 @@ class NotificationsManager } Call.State.Connected, Call.State.StreamsRunning -> { - if (call.state == Call.State.Connected && call.dir == Call.Dir.Incoming) { + if (currentState == Call.State.Connected && call.dir == Call.Dir.Incoming) { Log.i( "$TAG Connected call was incoming (so it was answered), removing incoming call notification" ) removeIncomingCallNotification() } - if (call.state == Call.State.Connected || call.dir == Call.Dir.Incoming) { + if (currentState == Call.State.Connected || call.dir == Call.Dir.Incoming) { Log.i( "$TAG Showing connected call notification for [${call.remoteAddress.asStringUriOnly()}]" ) diff --git a/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt b/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt index b564bd2cc..4f71adcaa 100644 --- a/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt +++ b/app/src/main/java/org/linphone/telecom/TelecomCallControlCallback.kt @@ -60,21 +60,7 @@ class TelecomCallControlCallback( Log.i("$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]") if (state == Call.State.Connected) { if (call.dir == Call.Dir.Incoming) { - val isVideo = LinphoneUtils.isVideoEnabled(call) - val type = if (isVideo) { - CallAttributesCompat.Companion.CALL_TYPE_VIDEO_CALL - } else { - CallAttributesCompat.Companion.CALL_TYPE_AUDIO_CALL - } - scope.launch { - Log.i("$TAG Answering [${if (isVideo) "video" else "audio"}] call") - callControl.answer(type) - } - - if (isVideo && corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) { - Log.i("$TAG Answering video call, routing audio to speaker") - AudioUtils.routeAudioToSpeaker(call) - } + answerCall() } else { scope.launch { Log.i("$TAG Setting call active") @@ -116,6 +102,7 @@ class TelecomCallControlCallback( val state = call.state Log.i("$TAG Call state currently is [$state]") when (state) { + Call.State.Connected, Call.State.StreamsRunning -> answerCall() Call.State.End -> callEnded() Call.State.Error -> callError("") Call.State.Released -> callEnded() @@ -283,6 +270,24 @@ class TelecomCallControlCallback( return false } + private fun answerCall() { + val isVideo = LinphoneUtils.isVideoEnabled(call) + val type = if (isVideo) { + CallAttributesCompat.Companion.CALL_TYPE_VIDEO_CALL + } else { + CallAttributesCompat.Companion.CALL_TYPE_AUDIO_CALL + } + scope.launch { + Log.i("$TAG Answering [${if (isVideo) "video" else "audio"}] call") + callControl.answer(type) + } + + if (isVideo && corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) { + Log.i("$TAG Answering video call, routing audio to speaker") + AudioUtils.routeAudioToSpeaker(call) + } + } + private fun callEnded() { val reason = call.reason val direction = call.dir diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt index 733b7360c..bcd6c96ae 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt @@ -131,9 +131,9 @@ class SettingsAdvancedFragment : GenericMainFragment() { viewModel.mediaEncryptionLabels ) adapter.setDropDownViewResource(R.layout.generic_dropdown_cell) - binding.mediaEncryption.adapter = adapter - binding.mediaEncryption.onItemSelectedListener = mediaEncryptionDropdownListener - binding.mediaEncryption.setSelection(index) + binding.advancedCallsSettings.mediaEncryption.adapter = adapter + binding.advancedCallsSettings.mediaEncryption.onItemSelectedListener = mediaEncryptionDropdownListener + binding.advancedCallsSettings.mediaEncryption.setSelection(index) } private fun setupInputAudioDevicePicker() { 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 aab34bdbb..5fc1444c1 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 @@ -203,6 +203,8 @@ class SettingsViewModel val fileSharingServerUrl = MutableLiveData() val remoteProvisioningUrl = MutableLiveData() + val expandAdvancedCalls = MutableLiveData() + val mediaEncryptionIndex = MutableLiveData() val mediaEncryptionLabels = arrayListOf() private val mediaEncryptionValues = arrayListOf() @@ -210,6 +212,8 @@ class SettingsViewModel val createEndToEndEncryptedConferences = MutableLiveData() val acceptEarlyMedia = MutableLiveData() val allowOutgoingEarlyMedia = MutableLiveData() + val autoAnswerIncomingCalls = MutableLiveData() + val autoAnswerIncomingCallsDelay = MutableLiveData() val expandAudioDevices = MutableLiveData() val inputAudioDeviceIndex = MutableLiveData() @@ -262,6 +266,7 @@ class SettingsViewModel expandNetwork.value = false expandUserInterface.value = false expandTunnel.value = false + expandAdvancedCalls.value = false expandAudioDevices.value = false expandAudioCodecs.value = false expandVideoCodecs.value = false @@ -331,6 +336,12 @@ class SettingsViewModel fileSharingServerUrl.postValue(core.fileTransferServer) remoteProvisioningUrl.postValue(core.provisioningUri) + createEndToEndEncryptedConferences.postValue(corePreferences.createEndToEndEncryptedMeetingsAndGroupCalls) + acceptEarlyMedia.postValue(corePreferences.acceptEarlyMedia) + allowOutgoingEarlyMedia.postValue(corePreferences.allowOutgoingEarlyMedia) + autoAnswerIncomingCalls.postValue(corePreferences.autoAnswerEnabled) + autoAnswerIncomingCallsDelay.postValue(corePreferences.autoAnswerDelay) + setupMediaEncryption() setupAudioDevices() setupCodecs() @@ -763,9 +774,6 @@ class SettingsViewModel } mediaEncryptionMandatory.postValue(core.isMediaEncryptionMandatory) - createEndToEndEncryptedConferences.postValue(corePreferences.createEndToEndEncryptedMeetingsAndGroupCalls) - acceptEarlyMedia.postValue(corePreferences.acceptEarlyMedia) - allowOutgoingEarlyMedia.postValue(corePreferences.allowOutgoingEarlyMedia) } @UiThread @@ -821,6 +829,28 @@ class SettingsViewModel } } + @UiThread + fun toggleEnableAutoAnswerIncomingCalls() { + val newValue = autoAnswerIncomingCalls.value == false + + coreContext.postOnCoreThread { core -> + corePreferences.autoAnswerEnabled = newValue + autoAnswerIncomingCalls.postValue(newValue) + } + } + + @UiThread + fun updateAutoAnswerIncomingCallsDelay(newValue: String) { + if (newValue.isNotEmpty()) { + try { + val delay = newValue.toInt() + corePreferences.autoAnswerDelay = delay + } catch (nfe: NumberFormatException) { + Log.e("$TAG Ignoring new auto answer incoming calls delay as it can't be converted to int: $nfe") + } + } + } + @UiThread fun updateDeviceName() { coreContext.postOnCoreThread { @@ -874,6 +904,11 @@ class SettingsViewModel } } + @UiThread + fun toggleAdvancedCallsExpand() { + expandAdvancedCalls.value = expandAdvancedCalls.value == false + } + @UiThread fun toggleAudioDevicesExpand() { expandAudioDevices.value = expandAudioDevices.value == false diff --git a/app/src/main/res/layout/settings_advanced_calls.xml b/app/src/main/res/layout/settings_advanced_calls.xml new file mode 100644 index 000000000..d65c94231 --- /dev/null +++ b/app/src/main/res/layout/settings_advanced_calls.xml @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_advanced_fragment.xml b/app/src/main/res/layout/settings_advanced_fragment.xml index c250f9221..e7a30bf90 100644 --- a/app/src/main/res/layout/settings_advanced_fragment.xml +++ b/app/src/main/res/layout/settings_advanced_fragment.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:bind="http://schemas.android.com/tools"> @@ -114,241 +115,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/start_at_boot_switch"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/keep_alive_service_switch"/> + + + + + app:layout_constraintTop_toBottomOf="@id/advanced_calls_settings"/> Créer en mode chiffré de bout en bout les réunions et les appels de groupe Accepter l\'early media Autoriser l\'early media pour les appels sortants + Décrocher automatiquement les appels entrants + Délai avant le décrochage automatique + Délai en millisecondes 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 5adcd07ff..2cb537fa0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -314,6 +314,9 @@ Create end-to-end encrypted meetings & group calls Accept early media Allow outgoing early media + Auto answer incoming calls + Delay before auto answering call + Delay in milliseconds Remote provisioning URL Download & apply Audio devices