Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add show showBackgroundCallUI implementation #212

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.twilio.twilio_voice.constants.Constants
import com.twilio.twilio_voice.constants.FlutterErrorCodes
import com.twilio.twilio_voice.fcm.VoiceMessagingService
import com.twilio.twilio_voice.receivers.TVBroadcastReceiver
import com.twilio.twilio_voice.service.TVConnectionService
import com.twilio.twilio_voice.storage.Storage
Expand Down Expand Up @@ -136,6 +137,7 @@ class TwilioVoicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamH
flutterPluginBinding.applicationContext
)
hasStarted = true
context = flutterPluginBinding.applicationContext
}

override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
Expand Down Expand Up @@ -853,7 +855,19 @@ class TwilioVoicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamH
}

TVMethodChannels.BACKGROUND_CALL_UI -> {
// Deprecated in favour of ConnectionService implementation
val args = call.arguments as? Map<String, String> ?: run {
result.error(
FlutterErrorCodes.MALFORMED_ARGUMENTS,
"Arguments should be a Map<*, *>",
null
)
return@onMethodCall
}

Log.d(TAG, "onMethodCall: BACKGROUND_CALL_UI args: $args")

context?.let { val voiceMessagingService = VoiceMessagingService(it)
Voice.handleMessage(it, args, voiceMessagingService) }
result.success(true)
}

Expand Down Expand Up @@ -1892,4 +1906,4 @@ class TwilioVoicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamH
// logEvent("onDisconnected")
// }
//endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.twilio.twilio_voice.fcm

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.telecom.TelecomManager
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.firebase.messaging.RemoteMessage
import com.twilio.twilio_voice.receivers.TVBroadcastReceiver
import com.twilio.twilio_voice.service.TVConnectionService
import com.twilio.twilio_voice.storage.StorageImpl
import com.twilio.twilio_voice.types.TelecomManagerExtension.canReadPhoneNumbers
import com.twilio.twilio_voice.types.TelecomManagerExtension.canReadPhoneState
import com.twilio.twilio_voice.types.TelecomManagerExtension.hasCallCapableAccount
import com.twilio.voice.CallException
import com.twilio.voice.CallInvite
import com.twilio.voice.CancelledCallInvite
import com.twilio.voice.MessageListener
import com.twilio.voice.Voice

class VoiceMessagingService(private val applicationContext: Context) : MessageListener{
companion object {
private const val TAG = "VoiceMessagingService"


}

//region MessageListener
@RequiresPermission(allOf = [Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS])
@SuppressLint("MissingPermission")
override fun onCallInvite(callInvite: CallInvite) {
Log.d(
TAG,
"onCallInvite: {\n\t" +
"CallSid: ${callInvite.callSid}, \n\t" +
"From: ${callInvite.from}, \n\t" +
"To: ${callInvite.to}, \n\t" +
"Parameters: ${callInvite.customParameters.entries.joinToString { "${it.key}:${it.value}" }},\n\t" +
"}"
)
// Get TelecomManager instance
val tm = applicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager

val shouldRejectOnNoPermissions: Boolean = StorageImpl(applicationContext).rejectOnNoPermissions
var missingPermissions: Array<String> = emptyArray()

// Check permission READ_PHONE_STATE
if (!tm.canReadPhoneState(applicationContext)) {
missingPermissions += "No `READ_PHONE_STATE` permission, cannot check if phone account is registered. Request this with `requestReadPhoneStatePermission()`"
}

// Check permission READ_PHONE_NUMBERS
if (!tm.canReadPhoneNumbers(applicationContext)) {
missingPermissions += "No `READ_PHONE_NUMBERS` permission, cannot communicate with ConnectionService if not granted. Request this with `requestReadPhoneNumbersPermission()`"
}

// NOTE(cybex-dev): Foreground services requiring privacy permission e.g. microphone or
// camera are required to be started in the foreground. Since we're using the Telecom's
// PhoneAccount, we don't directly require microphone access. Further, microphone access
// is always denied if the app requiring microphone access via a Foreground service
// is in the background (by design).
// // Check permission RECORD_AUDIO
// if (!applicationContext.hasMicrophoneAccess()) {
// shouldRejectCall = true
// requiredPermissions += "No `RECORD_AUDIO` permission, VoiceSDK requires this permission. Request this with `requestMicPermission()`"
// }

if(!tm.hasCallCapableAccount(applicationContext, TVConnectionService::class.java.name)) {
missingPermissions += "No call capable phone account registered. Request this with `registerPhoneAccount()`"
}

// If we have missingPermissions, then we cannot proceed with answering the call.
if (missingPermissions.isNotEmpty()) {
missingPermissions.forEach { Log.e(TAG, it) }

// If we're not rejecting on no permissions, and can't answer because we don't have the required permissions / phone account, we let it ring.
// This details a use-case where multiple instances of a user is logged in, and can accept the call on another device.
if(!shouldRejectOnNoPermissions) {
return
}

Log.e(TAG, "onCallInvite: Rejecting incoming call\nSID: ${callInvite.callSid}")

// send broadcast to TVBroadcastReceiver, we notify Flutter about incoming call
Intent(applicationContext, TVBroadcastReceiver::class.java).apply {
action = TVBroadcastReceiver.ACTION_INCOMING_CALL_IGNORED
putExtra(TVBroadcastReceiver.EXTRA_INCOMING_CALL_IGNORED_REASON, missingPermissions)
putExtra(TVBroadcastReceiver.EXTRA_CALL_HANDLE, callInvite.callSid)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(this)
}

// Reject incoming call
Log.d(TAG, "onCallInvite: Rejecting incoming call")
callInvite.reject(applicationContext)

return
}

// send broadcast to TVConnectionService, we notify the TelecomManager about incoming call
Intent(applicationContext, TVConnectionService::class.java).apply {
action = TVConnectionService.ACTION_INCOMING_CALL
putExtra(TVConnectionService.EXTRA_INCOMING_CALL_INVITE, callInvite)
applicationContext.startService(this)
}

// send broadcast to TVBroadcastReceiver, we notify Flutter about incoming call
Intent(applicationContext, TVBroadcastReceiver::class.java).apply {
action = TVBroadcastReceiver.ACTION_INCOMING_CALL
putExtra(TVBroadcastReceiver.EXTRA_CALL_INVITE, callInvite)
putExtra(TVBroadcastReceiver.EXTRA_CALL_HANDLE, callInvite.callSid)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(this)
}
}

override fun onCancelledCallInvite(cancelledCallInvite: CancelledCallInvite, callException: CallException?) {
Log.d(TAG, "onCancelledCallInvite: ", callException)
Intent(applicationContext, TVConnectionService::class.java).apply {
action = TVConnectionService.ACTION_CANCEL_CALL_INVITE
putExtra(TVConnectionService.EXTRA_CANCEL_CALL_INVITE, cancelledCallInvite)
applicationContext.startService(this)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ enum class TVMethodChannels(val method: String) {
REQUEST_READ_PHONE_STATE_PERMISSION("requestReadPhoneStatePermission"),
HAS_CALL_PHONE_PERMISSION("hasCallPhonePermission"),
REQUEST_CALL_PHONE_PERMISSION("requestCallPhonePermission"),
@Deprecated("No longer required due to Custom UI replaced with native call screen")
BACKGROUND_CALL_UI("backgroundCallUi"),
SHOW_NOTIFICATIONS("showNotifications"),
HAS_READ_PHONE_NUMBERS_PERMISSION("hasReadPhoneNumbersPermission"),
Expand Down
Loading