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) + } + } + } }