diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5bf85a42e..9af2b17ef 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -121,6 +121,11 @@
+
+
+
+
+
-->
+
+
+
+
+
+
+
+
.
+ */
+package org.linphone.telecom
+
+import android.os.Build
+import android.telecom.Call
+import android.telecom.InCallService
+import android.telecom.VideoProfile
+import androidx.annotation.RequiresApi
+import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.core.tools.Log
+
+class TelecomInCallService : InCallService() {
+ companion object {
+ private const val TAG = "[Telecom InCall Service]"
+ }
+
+ private val callsList = arrayListOf()
+
+ private val callListener = object : Call.Callback() {
+ override fun onStateChanged(call: Call, state: Int) {
+ Log.i("$TAG Call state changed [${callStateToString(state)}]")
+ }
+ }
+
+ override fun onBringToForeground(showDialpad: Boolean) {
+ Log.i("$TAG onBringToForeground, showDialpad: $showDialpad")
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ override fun onCallAdded(call: Call) {
+ super.onCallAdded(call)
+
+ val uri = call.details.handle
+ val isIncoming = call.details.callDirection == Call.Details.DIRECTION_INCOMING
+ val state = call.details.state
+ Log.i("$TAG Added call direction is [${if (isIncoming) "incoming" else "outgoing"}], URI is [$uri], state [${callStateToString(state)}], call [$call]")
+
+ call.registerCallback(callListener)
+ callsList.add(call)
+
+ if (uri.scheme == "tel") {
+ val number = uri.schemeSpecificPart
+ Log.i("$TAG Extract number [$number] from tel: URI")
+ if (!isIncoming) {
+ coreContext.postOnCoreThread { core ->
+ val address = core.interpretUrl(number, true)
+ if (address != null) {
+ Log.i("$TAG Calling SIP URI [${address.asStringUriOnly()}]")
+ if (call.details.videoState == VideoProfile.STATE_AUDIO_ONLY) {
+ coreContext.startAudioCall(address)
+ } else {
+ coreContext.startVideoCall(address)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCallRemoved(call: Call) {
+ super.onCallRemoved(call)
+
+ Log.i("$TAG onCallRemoved with call [$call]")
+ call.unregisterCallback(callListener)
+ callsList.remove(call)
+ }
+
+ override fun onCanAddCallChanged(canAddCall: Boolean) {
+ Log.i("$TAG onCanAddCallChanged: $canAddCall")
+ }
+
+ override fun onMuteStateChanged(isMuted: Boolean) {
+ Log.i("$TAG onMuteStateChanged: $isMuted")
+ }
+
+ override fun onSilenceRinger() {
+ Log.i("$TAG onSilenceRinger")
+ }
+
+ private fun callStateToString(state: Int): String {
+ return when (state) {
+ Call.STATE_NEW -> "NEW"
+ Call.STATE_DIALING -> "DIALING"
+ Call.STATE_RINGING -> "RINGING"
+ Call.STATE_HOLDING -> "HOLDING"
+ Call.STATE_ACTIVE -> "ACTIVE"
+ Call.STATE_DISCONNECTED -> "DISCONNECTED"
+ Call.STATE_SELECT_PHONE_ACCOUNT -> "SELECT_PHONE_ACCOUNT"
+ Call.STATE_CONNECTING -> "CONNECTING"
+ Call.STATE_DISCONNECTING -> "DISCONNECTING"
+ Call.STATE_PULLING_CALL -> "PULLING_CALL"
+ Call.STATE_AUDIO_PROCESSING -> "AUDIO_PROCESSING"
+ Call.STATE_SIMULATED_RINGING -> "SIMULATED_RINGING"
+ else -> "UNKNOWN [$state]"
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt
index 55eac0aa6..558cc4e47 100644
--- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt
+++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt
@@ -21,7 +21,10 @@ package org.linphone.ui.main
import android.Manifest
import android.annotation.SuppressLint
+import android.app.ComponentCaller
import android.app.Dialog
+import android.app.role.RoleManager
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
@@ -89,6 +92,8 @@ class MainActivity : GenericActivity() {
private const val CHAT_FRAGMENT_ID = 3
private const val MEETINGS_FRAGMENT_ID = 4
+ private const val DIALER_ROLE_REQUEST_CODE = 1
+
const val ARGUMENTS_CHAT = "Chat"
const val ARGUMENTS_CONVERSATION_ID = "ConversationId"
}
@@ -391,6 +396,8 @@ class MainActivity : GenericActivity() {
Log.d("$TAG savedInstanceState is null but intent isn't, handling it")
handleIntent(intent)
}
+
+ requestDialerRole()
}
override fun onPause() {
@@ -438,6 +445,21 @@ class MainActivity : GenericActivity() {
handleIntent(intent)
}
+ override fun onActivityResult(
+ requestCode: Int,
+ resultCode: Int,
+ data: Intent?,
+ caller: ComponentCaller
+ ) {
+ if (requestCode == DIALER_ROLE_REQUEST_CODE) {
+ if (resultCode == RESULT_OK) {
+ Log.i("$TAG Dialer role was granted!")
+ } else {
+ Log.w("$TAG Dialer role was denied!")
+ }
+ }
+ }
+
@SuppressLint("RtlHardcoded")
fun toggleDrawerMenu() {
if (binding.drawerMenu.isDrawerOpen(Gravity.LEFT)) {
@@ -849,4 +871,16 @@ class MainActivity : GenericActivity() {
}
}
}
+
+ private fun requestDialerRole() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
+ val roleManager: RoleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
+ val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_DIALER) &&
+ !roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
+ if (shouldRequestRole) {
+ val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER)
+ startActivityForResult(intent, DIALER_ROLE_REQUEST_CODE)
+ }
+ }
+ }
}